Repository: realm/realm-swift Branch: community Commit: 1cd09f1a41e7 Files: 736 Total size: 10.3 MB Directory structure: gitextract_n99e6kjn/ ├── .dir-locals.el ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.yml │ └── workflows/ │ ├── build-binaries.yml │ ├── build-pr.yml │ ├── check-changelog.yml │ ├── master-push.yml │ └── publish-release.yml ├── .gitignore ├── .ruby-version ├── .swiftlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Configuration/ │ ├── Base.xcconfig │ ├── Debug.xcconfig │ ├── Realm/ │ │ ├── PrivateSymbols.txt │ │ ├── Realm.xcconfig │ │ └── Tests.xcconfig │ ├── RealmSwift/ │ │ ├── RealmSwift.xcconfig │ │ └── Tests.xcconfig │ ├── Release.xcconfig │ ├── Static.xcconfig │ ├── SwiftUITestHost.xcconfig │ ├── SwiftUITests.xcconfig │ ├── TestBase.xcconfig │ └── TestHost.xcconfig ├── Gemfile ├── LICENSE ├── Package.swift ├── README.md ├── Realm/ │ ├── PrivacyInfo.xcprivacy │ ├── RLMAccessor.h │ ├── RLMAccessor.hpp │ ├── RLMAccessor.mm │ ├── RLMArray.h │ ├── RLMArray.mm │ ├── RLMArray_Private.h │ ├── RLMArray_Private.hpp │ ├── RLMAsyncTask.h │ ├── RLMAsyncTask.mm │ ├── RLMAsyncTask_Private.h │ ├── RLMClassInfo.hpp │ ├── RLMClassInfo.mm │ ├── RLMCollection.h │ ├── RLMCollection.mm │ ├── RLMCollection_Private.h │ ├── RLMCollection_Private.hpp │ ├── RLMConstants.h │ ├── RLMConstants.m │ ├── RLMDecimal128.h │ ├── RLMDecimal128.mm │ ├── RLMDecimal128_Private.hpp │ ├── RLMDictionary.h │ ├── RLMDictionary.mm │ ├── RLMDictionary_Private.h │ ├── RLMDictionary_Private.hpp │ ├── RLMEmbeddedObject.h │ ├── RLMEmbeddedObject.mm │ ├── RLMError.h │ ├── RLMError.mm │ ├── RLMError_Private.hpp │ ├── RLMGeospatial.h │ ├── RLMGeospatial.mm │ ├── RLMGeospatial_Private.hpp │ ├── RLMLogger.h │ ├── RLMLogger.mm │ ├── RLMLogger_Private.h │ ├── RLMManagedArray.mm │ ├── RLMManagedDictionary.mm │ ├── RLMManagedSet.mm │ ├── RLMMigration.h │ ├── RLMMigration.mm │ ├── RLMMigration_Private.h │ ├── RLMObject.h │ ├── RLMObject.mm │ ├── RLMObjectBase.h │ ├── RLMObjectBase.mm │ ├── RLMObjectBase_Dynamic.h │ ├── RLMObjectBase_Private.h │ ├── RLMObjectId.h │ ├── RLMObjectId.mm │ ├── RLMObjectId_Private.hpp │ ├── RLMObjectSchema.h │ ├── RLMObjectSchema.mm │ ├── RLMObjectSchema_Private.h │ ├── RLMObjectSchema_Private.hpp │ ├── RLMObjectStore.h │ ├── RLMObjectStore.mm │ ├── RLMObject_Private.h │ ├── RLMObject_Private.hpp │ ├── RLMObservation.hpp │ ├── RLMObservation.mm │ ├── RLMPredicateUtil.hpp │ ├── RLMPredicateUtil.mm │ ├── RLMPrefix.h │ ├── RLMProperty.h │ ├── RLMProperty.mm │ ├── RLMProperty_Private.h │ ├── RLMProperty_Private.hpp │ ├── RLMQueryUtil.hpp │ ├── RLMQueryUtil.mm │ ├── RLMRealm.h │ ├── RLMRealm.mm │ ├── RLMRealmConfiguration.h │ ├── RLMRealmConfiguration.mm │ ├── RLMRealmConfiguration_Private.h │ ├── RLMRealmConfiguration_Private.hpp │ ├── RLMRealmUtil.hpp │ ├── RLMRealmUtil.mm │ ├── RLMRealm_Dynamic.h │ ├── RLMRealm_Private.h │ ├── RLMRealm_Private.hpp │ ├── RLMResults.h │ ├── RLMResults.mm │ ├── RLMResults_Private.h │ ├── RLMResults_Private.hpp │ ├── RLMScheduler.h │ ├── RLMScheduler.mm │ ├── RLMSchema.h │ ├── RLMSchema.mm │ ├── RLMSchema_Private.h │ ├── RLMSchema_Private.hpp │ ├── RLMSectionedResults.h │ ├── RLMSectionedResults.mm │ ├── RLMSectionedResults_Private.hpp │ ├── RLMSet.h │ ├── RLMSet.mm │ ├── RLMSet_Private.h │ ├── RLMSet_Private.hpp │ ├── RLMSwiftBridgingHeader.h │ ├── RLMSwiftCollectionBase.h │ ├── RLMSwiftCollectionBase.mm │ ├── RLMSwiftObject.h │ ├── RLMSwiftProperty.h │ ├── RLMSwiftSupport.h │ ├── RLMSwiftSupport.m │ ├── RLMSwiftValueStorage.h │ ├── RLMSwiftValueStorage.mm │ ├── RLMThreadSafeReference.h │ ├── RLMThreadSafeReference.mm │ ├── RLMThreadSafeReference_Private.hpp │ ├── RLMUUID.mm │ ├── RLMUUID_Private.hpp │ ├── RLMUtil.hpp │ ├── RLMUtil.mm │ ├── RLMValue.h │ ├── RLMValue.mm │ ├── Realm-Info.plist │ ├── Realm.h │ ├── Realm.modulemap │ ├── Swift/ │ │ └── RLMSupport.swift │ ├── TestUtils/ │ │ ├── RLMChildProcessEnvironment.m │ │ ├── RLMMultiProcessTestCase.m │ │ ├── RLMTestCase.m │ │ ├── RLMTestObjects.m │ │ ├── RealmTestSupport.h │ │ ├── TestUtils.mm │ │ └── include/ │ │ ├── RLMAssertions.h │ │ ├── RLMChildProcessEnvironment.h │ │ ├── RLMMultiProcessTestCase.h │ │ ├── RLMTestCase.h │ │ ├── RLMTestObjects.h │ │ └── TestUtils.h │ └── Tests/ │ ├── ArrayPropertyTests.m │ ├── AsyncTests.mm │ ├── CompactionTests.m │ ├── Decimal128Tests.m │ ├── DictionaryPropertyTests.m │ ├── DynamicTests.m │ ├── EncryptionTests.mm │ ├── EnumeratorTests.m │ ├── InterprocessTests.m │ ├── KVOTests.mm │ ├── LinkTests.m │ ├── LinkingObjectsTests.mm │ ├── MigrationTests.mm │ ├── NotificationTests.m │ ├── ObjectCreationTests.mm │ ├── ObjectIdTests.m │ ├── ObjectInterfaceTests.m │ ├── ObjectSchemaTests.m │ ├── ObjectTests.m │ ├── PerformanceTests.m │ ├── PredicateUtilTests.mm │ ├── PrimitiveArrayPropertyTests.m │ ├── PrimitiveArrayPropertyTests.tpl.m │ ├── PrimitiveDictionaryPropertyTests.m │ ├── PrimitiveDictionaryPropertyTests.tpl.m │ ├── PrimitiveRLMValuePropertyTests.m │ ├── PrimitiveRLMValuePropertyTests.tpl.m │ ├── PrimitiveSetPropertyTests.m │ ├── PrimitiveSetPropertyTests.tpl.m │ ├── PropertyTests.m │ ├── QueryTests.m │ ├── RLMValueTests.m │ ├── RealmConfigurationTests.mm │ ├── RealmTests-Info.plist │ ├── RealmTests.mm │ ├── ResultsTests.m │ ├── SchemaTests.mm │ ├── SectionedResultsTests.m │ ├── SetPropertyTests.m │ ├── Swift/ │ │ ├── RLMTestCaseUtils.swift │ │ ├── RealmObjcSwiftTests-Info.plist │ │ ├── Swift-Tests-Bridging-Header.h │ │ ├── SwiftArrayPropertyTests.swift │ │ ├── SwiftArrayTests.swift │ │ ├── SwiftDynamicTests.swift │ │ ├── SwiftLinkTests.swift │ │ ├── SwiftObjectInterfaceTests.swift │ │ ├── SwiftPropertyTypeTest.swift │ │ ├── SwiftRLMDictionaryTests.swift │ │ ├── SwiftRealmTests.swift │ │ ├── SwiftSchemaTests.swift │ │ ├── SwiftSetPropertyTests.swift │ │ ├── SwiftSetTests.swift │ │ ├── SwiftTestObjects.swift │ │ └── SwiftUnicodeTests.swift │ ├── SwiftUITestHost/ │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── Objects.swift │ │ └── SwiftUITestHostApp.swift │ ├── SwiftUITestHostUITests/ │ │ ├── Info.plist │ │ └── SwiftUITestHostUITests.swift │ ├── TestHost/ │ │ ├── Info.plist │ │ └── main.m │ ├── ThreadSafeReferenceTests.m │ ├── TransactionTests.m │ ├── UnicodeTests.m │ ├── UtilTests.mm │ ├── array_tests.py │ ├── dictionary_tests.py │ ├── mixed_tests.py │ └── set_tests.py ├── Realm.podspec ├── Realm.xcodeproj/ │ ├── Realm.xcworkspace/ │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── project.pbxproj │ └── xcshareddata/ │ ├── xcbaselines/ │ │ ├── 5D660FD71BE98C7C0021E04F.xcbaseline/ │ │ │ ├── 6890B8D9-CE81-403E-8EF6-B95A367D65B5.plist │ │ │ ├── B87A7896-8B70-4814-B20D-7CD723D62868.plist │ │ │ └── Info.plist │ │ └── E856D1DE195614A400FB2FCF.xcbaseline/ │ │ ├── 2EB6396F-9FD1-4243-AA83-2D9F9D4DD919.plist │ │ ├── 3AE81604-3FF9-49E2-A414-9B18BEEAE28F.plist │ │ ├── AAC6BA1A-785D-4850-B3EC-68BC9F1D3EEF.plist │ │ └── Info.plist │ └── xcschemes/ │ ├── CI.xcscheme │ ├── Realm.xcscheme │ ├── RealmSwift.xcscheme │ ├── SwiftLint.xcscheme │ ├── SwiftUITestHost.xcscheme │ ├── SwiftUITests.xcscheme │ └── TestHost.xcscheme ├── RealmSwift/ │ ├── Aliases.swift │ ├── AnyRealmValue.swift │ ├── Combine.swift │ ├── CustomPersistable.swift │ ├── Decimal128.swift │ ├── EmbeddedObject.swift │ ├── Error.swift │ ├── Geospatial.swift │ ├── Impl/ │ │ ├── BasicTypes.swift │ │ ├── CollectionAccess.swift │ │ ├── ComplexTypes.swift │ │ ├── KeyPathStrings.swift │ │ ├── ObjcBridgeable.swift │ │ ├── Persistable.swift │ │ ├── PropertyAccessors.swift │ │ ├── RealmCollectionImpl.swift │ │ └── SchemaDiscovery.swift │ ├── LinkingObjects.swift │ ├── List.swift │ ├── Map.swift │ ├── Migration.swift │ ├── MutableSet.swift │ ├── Object.swift │ ├── ObjectId.swift │ ├── ObjectSchema.swift │ ├── ObjectiveCSupport+AnyRealmValue.swift │ ├── ObjectiveCSupport.swift │ ├── Optional.swift │ ├── PersistedProperty.swift │ ├── PrivacyInfo.xcprivacy │ ├── Projection.swift │ ├── Property.swift │ ├── Query.swift │ ├── Realm.swift │ ├── RealmCollection.swift │ ├── RealmConfiguration.swift │ ├── RealmKeyedCollection.swift │ ├── RealmProperty.swift │ ├── Results.swift │ ├── Schema.swift │ ├── SectionedResults.swift │ ├── SortDescriptor.swift │ ├── SwiftUI.swift │ ├── Tests/ │ │ ├── AnyRealmValueTests.swift │ │ ├── CodableTests.swift │ │ ├── CombineTests.swift │ │ ├── CompactionTests.swift │ │ ├── CustomColumnNameTests.swift │ │ ├── CustomObjectCreationTests.swift │ │ ├── CustomPersistableTestObjects.swift │ │ ├── Decimal128Tests.swift │ │ ├── GeospatialTests.swift │ │ ├── KVOTests.swift │ │ ├── KeyPathTests.swift │ │ ├── ListTests.swift │ │ ├── MapTests.swift │ │ ├── MigrationTests.swift │ │ ├── MixedCollectionTest.swift │ │ ├── ModernKVOTests.swift │ │ ├── ModernObjectAccessorTests.swift │ │ ├── ModernObjectCreationTests.swift │ │ ├── ModernObjectTests.swift │ │ ├── ModernTestObjects.swift │ │ ├── MutableSetTests.swift │ │ ├── ObjectAccessorTests.swift │ │ ├── ObjectCreationTests.swift │ │ ├── ObjectCustomPropertiesTests.swift │ │ ├── ObjectIdTests.swift │ │ ├── ObjectSchemaInitializationTests.swift │ │ ├── ObjectSchemaTests.swift │ │ ├── ObjectTests.swift │ │ ├── ObjectiveCSupportTests.swift │ │ ├── PerformanceTests.swift │ │ ├── PrimitiveListTests.swift │ │ ├── PrimitiveMapTests.swift │ │ ├── PrimitiveMutableSetTests.swift │ │ ├── ProjectedCollectTests.swift │ │ ├── ProjectionTests.swift │ │ ├── PropertyTests.swift │ │ ├── QueryTests.swift │ │ ├── QueryTests.swift.gyb │ │ ├── RealmCollectionTypeTests.swift │ │ ├── RealmConfigurationTests.swift │ │ ├── RealmPropertyTests.swift │ │ ├── RealmSwiftTests-BridgingHeader.h │ │ ├── RealmTests.swift │ │ ├── SchemaTests.swift │ │ ├── SectionedResultsTests.swift │ │ ├── SortDescriptorTests.swift │ │ ├── SwiftLinkTests.swift │ │ ├── SwiftTestObjects.swift │ │ ├── SwiftUITests.swift │ │ ├── SwiftUnicodeTests.swift │ │ ├── TestCase.swift │ │ ├── TestUtils.swift │ │ ├── TestValueFactory.swift │ │ └── ThreadSafeReferenceTests.swift │ ├── ThreadSafeReference.swift │ └── Util.swift ├── RealmSwift.podspec ├── SUPPORT.md ├── build.sh ├── contrib/ │ ├── Development.md │ ├── ReleaseProcess.md │ ├── SignXCFramework.md │ └── UpgradingXcode.md ├── dependencies.list ├── docs/ │ ├── README.md │ ├── custom_head.html │ ├── example-projects.md │ ├── guides/ │ │ ├── crud/ │ │ │ ├── create.md │ │ │ ├── crud.md │ │ │ ├── delete.md │ │ │ ├── filter-data.md │ │ │ ├── react-to-changes.md │ │ │ ├── read.md │ │ │ ├── threading.md │ │ │ └── update.md │ │ ├── logging.md │ │ ├── model-data/ │ │ │ ├── change-an-object-model.md │ │ │ ├── model-data.md │ │ │ ├── object-models.md │ │ │ ├── relationships.md │ │ │ └── supported-types.md │ │ ├── quick-start.md │ │ ├── realm-files/ │ │ │ ├── bundle-a-realm.md │ │ │ ├── compacting.md │ │ │ ├── configure-and-open-a-realm.md │ │ │ ├── delete-a-realm.md │ │ │ ├── encrypt-a-realm.md │ │ │ ├── realm-files.md │ │ │ └── tvos.md │ │ ├── swift-concurrency.md │ │ ├── swiftui/ │ │ │ ├── configure-and-open-realm.md │ │ │ ├── filter-data.md │ │ │ ├── model-data/ │ │ │ │ ├── change-an-object-model.md │ │ │ │ └── define-a-realm-object-model.md │ │ │ ├── pass-realm-data-between-views.md │ │ │ ├── react-to-changes.md │ │ │ ├── swiftui-previews.md │ │ │ ├── swiftui-tutorial.md │ │ │ └── write.md │ │ ├── swiftui.md │ │ ├── test-and-debug.md │ │ ├── use-realm-with-actors.md │ │ └── xcode-playgrounds.md │ └── install.md ├── examples/ │ ├── README.md │ ├── installation/ │ │ ├── .gitignore │ │ ├── Carthage.xcodeproj/ │ │ │ └── project.pbxproj │ │ ├── CocoaPods.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── App.xcscheme │ │ ├── CocoaPods.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Podfile │ │ ├── Source/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ObjCImport.m │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── SwiftExample.entitlements │ │ │ └── SwiftExampleApp.swift │ │ ├── Static/ │ │ │ ├── StaticExample/ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ ├── LaunchScreen.xib │ │ │ │ │ └── Main.storyboard │ │ │ │ ├── Images.xcassets/ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Info.plist │ │ │ │ └── main.m │ │ │ └── StaticExample.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── StaticExample.xcscheme │ │ ├── SubRealm/ │ │ │ ├── SubRealm.podspec │ │ │ └── SubRealm.swift │ │ ├── SwiftPackageManager.notxcodeproj/ │ │ │ └── project.pbxproj │ │ ├── SwiftPackageManagerDynamic.notxcodeproj/ │ │ │ └── project.pbxproj │ │ ├── XCFramework.xcodeproj/ │ │ │ └── project.pbxproj │ │ └── build.rb │ ├── ios/ │ │ ├── objc/ │ │ │ ├── .gitignore │ │ │ ├── Backlink/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Backlink-Info.plist │ │ │ │ └── main.m │ │ │ ├── Common/ │ │ │ │ └── LaunchScreen.xib │ │ │ ├── Encryption/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Encryption-Info.plist │ │ │ │ ├── LabelViewController.h │ │ │ │ ├── LabelViewController.m │ │ │ │ └── main.m │ │ │ ├── Extension/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Extension.entitlements │ │ │ │ ├── Info.plist │ │ │ │ ├── Tick.h │ │ │ │ ├── Tick.m │ │ │ │ └── main.m │ │ │ ├── GroupedTableView/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Images.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Info.plist │ │ │ │ ├── TableViewController.h │ │ │ │ ├── TableViewController.m │ │ │ │ └── main.m │ │ │ ├── Migration/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Examples/ │ │ │ │ │ ├── Example_v0.h │ │ │ │ │ ├── Example_v1.h │ │ │ │ │ ├── Example_v2.h │ │ │ │ │ ├── Example_v3.h │ │ │ │ │ ├── Example_v4.h │ │ │ │ │ └── Example_v5.h │ │ │ │ ├── Migration-Info.plist │ │ │ │ ├── RealmTemplates/ │ │ │ │ │ ├── default-v0.realm │ │ │ │ │ ├── default-v1.realm │ │ │ │ │ ├── default-v2.realm │ │ │ │ │ ├── default-v3.realm │ │ │ │ │ ├── default-v4.realm │ │ │ │ │ └── default-v5.realm │ │ │ │ └── main.m │ │ │ ├── REST/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── REST-Info.plist │ │ │ │ ├── Venue.h │ │ │ │ ├── Venue.m │ │ │ │ └── main.m │ │ │ ├── RealmExamples.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ └── xcshareddata/ │ │ │ │ └── xcschemes/ │ │ │ │ ├── Backlink.xcscheme │ │ │ │ ├── Encryption.xcscheme │ │ │ │ ├── Extension.xcscheme │ │ │ │ ├── GroupedTableView.xcscheme │ │ │ │ ├── Migration.xcscheme │ │ │ │ ├── REST.xcscheme │ │ │ │ ├── Simple.xcscheme │ │ │ │ ├── TableView.xcscheme │ │ │ │ └── TodayExtension.xcscheme │ │ │ ├── RealmExamples.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ ├── RealmExamples.xcscmblueprint │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ ├── Simple/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Simple-Info.plist │ │ │ │ └── main.m │ │ │ ├── TableView/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Images.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── TableView-Info.plist │ │ │ │ ├── TableViewController.h │ │ │ │ ├── TableViewController.m │ │ │ │ └── main.m │ │ │ └── TodayExtension/ │ │ │ ├── Info.plist │ │ │ ├── MainInterface.storyboard │ │ │ ├── TodayExtension.entitlements │ │ │ ├── TodayViewController.h │ │ │ └── TodayViewController.m │ │ └── swift/ │ │ ├── .gitignore │ │ ├── AppClip/ │ │ │ ├── AppClip.entitlements │ │ │ ├── AppClipApp.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ └── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ ├── AppClipParent/ │ │ │ ├── AppClipParent.entitlements │ │ │ ├── AppClipParentApp.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Constants.swift │ │ │ ├── ContentView.swift │ │ │ ├── DemoObject.swift │ │ │ ├── Info.plist │ │ │ └── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ ├── Backlink/ │ │ │ ├── AppDelegate.swift │ │ │ └── Info.plist │ │ ├── Common/ │ │ │ └── LaunchScreen.xib │ │ ├── Encryption/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Info.plist │ │ │ └── ViewController.swift │ │ ├── GettingStarted.playground/ │ │ │ ├── Contents.swift │ │ │ └── contents.xcplayground │ │ ├── GroupedTableView/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Images.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ └── TableViewController.swift │ │ ├── ListSwiftUI/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ └── Views/ │ │ │ ├── App.swift │ │ │ └── ContentView.swift │ │ ├── Migration/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Examples/ │ │ │ │ ├── Example_v0.swift │ │ │ │ ├── Example_v1.swift │ │ │ │ ├── Example_v2.swift │ │ │ │ ├── Example_v3.swift │ │ │ │ ├── Example_v4.swift │ │ │ │ └── Example_v5.swift │ │ │ ├── Info.plist │ │ │ ├── Migration.xcconfig │ │ │ ├── README.md │ │ │ └── RealmTemplates/ │ │ │ ├── default-v0.realm │ │ │ ├── default-v1.realm │ │ │ ├── default-v2.realm │ │ │ ├── default-v3.realm │ │ │ ├── default-v4.realm │ │ │ └── default-v5.realm │ │ ├── PlaygroundFrameworkWrapper/ │ │ │ ├── Info.plist │ │ │ └── PlaygroundFrameworkWrapper.swift │ │ ├── Projections/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── Info.plist │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ └── ProjectionsApp.swift │ │ ├── RealmExamples.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ ├── AppClip.xcscheme │ │ │ ├── Backlink.xcscheme │ │ │ ├── Encryption.xcscheme │ │ │ ├── GroupedTableView.xcscheme │ │ │ ├── Migration.xcscheme │ │ │ ├── Simple.xcscheme │ │ │ └── TableView.xcscheme │ │ ├── RealmExamples.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ ├── RealmExamples.xcscmblueprint │ │ │ └── WorkspaceSettings.xcsettings │ │ ├── Simple/ │ │ │ ├── AppDelegate.swift │ │ │ └── Info.plist │ │ └── TableView/ │ │ ├── AppDelegate.swift │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── TableViewController.swift │ ├── osx/ │ │ └── objc/ │ │ ├── JSONImport/ │ │ │ ├── Person.h │ │ │ ├── Person.m │ │ │ ├── main.m │ │ │ └── persons.json │ │ ├── RealmExamples.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── JSONImport.xcscheme │ │ └── RealmExamples.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── WorkspaceSettings.xcsettings │ └── tvos/ │ ├── objc/ │ │ ├── DownloadCache/ │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── App Icon & Top Shelf Image.brandassets/ │ │ │ │ │ ├── App Icon - Large.imagestack/ │ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── App Icon - Small.imagestack/ │ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Top Shelf Image.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── RepositoriesViewController.h │ │ │ ├── RepositoriesViewController.m │ │ │ ├── Repository.h │ │ │ ├── Repository.m │ │ │ ├── RepositoryCell.h │ │ │ ├── RepositoryCell.m │ │ │ └── main.m │ │ ├── PreloadedData/ │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── App Icon & Top Shelf Image.brandassets/ │ │ │ │ │ ├── App Icon - Large.imagestack/ │ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── App Icon - Small.imagestack/ │ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Top Shelf Image.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── Place.h │ │ │ ├── Place.m │ │ │ ├── PlacesViewController.h │ │ │ ├── PlacesViewController.m │ │ │ └── main.m │ │ ├── RealmExamples.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ ├── DownloadCache.xcscheme │ │ │ └── PreloadedData.xcscheme │ │ └── RealmExamples.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── WorkspaceSettings.xcsettings │ └── swift/ │ ├── DownloadCache/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── App Icon & Top Shelf Image.brandassets/ │ │ │ │ ├── App Icon - Large.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── App Icon - Small.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Top Shelf Image.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── RepositoriesViewController.swift │ │ ├── Repository.swift │ │ └── RepositoryCell.swift │ ├── PreloadedData/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── App Icon & Top Shelf Image.brandassets/ │ │ │ │ ├── App Icon - Large.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── App Icon - Small.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Top Shelf Image.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── Place.swift │ │ └── PlacesViewController.swift │ ├── RealmExamples.xcodeproj/ │ │ ├── project.pbxproj │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ ├── DownloadCache.xcscheme │ │ └── PreloadedData.xcscheme │ └── RealmExamples.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ ├── RealmExamples.xcscmblueprint │ └── WorkspaceSettings.xcsettings ├── plugin/ │ ├── README.md │ ├── RealmPlugin/ │ │ ├── RLMPRealmPlugin.h │ │ ├── RLMPRealmPlugin.m │ │ ├── RLMPSimulatorManager.h │ │ ├── RLMPSimulatorManager.m │ │ ├── RealmPlugin-Info.plist │ │ ├── RealmPlugin-Prefix.pch │ │ └── en.lproj/ │ │ └── InfoPlist.strings │ ├── RealmPlugin.xcodeproj/ │ │ └── project.pbxproj │ ├── Templates/ │ │ ├── file_templates/ │ │ │ └── Realm Model Object.xctemplate/ │ │ │ ├── Objective-C/ │ │ │ │ ├── ___FILEBASENAME___.h │ │ │ │ └── ___FILEBASENAME___.m │ │ │ ├── Swift/ │ │ │ │ └── ___FILEBASENAME___.swift │ │ │ ├── TemplateIcon.icns │ │ │ └── TemplateInfo.plist │ │ └── install_templates.sh │ └── rlm_lldb.py └── scripts/ ├── create-release-package.rb ├── download-core.sh ├── generate-rlmversion.sh ├── github_release.rb ├── package_examples.rb ├── pr-ci-matrix.rb ├── release-matrix.rb ├── setup-cocoapods.sh └── swift-version.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dir-locals.el ================================================ ;; Project specific Emacs settings ((nil . ((c-basic-offset . 4) (indent-tabs-mode . nil) (c-file-style . "ellemtel") (c-file-offsets . ((innamespace . 0))) (show-trailing-whitespace . t)))) ================================================ FILE: .gitattributes ================================================ CHANGELOG.md merge=union *.mm linguist-language=Objective-C ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: Bug Report description: Report a bug labels: [T-Bug] body: - type: markdown attributes: value: | Please provide as much detail as you can so we have a better chance of fixing the bug quickly. Thanks for your contribution to improve this project! - type: dropdown id: frequency attributes: label: How frequently does the bug occur? options: - -- select -- - Once - Sometimes - Always validations: required: true # Description - type: textarea id: description attributes: label: Description description: | Describe what you were expecting and what actually happened. validations: required: true - type: textarea id: stacktrace attributes: label: Stacktrace & log output description: Please paste any relevant log output or stacktrace if you're getting an exception/crash. render: shell # Repro information - type: dropdown id: repro attributes: label: Can you reproduce the bug? options: - -- select -- - Always - Sometimes - 'No' validations: required: true - type: textarea id: code-snippets attributes: label: Reproduction Steps description: | If you can reproduce the bug, please provide detailed steps for how WE can reproduce it. Ideally, please provide a self contained test case or link (e.g. github repo) to a sample app that demonstrates the bug. If that's not possible, please show code samples that highlight or reproduce the issue. If relevant, include your model definitions. Should you need to share code confidentially, you can send a link to: realm-help (the @) mongodb.com. # Version - type: input id: version attributes: label: Version description: What version(s) of the SDK has the bug been observed in? validations: required: true - type: dropdown id: services attributes: label: What Atlas Services are you using? options: - -- select -- - Local Database only - Atlas Device Sync - 'Atlas App Services: Functions or GraphQL or DataAPI etc' - Both Atlas Device Sync and Atlas App Services validations: required: true - type: dropdown id: encryption attributes: label: Are you using encryption? options: - -- select -- - 'Yes' - 'No' validations: required: true # Environment - type: input id: platform attributes: label: Platform OS and version(s) description: OS and version(s) are you seeing the issue on? validations: required: true - type: textarea id: cocoa-build-environment attributes: label: "Build environment" description: | Build environment versions In the [CONTRIBUTING guidelines](https://github.com/realm/realm-cocoa/blob/master/CONTRIBUTING.md#speeding-things-up-runner), you will find a script, which will help determining some of these versions. # ? Is the script still correct and useful? It seems to output more than needed? value: | Xcode version: ... Dependency manager and version: ... validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: General Questions and Inquiries url: https://www.mongodb.com/community/forums/tags/c/realm-sdks/58/swift about: Please ask general design/architecture questions in the community forums. - name: MongoDB Device Sync Production Issues url: https://support.mongodb.com/ about: Please report urgent production issues to the support portal directly. ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: Feature Request description: Request a new feature or enhancement labels: [T-Enhancement] body: - type: markdown attributes: value: | Thanks for taking the time to suggest improvements to Realm! - type: textarea id: problem attributes: label: Problem description: A clear and concise description of the problem you are trying to solve. validations: required: true - type: textarea id: solution attributes: label: Solution description: Describe the solution you envision, including API and usage example if possible. validations: required: false - type: textarea id: alternative-solution attributes: label: Alternatives description: Describe the alternative solutions or features you have considered validations: required: false - type: dropdown id: importance attributes: label: How important is this improvement for you? options: - -- select -- - Dealbreaker - Would be a major improvement - I would like to have it but have a workaround - Fairly niche but nice to have anyway validations: required: true - type: dropdown id: sync attributes: label: Feature would mainly be used with options: - -- select -- - Local Database only - Atlas Device Sync - 'Atlas App Services: Auth or Functions etc' validations: required: true ================================================ FILE: .github/workflows/build-binaries.yml ================================================ name: Build Core binaries on: workflow_dispatch: inputs: core-version: type: string required: false default: '' description: Core version to use to generate the binaries. It should either be a tag or the version returned by (git describe). If not provided, the version in dependencies.list will be used. jobs: build-packages: runs-on: macos-26 name: Build Core ${{ inputs.core-version }} for ${{ matrix.target }} outputs: core-version: ${{ steps.get-core-version.outputs.version }} strategy: fail-fast: false matrix: target: [macosx, iphoneos, iphonesimulator, appletvos, appletvsimulator, watchos, watchsimulator, maccatalyst, xros, xrsimulator] env: DEVELOPER_DIR: /Applications/Xcode_26.1.1.app/Contents/Developer steps: - uses: actions/checkout@v4 - name: Download additional platforms run: xcodebuild -downloadAllPlatforms - name: Get Core Version id: get-core-version run: | REALM_CORE_VERSION=${{ inputs.core-version }} if [[ -z $REALM_CORE_VERSION ]]; then REALM_CORE_VERSION=$(sed -n 's/^REALM_CORE_VERSION=\(.*\)$/\1/p' dependencies.list) fi echo "version=$REALM_CORE_VERSION" >> "$GITHUB_OUTPUT" - name: Clone Core uses: actions/checkout@v4 with: repository: realm/realm-core path: core submodules: recursive fetch-depth: 0 fetch-tags: true # CMake 3.30 introduced a check which tries to validate that the compiler # supports the requested architextures but it doesn't work. - name: Patch CMake run: sed -i '' 's/CMAKE_HOST_APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin"/CMAKE_HOST_APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_GENERATOR STREQUAL "Xcode"/' /opt/homebrew/Cellar/cmake/*/share/cmake/Modules/CMakeDetermineCompilerABI.cmake - name: Checkout Core@${{ steps.get-core-version.outputs.version }} run: git checkout ${{ steps.get-core-version.outputs.version }} --recurse-submodules -f working-directory: core - name: Build for ${{ matrix.target }} run: sh tools/${{ matrix.target != 'macosx' && format('build-apple-device.sh -p {0} -c Release', matrix.target) || 'build-cocoa.sh -bm' }} -v ${{ steps.get-core-version.outputs.version }} working-directory: core - name: Archive binaries uses: actions/upload-artifact@v4 with: name: binaries-${{ matrix.target }} path: core/realm-Release-*.tar.gz combine-xcframework: runs-on: macos-14 name: Publish xcframework for Core ${{ inputs.core-version }} environment: name: Prebuilds url: ${{ steps.upload-to-s3.outputs.url }} needs: - build-packages steps: - uses: actions/checkout@v4 - name: Clone Core uses: actions/checkout@v4 with: repository: realm/realm-core path: core fetch-depth: 0 fetch-tags: true - name: Checkout Core@${{ needs.build-packages.outputs.core-version }} run: git checkout ${{ needs.build-packages.outputs.core-version }} working-directory: core - name: Download binaries uses: actions/download-artifact@v4 with: path: core - name: Combine xcframework run: | mv binaries-*/* ./ sh tools/build-cocoa.sh -v ${{ needs.build-packages.outputs.core-version }} mkdir ../release mv realm-monorepo-xcframework-${{ needs.build-packages.outputs.core-version }}.tar.xz ../release/ working-directory: core - name: Archive xcframework uses: actions/upload-artifact@v4 with: name: Realm-${{ needs.build-packages.outputs.core-version }}.xcframework.tar.xz path: release - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_S3_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_S3_SECRET_KEY }} aws-region: us-east-1 - name: Upload release folder to S3 id: upload-to-s3 run: | s3_folder="static.realm.io/downloads/core/${{ needs.build-packages.outputs.core-version }}/cocoa" aws s3 sync --acl public-read . "s3://$s3_folder" echo "url=https://$s3_folder/realm-monorepo-xcframework-${{ needs.build-packages.outputs.core-version }}.tar.xz" >> $GITHUB_OUTPUT working-directory: release ================================================ FILE: .github/workflows/build-pr.yml ================================================ # This is a generated file produced by scripts/pr-ci-matrix.rb. name: Pull request build and test on: pull_request: paths-ignore: - '**.md' workflow_dispatch: jobs: docs: runs-on: macos-26 name: Test docs steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: sudo xcode-select -switch /Applications/Xcode_26.3.app - run: bundle exec sh build.sh verify-docs swiftlint: runs-on: macos-26 name: Check swiftlint steps: - uses: actions/checkout@v4 - run: sudo xcode-select -switch /Applications/Xcode_26.3.app - run: brew install swiftlint - run: sh build.sh verify-swiftlint osx-26_1: runs-on: macos-26 name: Test osx on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx osx-26_2: runs-on: macos-26 name: Test osx on Xcode 26.2 env: DEVELOPER_DIR: '/Applications/Xcode_26.2.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx osx-26_3: runs-on: macos-26 name: Test osx on Xcode 26.3 env: DEVELOPER_DIR: '/Applications/Xcode_26.3.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx osx-26_4: runs-on: macos-26 name: Test osx on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx osx-encryption-26_4: runs-on: macos-26 name: Test osx-encryption on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-encryption swiftpm-26_1: runs-on: macos-26 name: Test swiftpm on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm swiftpm-26_4: runs-on: macos-26 name: Test swiftpm on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm swiftpm-debug-26_1: runs-on: macos-26 name: Test swiftpm-debug on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-debug swiftpm-debug-26_2: runs-on: macos-26 name: Test swiftpm-debug on Xcode 26.2 env: DEVELOPER_DIR: '/Applications/Xcode_26.2.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-debug swiftpm-debug-26_3: runs-on: macos-26 name: Test swiftpm-debug on Xcode 26.3 env: DEVELOPER_DIR: '/Applications/Xcode_26.3.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-debug swiftpm-debug-26_4: runs-on: macos-26 name: Test swiftpm-debug on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-debug swiftpm-address-26_4: runs-on: macos-26 name: Test swiftpm-address on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-address swiftpm-thread-26_4: runs-on: macos-26 name: Test swiftpm-thread on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr swiftpm-thread ios-static-26_1: runs-on: macos-26 name: Test ios-static on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-static ios-static-26_4: runs-on: macos-26 name: Test ios-static on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-static ios-26_1: runs-on: macos-26 name: Test ios on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios ios-26_4: runs-on: macos-26 name: Test ios on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios watchos-26_1: runs-on: macos-26 name: Test watchos on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr watchos watchos-26_4: runs-on: macos-26 name: Test watchos on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr watchos tvos-26_1: runs-on: macos-26 name: Test tvos on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr tvos tvos-26_4: runs-on: macos-26 name: Test tvos on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr tvos visionos-26_1: runs-on: macos-26 name: Test visionos on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr visionos visionos-26_4: runs-on: macos-26 name: Test visionos on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr visionos osx-swift-26_1: runs-on: macos-26 name: Test osx-swift on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-swift osx-swift-26_2: runs-on: macos-26 name: Test osx-swift on Xcode 26.2 env: DEVELOPER_DIR: '/Applications/Xcode_26.2.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-swift osx-swift-26_3: runs-on: macos-26 name: Test osx-swift on Xcode 26.3 env: DEVELOPER_DIR: '/Applications/Xcode_26.3.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-swift osx-swift-26_4: runs-on: macos-26 name: Test osx-swift on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-swift ios-swift-26_1: runs-on: macos-26 name: Test ios-swift on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-swift ios-swift-26_4: runs-on: macos-26 name: Test ios-swift on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-swift tvos-swift-26_1: runs-on: macos-26 name: Test tvos-swift on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr tvos-swift tvos-swift-26_4: runs-on: macos-26 name: Test tvos-swift on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr tvos-swift osx-swift-evolution-26_4: runs-on: macos-26 name: Test osx-swift-evolution on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr osx-swift-evolution ios-swift-evolution-26_4: runs-on: macos-26 name: Test ios-swift-evolution on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-swift-evolution tvos-swift-evolution-26_4: runs-on: macos-26 name: Test tvos-swift-evolution on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr tvos-swift-evolution catalyst-26_1: runs-on: macos-26 name: Test catalyst on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr catalyst catalyst-26_4: runs-on: macos-26 name: Test catalyst on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr catalyst catalyst-swift-26_1: runs-on: macos-26 name: Test catalyst-swift on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr catalyst-swift catalyst-swift-26_4: runs-on: macos-26 name: Test catalyst-swift on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr catalyst-swift xcframework-26_4: runs-on: macos-26 name: Test xcframework on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr xcframework cocoapods-osx-26_1: runs-on: macos-26 name: Test cocoapods-osx on Xcode 26.1 env: DEVELOPER_DIR: '/Applications/Xcode_26.1.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-osx cocoapods-osx-26_2: runs-on: macos-26 name: Test cocoapods-osx on Xcode 26.2 env: DEVELOPER_DIR: '/Applications/Xcode_26.2.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-osx cocoapods-osx-26_3: runs-on: macos-26 name: Test cocoapods-osx on Xcode 26.3 env: DEVELOPER_DIR: '/Applications/Xcode_26.3.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-osx cocoapods-osx-26_4: runs-on: macos-26 name: Test cocoapods-osx on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-osx cocoapods-ios-static-26_4: runs-on: macos-26 name: Test cocoapods-ios-static on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-ios-static cocoapods-ios-26_4: runs-on: macos-26 name: Test cocoapods-ios on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-ios cocoapods-watchos-26_4: runs-on: macos-26 name: Test cocoapods-watchos on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-watchos cocoapods-tvos-26_4: runs-on: macos-26 name: Test cocoapods-tvos on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-tvos cocoapods-catalyst-26_4: runs-on: macos-26 name: Test cocoapods-catalyst on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr cocoapods-catalyst ios-swiftui-26_4: runs-on: macos-26 name: Test ios-swiftui on Xcode 26.4 env: DEVELOPER_DIR: '/Applications/Xcode_26.4.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr ios-swiftui ================================================ FILE: .github/workflows/check-changelog.yml ================================================ # NOTE: This is a common file that is overwritten by realm/ci-actions sync service # and should only be modified in that repository. name: "Check Changelog" on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] jobs: changelog: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: false - name: Enforce Changelog uses: dangoslen/changelog-enforcer@c0b9fd225180a405c5f21f7a74b99e2eccc3e951 with: skipLabels: no-changelog missingUpdateErrorMessage: Please add an entry in CHANGELOG.md or apply the 'no-changelog' label to skip this check. ================================================ FILE: .github/workflows/master-push.yml ================================================ name: Prepare Release on: workflow_dispatch: push: branches: - "master" - "release/**" env: XCODE_VERSION: "['26.1.1', '26.2', '26.3', '26.4']" PLATFORM: "['ios', 'osx', 'watchos', 'tvos', 'catalyst', 'visionos']" RELEASE_VERSION: '26.3' DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer jobs: prepare: runs-on: ubuntu-latest name: Prepare outputs outputs: XCODE_VERSIONS_MATRIX: ${{ env.XCODE_VERSION }} PLATFORM_MATRIX: ${{ env.PLATFORM }} VERSION: ${{ steps.get-version.outputs.VERSION }} steps: - name: Compute outputs run: | echo "XCODE_VERSIONS_MATRIX=${{ env.XCODE_VERSION }}" >> $GITHUB_OUTPUT echo "PLATFORM_MATRIX=${{ env.PLATFORM }}" >> $GITHUB_OUTPUT - uses: actions/checkout@v4 - name: Read SDK version id: get-version run: | version="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${GITHUB_WORKSPACE}/dependencies.list")" echo "VERSION=$version" >> $GITHUB_OUTPUT build-docs: runs-on: macos-26 name: Package docs needs: prepare steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Prepare docs for packaging run: bundle exec sh -x build.sh release-package-docs - name: Upload docs to artifacts library uses: actions/upload-artifact@v4 with: name: realm-docs path: docs/realm-docs.zip build-examples: runs-on: macos-26 name: Package examples needs: prepare steps: - uses: actions/checkout@v4 - name: Prepare examples for packaging run: sh -x build.sh release-package-examples - name: Upload examples to artifacts library uses: actions/upload-artifact@v4 with: name: realm-examples path: realm-examples.zip build-product: # Creates framework for each platform, xcode version, target and configuration name: Package framework needs: prepare strategy: max-parallel: 20 fail-fast: false matrix: platform: ${{ fromJSON(needs.prepare.outputs.PLATFORM_MATRIX) }} xcode-version: ${{ fromJSON(needs.prepare.outputs.XCODE_VERSIONS_MATRIX) }} configuration: [swift, static] include: - xcode-version: 26.1.1 xcode-version-tag: 26.1 os: macos-26 - xcode-version: 26.2 xcode-version-tag: 26.2 os: macos-26 - xcode-version: 26.3 xcode-version-tag: 26.3 os: macos-26 - xcode-version: 26.4 xcode-version-tag: 26.4 os: macos-26 exclude: - platform: osx configuration: static - platform: tvos configuration: static - platform: watchos configuration: static - platform: visionos configuration: static - platform: catalyst configuration: static runs-on: ${{ matrix.os }} env: DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode-version}}.app/Contents/Developer steps: - uses: actions/checkout@v4 - name: Install specific platform run: sh build.sh install-xcode-platform "${{matrix.platform}}" - run: sh build.sh ${{matrix.platform}}-${{matrix.configuration}} - run: tar cf build.tar build/*/${{matrix.platform}} - name: Upload framework uses: actions/upload-artifact@v4 with: name: build-${{matrix.platform}}-${{matrix.xcode-version-tag}}-${{matrix.configuration}} path: build.tar compression-level: 1 package-release: runs-on: macos-26 name: Package release file needs: [build-product, prepare] steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 - name: Install the Apple certificate and provisioning profile env: DEVELOPMENT_CERTIFICATE_BASE64: ${{ secrets.DEVELOPMENT_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: sh build.sh install-apple-certificates - name: Create release env: SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }} run: sh -x build.sh release-package - name: Upload release artifactss uses: actions/upload-artifact@v4 with: name: release-package path: pkg/*.zip - name: Upload release for testing uses: actions/upload-artifact@v4 with: name: test-release-package path: pkg/realm-swift-${{ needs.prepare.outputs.VERSION }}.zip test-package-examples: runs-on: macos-26 name: Test examples needs: [package-release, prepare] steps: - uses: actions/checkout@v4 - name: Restore release uses: actions/download-artifact@v4 with: name: test-release-package - name: Test examples run: sh -x build.sh release-test-examples test-ios-static: runs-on: macos-26 name: Run tests on iOS with configuration Static needs: package-release steps: - uses: actions/checkout@v4 - name: Test ios static run: sh -x build.sh test-ios-static test-osx-static: runs-on: macos-26 name: Run tests on macOS needs: package-release steps: - uses: actions/checkout@v4 - name: Test osx static run: | export REALM_DISABLE_METADATA_ENCRYPTION=1 sh -x build.sh test-osx test-installation: runs-on: macos-26 name: Run installation test needs: [package-release, prepare] env: REALM_TEST_BRANCH: "${{github.ref_name}}" strategy: matrix: platform: ${{ fromJSON(needs.prepare.outputs.PLATFORM_MATRIX) }} installation: [cocoapods, spm, carthage] linkage: [dynamic, static] exclude: - platform: visionos - platform: catalyst installation: carthage - installation: carthage linkage: static include: - platform: ios installation: xcframework linkage: static steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Download visionOS if: matrix.platform == 'visionos' run: xcodebuild -downloadPlatform visionOS - name: Restore release uses: actions/download-artifact@v4 if: ${{ matrix.installation == 'xcframework' }} with: name: test-release-package - name: Run installation test run: | find . -name '*.zip' -depth 1 -exec mv {} examples/installation \; cd examples/installation bundle exec ./build.rb ${{ matrix.platform }} ${{ matrix.installation }} ${{ matrix.linkage }} test-installation-xcframework: name: Run installation test for xcframework needs: [package-release, prepare] strategy: matrix: xcode-version: ${{ fromJSON(needs.prepare.outputs.XCODE_VERSIONS_MATRIX) }} include: - xcode-version: 26.1 os: macos-26 - xcode-version: 26.2 os: macos-26 - xcode-version: 26.3 os: macos-26 - xcode-version: 26.4 os: macos-26 env: PLATFORM: 'osx' DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode-version }}.app/Contents/Developer REALM_TEST_BRANCH: "${{github.ref_name}}" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Restore release uses: actions/download-artifact@v4 with: name: test-release-package - name: Run installation test run: | find . -name '*.zip' -depth 1 -exec mv {} examples/installation \; cd examples/installation bundle exec ./build.rb osx xcframework dynamic ================================================ FILE: .github/workflows/publish-release.yml ================================================ name: Publish release on: workflow_dispatch env: XCODE_VERSION: "['26.1', '26.2', '26.3']" TEST_XCODE_VERSION: '26.3' jobs: prepare: runs-on: ubuntu-latest name: Prepare outputs outputs: XCODE_VERSIONS_MATRIX: ${{ env.XCODE_VERSION }} VERSION: ${{ steps.get-version.outputs.VERSION }} steps: - uses: actions/checkout@v4 - name: Compute outputs run: | echo "XCODE_VERSIONS_MATRIX=${{ env.XCODE_VERSION }}" >> $GITHUB_OUTPUT - name: Read SDK version id: get-version run: | version="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${GITHUB_WORKSPACE}/dependencies.list")" echo "VERSION=$version" >> $GITHUB_OUTPUT tag-release: runs-on: ubuntu-latest name: Tag Release needs: prepare steps: - uses: actions/checkout@v4 - uses: rickstaa/action-create-tag@v1 id: "tag_create" with: tag: "v${{ needs.prepare.outputs.VERSION }}" tag_exists_error: false message: "" create-release: runs-on: macos-26 name: Create github release needs: [tag-release, prepare] env: GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Create Github release run: bundle exec ./build.sh publish-github ${{ github.sha }} publish-cocoapods: runs-on: macos-26 name: Publish Cocoapods specs needs: [tag-release, prepare] env: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Publish run: bundle exec ./build.sh publish-cocoapods v${{ needs.prepare.outputs.VERSION }} update-checker: runs-on: macos-26 name: Update to latest version update checker file needs: tag-release env: AWS_ACCESS_KEY_ID: ${{ secrets.UPDATE_CHECKER_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.UPDATE_CHECKER_SECRET_KEY }} steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: brew install s3cmd - run: bundle exec ./build.sh publish-update-checker test-installation: runs-on: macos-26 name: Run installation test for ${{ matrix.platform }}, ${{ matrix.installation }} and ${{ matrix.linkage }} needs: [create-release, prepare, publish-cocoapods] strategy: fail-fast: false matrix: platform: [ios, osx, watchos, tvos, catalyst, visionos] installation: [cocoapods, spm, carthage, xcframework] linkage: [static, dynamic] exclude: - installation: carthage linkage: static - platform: catalyst installation: carthage - platform: osx installation: xcframework linkage: static - platform: watchos installation: xcframework linkage: static - platform: tvos installation: xcframework linkage: static - platform: catalyst installation: xcframework linkage: static - platform: visionos installation: xcframework linkage: static - platform: catalyst installation: carthage linkage: static - platform: visionos installation: carthage - platform: visionos installation: cocoapods steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: ${{ env.TEST_XCODE_VERSION }} - name: Run installation test uses: nick-fields/retry@v3 env: REALM_TEST_RELEASE: ${{ needs.prepare.outputs.VERSION }} with: command: | cd examples/installation bundle exec ./build.rb ${{ matrix.platform }} ${{ matrix.installation }} ${{ matrix.linkage }} timeout_minutes: 30 max_attempts: 10 retry_wait_seconds: 60 retry_on: error ================================================ FILE: .gitignore ================================================ *~ .DS_Store # Merge files *.orig # Binaries *.dylib *.a *.o *.d *.libdeps *.zip *.realm !examples/ios/swift/Migration/RealmTemplates/*.realm !examples/ios/objc/Migration/RealmTemplates/*.realm *.realm.lock # core core core-* realm-core-*-xcframework # sh build.sh test build/ # sh build.sh cocoapods-setup /include # sh build.sh docs /docs/objc_output /docs/swift_output /Realm/RLMPlatform.h # XCode *.bak xcuserdata/ project.xcworkspace *.xccheckout DerivedData /.build # AppCode .idea/ *.iml # backup and crash files *.swp # xcpretty build.log # ruby *.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /test/tmp/ /test/version_tmp/ /tmp/ ## Carthage # Cartfiles are ignored because they're generated on demand in the installation examples Cartfile Carthage ## Swift Version SwiftVersion.swift examples/ios/objc/Draw/Constants.h ## Sync testing /ci_scripts/setup_baas/.baas ## Swiftpm .swiftpm .build Package.resolved examples/installation/ios/swift/SwiftPackageManagerExample/SwiftPackageManagerExample.xcodeproj ================================================ FILE: .ruby-version ================================================ 3.3.4 ================================================ FILE: .swiftlint.yml ================================================ included: - Realm/ObjectServerTests - RealmSwift - Realm/Swift - examples/installation/watchos/swift - examples/installation/osx/swift - examples/installation/ios/swift - examples/ios/swift - examples/tvos/swift type_name: allowed_symbols: - _ identifier_name: allowed_symbols: - _ min_length: # not possible to disable this partial rule, so set it to zero warning: 0 error: 0 excluded: - id - pk - to disabled_rules: - blanket_disable_command - block_based_kvo # SwiftLint considers 'Realm' and 'Realm.Private' to be duplicate imports # because we're using submodules in an unsual way, and normally the parent # module re-exports all of its children. - duplicate_imports - file_length - force_cast - force_try - function_body_length - function_parameter_count - line_length - nesting - syntactic_sugar - todo - trailing_comma - type_body_length - vertical_whitespace # swiftlint complains about superfluous disable commands when the violation # occurs in an inactive #if and doesn't support conditionally disabling it - cyclomatic_complexity # #unavailable was implemented in Swift 5.6 so we can't use it until that's # the minimum version we support - unavailable_condition ================================================ FILE: CHANGELOG.md ================================================ 20.0.4 Release notes (2026-02-22) ============================================================= * Update build scripts for Xcode 26. * Drop support for Xcode < 26. * Prebuilt binaries are no longer code signed as Realm is no longer officially distributed by MongoDB. * Fix compilation with Xcode 26.4. ### Compatibility * Carthage release for Swift is built with Xcode 26.3. * CocoaPods: 1.10 or later. * Xcode: 26.1-26.4 20.0.3 Release notes (2025-06-15) ============================================================= ### Enhancements * Update build scripts for Xcode 16.4. * Add support for building with Xcode 26 beta 1. ### Compatibility * Carthage release for Swift is built with Xcode 16.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-26 beta 1. 20.0.2 Release notes (2025-04-14) ============================================================= ### Enhancements * Update build scripts for Xcode 16.3. ### Compatibility * Carthage release for Swift is built with Xcode 16.3.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-16.3. 20.0.1 Release notes (2024-12-27) ============================================================= ### Enhancements * Update build scripts for Xcode 16.2. ### Fixed * A query with a number of predicates ORed together may result in a crash on some platforms (strict weak ordering check failing on iphone) ([#8028](https://github.com/realm/realm-core/issues/8028), since v10.50.0). ### Compatibility * Realm Studio: 15.0.0 or later. * Carthage release for Swift is built with Xcode 16.2.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-16.2. ### Internal * Upgraded realm-core from v20.0.0 to 20.1.0 20.0.0 Release notes (2024-09-09) ============================================================= The minimum supported version of Xcode is now 15.3. ### Enhancements * Build in Swift 6 language mode when using Xcode 16. Libraries build in Swift 6 mode can be consumed by apps built in Swift 5 mode, so this should not have any immediate effects beyond eliminating some warnings and ensuring that all Realm APIs can be used in Swift 6 mode. Some notes about using Realm Swift in Swift 6: - `try await Realm(actor: actor)` has been replaced with `try await Realm.open()` to work around isolated parameters not being implemented for initializers (https://github.com/swiftlang/swift/issues/71174). The actor is now automatically inferred and should not be manually passed in. - `@ThreadSafe` is not usable as a property wrapper on local variables and function arguments in Swift 6 mode. Sendability checking for property wrappers never got implemented due to them being quietly deprecated in favor of macros. It can still be used as a property wrapper for class properties and as a manual wrapper locally, but note that it does not combine well with actor-isolated Realms. * Some SwiftUI components are now explicitly marked as `@MainActor`. These types were implicitly `@MainActor` in Swift 5, but became nonisolated when using Xcode 16 in Swift 5 mode due to the removal of implicit isolation when using property wrappers on member variables. This resulted in some new sendability warnings in Xcode 16 (or errors in Swift 6 mode). * Add Xcode 16 and 16.1 binaries to the release packages (currently built with beta 6 and beta 1 respectively). ### Breaking Changes * All Atlas App Services and Atlas Device Sync functionality has been removed. Users of Atlas Device Sync should pin to v10. * Queries on AnyRealmValue properties previously considered strings to be equivalent to Data containing the UTF-8 encoded string. Strings and Data are now considered different types and queries for one of them will not match the other. * Realms are no longer autoreleased when initialized. This means that code along the lines of the following will no longer work: ```Swift try! Realm().beginWrite() try! Realm().create(MyObject.self, value: ...) try! Realm().commitWrite() ``` This was a pattern which was somewhat natural with the original version of the objective-c API, but only worked in debug builds and would fail in release builds. We decided to make it consistently work by forcing the Realm to be autoreleased rather than let users write code which appeared to work but was actually broken. In modern Swift this code is very strange, and autoreleasing the Realm made it much more difficult to ensure that the file is actually closed at predictable times. Realms are now returned retained in both debug and release modes, so this will always break rather than appearing to work. Note that there is still a weak cache of Realms and `Realm()` will still return a reference to the existing Realm if there is one open on the current thread. * Iterating a Map now produces the tuple `(key: KeyType, value: ValueType)` rather than a very similar struct, and `.asKeyValueSequence()` has been removed. This aligns `Map` with `Dictionary` and makes many operations defined by `Sequence` work on `Map`. * Passing an empty array for notification keypaths to filter on (e.g. `obj.observe(keyPaths: [])`) was treated the same as passing `nil`, i.e. no filtering was done. It now instead observes no keypaths. For objects this means it will only report the object being deleted, and for collections it will only report collection-level changes and not changes to the objects inside the collection. * `Decimal128(string:)` was marked as `throws`, but it never actually threw an error and instead returned `NaN` if the string could not be parsed as a decimal128. That behavior was kept and it is no longer marked as `throws`. ### Compatibility * Realm Studio: 15.0.0 or later. * Carthage release for Swift is built with Xcode 15.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.3.0-16.1 beta. ### Internal * Upgraded realm-core from v14.12.1 to v20.0.0. 10.53.1 Release notes (2024-09-05) ============================================================= ### Enhancements * Add the file path to the exception thrown by File::rw_lock() when it fails to open the file. ([Core #7999](https://github.com/realm/realm-core/issues/7999)) ### Fixed * Filtering notifications with a LinkingObjects property as the final element could sometimes give wrong results ([Core #7530](https://github.com/realm/realm-core/issues/7530), since v10.11.0) * Fix a potential crash during process termination when Logger log level is set higher than Info. ([Core #7969](https://github.com/realm/realm-core/issues/7969), since v10.45.0) * The check for maximum path length was incorrect and lengths between 240 and 250 would fail to use the hashed fallback ([Core #8007](https://github.com/realm/realm-core/issues/8007), since v10.0.0). * API misuse resulting in an exception being thrown from within a callback would sometimes terminate due to hitting `REALM_UNREACHABLE()` rather than the exception being propagated to the caller ([Core #7836](https://github.com/realm/realm-core/issues/7836)). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-16 beta 5. ### Internal * Upgraded realm-core from v14.12.0 to 14.12.1 10.53.0 Release notes (2024-08-20) ============================================================= ### Enhancements * Code sign our published xcframeworks. By Apple's requirements, we should sign our release binaries so Xcode can validate it was signed by the same developer on every new version. ([Apple](https://developer.apple.com/support/third-party-SDK-requirements/)). * Report sync warnings from the server such as sync being disabled server-side to the sync error handler. ([#8020](https://github.com/realm/realm-swift/issues/8020)). * Add support for string comparison queries, which allows building string queries with the following operators (`>`, `>=`, `<`, `<=`). This is a case sensitive lexicographical comparison. ([#8008](https://github.com/realm/realm-swift/issues/8008)). ### Fixed * `-[RLMAsymmetricObject createObject:withValue:]` was marked as having a non-null return value despite always returning `nil` (since v10.29.0). * Eliminate several clang static analyzer warnings which did not report actual bugs. * The async and Future versions of `User.functions` only worked for functions which took exactly one argument, which had to be an array ([#8669](https://github.com/realm/realm-swift/issues/8669), since 10.16.0). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-16 beta 5. 10.52.3 Release notes (2024-08-09) ============================================================= ### Enhancements * Improve performance of bulk object creation when the objects have embedded objects. This is particularly significant for applying sync bootstraps. ([Core #7945](https://github.com/realm/realm-core/issues/7945)) * Client reset cycle detection now checks if the previous recovery attempt was made by the same version of Realm, and if not attempts recovery again ([Core #7944](https://github.com/realm/realm-core/pull/7944)). ### Fixed * App change notifications were being sent too soon when a new user was logged in, resulting in the user's profile being empty if it was read from within the change notification (since v10.51.0). * A conflict resolution bug related to ArrayErase and Clear instructions could sometimes cause an "Invalid prior_size" exception when synchronizing ([Core #7893](https://github.com/realm/realm-core/issues/7893), since v10.51.0). * Sync merges which resulted in a changeset's reciprotal transformation being empty were handled incorrectly, possibly resulting in data divergence. No instances of this actually happening have been reported. ([Core #7955](https://github.com/realm/realm-core/pull/7955), since v10.51.0) * `Realm.writeCopy()` would sometimes incorrectly throw an exception claiming that there were unuploaded local changes when the source Realm is a synchronized Realm ([Core #7966](https://github.com/realm/realm-core/issues/7966), since v10.7.6). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-16 beta 5. ### Internal * Upgraded realm-core from v14.11.1 to 14.12.0 10.52.2 Release notes (2024-07-19) ============================================================= ### Enhancements * Server-side role and permissions changes no longer require a client reset to update the local Realm. ([Core #7440](https://github.com/realm/realm-core/pull/7440)) ### Fixed * Deleting an object with a `List> 1) == key.value` when removing links to an object (either by reassigning the link or by deleting the object). This could happen if the link came from a collection inside a `AnyRealmValue`, any `Map`, or a `List`, and there were more than 256 objects of the type which contained the link. ([Core #7594](https://github.com/realm/realm-core/issues/7594), since v10.8.0) * Fix the assertion failure `array.cpp:319: Array::move() Assertion failed: begin <= end [2, 1]` when deleting objects containing collections nested inside a `AnyRealmValue` when this caused bptree leaves to be merged. ()[Core #7839](https://github.com/realm/realm-core/issues/7839), since v10.51.0). * `SyncSession.wait(for .upload)` was inconsistent in how it handled commits which do no produce any changesets to upload (such as modifying flexible sync subscriptions). Previously if all unuploaded commits had empty changesets and the session had never completed a download it would wait for download completion, and otherwise it would complete immediate. It now always completes immediately. ([Core #7796](https://github.com/realm/realm-core/pull/7796)). * The sync client could hit an assertion failure if a session is resumed while the session is being suspended. ([Core #7860](https://github.com/realm/realm-core/issues/7860), since v10.27.0) * If a sync session was interrupted by a disconnect or restart while downloading a bootstrap (a set of downloads caused by adding or changing a query subscription), stale data from the previous bootstrap could be included when the session reconnected and completed downloading the bootstrap. This could lead to objects stored in the database that do not match the actual state of the server and potentially leading to compensating writes. ([Core #7827](https://github.com/realm/realm-core/issues/7827), since v10.27.0) * Fixed unnecessary server roundtrips when there was no download to acknowledge ([Core #2129](https://jira.mongodb.org/browse/RCORE-2129), since v10.51.0). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-16 beta 3. ### Internal * Upgraded realm-core from v14.10.2 to 14.11.0 10.52.1 Release notes (2024-06-28) ============================================================= ### Fixed * Realm compaction (triggered by setting `shouldCompactOnLaunch`) on an encrypted Realm file could produce an invalid file unless the encryption key happened to be a valid nul-terminated string. ([Core #7842](https://github.com/realm/realm-core/issues/7842), since v10.52.0. * Assigning a List or Dictionary to an AnyRealmValue property which already stored that type of collection would only emit a clear instruction if the collection was not already empty. This meant that assigning to the property on two different clients would merge the collections if the property initially stored an empty collection, but would pick one of the two assignments to win if it was initially non-empty. If merging is the desired behavior, appending to the List rather than assigning a new List will still achieve that ([Core #7809](https://github.com/realm/realm-core/issues/7809), since v10.51.0). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-15.4.0. ### Internal * Upgraded realm-core from v14.10.1 to 14.10.2 10.52.0 Release notes (2024-06-18) ============================================================= ### Enhancements * Add `@ObservedSectionedResults.remove(atOffsets:section:)` which adds the ability to remove a Realm Object when using `onDelete` on `ForEach` in a SwiftUI `List`. * Add support for Xcode 16 beta 1 and fix some of the new warnings. Note that this does not yet include full support for Swift 6 language mode ([#8618](https://github.com/realm/realm-swift/pull/8618)). * `Realm.asyncWrite()` and `Realm.asyncRefresh()` now use the new `#isolation` feature to avoid sendability warnings when building with Xcode 16 ([#8618](https://github.com/realm/realm-swift/pull/8618)). * Include the originating client reset error message in errors reporting that automatic client reset handling failed. ([Core #7761](https://github.com/realm/realm-core/pull/7761)) * Improve the performance of insertion-heavy write transactions, particularly when setting a large number of properties on each object created ([Core #7734](https://github.com/realm/realm-core/pull/7734)). * App now trims trailing slashes from the base url rather than producing confusing 404 errors. ([Core #7791](https://github.com/realm/realm-core/pull/7791)). ### Fixed * Deleting a Realm Object used in a `@ObservedSectionedResults` collection in `SwiftUI` would cause a crash during the diff on the `View`. ([#8294](https://github.com/realm/realm-swift/issues/8294), since v10.29.0) * Fix some client resets (such as migrating to flexible sync) potentially failing if a new client reset condition (such as rolling back a flexible sync migration) occurred before the first one completed. ([Core #7542](https://github.com/realm/realm-core/pull/7542), since v10.40.0) * The encryption code no longer behaves differently depending on the system page size, which should entirely eliminate a recurring source of bugs related to copying encrypted Realm files between platforms with different page sizes. One known outstanding bug was ([RNET-1141](https://github.com/realm/realm-dotnet/issues/3592)), where opening files on a system with a larger page size than the writing system would attempt to read sections of the file which had never been written to ([Core #7698](https://github.com/realm/realm-core/pull/7698)). * There were several complicated scenarios which could result in stale reads from encrypted files in multiprocess scenarios. These were very difficult to hit and would typically lead to a crash, either due to an assertion failure or DecryptionFailure being thrown ([Core #7698](https://github.com/realm/realm-core/pull/7698), since v10.38.0). * Encrypted files have some benign data races where we can memcpy a block of memory while another thread is writing to a limited range of it. It is logically impossible to ever read from that range when this happens, but Thread Sanitizer quite reasonably complains about this. We now perform a slower operations when running with TSan which avoids this benign race ([Core #7698](https://github.com/realm/realm-core/pull/7698)). * `Realm.asyncOpen()` on a flexible sync Realm would sometimes fail to wait for pending subscriptions to complete, resulting in it not actually waiting for all data to be downloaded. ([Core #7720](https://github.com/realm/realm-core/issues/7720), since flexible sync was introduced). * `List.clear()` would hit an assertion failure when used on a file originally created by a version of Realm older than v10.49.0. ([Core #7771](https://github.com/realm/realm-core/issues/7771), since 10.49.0) ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-16 beta ### Internal * Upgraded realm-core from v14.9.0 to 14.10.1 10.51.0 Release notes (2024-06-06) ============================================================= ### Enhancements * Added support for storing nested collections (List and Map not ManagedSet) in a `AnyRealmValue`. ```swift class MixedObject: Object { @Persisted var anyValue: AnyRealmValue } // You can build a AnyRealmValue from a Swift's Dictionary. let dictionary: Dictionary = ["key1": .string("hello"), "key2": .bool(false)] // You can build a AnyRealmValue from a Swift's Map // and nest a collection within another collection. let list: Array = [.int(12), .double(14.17), AnyRealmValue.fromDictionary(dictionary)] let realm = realmWithTestPath() try realm.write { let obj = MixedObject() obj.anyValue = AnyRealmValue.fromArray(list) realm.add(obj) } ``` * Added new operators to Swift's Query API for supporting querying nested collections. ```swift realm.objects(MixedObject.self).where { $0.anyValue[0][0][1] == .double(76.54) } ``` The `.any` operator allows looking up in all keys or indexes. ```swift realm.objects(MixedObject.self).where { $0.anyValue["key"].any == .bool(false) } ``` * Report the originating error that caused a client reset to occur. ([Core #6154](https://github.com/realm/realm-core/issues/6154)) ### Fixed * Accessing `App.currentUser` from within a notification produced by `App.switchToUser()` (which includes notifications for a newly logged in user) would deadlock. ([Core #7670](https://github.com/realm/realm-core/issues/7670), since v10.50.0). * Inserting the same link to the same key in a dictionary more than once would incorrectly create multiple backlinks to the object. This did not appear to cause any crashes later, but would have affecting explicit backlink count queries and possibly notifications. ([Core #7676](https://github.com/realm/realm-core/issues/7676), since v10.49.2). * A non-streaming progress notifier would not immediately call its callback after registration. Instead you would have to wait for a download message to be received to get your first update - if you were already caught up when you registered the notifier you could end up waiting a long time for the server to deliver a download that would call/expire your notifier ([Core #7627](https://github.com/realm/realm-core/issues/7627), since v10.50.0). * After compacting, a file upgrade would be triggered. This could cause loss of data if `deleteRealmIfMigrationNeeded` is set to true. ([Core #7747](https://github.com/realm/realm-core/issues/7747), since v10.49.0). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-15.4.0. ### Internal * Upgraded realm-core from v14.6.2 to 14.9.0 10.50.1 Release notes (2024-05-21) ============================================================= ### Enhancements * Update release packaging for Xcode 15.4. ### Fixed * `@AutoOpen` and `@AsyncOpen` failed to use the `initialSubscriptions` set in the configuration passed to them ([PR #8572](https://github.com/realm/realm-swift/pull/8572), since v10.50.0). * `App.baseURL` was always `nil` ([PR #8573](https://github.com/realm/realm-swift/pull/8573), since it was introduced in v10.50.0). ### Compatibility * Realm Studio: 15.0.0 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.4.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-15.4.0. 10.50.0 Release notes (2024-05-02) ============================================================= Drop support for Xcode 14, as it can no longer be used to submit app to the app store. Xcode 15.1 is now the minimum supported version. ### Known Issues * Accessing `App.currentUser` within an `App.subscribe` callback would lead to a deadlock. ### Enhancements * Added `SyncConfiguration.initialSubscriptions` which describes the initial subscription configuration that was passed when constructing the `SyncConfiguration`. ([#8548](https://github.com/realm/realm-swift/issues/8548)) * When connecting to multiple server apps, a unique encryption key is used for each of the metadata Realms rather than sharing one between them ([Core #7552](https://github.com/realm/realm-core/pull/7552)). * Improve perfomance of IN queries and chained OR equality queries for UUID/ObjectId types. ([.Net #3566](https://github.com/realm/realm-dotnet/issues/3566)) * Added support for updating Atlas Device Sync's base url, in case the need to roam between servers (cloud and/or edge server). This API is private and can only be imported using `@_spi(Private)` ```swift @_spi(RealmSwiftExperimental) import RealmSwift try await app.updateBaseUrl(to: "https://services.cloud.mongodb.com") ``` ([#8486](https://github.com/realm/realm-swift/issues/8486)). * Enable building RealmSwift as a dynamic framework when installing via SPM, which lets us supply a privacy manifest. When RealmSwift is built as a static library you must supply your own manifest, as Xcode does not build static libraries in a way compatible with xcprivacy embedding. Due to some bugs in Xcode, this may require manual changes to your project: - Targets must now depend on only Realm or RealmSwift. If you use both the obj-c and swift API, depending on RealmSwift will let you import Realm. Trying to directly depend on both will give the error "Swift package target 'Realm' is linked as a static library by 'App' and 'Realm', but cannot be built dynamically because there is a package product with the same name." - To actually build RealmSwift as a dynamic framework, change "Do Not Embed" to "Embed & Sign" in the "Frameworks, Libraries, and Embedded Content" section on the General tab of your target's settings. ([#8561](https://github.com/realm/realm-swift/pull/8561)). * The `transferredBytes` and `transferrableBytes` fields on `Progress` have been deprecated in favor of `progressEstimate` which is a value between 0.0 and 1.0 indicating the estimated progress toward the upload/download transfer. ([#8476](https://github.com/realm/realm-swift/issues/8476)) ### Fixed * `-[RLMUser allSessions]` did not include sessions which were currently waiting for an access token despite including sessions in other non-active states. ([Core #7300](https://github.com/realm/realm-core/pull/7300), since v10.0.0). * `[RLMApp allUsers]` included users which were logged out during the current run of the app, but not users which had previously been logged out. It now always includes all logged out users. ([Core #7300](https://github.com/realm/realm-core/pull/7300), since v10.0.0). * Deleting the active user (via `User.delete()`) left the active user unset rather than selecting another logged-in user as the active user like logging out and removing users does. ([Core #7300](https://github.com/realm/realm-core/pull/7300), since v10.23.0). * Fixed several issues around copying an encrypted Realm between platforms with different page sizes (such as between x86_64 and arm64 Apple platforms): - Fixed `Assertion failed: new_size % (1ULL << m_page_shift) == 0` when opening an encrypted Realm less than 64Mb that was generated on a platform with a different page size than the current platform. ([Core #7322](https://github.com/realm/realm-core/issues/7322), since v10.42.0) - Fixed a `DecryptionFailed` exception thrown when opening a small (<4k of data) Realm generated on a device with a page size of 4k if it was bundled and opened on a device with a larger page size (since the beginning). - Fixed an issue during a subsequent open of an encrypted Realm for some rare allocation patterns when the top ref was within ~50 bytes of the end of a page. This could manifest as a DecryptionFailed exception or as an assertion: `encrypted_file_mapping.hpp:183: Assertion failed: local_ndx < m_page_state.size()`. ([Core #7319](https://github.com/realm/realm-core/issues/7319)) * Schema initialization could hit an assertion failure if the sync client applied a downloaded changeset while the Realm file was in the process of being opened ([#7041](https://github.com/realm/realm-core/issues/7041), since v10.15.0). * The reported download progress for flexible sync Realms was incorrect. It is now replaced by a progress estimate, which is derived by the server based on historical data and other heuristics. ([#8476](https://github.com/realm/realm-swift/issues/8476)) ### Deprecations * `rlm_valueType` is deprecated in favour of `rlm_anyValueType` which now includes collections (List and Dictionary). ### Compatibility * Realm Studio: 15.0.0 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 15.1.0-15.3.0. ### Internal * Upgraded realm-core from v14.5.2 to 14.6.2 10.49.2 Release notes (2024-04-17) ============================================================= ### Enhancements * The default base url in `AppConfiguration` has been updated to point to `services.cloud.mongodb.com`. See https://www.mongodb.com/docs/atlas/app-services/domain-migration/ for more information. ([#8512](https://github.com/realm/realm-swift/issues/8512)) ### Fixed * Fixed a crash that would occur when an http error 401 or 403 is returned upon opening a watch stream for a MongoDB collection. ([#8519](https://github.com/realm/realm-swift/issues/8519)) * Fix an assertion failure "m_lock_info && m_lock_info->m_file.get_path() == m_filename" that appears to be related to opening a Realm while the file is in the process of being closed on another thread. ([#8507](https://github.com/realm/realm-swift/issues/8507)) * Fixed diverging history due to a bug in the replication code when setting default null values (embedded objects included). ([Core #7536](https://github.com/realm/realm-core/issues/7536)) * Null pointer exception may be triggered when logging out and async commits callbacks not executed. ([Core #7434](https://github.com/realm/realm-core/issues/7434)) * `AppConfiguration.baseUrl` will now return the default value of the url when not set rather than `nil`. ([#8512](https://github.com/realm/realm-swift/issues/8512)) * Added privacy manifest to Core's Swift package ([Swift #8535](https://github.com/realm/realm-swift/issues/8535)) * Fixed crash when integrating removal of already removed dictionary key ([Core #7488](https://github.com/realm/realm-core/issues/7488), since v10.0.0) ### Compatibility * Realm Studio: 15.0.0 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 14.2-15.3.0. ### Internal * Upgraded realm-core from 14.4.1 to 14.5.2 10.49.1 Release notes (2024-03-22) ============================================================= ### Enhancements * Improve file compaction performance on arm64 platforms for encrypted files between 16kB and 4MB in size. ([PR #7492](https://github.com/realm/realm-core/pull/7492)). ### Fixed * Opening a Realm with a cached user while offline would fail to retry some steps of the connection process and instead report a fatal error. ([#7349](https://github.com/realm/realm-core/issues/7349), since v10.46.0) ### Compatibility * Realm Studio: 15.0.0 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 14.2-15.3.0. ### Internal * Upgraded realm-core from v14.3.0 to 14.4.1 10.49.0 Release notes (2024-03-22) ============================================================= This version introduces a new Realm file format version (v24). Opening existing Realm files will automatically upgrade the files, making them unable to be opened by older versions. This upgrade process should typically be very fast unless you have large Sets of AnyRealmValue, String, or Data, which have to be rewritten. A backup will automatically be created next to the Realm before performing the upgrade. Downgrading to older versions of Realm will attempt to automatically restore the backup, or it will be deleted after three months. ### Enhancements * Storage of Decimal128 properties has been optimised similarly to Int properties so that the individual values will take up 0 bits (if all nulls), 32 bits, 64 bits or 128 bits depending on what is needed. ([Core #6111](https://github.com/realm/realm-core/pull/6111)) ### Fixed * Sorting on binary Data was done by comparing bytes as signed char rather than unsigned char, resulting in very strange orders (since sorting on Data was enabled in v6.0.4) * Sorting on AnyRealmValue did not use a valid total ordering, and certain combinations of values could result in values not being sorted or potentially even crashes. The resolution for this will result in some previously-valid combinations of values of different types being sorted in different orders than previously (since the introduction of AnyRealmValue in 10.8.0). * RLMSet/MutableSet was inconsistent about if it considered a String and a Data containing the utf-8 encoded bytes of that String to be equivalent. They are now always considered distinct. (since the introduction of sets in v10.8.0). * Equality queries on a Mixed property with an index could sometimes return incorrect results if values of different types happened to have the same hash code. ([Core 6407](https://github.com/realm/realm-core/issues/6407) since v10.8.0). * Creating more than 8388606 links pointing to a single object would crash. ([Core #6577](https://github.com/realm/realm-core/issues/6577), since v5.0.0) * A Realm generated on a non-apple ARM 64 device and copied to another platform (and vice-versa) were non-portable due to a sorting order difference. This impacts strings or binaries that have their first difference at a non-ascii character. These items may not be found in a set, or in an indexed column if the strings had a long common prefix (> 200 characters). ([Core #6670](https://github.com/realm/realm-core/pull/6670), since 2.0.0 for indexes, and since since the introduction of sets in v10.8.0) * Fix a spurious crash related to opening a Realm on background thread while the process was in the middle of exiting ([Core #7420](https://github.com/realm/realm-core/pull/7420)). ### Breaking Changes * Drop support for opening pre-v5.0.0 Realm files. ### Compatibility * Realm Studio: 15.0.0 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 14.2-15.3.0. Note that we will be dropping support for Xcode 14 when Apple begins requiring Xcode 15 for app store submissions on April 29. ### Internal * Upgraded realm-core from 13.26.0 to 14.3.0 10.48.1 Release notes (2024-03-15) ============================================================= ### Fixed * The Realm.framework privacy manifest was missing NSPrivacyAccessedAPICategoryDiskSpace, but we check free disk space before attempting to automatically back up Realm files (since 10.46.0). ### 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 14.2-15.3.0. 10.48.0 Release notes (2024-03-07) ============================================================= ### Enhancements * Lifted a limitation that would prevent declaring a model with only computed properties. ([#8414](https://github.com/realm/realm-swift/issues/8414)) * Add Xcode 15.3 to the release package ([PR #8502](https://github.com/realm/realm-swift/pull/8502)). ### Fixed * Fix multiple arguments support via the `REALM_EXTRA_BUILD_ARGUMENTS` environment variable in `build.sh`. ([PR #8413](https://github.com/realm/realm-swift/pulls/8413)). Thanks, [@hisaac](https://github.com/hisaac)! * Fix some of the new sendability warnings introduced in Xcode 15.3 ([PR #8502](https://github.com/realm/realm-swift/pull/8502)). ### 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.3.0. * CocoaPods: 1.10 or later. * Xcode: 14.2-15.3.0. 10.47.0 Release notes (2024-02-12) ============================================================= ### Enhancements * Added initial support for geospatial queries on points. There is no new dedicated type to store Geospatial points, instead points should be stored as ([GeoJson-shaped](https://www.mongodb.com/docs/manual/reference/geojson/)) embedded object, as the example below: ```swift public class Location: EmbeddedObject { @Persisted private var coordinates: List @Persisted private var type: String = "Point" public var latitude: Double { return coordinates[1] } public var longitude: Double { return coordinates[0] } convenience init(_ latitude: Double, _ longitude: Double) { self.init() // Longitude comes first in the coordinates array of a GeoJson document coordinates.append(objectsIn: [longitude, latitude]) } } ``` Geospatial queries (`geoWithin`) can only be executed on such a type of objects and will throw otherwise. The queries can be used to filter objects whose points lie within a certain area, using the following pre-established shapes (`GeoBox`, `GeoPolygon`, `GeoCircle`). ```swift class Person: Object { @Persisted var name: String @Persisted var location: Location? // GeoJson embedded object } let realm = realmWithTestPath() try realm.write { realm.add(PersonLocation(name: "Maria", location: Location(latitude: 55.6761, longitude: 12.5683))) } let shape = GeoBox(bottomLeft: (55.6281, 12.0826), topRight: (55.6762, 12.5684))! let locations = realm.objects(PersonLocation.self).where { $0.location.geoWithin(shape) }) ``` A `filter` or `NSPredicate` can be used as well to perform a Geospatial query. ```swift let shape = GeoPolygon(outerRing: [(-2, -2), (-2, 2), (2, 2), (2, -2), (-2, -2)], holes: [[(0, 0), (1, 1), (-1, 1), (0, 0)]])! let locations = realm.objects(PersonLocation.self).filter("location IN %@", shape) let locations = realm.objects(PersonLocation.self).filter(NSPredicate(format: "location IN %@", shape)) ``` ### 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 * Migrated Release pipelines to Github Actions. 10.46.0 Release notes (2024-01-23) ============================================================= ### Enhancements * Add a privacy manifest to both frameworks. * Internal C++ symbols are no longer exported from Realm.framework when installing via CocoaPods, which reduces the size of the binary by ~5%, improves app startup time a little, and eliminates some warnings when linking the framework. This was already the case when using Carthage or a prebuilt framework ([PR #8464](https://github.com/realm/realm-swift/pull/8464)). * The `baseURL` field of `AppConfiguration` can now be updated, rather than the value being persisted between runs of the application in the metadata storage. ([Core #7201](https://github.com/realm/realm-core/issues/7201)) * Allow in-memory synced Realms. This will allow setting an in-memory identifier on a flexible sync realm. ### Fixed * `@Persisted`'s Encodable implementation did not allow the encoder to customize the encoding of values, which broke things like JSONEncoder's `dateEncodingStrategy` ([#8425](https://github.com/realm/realm-swift/issues/8425)). * Fix running Package.swift on Linux to support tools like Dependabot which need to build the package description but not the package itself ([#8458](https://github.com/realm/realm-swift/issues/8458), since v10.40.1). ### Breaking Changes * The `schemaVersion` field of `Realm.Configuration` must now always be zero for synchronized Realms. Schema versions are currently not applicable to synchronized Realms and the field was previously not read. ### 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 13.25.1 to 13.26.0 10.45.3 Release notes (2024-01-08) ============================================================= ### Enhancements * Update release packaging for Xcode 15.2. Prebuilt binaries for 14.1 and 15.0 have now been dropped from the release package. ### 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. 10.45.2 Release notes (2023-12-22) ============================================================= ### Enhancements * Greatly improve the performance of creating objects with a very large number of pre-existing incoming links. This is primarily relevant to initial sync bootstrapping when linking objects happen to be synchronized before the target objects they link to ([Core #7217](https://github.com/realm/realm-core/issues/7217), since v10.0.0). ### Fixed * Registering new notifications inside write transactions before actually making any changes is now actually allowed. This was supposed to be allowed in 10.39.1, but it did not actually work due to some redundant validation. * `SyncSession.ProgressDirection` and `SyncSession.ProgressMode` were missing `Sendable` annotations ([PR #8435](https://github.com/realm/realm-swift/pull/8435)). * `Realm.Error.subscriptionFailed` was reported with the incorrect error domain, making it impossible to catch (since v10.42.2, [PR #8435](https://github.com/realm/realm-swift/pull/8435)). ### 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.1.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.1.0. ### Internal * Upgraded realm-core from 13.25.0 to 13.25.1 10.45.1 Release notes (2023-12-18) ============================================================= ### Fixed * Exceptions thrown while applying the initial download for a sync subscription change terminated the program rather than being reported to the sync error handler ([Core #7196](https://github.com/realm/realm-core/issues/7196) and [Core #7197](https://github.com/realm/realm-core/pull/7197)). * Calling `SyncSession.reconnect()` while a reconnect after a non-fatal error was pending would result in an assertion failure mentioning "!m_try_again_activation_timer" if another non-fatal error was received ([Core #6961](https://github.com/realm/realm-core/issues/6961)). ### 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.1.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.1.0. ### Internal * Upgraded realm-core from 13.24.1 to 13.25.0 10.45.0 Release notes (2023-12-15) ============================================================= ### Enhancements * Update release packaging for Xcode 15.1. * Expose waiting for upload/download on SyncSession, which will suspend the current method (or call an asynchronous block) until an upload or download completes for a given sync session, e.g.,: ```swift try realm.write { realm.add(Person()) } try await realm.syncSession?.wait(for: .upload) ``` Note that this should not generally be used– sync is eventually consistent and should be used as such. However, there are special cases (notably in testing) where this may be used. * Sync subscription change notifications are now cancelled if the sync session becomes inactive as is done for upload and download progress handlers. If a fatal sync error occurs it will be reported to the completion handler, and if the user is logged out an "operation cancelled" error will be reported. Non-fatal errors are unchanged (i.e. the sync client internally retries without reporting errors). Previously fatal errors would result in the completion handler never being called. ([Core #7073](https://github.com/realm/realm-core/pull/7073)) * Automatic client reset recovery now preserves the original division of changesets, rather than combining all unsynchronized changes into a single changeset. This will typically improve server-side performance when there are a large number of recovered changes ([Core #7161](https://github.com/realm/realm-core/pull/7161)). * Automatic client reset recovery now does a better job of recovering changes when changesets were downloaded from the server after the unuploaded local changes were committed. If the local Realm happened to be fully up to date with the server prior to the client reset, automatic recovery should now always produce exactly the same state as if no client reset was involved ([Core #7161](https://github.com/realm/realm-core/pull/7161)). ### Fixed * Flexible sync subscriptions would sometimes not be sent to the server if they were created while the client was downloading the bootstrap state for a previous subscription change and the bootstrap did not complete successfully. ([Core #7077](https://github.com/realm/realm-core/issues/7077), since v10.21.1) * Flexible sync subscriptions would sometimes not be sent to the server if an UPLOAD message was sent immediately after the subscription was created. ([Core #7076](https://github.com/realm/realm-core/issues/7076), since v10.43.1) * Creating or removing flexible sync subscriptions while a client reset with automatic recovery enabled was being processed in the background would occasionally crash with a `KeyNotFound` exception. ([Core #7090](https://github.com/realm/realm-core/issues/7090), since v10.28.2) * Automatic client reset recovery would sometimes fail with the error "Invalid schema change (UPLOAD): cannot process AddColumn instruction for non-existent table" when recovering schema changes while made offline. This would only occur if the server is using the recently introduced option to allow breaking schema changes in developer mode. ([Core #7042](https://github.com/realm/realm-core/pull/7042)). * `MutableSet.formIntersection()` would sometimes cause a use-after-free if asked to intersect a set with itself (since v10.0.0). * Errors encountered while reapplying local changes for client reset recovery on partition-based sync Realms would result in the client reset attempt not being recorded, possibly resulting in an endless loop of attempting and failing to automatically recover the client reset. Flexible sync and errors from the server after completing the local recovery were handled correctly ([Core #7149](https://github.com/realm/realm-core/pull/7149), since v10.0.0). * During a client reset with recovery when recovering a move or set operation on a `List` or `List` that operated on indices that were not also added in the recovery, links to an object which had been deleted by another client while offline would be recreated by the recovering client, but the objects of these links would only have the primary key populated and all other fields would be default values. Now, instead of creating these zombie objects, the lists being recovered skip such deleted links. ([Core #7112](https://github.com/realm/realm-core/issues/7112), since client reset recovery was implemented in v10.25.0). * During a client reset recovery a Set of links could be missing items, or an exception could be thrown that prevents recovery (e.g. "Requested index 1 calling get() on set 'source.collection' when max is 0") ([Core #7112](https://github.com/realm/realm-core/issues/7112), since client reset recovery was implemented in v10.25.0). * Calling `sort()` or `distinct()` on a `MutableSet` that had unresolved links in it (i.e. objects which had been deleted by a different sync client) would produce a Results with duplicate entries. * Automatic client reset recovery would duplicate insertions in a list when recovering a write which made an unrecoverable change to a list (i.e. modifying or deleting a pre-existing entry), followed by a subscription change, followed by a write which added an entry to the list ([Core #7155](https://github.com/realm/realm-core/pull/7155), since the introduction of automatic client reset recovery for flexible sync). * Fixed several causes of "decryption failed" exceptions that could happen when opening multiple encrypted Realm files in the same process while using Realms stored on an exFAT file system. ([Core #7156](https://github.com/realm/realm-core/issues/7156), since v1.0.0) * Fixed deadlock which occurred when accessing the current user from the `App` from within a callback from the `User` listener ([Core #7183](https://github.com/realm/realm-core/issues/7183), since v10.42.0) * Having a class name of length 57 would make client reset crash as a limit of 56 was wrongly enforced (57 is the correct limit) ([Core #7176](https://github.com/realm/realm-core/issues/7176), since v10.0.0) * Automatic client reset recovery on flexible sync Realms would apply recovered changes in multiple write transactions, releasing the write lock in between. This had several observable negative effects: - Other threads reading from the Realm while a client reset was in progress could observe invalid mid-reset state. - Other threads could potentially write in the middle of a client reset, resulting in history diverging from the server. - The change notifications produced by client resets were not minimal and would report that some things changed which actually didn't. - All pending subscriptions were marked as Superseded and then recreating, resulting in anything waiting for subscriptions to complete firing early. ([Core #7161](https://github.com/realm/realm-core/pull/7161), since v10.29.0). * If the very first open of a flexible sync Realm triggered a client reset, the configuration had an initial subscriptions callback, both before and after reset callbacks, and the initial subscription callback began a read transaction without ending it (which is normally going to be the case), opening the frozen Realm for the after reset callback would trigger a BadVersion exception ([Core #7161](https://github.com/realm/realm-core/pull/7161), since v10.29.0). ### 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.1.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.1.0. ### Internal * Migrated our current CI Pipelines to Xcode Cloud. * Upgraded realm-core from 13.23.1 to 13.24.1 10.44.0 Release notes (2023-10-29) ============================================================= ### Enhancements * Expose `SyncSession.reconnect()`, which requests an immediate reconnection if the session is currently disconnected rather than waiting for the normal reconnect delay. * Update release packaging for Xcode 15.1 beta. visionOS slices are now only included for 15.1 rather than splicing them into the non-beta 15.0 release. ### 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.0.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.0.0. 10.43.1 Release notes (2023-10-13) ============================================================= ### Enhancements * Empty commits no longer trigger an extra invocation of the sync progress handler reporting the exact same information as the previous invocation ([Core #7031](https://github.com/realm/realm-core/pull/7031)). ### Fixed * Updating subscriptions did not trigger Realm autorefreshes, sometimes resulting in Realm.asyncRefresh() hanging until another write was performed by something else ([Core #7031](https://github.com/realm/realm-core/pull/7031)). ### 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.0.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.0.0. ### Internal * Upgraded realm-core from 13.22.0 to 13.23.1 10.43.0 Release notes (2023-09-29) ============================================================= ### Enhancements * Added `Results.subscribe` API for flexible sync. Now you can subscribe and unsubscribe to a flexible sync subscription through an object `Result`. ```swift // Named subscription query let results = try await realm.objects(Dog.self).where { $0.age > 18 }.subscribe(name: "adults") results.unsubscribe() // Unnamed subscription query let results = try await realm.objects(Dog.self).subscribe() results.unsubscribe() ```` After committing the subscription to the realm's local subscription set, the method will wait for downloads according to the `WaitForSyncMode`. ```swift let results = try await realm.objects(Dog.self).where { $0.age > 1 }.subscribe(waitForSync: .always) ``` Where `.always` will always download the latest data for the subscription, `.onCreation` will do it only the first time the subscription is created, and `.never` will never wait for the data to be downloaded. This API is currently in `Preview` and may be subject to changes in the future. * Added a new API which allows to remove all the unnamed subscriptions from the subscription set. ```swift realm.subscriptions.removeAll(unnamedOnly: true) ``` ### Fixed * Build the prebuilt libraries with the classic linker to work around the new linker being broken on iOS <15. When using CocoaPods or SPM, you will need to manually add `-Wl,-ld_classic` to `OTHER_LDFLAGS` for your application until Apple fixes the bug. * Remove the visionOS slice from the Carthage build as it makes Carthage reject the xcframework ([#8370](https://github.com/realm/realm-swift/issues/8370)). * Permission errors when creating asymmetric objects were not handled correctly, leading to a crash ([Core #6978](https://github.com/realm/realm-core/issues/6978), since 10.35.0) ### 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.0.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.0.0. ### Internal * Upgraded realm-core from 13.21.0 to 13.22.0 10.42.4 Release notes (2023-09-25) ============================================================= ### Enhancements * Asymmetric objects are now allowed to link to non-embedded, non-asymmetric objects. ([Core #6981](https://github.com/realm/realm-core/pull/6981)) ### Fixed * The Swift package failed to link some required system libraries when building for Catalyst, potentially resulting in linker errors if the application did not pull them in (since v10.40.1) * Logging into a single user using multiple auth providers created a separate SyncUser per auth provider. This mostly worked, but had some quirks: - Sync sessions would not necessarily be associated with the specific SyncUser used to create them. As a result, querying a user for its sessions could give incorrect results, and logging one user out could close the wrong sessions. - Removing one of the SyncUsers would delete all local Realm files for all SyncUsers for that user. - Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state. - A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout. ([Core #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0) * Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm ([Core #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). * If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([Core #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). ### 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.0.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.0.0. ### Internal * Upgraded realm-core from 13.20.1 to 13.21.0 * The schema version of the metadata Realm used to cache logged in users has been bumped. Upgrading is handled automatically, but downgrading from this version to older versions will result in cached logins being discarded. 10.42.3 Release notes (2023-09-18) ============================================================= ### Enhancements * Update packaging for the Xcode 15.0 release. Carthage release and obj-c binaries are now built with Xcode 15. ### Fixed * The prebuilt Realm.xcframework for SPM was packaged incorrectly and did not work ([#8361](https://github.com/realm/realm-swift/issues/8361), since v10.42.1). ### 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.0.0. * CocoaPods: 1.10 or later. * Xcode: 14.1-15.0.0. 10.42.2 Release notes (2023-09-13) ============================================================= ### Enhancements * Add support for logging messages sent by the server. ([Core #6476](https://github.com/realm/realm-core/pull/6476)) * Unknown protocol errors received from the baas server will no longer cause the application to crash if a valid error action is also received. Unknown error actions will be treated as an ApplicationBug error action and will cause sync to fail with an error via the sync error handler. ([Core #6885](https://github.com/realm/realm-core/pull/6885)) * Some sync error messages now contain more information about what went wrong. ### Fixed * The `MultipleSyncAgents` exception from opening a synchronized Realm in multiple processes at once no longer leaves the sync client in an invalid state. ([Core #6868](https://github.com/realm/realm-core/pull/6868), since v10.36.0) * Testing the size of a collection of links against zero would sometimes fail (sometimes = "difficult to explain"). In particular: ([Core #6850](https://github.com/realm/realm-core/issues/6850), since v10.41.0) * When async writes triggered a file compaction some internal state could be corrupted, leading to later crashes in the slab allocator. This typically resulted in the "ref + size <= next->first" assertion failure, but other failures were possible. Many issues reported; see [Core #6340](https://github.com/realm/realm-core/issues/6340). (since 10.35.0) * `Realm.Configuration.maximumNumberOfActiveVersions` now handles intermediate versions which have been cleaned up correctly and checks the number of live versions rather than the number of versions between the oldest live version and current version (since 10.35.0). * If the client disconnected between uploading a change to flexible sync subscriptions and receiving the new object data from the server resulting from that subscription change, the next connection to the server would sometimes result in a client reset ([Core #6966](https://github.com/realm/realm-core/issues/6966), since v10.21.1). ### Deprecations * `RLMApp` has `localAppName` and `localAppVersion` fields which never ended up being used for anything and are now deprecated. * `RLMSyncAuthError` has not been used since v10.0.0 and is now deprecated. ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 7. ### Internal * Upgraded realm-core from 13.17.1 to 13.20.1 10.42.1 Release notes (2023-08-28) ============================================================= ### Fixed * The names of the prebuilt zips for SPM have changed to avoid having Carthage download them instead of the intended Carthage zip ([#8326](https://github.com/realm/realm-swift/issues/8326), since v10.42.0). * The prebuild Realm.xcframework for SwiftPM now has all platforms other than visionOS built with Xcode 14 to comply with app store rules ([#8339](https://github.com/realm/realm-swift/issues/8339), since 10.42.0). * Fix visionOS compilation with Xcode beta 7. ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 7. 10.42.0 Release notes (2023-07-30) ============================================================= ### Enhancements * Add support for building for visionOS and add Xcode 15 binaries to the release package. visionOS currently requires installing Realm via either Swift Package Manager or by using a XCFramework as CocoaPods and Carthage do not yet support it. * Zips compatible with SPM's `.binaryTarget()` are now published as part of the releases on Github. * Prebuilt XCFrameworks are now built with LTO enabled. This has insignificant performance benefits, but cuts the size of the library by ~15%. ### Fixed * Fix nested properties observation on a `Projections` not notifying when there is a property change. ([#8276](https://github.com/realm/realm-swift/issues/8276), since v10.34.0). * Fix undefined symbol error for `UIKit` when linking Realm to a framework using SPM. ([#8308](https://github.com/realm/realm-swift/issues/8308), since v10.41.0) * If the app crashed at exactly the wrong time while opening a freshly compacted Realm the file could be left in an invalid state ([Core #6807](https://github.com/realm/realm-core/pull/6807), since v10.33.0). * Sync progress for DOWNLOAD messages was sometimes stored incorrectly, resulting in an extra round trip to the server. ([Core #6827](https://github.com/realm/realm-core/issues/6827), since v10.31.0) ### Breaking Changes * Legacy non-xcframework Carthage installations are no longer supported. Please ensure you are using `--use-xcframeworks` if installing via Carthage. ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 5. ### Internal * Upgraded realm-core from 13.17.0 to 13.17.1 * Release packages were being uploaded to several static.realm.io URLs which are no longer linked to anywhere. These are no longer being updated, and release packages are now only being uploaded to Github. 10.41.1 Release notes (2023-07-17) ============================================================= ### Enhancements * Filesystem errors now include more information in the error message. * Sync connection and session reconnect timing/backoff logic has been reworked and unified into a single implementation. Previously some categories of errors would cause an hour-long wait before attempting to reconnect, while others would use an exponential backoff strategy. All errors now result in the sync client waiting for 1 second before retrying, doubling the wait after each subsequent failure up to a maximum of five minutes. If the cause of the error changes, the backoff will be reset. If the sync client voluntarily disconnects, no backoff will be used. ([Core #6526]((https://github.com/realm/realm-core/pull/6526))) ### Fixed * Removed warnings for deprecated APIs internal use. ([#8251](https://github.com/realm/realm-swift/issues/8251), since v10.39.0) * Fix an error during async open and client reset if properties have been added to the schema. This fix also applies to partition-based to flexible sync migration if async open is used. ([Core #6707](https://github.com/realm/realm-core/issues/6707), since v10.28.2) ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 4. ### Internal * Upgraded realm-core from 13.15.1 to 13.17.0 * The location where prebuilt core binaries are published has changed slightly. If you are using `REALM_BASE_URL` to mirror the binaries, you may need to adjust your mirroring logic. 10.41.0 Release notes (2023-06-26) ============================================================= ### Enhancements * Add support for multiplexing sync connections. When enabled (the default), a single connection is used per sync user rather than one per synchronized Realm. This reduces resource consumption when multiple Realms are opened and will typically improve performance ([PR #8282](https://github.com/realm/realm-swift/pull/8282)). * Sync timeout options can now be set on `RLMAppConfiguration` along with the other app-wide configuration settings ([PR #8282](https://github.com/realm/realm-swift/pull/8282)). ### Fixed * Import as `RLMRealm_Private.h` as a module would cause issues when using Realm as a subdependency. ([#8164](https://github.com/realm/realm-swift/issues/8164), since 10.37.0) * Disable setting a custom logger by default on the sync client when the sync manager is created. This was overriding the default logger set using `RLMLogger.defaultLogger`. (since v10.39.0). ### Breaking Changes * The `RLMSyncTimeouts.appConfiguration` property has been removed. This was an unimplemented read-only property which did not make any sense on the containing type ([PR #8282](https://github.com/realm/realm-swift/pull/8282)). ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 2. ### Internal * Upgraded realm-core from 13.15.0 to 13.15.1 10.40.2 Release notes (2023-06-09) ============================================================= ### Enhancements * `Actor.preconditionIsolated()` is now used for runtime actor checking when available (i.e. building with Xcode 15 and running on iOS 17) rather than the less reliable workaround. ### Fixed * If downloading the fresh Realm file failed during a client reset on a flexible sync Realm, the sync client would crash the next time the Realm was opened. ([Core #6494](https://github.com/realm/realm-core/issues/6494), since v10.28.2) * If the order of properties in the local class definitions did not match the order in the server-side schema, the before-reset Realm argument passed to a client reset handler would have an invalid schema and likely crash if any data was read from it. ([Core #6693](https://github.com/realm/realm-core/issues/6693), since v10.40.0) ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 1. ### Internal * Upgraded realm-core from 13.13.0 to 13.15.0. * The prebuilt library used for CocoaPods installations is now built with Xcode 14. This should not have any observable effects other than the download being much smaller due to no longer including bitcode. 10.40.1 Release notes (2023-06-06) ============================================================= ### Enhancements * Fix compilation with Xcode 15. Note that iOS 12 is the minimum supported deployment target when using Xcode 15. * Switch to building the Carthage release with Xcode 14.3.1. ### 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 14.3.1. * CocoaPods: 1.10 or later. * Xcode: 14.1-15 beta 1. ### Internal * Overhauled SDK metrics collection to better drive future development efforts. 10.40.0 Release notes (2023-05-26) ============================================================= Drop support for Xcode 13 and add Xcode 14.3.1. Xcode 14.1 is now the minimum supported version. ### Enhancements * Adjust the error message for private `Object` subclasses and subclasses nested inside other types to explain how to make them work rather than state that it's impossible. ([#5662](https://github.com/realm/realm-cocoa/issues/5662)). * Improve performance of SectionedResults. With a single section it is now ~10% faster, and the runtime of sectioning no longer scales significantly with section count, giving >100% speedups when there are large numbers of sections ([Core #6606](https://github.com/realm/realm-core/pull/6606)). * Very slightly improve performance of runtime thread checking on the main thread. ([Core #6606](https://github.com/realm/realm-core/pull/6606)) ### Fixed * Allow support for implicit boolean queries on Swift's Type Safe Queries API ([#8212](https://github.com/realm/realm-swift/issues/8212)). * Fixed a fatal error (reported to the sync error handler) during client reset or automatic partition-based to flexible sync migration if the reset has been triggered during an async open and the schema being applied has added new classes. Due to this bug automatic flexibly sync migration has been disabled for older releases and this is now the minimum version required. ([#6601](https://github.com/realm/realm-core/issues/6601), since automatic client resets were introduced in v10.25.0) * Dictionaries sometimes failed to map unresolved links to nil. If the target of a link in a dictionary was deleted by another sync client, reading that field from the dictionary would sometimes give an invalid object rather than nil. In addition, queries on dictionaries would sometimes have incorrect results. ([Core #6644](https://github.com/realm/realm-core/pull/6644), since v10.8.0) * Older versions of Realm would sometimes fail to properly mark objects as being the target of an incoming link from another object. When this happened, deleting the target object would hit an assertion failure due to the inconsistent state. We now reconstruct a valid state rather than crashing. ([Core #6585](https://github.com/realm/realm-core/issues/6585), since v5.0.0) * Fix several UBSan failures which did not appear to result in functional bugs ([Core #6649](https://github.com/realm/realm-core/pull/6649)). * Using both synchronous and asynchronous transactions on the same thread or scheduler could hit the assertion failure "!realm.is_in_transaction()" if one of the callbacks for an asynchronous transaction happened to be scheduled during a synchronous transaction ([Core #6659](https://github.com/realm/realm-core/issues/6659), since v10.26.0) * The stored deployment location for Apps was not being updated correctly after receiving a redirect response from the server, resulting in every connection attempting to connect to the old URL and getting redirected rather than only the first connection after the deployment location changed. ([Core #6630](https://github.com/realm/realm-core/issues/6630), since v10.38.2) ### 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 14.3. * CocoaPods: 1.10 or later. * Xcode: 14.1-14.3.1. ### Internal * Upgraded realm-core from 13.10.1 to 13.13.0. 10.39.1 Release notes (2023-05-05) ============================================================= ### Enhancements * New notifiers can now be registered in write transactions until changes have actually been made in the write transaction. This makes it so that new notifications can be registered inside change notifications triggered by beginning a write transaction (unless a previous callback performed writes). ([#4818](https://github.com/realm/realm-swift/issues/4818)). * Reduce the memory footprint of an automatic (discard or recover) client reset when there are large incoming changes from the server. ([Core #6567](https://github.com/realm/realm-core/issues/6567)). ### 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 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. ### Internal * Upgraded realm-core from 13.10.0 to 13.10.1. 10.39.0 Release notes (2023-05-03) ============================================================= ### Enhancements * Add support for actor-isolated Realms, opened with `try await Realm(actor: actor)`. Rather than being confined to the current thread or a dispatch queue, actor-isolated Realms are isolated to an actor. This means that they can be used from any thread as long as it's within a function isolated to that actor, and they remain valid over suspension points where a task may hop between threads. Actor-isolated Realms can be used with either global or local actors: ```swift @MainActor function mainThreadFunction() async throws { // These are identical: the async init continues to produce a // MainActor-confined Realm if no actor is supplied let realm1 = try await Realm() let realm2 = try await Realm(MainActor.shared) } // A simple example of a custom global actor @globalActor actor BackgroundActor: GlobalActor { static var shared = BackgroundActor() } @BackgroundActor backgroundThreadFunction() async throws { // Explicitly specifying the actor is required for everything but MainActor let realm = try await Realm(actor: BackgroundActor.shared) try await realm.write { _ = realm.create(MyObject.self) } // Thread-confined Realms would sometimes throw an exception here, as we // may end up on a different thread after an `await` print("\(realm.objects(MyObject.self).count)") } actor MyActor { // An implicitly-unwrapped optional is used here to let us pass `self` to // `Realm(actor:)` within `init` var realm: Realm! init() async throws { realm = try await Realm(actor: self) } var count: Int { realm.objects(MyObject.self).count } func create() async throws { try await realm.asyncWrite { realm.create(MyObject.self) } } } // This function isn't isolated to the actor, so each operation has to be async func createObjects() async throws { let actor = try await MyActor() for _ in 0..<5 { await actor.create() } print("\(await actor.count)") } // In an isolated function, an actor-isolated Realm can be used synchronously func createObjects(in actor: isolated MyActor) async throws { await actor.realm.write { actor.realm.create(MyObject.self) } print("\(actor.realm.objects(MyObject.self).count)") } ``` Actor-isolated Realms come with a more convenient syntax for asynchronous writes. `try await realm.write { ... }` will suspend the current task, acquire the write lock without blocking the current thread, and then invoke the block. The actual data is then written to disk on a background thread, and the task is resumed once that completes. As this does not block the calling thread while waiting to write and does not perform i/o on the calling thread, this will often be safe to use from `@MainActor` functions without blocking the UI. Sufficiently large writes may still benefit from being done on a background thread. Asynchronous writes are only supported for actor-isolated Realms or in `@MainActor` functions. Actor-isolated Realms require Swift 5.8 (Xcode 14.3). Enabling both strict concurrency checking (`SWIFT_STRICT_CONCURRENCY=complete` in Xcode) and runtime actor data race detection (`OTHER_SWIFT_FLAGS=-Xfrontend -enable-actor-data-race-checks`) is strongly recommended when using actor-isolated Realms. * Add support for automatic partition-based to flexible sync migration. Connecting to a server-side app configured to use flexible sync with a client-side partition-based sync configuration is now supported, and will automatically create the appropriate flexible sync subscriptions to subscribe to the requested partition. This allows changing the configuration on the server from partition-based to flexible without breaking existing clients. ([Core #6554](https://github.com/realm/realm-core/issues/6554)) * Now you can use an array `[["_id": 1], ["breed": 0]]` as sorting option for a MongoCollection. This new API fixes the issue where the resulting documents when using more than one sort parameter were not consistent between calls. ([#7188](https://github.com/realm/realm-swift/issues/7188), since v10.0.0). * Add support for adding a user created default logger, which allows implementing your own logging logic and the log threshold level. You can define your own logger creating an instance of `Logger` and define the log function which will be invoked whenever there is a log message. ```swift let logger = Logger(level: .all) { level, message in print("Realm Log - \(level): \(message)") } ``` Set this custom logger as Realm default logger using `Logger.shared`. ```swift Logger.shared = logger ``` * It is now possible to change the default log threshold level at any point of the application's lifetime. ```swift Logger.shared.logLevel = .debug ``` This will override the log level set anytime before by a user created logger. * We have set `.info` as the default log threshold level for Realm. You will now see some log message in your console. To disable use `Logger.shared.level = .off`. ### Fixed * Several schema initialization functions had incorrect `@MainActor` annotations, resulting in runtime warnings if the first time a Realm was opened was on a background thread ([#8222](https://github.com/realm/realm-swift/issues/8222), since v10.34.0). ### Deprecations * `App.SyncManager.logLevel` and `App.SyncManager.logFunction` are deprecated in favour of setting a default logger. ### 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 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. ### Internal * Upgraded realm-core from v13.9.4 to v13.10.0. 10.38.3 Release notes (2023-04-28) ============================================================= ### Enhancements * Improve performance of cancelling a write transactions after making changes. If no KVO observers are used this is now constant time rather than taking time proportional to the number of changes to be rolled back. Cancelling a write transaction with KVO observers is 10-20% faster. ([Core PR #6513](https://github.com/realm/realm-core/pull/6513)). ### Fixed * Performing a large number of queries without ever performing a write resulted in steadily increasing memory usage, some of which was never fully freed due to an unbounded cache ([#7978](https://github.com/realm/realm-swift/issues/7978), since v10.27.0). ### 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 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. ### Internal * Upgraded realm-core from 13.9.3 to 13.9.4 10.38.2 Release notes (2023-04-25) ============================================================= ### Enhancements * Improve performance of equality queries on a non-indexed AnyRealmValue property by about 30%. ([Core #6506](https://github.com/realm/realm-core/issues/6506)) ### Fixed * SSL handshake errors were treated as fatal errors rather than errors which should be retried. ([Core #6434](https://github.com/realm/realm-core/issues/6434), since v10.35.0) ### 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 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. ### Internal * Upgraded realm-core from 13.9.0 to 13.9.3. 10.38.1 Release notes (2023-04-25) ============================================================= ### Fixed * The error handler set on EventsConfiguration was not actually used (since v10.26.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. 10.38.0 Release notes (2023-03-31) ============================================================= Switch to building the Carthage release with Xcode 14.3. ### Enhancements * Add Xcode 14.3 binaries to the release package. Note that CocoaPods 1.12.0 does not support Xcode 14.3. * Add support for sharing encrypted Realms between multiple processes. ([Core #1845](https://github.com/realm/realm-core/issues/1845)) ### Fixed * Fix a memory leak reported by Instruments on `URL.path` in `Realm.Configuration.fileURL` when using a string partition key in Partition Based Sync ([#8195](https://github.com/realm/realm-swift/pull/8195)), since v10.0.0). * Fix a data race in version management. If one thread committed a write transaction which increased the number of live versions above the previous highest seen during the current session at the same time as another thread began a read, the reading thread could read from a no-longer-valid memory mapping. This could potentially result in strange crashes when opening, refreshing, freezing or thawing a Realm ([Core #6411](https://github.com/realm/realm-core/pull/6411), since v10.35.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.3. * CocoaPods: 1.10 or later. * Xcode: 13.4-14.3. ### Internal * Upgraded realm-core from 13.8.0 to 13.9.0. 10.37.2 Release notes (2023-03-29) ============================================================= ### Fixed * Copying a `RLMRealmConfiguration` failed to copy several fields. This resulted in migrations being passed the incorrect object type in Swift when using the default configuration (since v10.34.0) or async open (since v10.37.0). This also broke using the Events API in those two scenarios (since v10.26.0 for default configuration and v10.37.0 for async open). ([#8190](https://github.com/realm/realm-swift/issues/8190)) ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. 10.37.1 Release notes (2023-03-27) ============================================================= ### Enhancements * Performance improvement for the following queries ([Core #6376](https://github.com/realm/realm-core/issues/6376)): * Significant (~75%) improvement when counting (`Results.count`) the number of exact matches (with no other query conditions) on a string/int/UUID/ObjectID property that has an index. This improvement will be especially noticiable if there are a large number of results returned (duplicate values). * Significant (~99%) improvement when querying for an exact match on a Date property that has an index. * Significant (~99%) improvement when querying for a case insensitive match on an AnyRealmValue property that has an index. * Moderate (~25%) improvement when querying for an exact match on a Bool property that has an index. * Small (~5%) improvement when querying for a case insensitive match on an AnyRealmValue property that does not have an index. ### Fixed * Add missing `@Sendable` annotations to several sync and app services related callbacks ([PR #8169](https://github.com/realm/realm-swift/pull/8169), since v10.34.0). * Fix some bugs in handling task cancellation for async Realm init. Some very specific timing windows could cause crashes, and the download would not be cancelled if the Realm was already open ([PR #8178](https://github.com/realm/realm-swift/pull/8178), since v10.37.0). * Fix a crash when querying an AnyRealmValue property with a string operator (contains/like/beginswith/endswith) or with case insensitivity. ([Core #6376](https://github.com/realm/realm-core/issues/6376), since v10.8.0) * Querying for case-sensitive equality of a string on an indexed AnyRealmValue property was returning case insensitive matches. For example querying for `myIndexedAny == "Foo"` would incorrectly match on values of "foo" or "FOO" etc. ([Core #6376](https://github.com/realm/realm-core/issues/6376), since v10.8.0) * Adding an index to an AnyRealmValue property when objects of that type already existed would crash with an assertion. ([Core #6376](https://github.com/realm/realm-core/issues/6376), since v10.8.0). * Fix a bug that may have resulted in arrays being in different orders on different devices. Some cases of “Invalid prior_size” may be fixed too. ([Core #6191](https://github.com/realm/realm-core/issues/6191), since v10.25.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. ### Internal * Upgraded realm-core from 13.6.0 to 13.8.0 10.37.0 Release notes (2023-03-09) ============================================================= ### Enhancements * `MongoCollection.watch().subscribe(on:)` now supports any swift Scheduler rather than only dispatch queues ([PR #8131](https://github.com/realm/realm-swift/pull/8130)). * Add an async sequence wrapper for `MongoCollection.watch()`, allowing you to do `for try await change in collection.changeEvents { ... }` ([PR #8131](https://github.com/realm/realm-swift/pull/8130)). * The internals of error handling and reporting have been significantly reworked. The visible effects of this are that some errors which previously had unhelpful error messages now include more detail about what went wrong, and App errors now expose a much more complete set of error codes ([PR #8002](https://github.com/realm/realm-swift/pull/8002)). * Expose compensating write error information. When the server rejects a modification made by the client (such as if the user does not have the required permissions), a `SyncError` is delivered to the sync error handler with the code `.writeRejected` and a non-nil `compensatingWriteInfo` field which contains information about what was rejected and why. This information is intended primarily for debugging and logging purposes and may not have a stable format. ([PR #8002](https://github.com/realm/realm-swift/pull/8002)) * Async `Realm.init()` now handles Task cancellation and will cancel the async open if the Task is cancelled ([PR #8148](https://github.com/realm/realm-swift/pull/8148)). * Cancelling async opens now has more consistent behavior. The previously intended and documented behavior was that cancelling an async open would result in the callback associated with the specific task that was cancelled never being called, and all other pending callbacks would be invoked with an ECANCELED error. This never actually worked correctly, and the callback which was not supposed to be invoked at all sometimes would be. We now unconditionally invoke all of the exactly once, passing ECANCELED to all of them ([PR #8148](https://github.com/realm/realm-swift/pull/8148)). ### Fixed * `UserPublisher` incorrectly bounced all notifications to the main thread instead of setting up the Combine publisher to correctly receive on the main thread. ([#8132](https://github.com/realm/realm-swift/issues/8132), since 10.21.0) * Fix warnings when building with Xcode 14.3 beta 2. * Errors in async open resulting from invalid queries in `initialSubscriptions` would result in the callback being invoked with both a non-nil Realm and a non-nil Error even though the Realm was in an invalid state. Now only the error is passed to the callback ([PR #8148](https://github.com/realm/realm-swift/pull/8148), since v10.28.0). * Converting a local realm to a synced realm would crash if an embedded object was null ([Core #6294](https://github.com/realm/realm-core/issues/6294), since v10.22.0). * Subqueries on indexed properties performed extremely poorly. ([Core #6327](https://github.com/realm/realm-core/issues/6327), since v5.0.0) * Fix a crash when a SSL read successfully read a non-zero number of bytes and also reported an error. ([Core #5435](https://github.com/realm/realm-core/issues/5435), since 10.0.0) * The sync client could get stuck in an infinite loop if the server sent an invalid changeset which caused a transform error. This now results in a client reset instead. ([Core #6051](https://github.com/realm/realm-core/issues/6051), since v10.0.0) * Strings in queries which contained any characters which required multiple bytes when encoded as utf-8 were incorrectly encoded as binary data when serializing the query to send it to the server for a flexible sync subscription, resulting the server rejecting the query ([Core #6350](https://github.com/realm/realm-core/issues/6350), since 10.22.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. ### Internal * Upgraded realm-core from 13.4.1 to 13.6.0 10.36.0 Release notes (2023-02-15) ============================================================= ### Enhancements * Add support for multiple overlapping or nested event scopes. `Events.beginScope()` now returns a `Scope` object which is used to commit or cancel that scope, and if more than one scope is active at a time events are reported to all active scopes. ### Fixed * Fix moving `List` items to a higher index in SwiftUI results in wrong destination index ([#7956](https://github.com/realm/realm-swift/issues/7956), since v10.6.0). * Using the `searchable` view modifier with `@ObservedResults` in iOS 16 would cause the collection observation subscription to cancel. ([#8096](https://github.com/realm/realm-swift/issues/8096), since 10.21.0) * Client reset with recovery would sometimes crash if the recovery resurrected a dangling link ([Core #6292](https://github.com/realm/realm-core/issues/6292), since v10.32.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. ### Internal * Upgraded realm-core from 13.4.0 to 13.4.1 10.35.1 Release notes (2023-02-10) ============================================================= ### Fixed * Client reset with recovery would crash if a client reset occurred the very first time the Realm was opened with async open. The client reset callbacks are now not called if the Realm had never been opened before ([PR #8125](https://github.com/realm/realm-swift/pull/8125), since 10.32.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. 10.35.0 Release notes (2023-02-07) ============================================================= This version bumps the Realm file format version to 23. Realm files written by this version cannot be read by older versions of Realm. ### Enhancements * The Realm file is now automatically shrunk if the file size is larger than needed to store all of the data. ([Core PR #5755](https://github.com/realm/realm-core/pull/5755)) * Pinning old versions (either with frozen Realms or with Realms on background threads that simply don't get refreshed) now only prevents overwriting the data needed by that version, rather than the data needed by that version and all later versions. In addition, frozen Realms no longer pin the transaction logs used to drive change notifications. This mostly eliminates the file size growth caused by pinning versions. ([Core PR #5440](https://github.com/realm/realm-core/pull/5440)) * Rework how Dictionaries/Maps are stored in the Realm file. The new design uses less space and is typically significantly faster. This changes the iteration order of Maps, so any code relying on that may be broken. We continue to make no guarantees about iteration order on Maps ([Core #5764](https://github.com/realm/realm-core/issues/5764)). * Improve performance of freezing Realms ([Core PR #6211](https://github.com/realm/realm-core/pull/6211)). ### Fixed * Fix a crash when using client reset with recovery and flexible sync with a single subscription ([Core #6070](https://github.com/realm/realm-core/issues/6070), since v10.28.2) * Encrypted Realm files could not be opened on devices with a larger page size than the one which originally wrote the file. ([#8030](https://github.com/realm/realm-swift/issues/8030), since v10.32.1) * Creating multiple flexible sync subscriptions at once could hit an assertion failure if the server reported an error for any of them other than the last one ([Core #6038](https://github.com/realm/realm-core/issues/6038), since v10.21.1). * `Set` and `List` considered a string and binary data containing that string encoded as UTF-8 to be equivalent. This could result in a List entry not changing type on assignment and for the client be inconsistent with the server if a string and some binary data with equivalent content was inserted from Atlas. ([Core #4860](https://github.com/realm/realm-core/issues/4860) and [Core #6201](https://github.com/realm/realm-core/issues/6201), since v10.8.0) * Querying for NaN on Decimal128 properties did not match any objects ([Core #6182](https://github.com/realm/realm-core/issues/6182), since v10.8.0). * When client reset with recovery is used and the recovery did not need to make any changes to the local Realm, the sync client could incorrectly think the recovery failed and report the error "A fatal error occured during client reset: 'A previous 'Recovery' mode reset from did not succeed, giving up on 'Recovery' mode to prevent a cycle'". ([Core #6195](https://github.com/realm/realm-core/issues/6195), since v10.32.0) * Fix a crash when using client reset with recovery and flexible sync with a single subscription ([Core #6070](https://github.com/realm/realm-core/issues/6070), since v10.28.2) * Writing to newly in-view objects while a flexible sync bootstrap was in progress would not synchronize those changes to the server ([Core #5804](https://github.com/realm/realm-core/issues/5804), since v10.21.1). * If a client reset with recovery or discard local was interrupted while the "fresh" realm was being downloaded, the sync client could crash with a MultpleSyncAgents exception ([Core #6217](https://github.com/realm/realm-core/issues/6217), since v10.25.0). * Sharing Realm files between a Catalyst app and Realm Studio did not properly synchronize access to the Realm file ([Core #6258](https://github.com/realm/realm-core/pull/6258), since v10.0.0). ### Compatibility * Realm Studio: 13.0.2 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. ### Internal * Upgraded realm-core from 12.13.0 to 13.4.0 10.34.1 Release notes (2023-01-20) ============================================================= ### Fixed * Add some missing `@preconcurrency` annotations which lead to build failures with Xcode 14.0 when importing via SPM or CocoaPods ([#8104](https://github.com/realm/realm-swift/issues/8104), since v10.34.0). ### Compatibility * Realm Studio: 11.0.0 - 12.0.0. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. 10.34.0 Release notes (2023-01-13) ============================================================= Swift 5.5 is no longer supported. Swift 5.6 (Xcode 13.3) is now the minimum supported version. The prebuilt binary for Carthage is now build with Xcode 14.2. ### Enhancements * Improve performance of creating Projection objects and of change notifications on projections ([PR #8050](https://github.com/realm/realm-swift/pull/8050)). * Allow initialising any sync configuration with `cancelAsyncOpenOnNonFatalErrors`. * Improve performance of Combine value publishers which do not use the object/collection changesets a little. * All public types have been audited for sendability and are now marked as Sendable when applicable. A few types which were incidentally not thread-safe but make sense to use from multiple threads are now thread-safe. * Add support for building Realm with strict concurrency checking enabled. ### Fixed * Fix bad memory access exception that can occur when watching change streams. [PR #8039](https://github.com/realm/realm-swift/pull/8039). * Object change notifications on projections only included the first projected property for each source property ([PR #8050](https://github.com/realm/realm-swift/pull/8050), since v10.21.0). * `@AutoOpen` failed to open flexible sync Realms while offline ([#7986](https://github.com/realm/realm-swift/issues/7986), since v10.27.0). * Fix "Publishing changes from within view updates is not allowed" warnings when using `@ObservedResults` or `@ObservedSectionedResults` ([#7908](https://github.com/realm/realm-swift/issues/7908)). * Fix "Publishing changes from within view updates is not allowed" warnings when using `@AutoOpen` or `@AsyncOpen`. ([#7908](https://github.com/realm/realm-swift/issues/7908)). * Defer `Realm.asyncOpen` execution on `@AsyncOpen` and `@AutoOpen` property wrappers until all the environment values are set. This will guarantee the configuration and partition value are set set before opening the realm. ([#7931](https://github.com/realm/realm-swift/issues/7931), since v10.12.0). * `@ObservedResults.remove()` could delete the wrong object if a write on a background thread which changed the index of the object being removed occurred at a very specific time (since v10.6.0). ### Compatibility * Realm Studio: 11.0.0 - 12.0.0. 13.0.0 is currently incompatible. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.2. * CocoaPods: 1.10 or later. * Xcode: 13.3-14.2. 10.33.0 Release notes (2022-12-01) ============================================================= ### Enhancements * Flexible sync subscription state will change to `SyncSubscriptionState.pending` (`RLMSyncSubscriptionStatePending`) while waiting for the server to have sent all pending history after a bootstrap and before marking a subscription as Complete. ([#5795](https://github.com/realm/realm-core/pull/5795)) * Add custom column names API, which allows to set a different column name in the realm from the one used in your object declaration. ```swift class Person: Object { @Persisted var firstName: String @Persisted var birthDate: Date @Persisted var age: Int override class public func propertiesMapping() -> [String: String] { ["firstName": "first_name", "birthDate": "birth_date"] } } ``` This is very helpful in cases where you want to name a property differently from your `Device Sync` JSON schema. This API is only available for old and modern object declaration syntax on the `RealmSwift` SDK. * Flexible sync bootstraps now apply 1MB of changesets per write transaction rather than applying all of them in a single write transaction. ([Core PR #5999](https://github.com/realm/realm-core/pull/5999)). ### Fixed * Fix a race condition which could result in "operation cancelled" errors being delivered to async open callbacks rather than the actual sync error which caused things to fail ([Core PR #5968](https://github.com/realm/realm-core/pull/5968), since the introduction of async open). * Fix database corruption issues which could happen if an application was terminated at a certain point in the process of comitting a write transaciton. ([Core PR #5993](https://github.com/realm/realm-core/pull/5993), since v10.21.1) * `@AsyncOpen` and `@AutoOpen` would begin and then cancel a second async open operation ([PR #8038](https://github.com/realm/realm-swift/pull/8038), since v10.12.0). * Changing the search text when using the searchable SwiftUI extension would trigger multiple updates on the View for each change ([PR #8038](https://github.com/realm/realm-swift/pull/8038), since v10.19.0). * Changing the filter or search properties of an `@ObservedResults` or `@ObservedSectionedResults` would trigger up to three updates on the View ([PR #8038](https://github.com/realm/realm-swift/pull/8038), since v10.6.0). * Fetching a user's profile while the user logs out would result in an assertion failure. ([Core PR #6017](https://github.com/realm/realm-core/issues/5571), since v10.8.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. ### Internal * Upgraded realm-core from 12.11.0 to 12.13.0 10.32.3 Release notes (2022-11-10) ============================================================= ### Fixed * Fix name lookup errors when importing Realm Swift built in library evolution mode (([#8014](https://github.com/realm/realm-swift/issues/8014)). * The prebuilt watchOS library in the objective-c release package was missing an arm64 slice. The Swift release package was uneffected ([PR #8016](https://github.com/realm/realm-swift/pull/8016)). * Fix issue where `RLMUserAPIKey.key`/`UserAPIKey.key` incorrectly returned the name of the API key instead of the key itself. ([#8021](https://github.com/realm/realm-swift/issues/8021), since v10.0.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. 10.32.2 Release notes (2022-11-01) ============================================================= Switch to building the Carthage release with Xcode 14.1. ### Fixed * Fix linker errors when building a release build with Xcode 14.1 when installing via SPM ([#7995](https://github.com/realm/realm-swift/issues/7995)). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. 10.32.1 Release notes (2022-10-25) ============================================================= ### Enhancements * Improve performance of client reset with automatic recovery and converting top-level tables into embedded tables ([Core #5897](https://github.com/realm/realm-core/pull/5897)). * `Realm.Error` is now a typealias for `RLMError` rather than a manually-defined version of what the automatic bridging produces. This should have no effect on existing working code, but the manual definition was missing a few things supplied by the automatic bridging. * Some sync errors sent by the server include a link to the server-side logs associated with that error. This link is now exposed in the `serverLogURL` property on `SyncError` (or `RLMServerLogURLKey` userInfo field when using NSError). ### Fixed * Many sync and app errors were reported using undocumented internal error codes and/or domains and could not be progammatically handled. Some notable things which now have public error codes instead of unstable internal ones: - `Realm.Error.subscriptionFailed`: The server rejected a flexible sync subscription. - `AppError.invalidPassword`: A login attempt failed due to a bad password. - `AppError.accountNameInUse`: A registration attempt failed due to the account name being in use. - `AppError.httpRequestFailed`: A HTTP request to Atlas App Services completed with an error HTTP code. The failing code is available in the `httpStatusCode` property. - Many other less common error codes have been added to `AppError`. - All sync errors other than `SyncError.clientResetError` reported incorrect error codes. (since v10.0.0). * `UserAPIKey.objectId` was incorrectly bridged to Swift as `RLMObjectId` to `ObjectId`. This may produce warnings about an unneccesary cast if you were previously casting it to the correct type (since v10.0.0). * Fixed an assertion failure when observing change notifications on a sectioned result, if the first modification was to a linked property that did not cause the state of the sections to change. ([Core #5912](https://github.com/realm/realm-core/issues/5912), since the introduction of sectioned results in v10.29.0) * Fix a use-after-free if the last external reference to an encrypted synchronized Realm was closed between when a client reset error was received and when the download of the new Realm began. ([Core #5949](https://github.com/realm/realm-core/pull/5949), since 10.28.4). * Fix an assertion failure during client reset with recovery when recovering a list operation on an embedded object that has a link column in the path prefix to the list from the top level object. ([Core #5957](https://github.com/realm/realm-core/issues/5957), since introduction of automatic recovery in v10.32.0). * Creating a write transaction which is rejected by the server due to it exceeding the maximum transaction size now results in a client reset error instead of synchronization breaking and becoming stuck forever ([Core #5209](https://github.com/realm/realm-core/issues/5209), since v10). * Opening an unencrypted file with an encryption key would sometimes report a misleading error message that indicated that the problem was something other than a decryption failure ([Core #5915](https://github.com/realm/realm-core/pull/5915), since 0.89.0). * Fix a rare deadlock which could occur when closing a synchronized Realm immediately after committing a write transaction when the sync worker thread has also just finished processing a changeset from the server ([Core #5948](https://github.com/realm/realm-core/pull/5948)). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.0.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. ### Internal * Upgraded realm-core from 12.9.0 to 12.11.0. 10.32.0 Release notes (2022-10-10) ============================================================= ### Enhancements * Add `.recoverUnsyncedChanges` (`RLMClientResetModeRecoverUnsyncedChanges`) and `.recoverOrDiscardUnsyncedChanges` (`RLMClientResetModeRecoverOrDiscardUnsyncedChanges`) behaviors to `ClientResetMode` (`RLMClientResetMode`). - The newly added recover modes function by downloading a realm which reflects the latest state of the server after a client reset. A recovery process is run locally in an attempt to integrate the server state with any local changes from before the client reset occurred. The changes are integrated with the following rules: 1. Objects created locally that were not synced before client reset, will be integrated. 2. If an object has been deleted on the server, but was modified on the client, the delete takes precedence and the update is discarded. 3. If an object was deleted on the client, but not the server, then the client delete instruction is applied. 4. In the case of conflicting updates to the same field, the client update is applied. - The client reset process will fallback to `ClientResetMode.discardUnsyncedChanges` if the recovery process fails in `.recoverOrDiscardUnsyncedChanges`. - The client reset process will fallback to `ClientResetMode.manual` if the recovery process fails in `.recoverUnsyncedChanges`. - The two new swift recovery modes support client reset callbacks: `.recoverUnsyncedChanges(beforeReset: ((Realm) -> Void)? = nil, afterReset: ((Realm, Realm) -> Void)? = nil)`. - The two new Obj-C recovery modes support client reset callbacks in `notifyBeforeReset` and `notifyAfterReset`for both `[RLMUser configurationWithPartitionValue]` and `[RLMUser flexibleSyncConfigurationWithClientResetMode]` For more detail on client reset callbacks, see `ClientResetMode`, `RLMClientResetBeforeBlock`, `RLMClientResetAfterBlock`, and the 10.25.0 changelog entry. * Add two new additional interfaces to define a manual client reset handler: - Add a manual callback handler to `ClientResetMode.manual` -> `ClientResetMode.manual(ErrorReportingBlock? = nil)`. - Add the `RLMSyncConfiguration.manualClientResetHandler` property (type `RLMSyncErrorReportingBlock`). - These error reporting blocks are invoked in the event of a `RLMSyncErrorClientResetError`. - See `ErrorReportingBlock` (`RLMSyncErrorReportingBlock`), and `ClientResetInfo` for more detail. - Previously, manual client resets were handled only through the `SyncManager.ErrorHandler`. You have the option, but not the requirement, to define manual reset handler in these interfaces. Otherwise, the `SyncManager.ErrorHandler` is still invoked during the manual client reset process. - These new interfaces are only invoked during a `RLMSyncErrorClientResetError`. All other sync errors are still handled in the `SyncManager.ErrorHandler`. - See 'Breaking Changes' for information how these interfaces interact with an already existing `SyncManager.ErrorHandler`. ### Breaking Changes * The default `clientResetMode` (`RLMClientResetMode`) is switched from `.manual` (`RLMClientResetModeManual`) to `.recoverUnsyncedChanges` (`RLMClientResetModeRecoverUnsyncedChanges`). - If you are currently using `.manual` and continue to do so, the only change you must explicitly make is designating manual mode in your `Realm.Configuration.SyncConfiguration`s, since they will now default to `.recoverUnsyncedChanges`. - You may choose to define your manual client reset handler in the newly introduced `manual(ErrorReportingBlock? = nil)` or `RLMSyncConfiguration.manualClientResetHandler`, but this is not required. The `SyncManager.errorHandler` will still be invoked during a client reset if no callback is passed into these new interfaces. ### Deprecations * `ClientResetMode.discardLocal` is deprecated in favor of `ClientResetMode.discardUnsyncedChanges`. The reasoning is that the name better reflects the effect of this reset mode. There is no actual difference in behavior. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.0.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. 10.31.0 Release notes (2022-10-05) ============================================================= The prebuilt binary for Carthage is now build with Xcode 14.0.1. ### Enhancements * Cut the runtime of aggregate operations on large dictionaries in half ([Core #5864](https://github.com/realm/realm-core/pull/5864)). * Improve performance of aggregate operations on collections of objects by 2x to 10x ([Core #5864](https://github.com/realm/realm-core/pull/5864)). Greatly improve the performance of sorting or distincting a Dictionary's keys or values. The most expensive operation is now performed O(log N) rather than O(N log N) times, and large Dictionaries can see upwards of 99% reduction in time to sort. ([Core #5166](https://github.com/realm/realm-core/pulls/5166)) * Add support for changing the deployment location for Atlas Apps. Previously this was assumed to be immutable ([Core #5648](https://github.com/realm/realm-core/issues/5648)). * The sync client will now yield the write lock to other threads which are waiting to perform a write transaction even if it still has remaining work to do, rather than always applying all changesets received from the server even when other threads are trying to write. ([Core #5844](https://github.com/realm/realm-core/pull/5844)). * The sync client no longer writes an unused temporary copy of the changesets received from the server to the Realm file ([Core #5844](https://github.com/realm/realm-core/pull/5844)). ### Fixed * Setting a `List` property with `Results` no longer throws an unrecognized selector exception (since 10.8.0-beta.2) * `RLMProgressNotificationToken` and `ProgressNotificationToken` now hold a strong reference to the sync session, keeping it alive until the token is deallocated or invalidated, as the other notification tokens do. ([#7831](https://github.com/realm/realm-swift/issues/7831), since v2.3.0). * Results permitted some nonsensical aggregate operations on column types which do not make sense to aggregate, giving garbage results rather than reporting an error ([Core #5876](https://github.com/realm/realm-core/pull/5876), since v5.0.0). * Upserting a document in a Mongo collection would crash if the document's id type was anything other than ObjectId (since v10.0.0). * Fix a use-after-free when a sync session is closed and the app is destroyed at the same time ([Core #5752](https://github.com/realm/realm-core/issues/5752), since v10.19.0). ### Deprecations * `RLMUpdateResult.objectId` has been deprecated in favor of `RLMUpdateResult.documentId` to support reporting document ids which are not object ids. ### Breaking Changes * Private API `_realmColumnNames` has been renamed to a new public API called `propertiesMapping()`. This change only affects the Swift API and doesn't have any effects in the obj-c API. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 14.0.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14.1. ### Internal * Upgraded realm-core from 12.7.0 to 12.9.0 10.30.0 Release notes (2022-09-20) ============================================================= ### Fixed * Incoming links from `RealmAny` properties were not handled correctly when migrating an object type from top-level to embedded. `RealmAny` properties currently cannot link to embedded objects. ([Core #5796](https://github.com/realm/realm-core/pull/5796), since 10.8.0). * `Realm.refresh()` sometimes did not actually advance to the latest version. It attempted to be semi-non-blocking in a very confusing way which resulted in it sometimes advancing to a newer version that is not the latest version, and sometimes blocking until notifiers are ready so that it could advance to the latest version. This behavior was undocumented and didn't work correctly, so it now always blocks if needed to advance to the latest version. ([#7625](https://github.com/realm/realm-swift/issues/7625), since v0.98.0). * Fix the most common cause of thread priority inversions when performing writes on the main thread. If beginning the write transaction has to wait for the background notification calculations to complete, that wait is now done in a QoS-aware way. ([#7902](https://github.com/realm/realm-swift/issues/7902)) * Subscribing to link properties in a flexible sync Realm did not work due to a mismatch between what the client sent and what the server needed. ([Core #5409](https://github.com/realm/realm-core/issues/5409)) * Attempting to use `AsymmetricObject` with partition-based sync now reports a sensible error much earlier in the process. Asymmetric sync requires using flexible sync. ([Core #5691](https://github.com/realm/realm-core/issues/5691), since 10.29.0). * Case-insensitive but diacritic-sensitive queries would crash on 4-byte UTF-8 characters ([Core #5825](https://github.com/realm/realm-core/issues/5825), since v2.2.0) * Accented characters are now handled by case-insensitive but diacritic-sensitive queries. ([Core #5825](https://github.com/realm/realm-core/issues/5825), since v2.2.0) ### Breaking Changes * `-[RLMASLoginDelegate authenticationDidCompleteWithError:]` has been renamed to `-[RLMASLoginDelegate authenticationDidFailWithError:]` to comply with new app store requirements. This only effects the obj-c API. ([#7945](https://github.com/realm/realm-swift/issues/7945)) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1 - 14. ### Internal * Upgraded realm-core from 12.6.0 to 12.7.0 10.29.0 Release notes (2022-09-09) ============================================================= ### Enhancements * Add support for asymmetric sync. When a class inherits from `AsymmetricObject`, objects created are synced unidirectionally to the server and cannot be queried or read locally. ```swift class PersonObject: AsymmetricObject { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var name: String @Persisted var age: Int } try realm.write { // This will create the object on the server but not locally. realm.create(PersonObject.self, value: ["_id": ObjectId.generate(), "name": "Dylan", "age": 20]) } ``` * Add ability to section a collection which conforms to `RealmCollection`, `RLMCollection`. Collections can be sectioned by a unique key retrieved from a keyPath or a callback and will return an instance of `SectionedResults`/`RLMSectionedResults`. Each section in the collection will be an instance of `ResultsSection`/`RLMSection` which gives access to the elements corresponding to the section key. `SectionedResults`/`RLMSectionedResults` and `ResultsSection`/`RLMSection` have the ability to be observed. ```swift class DemoObject: Object { @Persisted var title: String @Persisted var date: Date var firstLetter: String { return title.first.map(String.init(_:)) ?? "" } } var sectionedResults: SectionedResults // ... sectionedResults = realm.objects(DemoObject.self) .sectioned(by: \.firstLetter, ascending: true) ``` * 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 { @ObservedSectionedResults(DemoObject.self, sectionKeyPath: \.firstLetter) var demoObjects var body: some View { VStack { List { ForEach(demoObjects) { section in Section(header: Text(section.key)) { ForEach(section) { object in MyRowView(object: object) } } } } } } } ``` * Add automatic handing for changing top-level objects to embedded objects in migrations. Any objects of the now-embedded type which have zero incoming links are deleted, and objects with multiple incoming links are duplicated. This happens after the migration callback function completes, so there is no functional change if you already have migration logic which correctly handles this. ([Core #5737](https://github.com/realm/realm-core/pull/5737)). * Improve performance when a new Realm file connects to the server for the first time, especially when significant amounts of data has been written while offline. ([Core #5772](https://github.com/realm/realm-core/pull/5772)) * Shift more of the work done on the sync worker thread out of the write transaction used to apply server changes, reducing how long it blocks other threads from writing. ([Core #5772](https://github.com/realm/realm-core/pull/5772)) * Improve the performance of the sync changeset parser, which speeds up applying changesets from the server. ([Core #5772](https://github.com/realm/realm-core/pull/5772)) ### Fixed * Fix all of the UBSan failures hit by our tests. It is unclear if any of these manifested as visible bugs. ([Core #5665](https://github.com/realm/realm-core/pull/5665)) * Upload completion callbacks were sometimes called before the final step of interally marking the upload as complete, which could result in calling `Realm.writeCopy()` from the completion callback failing due to there being unuploaded changes. ([Core #4865](https://github.com/realm/realm-core/issues/4865)). * Writing to a Realm stored on an exFAT drive threw the exception "fcntl() with F_BARRIERFSYNC failed: Inappropriate ioctl for device" when a write transaction needed to expand the file. ([Core #5789](https://github.com/realm/realm-core/issues/5789), since 10.27.0) * Syncing a Decimal128 with big significand could result in a crash. ([Core #5728](https://github.com/realm/realm-core/issues/5728)) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 RC. ### Internal * Upgraded realm-core from 12.5.1 to 12.6.0 10.28.7 Release notes (2022-09-02) ============================================================= ### Enhancements * Add prebuilt binaries for Xcode 14 to the release package. ### Fixed * Fix archiving watchOS release builds with Xcode 14. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 6. 10.28.6 Release notes (2022-08-19) ============================================================= ### Fixed * Fixed an issue where having realm-swift as SPM sub-target dependency leads to missing symbols error during iOS archiving ([Core #7645](https://github.com/realm/realm-core/pull/7645)). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 5. ### Internal * Upgraded realm-core from 12.5.0 to 12.5.1 10.28.5 Release notes (2022-08-09) ============================================================= ### Enhancements * Improve performance of accessing `SubscriptionSet` properties when no writes have been made to the Realm since the last access. ### Fixed * A use-after-free could occur if a Realm with audit events enabled was destroyed while processing an upload completion for the events Realm on a different thread. ([Core PR #5714](https://github.com/realm/realm-core/pull/5714)) * Opening a read-only synchronized Realm for the first time via asyncOpen did not set the schema version, which could lead to `m_schema_version != ObjectStore::NotVersioned` assertion failures later on. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 4. ### Internal * Upgraded realm-core from 12.4.0 to 12.5.0 10.28.4 Release notes (2022-08-03) ============================================================= ### Enhancements * Add support for building arm64 watchOS when installing Realm via CocoaPods. * Reduce the amount of virtual address space used ([Core #5645](https://github.com/realm/realm-core/pull/5645)). ### Fixed * Fix some warnings when building with Xcode 14 ([Core #5577](https://github.com/realm/realm-core/pull/5577)). * Fix compilation failures on watchOS platforms which do not support thread-local storage. ([#7694](https://github.com/realm/realm-swift/issues/7694), [#7695](https://github.com/realm/realm-swift/issues/7695) since v10.21.1) * Fix a data race when committing a transaction while multiple threads are waiting to begin write transactions. This appears to not have caused any functional problems. * Fix a data race when writing audit events which could occur if the sync client thread was busy with other work when the event Realm was opened. * Fix some cases of running out of virtual address space (seen/reported as mmap failures) ([Core #5645](https://github.com/realm/realm-core/pull/5645)). * Audit event scopes containing only write events and no read events would occasionally throw a `BadVersion` exception when a write transaction was committed (since v10.26.0). * The client reset callbacks for the DiscardLocal mode would be passed invalid Realm instances if the callback was invoked at a point where the Realm was not otherwise open. ([Core #5654](https://github.com/realm/realm-core/pull/5654), since the introduction of DiscardLocal reset mode in v10.25.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 4. ### Internal * Upgraded realm-core from 12.3.0 to 12.4.0. 10.28.3 Release notes (2022-07-27) ============================================================= ### Enhancements * Greatly improve the performance of obtaining cached Realm instances in Swift when using a sync configuration. ### Fixed * Add missing `initialSubscription` and `rerunOnOpen` to copyWithZone method on `RLMRealmConfiguration`. This resulted in incorrect values when using `RLMRealmConfiguration.defaultConfiguration`. * The sync error handler did not hold a strong reference to the sync session while dispatching the error from the worker thread to the main thread, resulting in the session passed to the error handler being invalid if there were no other remaining strong references elsewhere. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 3. 10.28.2 Release notes (2022-06-30) ============================================================= ### Fixed * Using `seedFilePath` threw an exception if the Realm file being opened already existed ([#7840](https://github.com/realm/realm-swift/issues/7840), since v10.26.0). * The `intialSubscriptions` callback was invoked every time a Realm was opened regardless of the value of `rerunOnOpen` and if the Realm was already open on another thread (since v10.28.0). * Allow using `RLMSupport.Swift` from RealmSwift's Cocoapods ([#6886](https://github.com/realm/realm-swift/pull/6886)). * Fix a UBSan failure when mapping encrypted pages. Fixing this did not change the resulting assembly, so there were probably no functional problems resulting from this (since v5.0.0). * Improved performance of sync clients during integration of changesets with many small strings (totalling > 1024 bytes per changeset) on iOS 14, and devices which have restrictive or fragmented memory. ([Core #5614](https://github.com/realm/realm-core/issues/5614)) * Fix a data race when opening a flexible sync Realm (since v10.28.0). * Add a missing backlink removal when assigning null or a non-link value to an `AnyRealmValue` property which previously linked to an object. This could have resulted in "key not found" exceptions or assertion failures such as `mixed.hpp:165: [realm-core-12.1.0] Assertion failed: m_type` when removing the destination link object. ([Core #5574](https://github.com/realm/realm-core/pull/5573), since the introduction of AnyRealmValue in v10.8.0) ### Compatibility * Realm Studio: 12.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 2. ### Internal * Upgraded realm-core from 12.1.0 to 12.3.0. 10.28.1 Release notes (2022-06-10) ============================================================= ### Enhancements * Add support for Xcode 14. When building with Xcode 14, the minimum deployment target is now iOS 11. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4.1. * CocoaPods: 1.10 or later. * Xcode: 13.1-14 beta 1. 10.28.0 Release notes (2022-06-03) ============================================================= ### Enhancements * Replace mentions of 'MongoDB Realm' with 'Atlas App Services' in the documentation and update appropriate links to documentation. * Allow adding a subscription querying for all documents of a type in swift for flexible sync. ``` try await subscriptions.update { subscriptions.append(QuerySubscription(name: "all_people")) } ``` * Add Combine API support for flexible sync beta. * Add an `initialSubscriptions` parameter when retrieving the flexible sync configuration from a user, which allows to specify a subscription update block, to bootstrap a set of flexible sync subscriptions when the Realm is first opened. There is an additional optional parameter flag `rerunOnOpen`, which allows to run this initial subscriptions on every app startup. ```swift let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in subs.append(QuerySubscription(name: "people_10") { $0.age > 10 }) }, rerunOnOpen: true) let realm = try Realm(configuration: config) ``` * The sync client error handler will report an error, with detailed info about which object caused it, when writing an object to a flexible sync Realm outside of any query subscription. ([#5528](https://github.com/realm/realm-core/pull/5528)) * Adding an object to a flexible sync Realm for a type that is not within a query subscription will now throw an exception. ([#5488](https://github.com/realm/realm-core/pull/5488)). ### Fixed * Flexible Sync query subscriptions will correctly complete when data is synced to the local Realm. ([#5553](https://github.com/realm/realm-core/pull/5553), since v12.0.0) ### Breaking Changes * Rename `SyncSubscriptionSet.write` to `SyncSubscriptionSet.update` to avoid confusion with `Realm.write`. * Rename `SyncSubscription.update` to `SyncSubscription.updateQuery` to avoid confusion with `SyncSubscriptionSet.update`. * Rename `RLMSyncSubscriptionSet.write` to `RLMSyncSubscriptionSet.update` to align it with swift API. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4. * CocoaPods: 1.10 or later. * Xcode: 13.1-13.4. ### Internal * Upgraded realm-core from 12.0.0 to 12.1.0. 10.27.0 Release notes (2022-05-26) ============================================================= ### Enhancements * `@AsyncOpen`/`@AutoOpen` property wrappers can be used with flexible sync. ### Fixed * When installing via SPM, debug builds could potentially hit an assertion failure during flexible sync bootstrapping. ([Core #5527](https://github.com/realm/realm-core/pull/5527)) * Flexible sync now only applies bootstrap data if the entire bootstrap is received. Previously orphaned objects could result from the read snapshot on the server changing. ([Core #5331](https://github.com/realm/realm-core/pull/5331)) * Partially fix a performance regression in write performance introduced in v10.21.1. v10.21.1 fixed a case where a kernel panic or device's battery dying at the wrong point in a write transaction could potentially result in a corrected Realm file, but at the cost of a severe performance hit. This version adjusts how file synchronization is done to provide the same safety at a much smaller performance hit. ([#7740](https://github.com/realm/realm-swift/issues/7740)). ### Compatibility * Realm Studio: 11.0.0 or later (but see note below). * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4. * CocoaPods: 1.10 or later. * Xcode: 13.1-13.4. ### Internal * Upgraded realm-core from 11.17.0 to 12.0.0. * Bump the version number for the lockfile used for interprocess synchronization. This has no effect on persistent data, but means that versions of Realm which use pre-12.0.0 realm-core cannot open Realm files at the same time as they are opened by this version. Notably this includes Realm Studio, and v11.1.2 (the latest at the time of this release) cannot open Realm files which are simultaneously open in the simulator. 10.26.0 Release notes (2022-05-19) ============================================================= Xcode 13.1 is now the minimum supported version of Xcode, as Apple no longer allows submitting to the app store with Xcode 12. ### Enhancements * Add Xcode 13.4 binaries to the release package. * Add Swift API for asynchronous transactions ```swift try? realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string"]) } onComplete: { error in // optional handling on write complete } try? realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.commitAsyncWrite() } let asyncTransactionId = try? realm.beginAsyncWrite { // ... } try! realm.cancelAsyncWrite(asyncTransactionId) ``` * Add Obj-C API for asynchronous transactions ``` [realm asyncTransactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; } onComplete:^(NSError *error) { // optional handling }]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; }]; RLMAsyncTransactionId asyncTransactionId = [realm beginAsyncWriteTransaction:^{ // ... }]; [realm cancelAsyncTransaction:asyncTransactionId]; ``` * Improve performance of opening a Realm with `objectClasses`/`objectTypes` set in the configuration. * Implement the Realm event recording API for reporting reads and writes on a Realm file to Atlas. ### Fixed * Lower minimum OS version for `async` login and FunctionCallables to match the rest of the `async` functions. ([#7791]https://github.com/realm/realm-swift/issues/7791) * Consuming a RealmSwift XCFramework with library evolution enabled would give the error `'Failed to build module 'RealmSwift'; this SDK is not supported by the compiler'` when the XCFramework was built with an older XCode version and is then consumed with a later version. ([#7313](https://github.com/realm/realm-swift/issues/7313), since v3.18.0) * A data race would occur when opening a synchronized Realm with the client reset mode set to `discardLocal` on one thread at the same time as a client reset was being processed on another thread. This probably did not cause any functional problems in practice and the broken timing window was very tight (since 10.25.0). * If an async open of a Realm triggered a client reset, the callbacks for `discardLocal` could theoretically fail to be called due to a race condition. The timing for this was probably not possible to hit in practice (since 10.25.0). * Calling `[RLMRealm freeze]`/`Realm.freeze` on a Realm which had been created from `writeCopy` would not produce a frozen Realm. ([#7697](https://github.com/realm/realm-swift/issues/7697), since v5.0.0) * Using the dynamic subscript API on unmanaged objects before first opening a Realm or if `objectTypes` was set when opening a Realm would throw an exception ([#7786](https://github.com/realm/realm-swift/issues/7786)). * The sync client may have sent a corrupted upload cursor leading to a fatal error from the server due to an uninitialized variable. ([#5460](https://github.com/realm/realm-core/pull/5460), since v10.25.1) * Flexible sync would not correctly resume syncing if a bootstrap was interrupted ([#5466](https://github.com/realm/realm-core/pull/5466), since v10.21.1). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.4. * CocoaPods: 1.10 or later. * Xcode: 13.1-13.4. ### Internal * Upgraded realm-core from v11.15.0 to v11.17.0 10.25.2 Release notes (2022-04-27) ============================================================= ### Enhancements * Replace Xcode 13.3 binaries with 13.3.1 binaries. ### Fixed * `List` would contain an invalidated object instead of null when the object linked to was deleted by a difference sync client ([Core #5215](https://github.com/realm/realm-core/pull/5215), since v10.8.0). * Adding an object to a Set, deleting the parent object of the Set, and then deleting the object which was added to the Set would crash ([Core #5387](https://github.com/realm/realm-core/issues/5387), since v10.8.0). * Synchronized Realm files which were first created using v10.0.0-beta.3 would be redownloaded instead of using the existing file, possibly resulting in the loss of any unsynchronized data in those files (since v10.20.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.3.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3.1. ### Internal * Upgraded realm-core from v11.14.0 to v11.15.0 10.25.1 Release notes (2022-04-11) ============================================================= ### Fixed * Fixed various memory corruption bugs when encryption is used caused by not locking a mutex when needed. ([#7640](https://github.com/realm/realm-swift/issues/7640), [#7659](https://github.com/realm/realm-swift/issues/7659), since v10.21.1) * Changeset upload batching did not calculate the accumulated size correctly, resulting in “error reading body failed to read: read limited at 16777217 bytes” errors from the server when writing large amounts of data ([Core #5373](https://github.com/realm/realm-core/pull/5373), since 10.25.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.3. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3. ### Internal * Upgraded realm-core from v11.13.0 to v11.14.0. 10.25.0 Release notes (2022-03-29) ============================================================= Synchronized Realm files written by this version cannot be opened by older versions of Realm. Existing files will be automatically upgraded when opened. Non-synchronized Realm files remain backwards-compatible. ### Enhancements * Add ability to use Swift Query syntax in `@ObservedResults`, which allows you to filter results using the `where` parameter. * Add ability to use `MutableSet` with `StateRealmObject` in SwiftUI. * Async/Await extensions are now compatible with iOS 13 and above when building with Xcode 13.3. * Sync changesets waiting to be uploaded to the server are now compressed, reducing the disk space needed when large write transactions are performed while offline or limited in bandwidth.([Core #5260](https://github.com/realm/realm-core/pull/5260)). * Added new `SyncConfiguration.clientResetMode` and `RLMSyncConfiguration.clientResetMode` properties. - The values of these properties will dictate client behavior in the event of a [client reset](https://docs.mongodb.com/realm/sync/error-handling/client-resets/). - See below for information on `ClientResetMode` values. - `clientResetMode` defaults to `.manual` if not set otherwise. * Added new `ClientResetMode` and `RLMClientResetMode` enums. - These enums represent possible client reset behavior for `SyncConfiguration.clientResetMode` and `RLMSyncConfiguration.clientResetMode`, respectively. - `.manual` and `RLMClientResetModeManual` - The local copy of the Realm is copied into a recovery directory for safekeeping, and then deleted from the original location. The next time the Realm for that partition value is opened, the Realm will automatically be re-downloaded from MongoDB Realm, and can be used as normal. - Data written to the Realm after the local copy of the Realm diverged from the backup remote copy will be present in the local recovery copy of the Realm file. The re-downloaded Realm will initially contain only the data present at the time the Realm was backed up on the server. - `rlmSync_clientResetBackedUpRealmPath` and `SyncError.clientResetInfo()` are used for accessing the recovery directory. - `.discardLocal` and `RLMClientResetDiscardLocal` - All unsynchronized local changes are automatically discarded and the local state is automatically reverted to the most recent state from the server. Unsynchronized changes can then be recovered in a post-client-reset callback block (See changelog below for more details). - If RLMClientResetModeDiscardLocal is enabled but the client reset operation is unable to complete 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 client reset blocks are set when initializing the user configuration ```swift var configuration = user.configuration(partitionValue: "myPartition", clientResetMode: .discardLocal(beforeClientResetBlock, afterClientResetBlock)) ``` - The before client reset block -- `((Realm) -> Void)? = nil` -- is executed prior to a client reset. Possible usage includes: ```swift let beforeClientResetBlock: (Realm) -> Void = { beforeRealm in var recoveryConfig = Realm.Configuration() recoveryConfig.fileURL = myRecoveryPath do { beforeRealm.writeCopy(configuration: recoveryConfig) /* The copied realm could be used later for recovery, debugging, reporting, etc. */ } catch { /* handle error */ } } ``` - The after client reset block -- `((Realm, Realm) -> Void)? = nil)` -- is executed after a client reset. Possible usage includes: ```Swift let afterClientResetBlock: (Realm, Realm) -> Void = { before, after in /* This block could be used to add custom recovery logic, back-up a realm file, send reporting, etc. */ for object in before.objects(myClass.self) { let res = after.objects(myClass.self) if (res.filter("primaryKey == %@", object.primaryKey).first != nil) { /* ...custom recovery logic... */ } else { /* ...custom recovery logic... */ } } ``` - **Realm Obj-c implementation**: Both before and after client reset callbacks exist as properties on `RLMSyncConfiguration` and are set at initialization. ```objective-c RLMRealmConfiguration *config = [user configurationWithPartitionValue:partitionValue clientResetMode:RLMClientResetModeDiscardLocal notifyBeforeReset:beforeBlock notifyAfterReset:afterBlock]; ``` where `beforeBlock` is of type `RLMClientResetBeforeBlock`. And `afterBlock` is of type `RLMClientResetAfterBlock`. ### Breaking Changes * Xcode 13.2 is no longer supported when building with Async/Await functions. Use Xcode 13.3 to build with Async/Await functionality. ### Fixed * Adding a Realm Object to a `ObservedResults` or a collections using `StateRealmObject` that is managed by the same Realm would throw if the Object was frozen and not thawed before hand. * Setting a Realm Configuration for @ObservedResults using it's initializer would be overrode by the Realm Configuration stored in `.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 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)). * Updating subscriptions while refreshing the access token would crash ([Core #5343](https://github.com/realm/realm-core/issues/5343), since v10.22.0) * Fix several race conditions in `SyncSession` related to setting `customRequestHeaders` while using the `SyncSession` on a different thread. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.3. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3. ### Internal * Upgraded realm-core from v11.12.0 to v11.13.0 10.24.2 Release notes (2022-03-18) ============================================================= ### Fixed * Application would sometimes crash with exceptions like 'KeyNotFound' or assertion "has_refs()". Other issues indicating file corruption may also be fixed by this. The one mentioned here is the one that lead to solving the problem. ([Core #5283](https://github.com/realm/realm-core/issues/5283), since v5.0.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.3. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3. ### Internal * Upgraded realm-core from 11.11.0 to 11.12.0 10.24.1 Release notes (2022-03-14) ============================================================= Switch to building the Carthage binary with Xcode 13.3. This release contains no functional changes from 10.24.0. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.3. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3. 10.24.0 Release notes (2022-03-05) ============================================================= ### Enhancements * Add ability to use Swift Query syntax in `@ObservedResults`, which allows you to filter results using the `where` parameter. ### Fixed * If a list of objects contains links to objects not included in the synchronized partition, collection change notifications for that list could be incorrect ([Core #5164](https://github.com/realm/realm-core/issues/5164), since v10.0.0). * Adding a new flexible sync subscription could crash with "Assertion failed: !m_unbind_message_sent" in very specific timing scenarios ([Core #5149](https://github.com/realm/realm-core/pull/5149), since v10.22.0). * Converting floats/doubles into Decimal128 would yield imprecise results ([Core #5184](https://github.com/realm/realm-core/pull/5184), since v10.0.0) * Using accented characters in class and field names in a synchronized Realm could result in sync errors ([Core #5196](https://github.com/realm/realm-core/pull/5196), since v10.0.0). * Calling `Realm.invalidate()` from inside a Realm change notification could result in the write transaction which produced the notification not being persisted to disk (since v10.22.0). * When a client reset error which results in the current Realm file being backed up and then deleted, deletion errors were ignored as long as the copy succeeded. When this happens the deletion of the old file is now scheduled for the next launch of the app. ([Core #5180](https://github.com/realm/realm-core/issues/5180), since v2.0.0) * Fix an error when compiling a watchOS Simulator target not supporting Thread-local storage ([#7623](https://github.com/realm/realm-swift/issues/7623), since v10.21.0). * Add a validation check to report a sensible error if a Realm configuration indicates that an in-memory Realm should be encrypted. ([Core #5195](https://github.com/realm/realm-core/issues/5195)) * The Swift package set the linker flags on the wrong target, resulting in linker errors when SPM decides to build the core library as a dynamic library ([#7266](https://github.com/realm/realm-swift/issues/7266)). * The download-core task failed if run in an environment without TMPDIR set ([#7688](https://github.com/realm/realm-swift/issues/7688), since v10.23.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3 beta 3. ### Internal * Upgraded realm-core from 11.9.0 to 11.11.0 10.23.0 Release notes (2022-02-28) ============================================================= ### Enhancements * Add `Realm.writeCopy(configuration:)`/`[RLMRealm writeCopyForConfiguration:]` which gives the following functionality: - Export a local non-sync Realm to be used with MongoDB Realm Sync when the configuration is derived from a sync `RLMUser`/`User`. - Write a copy of a local Realm to a destination specified in the configuration. - Write a copy of a synced Realm in use with user A, and open it with user B. - Note that migrations may be required when using a local realm configuration to open a realm file that was copied from a synchronized realm. An exception will be thrown if a Realm exists at the destination. * Add a `seedFilePath` option to `RLMRealmConfiguration` and `Configuration`. If this option is set then instead of creating an empty Realm, the realm at the `seedFilePath` will 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 `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:]`. * Add `NSCopying` conformance to `RLMDecimal128` and `RLMObjectId`. * Add Xcode 13.3 binaries to the release package (and remove 13.0). ### Fixed * Add support of arm64 in Carthage build ([#7154](https://github.com/realm/realm-cocoa/issues/7154) * Adding missing support for `IN` queries to primitives types on Type Safe Queries. ```swift let persons = realm.objects(Person.self).where { let acceptableNames = ["Tom", "James", "Tyler"] $0.name.in([acceptableNames]) } ``` ([Cocoa #7633](https://github.com/realm/realm-swift/issues/7633), since v10.19.0) * Work around a compiler crash when building with Swift 5.6 / Xcode 13.3. CustomPersistable's PersistedType must now always be a built-in type rather than possibly another CustomPersistable type as Swift 5.6 has removed support for infinitely-recursive associated types ([#7654](https://github.com/realm/realm-swift/issues/7654)). * Fix redundant call to filter on `@ObservedResults` from `searchable` component (since v10.19.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.3 beta 3. 10.22.0 Release notes (2022-01-25) ============================================================= ### Enhancements * Add beta support for flexible sync. See the [backend](https://docs.mongodb.com/realm/sync/data-access-patterns/flexible-sync/) and [SDK](https://docs.mongodb.com/realm/sdk/swift/examples/flexible-sync/) documentation for more information. Please report any issues with the beta through Github. ### Fixed * UserIdentity metadata table grows indefinitely. ([#5152](https://github.com/realm/realm-core/issues/5152), since v10.20.0) * We now report a useful error message when opening a sync Realm in non-sync mode or vice-versa.([#5161](https://github.com/realm/realm-core/pull/5161), since v5.0.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.2.1. ### Internal * Upgraded realm-core from 11.8.0 to 11.9.0 10.21.1 Release notes (2022-01-12) ============================================================= ### Fixed * The sync client will now drain the receive queue when a send fails with ECONNRESET, ensuring that any error message from the server gets received and processed. ([#5078](https://github.com/realm/realm-core/pull/5078)) * Schema validation was missing for embedded objects in sets, resulting in an unhelpful error being thrown if a Realm object subclass contained one (since v10.0.0). * Opening a Realm with a schema that has an orphaned embedded object type performed an extra empty write transaction (since v10.0.0). * Freezing a Realm with a schema that has orphaned embedded object types threw a "Wrong transactional state" exception (since v10.19.0). * `@sum` and `@avg` queries on Dictionaries of floats or doubles used too much precision for intermediates, resulting in incorrect rounding (since v10.5.0). * Change the exception message for calling refresh on an immutable Realm from "Continuous transaction through DB object without history information." to "Can't refresh a read-only Realm." ([#5061](https://github.com/realm/realm-core/issues/5061), since v10.8.0). * Queries of the form "link.collection.@sum = 0" where `link` is null matched when `collection` was a List or Set, but not a Dictionary ([#5080](https://github.com/realm/realm-core/pull/5080), since v10.8.0). * Types which require custom obj-c bridging (such as `PersistableEnum` or `CustomPersistable`) would crash with exceptions mentioning `__SwiftValue` in a variety of places on iOS versions older than iOS 14 ([#7604](https://github.com/realm/realm-swift/issues/7604), since v10.21.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.2.1. ### Internal * Upgraded realm-core from 11.6.1 to 11.8.0. 10.21.0 Release notes (2022-01-10) ============================================================= ### Enhancements * Add `metadata` property to `RLMUserProfile`/`UserProfile`. * Add class `Projection` to allow creation of light weight view models out of Realm Objects. ```swift public class Person: Object { @Persisted var firstName = "" @Persisted var lastName = "" @Persisted var address: Address? = nil @Persisted var friends = List() } public class Address: EmbeddedObject { @Persisted var city: String = "" @Persisted var country = "" } class PersonProjection: Projection { // `Person.firstName` will have same name and type @Projected(\Person.firstName) var firstName // There will be the only String for `city` of the original object `Address` @Projected(\Person.address.city) var homeCity // List will be mapped to list of firstNames @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } // `people` will contain projections for every `Person` object in the `realm` let people: Results = realm.objects(PersonProjection.self) ``` * Greatly improve performance of reading AnyRealmValue and enum types from Realm collections. * Allow using Swift enums which conform to `PersistableEnum` as the value type for all Realm collections. * `AnyRealmCollection` now conforms to `Encodable`. * AnyRealmValue and PersistableEnum values can now be passed directly to an NSPredicate used in a filter() call rather than having to pass the rawValue (the rawValue is still allowed). * Queries on collections of PersistableEnums can now be performed with `where()`. * Add support for querying on the rawValue of an enum with `where()`. * `.count` is supported for Maps of all types rather than just numeric types in `where()`. * Add support for querying on the properties of objects contained in dictionaries (e.g. "dictProperty.@allValues.name CONTAINS 'a'"). * Improve the error message for many types of invalid predicates in queries. * Add support for comparing `@allKeys` to another property on the same object. * Add `Numeric` conformance to `Decimal128`. * Make some invalid property declarations such as `List` a compile-time error instead of a runtime error. * Calling `.sorted(byKeyPath:)` on a collection with an Element type which does not support sorting by keypaths is now a compile-time error instead of a runtime error. * `RealmCollection.sorted(ascending:)` can now be called on all non-Object/EmbeddedObject collections rather than only ones where the `Element` conforms to `Comparable`. * Add support for using user-defined types with `@Persistable` and in Realm collections by defining a mapping to and from a type which Realm knows how to store. For example, `URL` can be made persistable with: ```swift extension URL: FailableCustomPersistable { // Store URL values as a String in Realm public typealias PersistedType = String // Convert a String to a URL public init?(persistedValue: String) { self.init(string: persistedValue) } // Convert a URL to a String public var persistableValue: String { self.absoluteString } } ``` After doing this, `@Persisted var url: URL` is a valid property declaration on a Realm object. More advanced mappings can be done by mapping to an EmbeddedObject which can store multiple values. ### Fixed * Accessing a non object collection inside a migration would cause a crash * [#5633](https://github.com/realm/realm-cocoa/issues/5633). * Accessing a `Map` of objects dynamically would not handle nulled values correctly (since v10.8.0). * `where()` allowed constructing some nonsensical queries due to boolean comparisons returning `Query` rather than `Query` (since v10.19.0). * `@allValues` queries on dictionaries accidentally did not require "ANY". * Case-insensitive and diacritic-insensitive modifiers were ignored when comparing the result of an aggregate operation to another property in a query. * `Object.init(value:)` did not allow initializing `RLMDictionary`/`Map` properties with null values for map entries (since v10.8.0). * `@ObservedResults` did not refresh when changes were made to the observed collection. (since v10.6.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.2.1. 10.20.1 Release notes (2021-12-14) ============================================================= Xcode 12.4 is now the minimum supported version of Xcode. ### Fixed * Add missing `Indexable` support for UUID. ([Cocoa #7545](https://github.com/realm/realm-swift/issues/7545), since v10.10.0) ### Breaking Changes * All `async` functions now require Xcode 13.2 to work around an App Store/TestFlight bug that results in apps built with 13.0/13.1 which do not use libConcurrency but link a library which does crashing on startup. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.2. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.2. 10.20.0 Release notes (2021-11-16) ============================================================= ### Enhancements * Conform `@ThreadSafe` and `ThreadSafeReference` to `Sendable`. * Allow using Swift enums which conform to `PersistableEnum` as the value type for all Realm collections. * `AnyRealmCollection` now conforms to `Encodable`. * Greatly improve performance of reading AnyRealmValue and enum types from Realm collections. * `AnyRealmCollection` now conforms to `Encodable`. ### Fixed * `@AutoOpen` will open the existing local Realm file on any connection error rather than only when the connection specifically times out. * Do not allow `progress` state changes for `@AutoOpen` and `@AsyncOpen` after changing state to `open(let realm)` or `error(let error)`. * Logging out a sync user failed to remove the local Realm file for partitions with very long partition values that would have exceeded the maximum path length. ([Core #4187](https://github.com/realm/realm-core/issues/4187), since v10.0.0) * Don't keep trying to refresh the access token if the client's clock is more than 30 minutes fast. ([Core #4941](https://github.com/realm/realm-core/issues/4941)) * Failed auth requests used a fixed long sleep rather than exponential backoff like other sync requests, which could result in very delayed reconnects after a device was offline long enough for the access token to expire (since v10.0.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.1. ### Internal * Upgraded realm-core from 11.6.0 to 11.6.1. 10.19.0 Release notes (2021-11-04) ============================================================= ### Enhancements * Add `.searchable()` SwiftUI View Modifier which allows filtering `@ObservedResult` results from a search field component by a key path. ```swift List { ForEach(reminders) { reminder in ReminderRowView(reminder: reminder) } }.searchable(text: $searchFilter, collection: $reminders, keyPath: \.name) { ForEach(reminders) { remindersFiltered in Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) } } ``` * Add an API for a type safe query syntax. This allows you to filter a Realm and collections managed by a Realm with Swift style expressions. Here is a brief example: ```swift class Person: Object { @Persisted var name: String @Persisted var hobbies: MutableSet @Persisted var pets: List } class Pet: Object { @Persisted var name: String @Persisted var age: Int } let persons = realm.objects(Person.self).where { $0.hobbies.contains("music") || $0.hobbies.contains("baseball") } persons = realm.objects(Person.self).where { ($0.pets.age >= 2) && $0.pets.name.starts(with: "L") } ``` ([#7419](https://github.com/realm/realm-swift/pull/7419)) * Add support for dictionary subscript expressions (e.g. `"phoneNumbers['Jane'] == '123-3456-123'"`) when querying with an NSPredicate. * Add UserProfile to User. This contains metadata from social logins with MongoDB Realm. * Slightly reduce the peak memory usage when processing sync changesets. ### Fixed * Change default request timeout for `RLMApp` from 6 seconds to 60 seconds. * Async `Realm` init would often give a Realm instance which could not actually be used and would throw incorrect thread exceptions. It now is `@MainActor` and gives a Realm instance which always works on the main actor. The non-functional `queue:` parameter has been removed (since v10.15.0). * Restore the pre-v10.12.0 behavior of calling `writeCopy()` on a synchronized Realm which produced a local non-synchronized Realm ([#7513](https://github.com/realm/realm-swift/issues/7513)). * Decimal128 did not properly normalize the value before hashing and so could have multiple values which are equal but had different hash values (since v10.8.0). * Fix a rare assertion failure or deadlock when a sync session is racing to close at the same time that external reference to the Realm is being released. ([Core #4931](https://github.com/realm/realm-core/issues/4931)) * Fix a assertion failure when opening a sync Realm with a user who had been removed. Instead an exception will be thrown. ([Core #4937](https://github.com/realm/realm-core/issues/4937), since v10.0.0) * Fixed a rare segfault which could trigger if a user was being logged out while the access token refresh response comes in. ([Core #4944](https://github.com/realm/realm-core/issues/4944), since v10.0.0) * Fixed a bug where progress notifiers on an AsyncOpenTask could be called after the open completed. ([Core #4919](https://github.com/realm/realm-core/issues/4919)) * SecureTransport was not enabled for macCatalyst builds when installing via SPM, resulting in `'SSL/TLS protocol not supported'` exceptions when using Realm Sync. ([#7474](https://github.com/realm/realm-swift/issues/7474)) * Users were left in the logged in state when their refresh token expired. ([Core #4882](https://github.com/realm/realm-core/issues/4882), since v10) * Calling `.count` on a distinct collection would return the total number of objects in the collection rather than the distinct count the first time it is called. ([#7481](https://github.com/realm/realm-swift/issues/7481), since v10.8.0). * `realm.delete(collection.distinct(...))` would delete all objects in the collection rather than just the first object with each distinct value in the property being distincted on, unless the distinct Results were read from at least once first (since v10.8.0). * Calling `.distinct()` on a collection, accessing the Results, then passing the Results to `realm.delete()` would delete the correct objects, but afterwards report a count of zero even if there were still objects in the Results (since v10.8.0). * Download compaction could result in non-streaming sync download notifiers never reporting completion (since v10.0.0, [Core #4989](https://github.com/realm/realm-core/pull/4989)). * Fix a deadlock in SyncManager that was probably not possible to hit in real-world code (since v10.0.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.1. * CocoaPods: 1.10 or later. * Xcode: 12.4-13.2. ### Internal * Upgraded realm-core from v11.4.1 to v11.6.0 10.18.0 Release notes (2021-10-25) ============================================================= ### Enhancements * Add support for using multiple users with `@AsyncOpen` and `@AutoOpen`. Setting the current user to a new user will now automatically reopen the Realm with the new user. * Add prebuilt binary for Xcode 13.1 to the release package. ### Fixed * Fix `@AsyncOpen` and `@AutoOpen` using `defaultConfiguration` by default if the user's doesn't provide one, will set an incorrect path which doesn't correspond to the users configuration one. (since v10.12.0) * Adding missing subscription completion for `AsyncOpenPublisher` after successfully returning a realm. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.1. 10.17.0 Release notes (2021-10-06) ============================================================= ### Enhancements * Add a new `@ThreadSafe` property wrapper. Objects and collections wrapped by `@ThreadSafe` may be passed between threads. It's intended to allow local variables and function parameters to be used across threads when needed. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.0. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0. 10.16.0 Release notes (2021-09-29) ============================================================= ### Enhancements * Add `async` versions of `EmailPasswordAuth.callResetPasswordFunction` and r `User.linkUser` methods. * Add `async` version of `MongoCollection` methods. * Add `async` support for user functions. ### Fixed * A race condition in Realm.asyncOpen() sometimes resulted in subsequent writes from Realm Sync failing to produce notifications ([#7447](https://github.com/realm/realm-swift/issues/7447), [#7453](https://github.com/realm/realm-swift/issues/7453), [Core #4909](https://github.com/realm/realm-core/issues/4909), since v10.15.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.0. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0. 10.15.1 Release notes (2021-09-15) ============================================================= ### Enhancements * Switch to building the Carthage release with Xcode 13. ### Fixed * Fix compilation error where Swift 5.5 is available but the macOS 12 SDK was not. This was notable for the Xcode 13 RC. This fix adds a #canImport check for the `_Concurrency` module that was not available before the macOS 12 SDK. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 13.0. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0. 10.15.0 Release notes (2021-09-10) ============================================================= ### Enhancements * Add `async` versions of the `Realm.asyncOpen` and `App.login` methods. * ThreadSafeReference no longer pins the source transaction version for anything other than a Results created by filtering a collection. This means that holding on to thread-safe references to other things (such as Objects) will no longer cause file size growth. * A ThreadSafeReference to a Results backed by a collection can now be created inside a write transaction as long as the collection was not created in the current write transaction. * Synchronized Realms are no longer opened twice, cutting the address space and file descriptors used in half. ([Core #4839](https://github.com/realm/realm-core/pull/4839)) * When using the SwiftUI helper types (@ObservedRealmObject and friends) to bind to an Equatable property, self-assignment no longer performs a pointless write transaction. SwiftUI appears to sometimes call a Binding's set function multiple times for a single UI action, so this results in significantly fewer writes being performed. ### Fixed * Adding an unmanaged object to a Realm that was declared with `@StateRealmObject` would throw the exception `"Cannot add an object with observers to a Realm"`. * The `RealmCollectionChange` docs refered to indicies in modifications as the 'new' collection. This is incorrect and the docs now state that modifications refer to the previous version of the collection. ([Cocoa #7390](https://github.com/realm/realm-swift/issues/7390)) * Fix crash in `RLMSyncConfiguration.initWithUser` error mapping when a user is disabled/deleted from MongoDB Realm dashboard. ([Cocoa #7399](https://github.com/realm/realm-swift/issues/7399), since v10.0.0) * If the application crashed at the wrong point when logging a user in, the next run of the application could hit the assertion failure "m_state == SyncUser::State::LoggedIn" when a synchronized Realm is opened with that user. ([Core #4875](https://github.com/realm/realm-core/issues/4875), since v10.0.0) * The `keyPaths:` parameter to `@ObservedResults` did not work (since v10.12.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 5. ### Internal * Upgraded realm-core from 11.3.1 to 11.4.1 10.14.0 Release notes (2021-09-03) ============================================================= ### Enhancements * Add additional `observe` methods for Objects and RealmCollections which take a `PartialKeyPath` type key path parameter. * The release package once again contains Xcode 13 binaries. * `PersistableEnum` properties can now be indexed or used as the primary key if the RawValue is an indexable or primary key type. ### Fixed * `Map` did not conform to `Codable`. ([Cocoa #7418](https://github.com/realm/realm-swift/pull/7418), since v10.8.0) * Fixed "Invalid data type" assertion failure in the sync client when the client recieved an AddColumn instruction from the server for an AnyRealmValue property when that property already exists locally. ([Core #4873](https://github.com/realm/realm-core/issues/4873), since v10.8.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 5. ### Internal * Upgraded realm-core from 11.3.0 to 11.3.1. 10.13.0 Release notes (2021-08-26) ============================================================= ### Enhancements * 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`. ([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. ([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. ([Cocoa #7372](https://github.com/realm/realm-swift/issues/7372), since v10.6.0) * Work around Xcode 13 beta 3+ shipping a broken swiftinterface file for Combine on 32-bit iOS. ([Cocoa #7368](https://github.com/realm/realm-swift/issues/7368)) * Fixes history corruption when replacing an embedded object in a list. ([Core #4845](https://github.com/realm/realm-core/issues/4845)), since v10.0.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 5. ### Internal * Upgraded realm-core from 11.2.0 to 11.3.0 10.12.0 Release notes (2021-08-03) ============================================================= ### Enhancements * `Object.observe()` and `RealmCollection.observe()` now include an optional `keyPaths` parameter which filters change notifications to those only occurring on the provided key path or key paths. See method documentation for extended detail on filtering behavior. * `ObservedResults` now includes an optional `keyPaths` parameter which filters change notifications to those only occurring on the provided key path or key paths. ex) `@ObservedResults(MyObject.self, keyPaths: ["myList.property"])` * Add two new property wrappers for opening a Realm asynchronously in a SwiftUI View: - `AsyncOpen` is a property wrapper that initiates Realm.asyncOpen for the current user, notifying the view when there is a change in Realm asyncOpen state. - `AutoOpen` behaves similarly to `AsyncOpen`, but in the case of no internet connection this will return an opened realm. * Add `EnvironmentValues.partitionValue`. This value can be injected into any view using one of our new property wrappers `AsyncOpen` and `AutoOpen`: `MyView().environment(\.partitionValue, "partitionValue")`. * Shift more of the work done when first initializing a collection notifier to the background worker thread rather than doing it on the main thread. ### Fixed * `configuration(partitionValue: AnyBSON)` would always set a nil partition value for the user sync configuration. * Decoding a `@Persisted` property would incorrectly throw a `DecodingError.keyNotFound` for an optional property if the key is missing. ([Cocoa #7358](https://github.com/realm/realm-swift/issues/7358), since v10.10.0) * Fixed a symlink which prevented Realm from building on case sensitive file systems. ([#7344](https://github.com/realm/realm-swift/issues/7344), since v10.8.0) * Removing a change callback from a Results would sometimes block the calling thread while the query for that Results was running on the background worker thread (since v10.11.0). * Object observers did not handle the object being deleted properly, which could result in assertion failures mentioning "m_table" in ObjectNotifier ([Core #4824](https://github.com/realm/realm-core/issues/4824), since v10.11.0). * Fixed a crash when delivering notifications over a nested hierarchy of lists of Mixed that contain links. ([Core #4803](https://github.com/realm/realm-core/issues/4803), since v10.8.0) * Fixed a crash when an object which is linked to by a Mixed is deleted via sync. ([Core #4828](https://github.com/realm/realm-core/pull/4828), since v10.8.0) * Fixed a rare crash when setting a mixed link for the first time which would trigger if the link was to the same table and adding the backlink column caused a BPNode split. ([Core #4828](https://github.com/realm/realm-core/pull/4828), since v10.8.0) ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 4. On iOS Xcode 13 beta 2 is the latest supported version due to betas 3 and 4 having a broken Combine.framework. ### Internal * Upgraded realm-core from v11.1.1 to v11.2.0 10.11.0 Release notes (2021-07-22) ============================================================= ### Enhancements * Add type safe methods for: - `RealmCollection.min(of:)` - `RealmCollection.max(of:)` - `RealmCollection.average(of:)` - `RealmCollection.sum(of:)` - `RealmCollection.sorted(by:ascending:)` - `RealmKeyedCollection.min(of:)` - `RealmKeyedCollection.max(of:)` - `RealmKeyedCollection.average(of:)` - `RealmKeyedCollection.sum(of:)` - `RealmKeyedCollection.sorted(by:ascending:)` - `Results.distinct(by:)` - `SortDescriptor(keyPath:ascending:) Calling these methods can now be done via Swift keyPaths, like so: ```swift class Person: Object { @Persisted var name: String @Persisted var age: Int } let persons = realm.objects(Person.self) persons.min(of: \.age) persons.max(of: \.age) persons.average(of: \.age) persons.sum(of: \.age) persons.sorted(by: \.age) persons.sorted(by: [SortDescriptor(keyPath: \Person.age)]) persons.distinct(by: [\Person.age]) ``` * Add `List.objects(at indexes:)` in Swift and `[RLMCollection objectsAtIndexes:]` in Objective-C. This allows you to select elements in a collection with a given IndexSet ([#7298](https://github.com/realm/realm-swift/issues/7298)). * Add `App.emailPasswordAuth.retryCustomConfirmation(email:completion:)` and `[App.emailPasswordAuth retryCustomConfirmation:completion:]`. These functions support retrying a [custom confirmation](https://docs.mongodb.com/realm/authentication/email-password/#run-a-confirmation-function) function. * Improve performance of creating collection notifiers for Realms with a complex schema. This means that the first run of a query or first call to observe() on a collection will do significantly less work on the calling thread. * Improve performance of calculating changesets for notifications, particularly for deeply nested object graphs and objects which have List or Set properties with small numbers of objects in the collection. ### Fixed * `RealmProperty` would crash when decoding a `null` json value. ([Cocoa #7323](https://github.com/realm/realm-swift/issues/7323), since v10.8.0) * `@Persisted` would crash when decoding a `null` value. ([#7332](https://github.com/realm/realm-swift/issues/7332), since v10.10.0). * Fixed an issue where `Realm.Configuration` would be set after views have been laid out when using `.environment(\.realmConfiguration, ...)` in SwiftUI. This would cause issues if you are required to bump your schema version and are using `@ObservedResults`. * Sync user profiles now correctly persist between runs. ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 3. Note that this release does not contain Xcode 13 beta binaries as beta 3 does not include a working version of Combine.framework for iOS. ### Internal * Upgraded realm-core from 11.0.4 to 11.1.1 10.10.0 Release notes (2021-07-07) ============================================================= ### Enhancements * Add a new property wrapper-based declaration syntax for properties on Realm Swift object classes. Rather than using `@objc dynamic` or the `RealmProperty` wrapper type, properties can now be declared with `@Persisted var property: T`, where `T` is any of the supported property types, including optional numbers and collections. This has a few benefits: - All property types are now declared in the same way. No more remembering that this type requires `@objc dynamic var` while this other type requires `let`, and the `RealmProperty` or `RealmOptional` helper is no longer needed for types not supported by Objective-C. - No more overriding class methods like `primaryKey()`, `indexedProperties()` or `ignoredProperties()`. The primary key and indexed flags are set directly in the property declaration with `@Persisted(primaryKey: true) var _id: ObjectId` or `@Persisted(indexed: true) var indexedProperty: Int`. If any `@Persisted` properties are present, all other properties are implicitly ignored. - Some performance problems have been fixed. Declaring collection properties as `let listProp = List()` resulted in the `List` object being created eagerly when the parent object is read, which could cause performance problems if a class has a large number of `List` or `RealmOptional` properties. `@Persisted var list: List` allows us to defer creating the `List` until it's accessed, improving performance when looping over objects and using only some of the properties. Similarly, `let _id = ObjectId.generate()` was a convenient way to declare a sync-compatible primary key, but resulted in new ObjectIds being generated in some scenarios where the value would never actually be used. `@Persisted var _id: ObjectId` has the same behavior of automatically generating primary keys, but allows us to only generate it when actually needed. - More types of enums are supported. Any `RawRepresentable` enum whose raw type is a type supported by Realm can be stored in an `@Persisted` project, rather than just `@objc` enums. Enums must be declared as conforming to the `PersistableEnum` protocol, and still cannot (yet) be used in collections. - `willSet` and `didSet` can be used with `@Persistable` properties, while they previously did not work on managed Realm objects. While we expect the switch to the new syntax to be very simple for most users, we plan to support the existing objc-based declaration syntax for the foreseeable future. The new style and old style cannot be mixed within a single class, but new classes can use the new syntax while existing classes continue to use the old syntax. Updating an existing class to the new syntax does not change what data is stored in the Realm file and so does not require a migration (as long as you don't also change the schema in the process, of course). * Add `Map.merge()`, which adds the key-value pairs from another Map or Dictionary to the map. * Add `Map.asKeyValueSequence()` which returns an adaptor that can be used with generic functions that operate on Dictionary-styled sequences. ### Fixed * AnyRealmValue enum values are now supported in more places when creating objects. * Declaring a property as `RealmProperty` will now report an error during schema discovery rather than doing broken things when the property is used. * Observing the `invalidated` property of `RLMDictionary`/`Map` via KVO did not set old/new values correctly in the notification (since 10.8.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 2. 10.9.0 Release notes (2021-07-01) ============================================================= ### Enhancements * Add `App.emailPasswordAuth.retryCustomConfirmation(email:completion:)` and `[App.emailPasswordAuth retryCustomConfirmation:completion:]`. These functions support retrying a [custom confirmation](https://docs.mongodb.com/realm/authentication/email-password/#run-a-confirmation-function) function. * Improve performance of many Dictionary operations, especially when KVO is being used. ### Fixed * Calling `-[RLMRealm deleteObjects:]` on a `RLMDictionary` cleared the dictionary but did not actually delete the objects in the dictionary (since v10.8.0). * Rix an assertion failure when observing a `List` contains object links. ([Core #4767](https://github.com/realm/realm-core/issues/4767), since v10.8.0) * Fix an assertion failure when observing a `RLMDictionary`/`Map` which links to an object which was deleting by a different sync client. ([Core #4770](https://github.com/realm/realm-core/pull/4770), since v10.8.0) * Fix an endless recursive loop that could cause a stack overflow when computing changes on a set of objects which contained cycles. ([Core #4770](https://github.com/realm/realm-core/pull/4770), since v10.8.0). * Hash collisions in dictionaries were not handled properly. ([Core #4776](https://github.com/realm/realm-core/issues/4776), since v10.8.0). * Fix a crash after clearing a list or set of AnyRealmValue containing links to objects ([Core #4774](https://github.com/realm/realm-core/issues/4774), since v10.8.0) * Trying to refresh a user token which had been revoked by an admin lead to an infinite loop and then a crash. This situation now properly logs the user out and reports an error. ([Core #4745](https://github.com/realm/realm-core/issues/4745), since v10.0.0). ### Compatibility * Realm Studio: 11.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 2. ### Internal * Upgraded realm-core from v11.0.3 to v11.0.4 10.8.1 Release notes (2021-06-22) ============================================================= ### Enhancements * Update Xcode 12.5 to Xcode 12.5.1. * Create fewer dynamic classes at runtime, improving memory usage and startup time slightly. ### Fixed * Importing the Realm swift package produced several warnings about excluded files not existing. Note that one warning will still remain after this change. ([#7295](https://github.com/realm/realm-swift/issues/7295), since v10.8.0). * Update the root URL for the API docs so that the links go to the place where new versions of the docs are being published. ([#7299](https://github.com/realm/realm-swift/issues/7299), since v10.6.0). ### Compatibility * Realm Studio: 11.0.0 or later. Note that this version of Realm Studio has not yet been released at the time of this release. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5.1. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 1. 10.8.0 Release notes (2021-06-14) ============================================================= NOTE: This version upgrades the Realm file format version to add support for the new data types and to adjust how primary keys are handled. Realm files opened will be automatically upgraded and cannot be read by versions older than v10.8.0. This upgrade should be a fairly fast one. Note that we now automatically create a backup of the pre-upgrade Realm. ### Enhancements * Add support for the `UUID` and `NSUUID` data types. These types can be used for the primary key property of Object classes. * Add two new collection types to complement the existing `RLMArray`/`List` type: - `RLMSet` in Objective-C and `MutableSet` in Swift are mutable unordered collections of distinct objects, similar to the built-in `NSMutableSet` and `Set`. The values in a set may be any non-collection type which can be stored as a Realm property. Sets are guaranteed to never contain two objects which compare equal to each other, including when conflicting writes are merged by sync. - `RLMDictionary` in Objective-C and `Map` are mutable key-value dictionaries, similar to the built-in `NSMutableDictionary` and `Dictionary`. The values in a dictionary may be any non-collection type which can be stored as a Realm property. The keys must currently always be a string. * Add support for dynamically typed properties which can store a value of any of the non-collection types supported by Realm, including Object subclasses (but not EmbeddedObject subclasses). These are declared with `@property id propertyName;` in Objective-C and `let propertyName = RealmProperty()` in Swift. ### Fixed * Setting a collection with a nullable value type to null via one of the dynamic interfaces would hit an assertion failure instead of clearing the collection. * Fixed an incorrect detection of multiple incoming links in a migration when changing a table to embedded and removing a link to it at the same time. ([#4694](https://github.com/realm/realm-core/issues/4694) since v10.0.0-beta.2) * Fixed a divergent merge on Set when one client clears the Set and another client inserts and deletes objects. ([#4720](https://github.com/realm/realm-core/issues/4720)) * Partially revert to pre-v5.0.0 handling of primary keys to fix a performance regression. v5.0.0 made primary keys determine the position in the low-level table where newly added objects would be inserted, which eliminated the need for a separate index on the primary key. This made some use patterns slightly faster, but also made some reasonable things dramatically slower. ([#4522](https://github.com/realm/realm-core/issues/4522)) * Fixed an incorrect detection of multiple incoming links in a migration when changing a table to embedded and removing a link to it at the same time. ([#4694](https://github.com/realm/realm-core/issues/4694) since v10.0.0-beta.2) * Fix collection notification reporting for modifications. This could be observed by receiving the wrong indices of modifications on sorted or distinct results, or notification blocks sometimes not being called when only modifications have occured. ([#4573](https://github.com/realm/realm-core/pull/4573) since v5.0.0). * Fix incorrect sync instruction emission when replacing an existing embedded object with another embedded object.([Core #4740](https://github.com/realm/realm-core/issues/4740) ### Deprecations * `RealmOptional` has been deprecated in favor of `RealmProperty`. `RealmProperty` is functionality identical to `RealmOptional` when storing optional numeric types, but can also store the new `AnyRealmValue` type. ### Compatibility * Realm Studio: 11.0.0 or later. Note that this version of Realm Studio has not yet been released at the time of this release. * Carthage release for Swift is built with Xcode 12.5. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 1. ### Internal * Upgraded realm-core from v10.7.2 to v11.0.3 10.8.0-beta.2 Release notes (2021-06-01) ============================================================= ### Enhancements * Add `RLMDictionary`/`Map<>` datatype. This is a Dictionary collection type used for storing key-value pairs in a collection. ### Compatibility * Realm Studio: 11.0.0-beta.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 12.5. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v11.0.0-beta.4 to v11.0.0-beta.6 10.8.0-beta.0 Release notes (2021-05-07) ============================================================= ### Enhancements * Add `RLMSet`/`MutableSet<>` datatype. This is a Set collection type used for storing distinct values in a collection. * Add support for `id`/`AnyRealmValue`. * Add support for `UUID`/`NSUUID` data type. ### Fixed * None. ### Deprecations * `RealmOptional` has been deprecated in favor of `RealmProperty`. ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.7.2 to v10.8.0-beta.5 10.7.7 Release notes (2021-06-10) ============================================================= Xcode 12.2 is now the minimum supported version. ### Enhancements * Add Xcode 13 beta 1 binaries to the release package. ### Fixed * Fix a runtime crash which happens in some Xcode version (Xcode < 12, reported in Xcode 12.5), where SwiftUI is not weak linked by default. This fix only works for Cocoapods projects. ([#7234](https://github.com/realm/realm-swift/issues/7234) * Fix warnings when building with Xcode 13 beta 1. ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5. * CocoaPods: 1.10 or later. * Xcode: 12.2-13.0 beta 1. 10.7.6 Release notes (2021-05-13) ============================================================= ### Enhancements * Realms opened in read-only mode can now be invalidated (although it is unlikely to be useful to do so). ### Fixed * Fix an availability warning when building Realm. The code path which gave the warning can not currently be hit, so this did not cause any runtime problems ([#7219](https://github.com/realm/realm-swift/issues/7219), since 10.7.3). * Proactively check the expiry time on the access token and refresh it before attempting to initiate a sync session. This prevents some error logs from appearing on the client such as: "ERROR: Connection[1]: Websocket: Expected HTTP response 101 Switching Protocols, but received: HTTP/1.1 401 Unauthorized" ([RCORE-473](https://jira.mongodb.org/browse/RCORE-473), since v10.0.0) * Fix a race condition which could result in a skipping notifications failing to skip if several commits using notification skipping were made in succession (since v5.0.0). * Fix a crash on exit inside TableRecycler which could happen if Realms were open on background threads when the app exited. ([Core #4600](https://github.com/realm/realm-core/issues/4600), since v5.0.0) * Fix errors related to "uncaught exception in notifier thread: N5realm11KeyNotFoundE: No such object" which could happen on sycnronized Realms if a linked object was deleted by another client. ([JS #3611](https://github.com/realm/realm-js/issues/3611), since v10.0.0). * Reading a link to an object which has been deleted by a different client via a string-based interface (such as value(forKey:) or the subscript operator on DynamicObject) could return an invalid object rather than nil. ([Core #4687](https://github.com/realm/realm-core/pull/4687), since v10.0.0) * Recreate the sync metadata Realm if the encryption key for it is missing from the keychain rather than crashing. This can happen if a device is restored from an unencrypted backup, which restores app data but not the app's keychain entries, and results in all cached logics for sync users being discarded but no data being lost. [Core #4285](https://github.com/realm/realm-core/pull/4285) * Thread-safe references can now be created for read-only Realms. ([#5475](https://github.com/realm/realm-swift/issues/5475)). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.6.0 to v10.7.2 10.7.5 Release notes (2021-05-07) ============================================================= ### Fixed * Iterating over frozen collections on multiple threads at the same time could throw a "count underflow" NSInternalInconsistencyException. ([#7237](https://github.com/realm/realm-swift/issues/7237), since v5.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5. * CocoaPods: 1.10 or later. 10.7.4 Release notes (2021-04-26) ============================================================= ### Enhancements * Add Xcode 12.5 binaries to the release package. ### Fixed * Add the Info.plist file to the XCFrameworks in the Carthage xcframwork package ([#7216](https://github.com/realm/realm-swift/issues/7216), since 10.7.3). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.5. * CocoaPods: 1.10 or later. 10.7.3 Release notes (2021-04-22) ============================================================= ### Enhancements * Package a prebuilt XCFramework for Carthage. Carthage 0.38 and later will download this instead of the old frameworks when using `--use-xcframeworks`. * We now make a backup of the realm file prior to any file format upgrade. The backup is retained for 3 months. Backups from before a file format upgrade allows for better analysis of any upgrade failure. We also restore a backup, if a) an attempt is made to open a realm file whith a "future" file format and b) a backup file exist that fits the current file format. ([Core #4166](https://github.com/realm/realm-core/pull/4166)) * The error message when the intial steps of opening a Realm file fails is now more descriptive. * Make conversion of Decimal128 to/from string work for numbers with more than 19 significant digits. This means that Decimal128's initializer which takes a string will now never throw, as it previously threw only for out-of-bounds values. The initializer is still marked as `throws` for backwards compatibility. ([#4548](https://github.com/realm/realm-core/issues/4548)) ### Fixed * Adjust the header paths for the podspec to avoid accidentally finding a file which isn't part of the pod that produced warnings when importing the framework. ([#7113](https://github.com/realm/realm-swift/issues/7113), since 10.5.2). * Fixed a crash that would occur when observing unmanaged Objects in multiple views in SwiftUI. When using `@StateRealmObject` or `@ObservedObject` across multiple views with an unmanaged object, each view would subscribe to the object. As each view unsubscribed (generally when trailing back through the view stack), our propertyWrappers would attempt to remove the KVOs for each cancellation, when it should only be done once. We now correctly remove KVOs only once. ([#7131](https://github.com/realm/realm-swift/issues/7131)) * Fixed `isInvalidated` not returning correct value after object deletion from Realm when using a custom schema. The object's Object Schema was not updated when the object was added to the realm. We now correctly update the object schema when adding it to the realm. ([#7181](https://github.com/realm/realm-swift/issues/7181)) * Syncing large Decimal128 values would cause "Assertion failed: cx.w[1] == 0" ([Core #4519](https://github.com/realm/realm-core/issues/4519), since v10.0.0). * Potential/unconfirmed fix for crashes associated with failure to memory map (low on memory, low on virtual address space). For example ([#4514](https://github.com/realm/realm-core/issues/4514), since v5.0.0). * Fix assertion failures such as "!m_notifier_skip_version.version" or "m_notifier_sg->get_version() + 1 == new_version.version" when performing writes inside change notification callbacks. Previously refreshing the Realm by beginning a write transaction would skip delivering notifications, leaving things in an inconsistent state. Notifications are now delivered recursively when needed instead. ([Cocoa #7165](https://github.com/realm/realm-swift/issues/7165)). * Fix collection notification reporting for modifications. This could be observed by receiving the wrong indices of modifications on sorted or distinct results, or notification blocks sometimes not being called when only modifications have occured. ([#4573](https://github.com/realm/realm-core/pull/4573) since v5.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.5.5 to v10.6.0 * Add additional debug validation to file map management that will hopefully catch cases where we unmap something which is still in use. 10.7.2 Release notes (2021-03-08) ============================================================= ### Fixed * During integration of a large amount of data from the server, you may get "Assertion failed: !fields.has_missing_parent_update()" ([Core #4497](https://github.com/realm/realm-core/issues/4497), since v6.0.0) ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.5.4 to v10.5.5 10.7.1 Release notes (2021-03-05) ============================================================= ### Fixed * Queries of the form "a.b.c == nil" would match objects where `b` is `nil` if `c` did not have an index and did not if `c` was indexed. Both will now match to align with NSPredicate's behavior. ([Core #4460]https://github.com/realm/realm-core/pull/4460), since 4.3.0). * Restore support for upgrading files from file format 5 (Realm Cocoa 1.x). ([Core #7089](https://github.com/realm/realm-swift/issues/7089), since v5.0.0) * On 32bit devices you may get exception with "No such object" when upgrading to v10.* ([Java #7314](https://github.com/realm/realm-java/issues/7314), since v5.0.0) * The notification worker thread would rerun queries after every commit rather than only commits which modified tables which could effect the query results if the table had any outgoing links to tables not used in the query. ([Core #4456](https://github.com/realm/realm-core/pull/4456), since v5.0.0). * Fix "Invalid ref translation entry [16045690984833335023, 78187493520]" assertion failure which could occur when using sync or multiple processes writing to a single Realm file. ([#7086](https://github.com/realm/realm-swift/issues/7086), since v5.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.5.3 to v10.5.4 10.7.0 Release notes (2021-02-23) ============================================================= ### Enhancements * Add support for some missing query operations on data propertys: - Data properties can be compared to other data properties (e.g. "dataProperty1 == dataProperty2"). - Case and diacritic-insensitive queries can be performed on data properties. This will only have meaningful results if the data property contains UTF-8 string data. - Data properties on linked objects can be queried (e.g. "link.dataProperty CONTAINS %@") * Implement queries which filter on lists other than object links (lists of objects were already supported). All supported operators for normal properties are now supported for lists (e.g. "ANY intList = 5" or "ANY stringList BEGINSWITH 'prefix'"), as well as aggregate operations on the lists (such as "intArray.@sum > 100"). * Performance of sorting on more than one property has been improved. Especially important if many elements match on the first property. Mitigates ([#7092](https://github.com/realm/realm-swift/issues/7092)) ### Fixed * Fixed a bug that prevented an object type with incoming links from being marked as embedded during migrations. ([Core #4414](https://github.com/realm/realm-core/pull/4414)) * The Realm notification listener thread could sometimes hit the assertion failure "!skip_version.version" if a write transaction was committed at a very specific time (since v10.5.0). * Added workaround for a case where upgrading an old file with illegal string would crash ([#7111](https://github.com/realm/realm-swift/issues/7111)) * Fixed a conflict resolution bug related to the ArrayMove instruction, which could sometimes cause an "Invalid prior_size" exception to prevent synchronization (since v10.5.0). * Skipping a change notification in the first write transaction after the observer was added could potentially fail to skip the notification (since v10.5.1). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.5.0 to v10.5.3 10.6.0 Release notes (2021-02-15) ============================================================= ### Enhancements * 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 Realm `Object`, `List`, or `EmbeddedObject` types. * 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")))`. 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 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 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. * Errors returned from the server when sync WebSockets get closed are now captured and surfaced as a SyncError. * Improve performance of sequential reads on a Results backed directly by a Table (i.e. `realm.object(ClasSName.self)` with no filter/sort/etc.) by 50x. * Orphaned embedded object types which are not linked to by any top-level types are now better handled. Previously the server would reject the schema, resulting in delayed and confusing error reporting. Explicitly including an orphan in `objectTypes` is now immediately reported as an error when opening the Realm, and orphans are automatically excluded from the auto-discovered schema when `objectTypes` is not specified. ### Fixed * Reading from a Results backed directly by a Table (i.e. `realm.object(ClasSName.self)` with no filter/sort/etc.) would give incorrect results if the Results was constructed and accessed before creating a new object with a primary key less than the smallest primary key which previously existed. ([#7014](https://github.com/realm/realm-swift/issues/7014), since v5.0.0). * During synchronization you might experience crash with "Assertion failed: ref + size <= next->first". ([Core #4388](https://github.com/realm/realm-core/issues/4388)) ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.4.0 to v10.5.0 10.5.2 Release notes (2021-02-09) ============================================================= ### Enhancements * Add support for "thawing" objects. `Realm`, `Results`, `List` and `Object` now have `thaw()` methods which return a live copy of the frozen object. This enables app behvaior where a frozen object can be made live again in order to mutate values. For example, first freezing an object passed into UI view, then thawing the object in the view to update values. * Add Xcode 12.4 binaries to the release package. ### Fixed * Inserting a date into a synced collection via `AnyBSON.datetime(...)` would be of type `Timestamp` and not `Date`. This could break synced objects with a `Date` property. ([#6654](https://github.com/realm/realm-swift/issues/6654), since v10.0.0). * Fixed an issue where creating an object after file format upgrade may fail with assertion "Assertion failed: lo() <= std::numeric_limits::max()" ([#4295](https://github.com/realm/realm-core/issues/4295), since v5.0.0) * Allow enumerating objects in migrations with types which are no longer present in the schema. * Add `RLMResponse.customStatusCode`. This fixes timeout exceptions that were occurring with a poor connection. ([#4188](https://github.com/realm/realm-core/issues/4188)) * Limit availability of ObjectKeyIdentifiable to platforms which support Combine to match the change made in the Xcode 12.5 SDK. ([#7083](https://github.com/realm/realm-swift/issues/7083)) ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.4. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.3.3 to v10.4.0 10.5.1 Release notes (2021-01-15) ============================================================= ### Enhancements * Add Xcode 12.3 binary to release package. * Add support for queries which have nil on the left side and a keypath on the right side (e.g. "nil == name" rather than "name == nil" as was previously required). ### Fixed * Timeouts when calling server functions via App would sometimes crash rather than report an error. * Fix a race condition which would lead to "uncaught exception in notifier thread: N5realm15InvalidTableRefE: transaction_ended" and a crash when the source Realm was closed or invalidated at a very specific time during the first run of a collection notifier ([#3761](https://github.com/realm/realm-core/issues/3761), since v5.0.0). * Deleting and recreating objects with embedded objects may fail. ([Core PR #4240](https://github.com/realm/realm-core/pull/4240), since v10.0.0) * Fast-enumerating a List after deleting the parent object would crash with an assertion failure rather than a more appropriate exception. ([Core #4114](https://github.com/realm/realm-core/issues/4114), since v5.0.0). * Fix an issue where calling a MongoDB Realm Function would never be performed as the reference to the weak `User` was lost. ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.3. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.3.2 to v10.3.3 10.5.0 Release notes (2020-12-14) ============================================================= ### Enhancements * MongoDB Realm is now supported when installing Realm via Swift Package Manager. ### Fixed * The user identifier was added to the file path for synchronized Realms twice and an extra level of escaping was performed on the partition value. This did not cause functional problems, but made file names more confusing than they needed to be. Existing Realm files will continue to be located at the old path, while newly created files will be created at a shorter path. (Since v10.0.0). * Fix a race condition which could potentially allow queries on frozen Realms to access an uninitialized structure for search indexes (since v5.0.0). * Fix several data races in App and SyncSession initialization. These could possibly have caused strange errors the first time a synchronized Realm was opened (since v10.0.0). * Fix a use of a dangling reference when refreshing a user’s custom data that could lead to a crash (since v10.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.2. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.1.4 to v10.3.2 10.4.0 Release notes (2020-12-10) ============================================================= ### Enhancements * Add Combine support for App and User. These two types now have a `objectWillChange` property that emits each time the state of the object has changed (such as due to the user logging in or out). ([PR #6977](https://github.com/realm/realm-swift/pull/6977)). ### Fixed * Integrating changesets from the server would sometimes hit the assertion failure "n != realm::npos" inside Table::create_object_with_primary_key() when creating an object with a primary key which previously had been used and had incoming links. ([Core PR #4180](https://github.com/realm/realm-core/pull/4180), since v10.0.0). * The arm64 simulator slices were not actually included in the XCFramework release package. ([PR #6982](https://github.com/realm/realm-swift/pull/6982), since v10.2.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.2. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.1.3 to v10.1.4 * Upgraded realm-sync from v10.1.4 to v10.1.5 10.3.0 Release notes (2020-12-08) ============================================================= ### Enhancements * Add Google OpenID Connect Credentials, an alternative login credential to the Google OAuth 2.0 credential. ### Fixed * Fixed a bug that would prevent eventual consistency during conflict resolution. Affected clients would experience data divergence and potentially consistency errors as a result if they experienced conflict resolution between cycles of Create-Erase-Create for objects with primary keys (since v10.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.2. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-sync from v10.1.3 to v10.1.4 10.2.0 Release notes (2020-12-02) ============================================================= ### Enhancements * The prebuilt binaries are now packaged as XCFrameworks. This adds support for Catalyst and arm64 simulators when using them to install Realm, removes the need for the strip-frameworks build step, and should simplify installation. * The support functionality for using the Objective C API from Swift is now included in Realm Swift and now includes all of the required wrappers for MongoDB Realm types. In mixed Objective C/Swift projects, we recommend continuing to use the Objective C types, but import both Realm and RealmSwift in your Swift files. ### Fixed * The user identifier was added to the file path for synchronized Realms twice and an extra level of escaping was performed on the partition value. This did not cause functional problems, but made file names more confusing than they needed to be. Existing Realm files will continue to be located at the old path, while newly created files will be created at a shorter path. (Since v10.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.2. * CocoaPods: 1.10 or later. 10.1.4 Release notes (2020-11-16) ============================================================= ### Enhancements * Add arm64 slices to the macOS builds. ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.2. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.0.1 to v10.1.3 * Upgraded realm-sync from v10.0.1 to v10.1.3 10.1.3 Release notes (2020-11-13) ============================================================= ### Enhancements * Add Xcode 12.2 binaries to the release package. ### Fixed * Disallow setting `RLMRealmConfiguration.deleteRealmIfMigrationNeeded`/`Realm.Config.deleteRealmIfMigrationNeeded` when sync is enabled. This did not actually work as it does not delete the relevant server state and broke in confusing ways ([PR #6931](https://github.com/realm/realm-swift/pull/6931)). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.1. * CocoaPods: 1.10 or later. 10.1.2 Release notes (2020-11-06) ============================================================= ### Enhancements * Some error states which previously threw a misleading "NoSuchTable" exception now throw a more descriptive exception. ### Fixed * One of the Swift packages did not have the minimum deployment target set, resulting in errors when archiving an app which imported Realm via SPM. * Reenable filelock emulation on watchOS so that the OS does not kill the app when it is suspended while a Realm is open on watchOS 7 ([#6861](https://github.com/realm/realm-swift/issues/6861), since v5.4.8 * Fix crash in case insensitive query on indexed string columns when nothing matches ([#6836](https://github.com/realm/realm-swift/issues/6836), since v5.0.0). * Null values in a `List` or `List` were incorrectly treated as non-null in some places. It is unknown if this caused any functional problems when using the public API. ([Core PR #3987](https://github.com/realm/realm-core/pull/3987), since v5.0.0). * Deleting an entry in a list in two different clients could end deleting the wrong entry in one client when the changes are merged (since v10.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.1. * CocoaPods: 1.10 or later. ### Internal * Upgraded realm-core from v10.0.0 to v10.1.1 * Upgraded realm-sync from v10.0.0 to v10.1.1 10.1.1 Release notes (2020-10-27) ============================================================= ### Enhancements * Set the minimum CocoaPods version in the podspec so that trying to install with older versions gives a more useful error ([PR #6892](https://github.com/realm/realm-swift/pull/6892)). ### Fixed * Embedded objects could not be marked as `ObjectKeyIdentifable` ([PR #6890](https://github.com/realm/realm-swift/pull/6890), since v10.0.0). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.1. * CocoaPods: 1.10 or later. 10.1.0 Release notes (2020-10-22) ============================================================= CocoaPods 1.10 or later is now required to install Realm. ### Enhancements * Throw an exception for Objects that have none of its properties marked with @objc. * Mac Catalyst and arm64 simulators are now supported when integrating via Cocoapods. * Add Xcode 12.1 binaries to the release package. * Add Combine support for `Realm.asyncOpen()`. ### Fixed * Implement precise and unbatched notification of sync completion events. This avoids a race condition where an earlier upload completion event will notify a later waiter whose changes haven't been uploaded yet. ([#1118](https://github.com/realm/realm-object-store/pull/1118)). ### Compatibility * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12.1. 10.0.0 Release notes (2020-10-16) ============================================================= This release is functionally identical to v10.0.0-rc.2. NOTE: This version upgrades the Realm file format version to add support for new data types. Realm files opened will be automatically upgraded and cannot be read by versions older than v10.0.0. ### Breaking Changes * Rename Realm.Publishers to RealmPublishers to avoid confusion with Combine.Publishers. * Remove `[RLMSyncManager shared]`. This is now instatiated as a property on App/RLMApp. * `RLMSyncManager.pinnedCertificatePaths` has been removed. * Classes `RLMUserAccountInfo` & `RLMUserInfo` (swift: `UserInfo`, `UserAccountInfo`) have been removed. * `RLMSyncUser`/`SyncUser` has been renamed to `RLMUser`/`User`. * We no longer support Realm Cloud (legacy), but instead the new "MongoDB Realm" Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. * Remove support for Query-based sync, including the configuration parameters and the `RLMSyncSubscription` and `SyncSubscription` types ([#6437](https://github.com/realm/realm-swift/pull/6437)). * Remove everything related to sync permissions, including both the path-based permission system and the object-level privileges for query-based sync. Permissions are now configured via MongoDB Atlas. ([#6445](https://github.com/realm/realm-swift/pulls/6445)) * Remove support for Realm Object Server. * Non-embedded objects in synchronized Realms must always have a primary key named "_id". * All Swift callbacks for asynchronous operations which can fail are now passed a `Result` parameter instead of two separate `Value?` and `Error?` parameters. ### Enhancements * Add support for next generation sync. Support for syncing to MongoDB instead of Realm Object Server. Applications must be created at realm.mongodb.com * The memory mapping scheme for Realm files has changed to better support opening very large files. * Add support for the ObjectId data type. This is an automatically-generated unique identifier similar to a GUID or a UUID. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for the Decimal128 data type. This is a 128-bit IEEE 754 decimal floating point number similar to NSDecimalNumber. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for embedded objects. Embedded objects are objects which are 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 `-[RLMUser customData]`/`User.customData`. Custom data is configured in your MongoDB Realm App. * Add `-[RLMUser callFunctionNamed:arguments:completion:]`/`User.functions`. This is the entry point for calling Remote MongoDB Realm functions. Functions allow you to define and execute server-side logic for your application. Functions are written in modern JavaScript (ES6+) and execute in a serverless manner. When you call a function, you can dynamically access components of the current application as well as information about the request to execute the function and the logged in user that sent the request. * Add `-[RLMUser mongoClientWithServiceName:]`/`User.mongoClient`. This is the entry point for calling your Remote MongoDB Service. The read operations are `-[RLMMongoCollection findWhere:completion:]`, `-[RLMMongoCollection countWhere:completion:]`and `-[RLMMongoCollection aggregateWithPipeline:completion:]`. The write operations are `-[RLMMongoCollection insertOneDocument:completion:]`, `-[RLMMongoCollection insertManyDocuments:completion:]`, `-[RLMMongoCollection updateOneDocument:completion:]`, `-[RLMMongoCollection updateManyDocuments:completion:]`, `-[RLMMongoCollection deleteOneDocument:completion:]`, and `-[RLMMongoCollection deleteManyDocuments:completion:]`. If you are already familiar with MongoDB drivers, it is important to understand that the remote MongoCollection only provides access to the operations available in MongoDB Realm. * Obtaining a Realm configuration from a user is now done with `[RLMUser configurationWithPartitionValue:]`/`User.configuration(partitionValue:)`. Partition values can currently be of types `String`, `Int`, or `ObjectId`, and fill a similar role to Realm URLs did with Realm Cloud. The main difference is that partitions are meant to be more closely associated with your data. For example, if you are running a `Dog` kennel, and have a field `breed` that acts as your partition key, you could open up realms based on the breed of the dogs. * Add ability to stream change events on a remote MongoDB collection with `[RLMMongoCollection watch:delegate:delegateQueue:]`, `MongoCollection.watch(delegate:)`. When calling `watch(delegate:)` you will be given a `RLMChangeStream` (`ChangeStream`) which can be used to end watching by calling `close()`. Change events can also be streamed using the `MongoCollection.watch` Combine publisher that will stream change events each time the remote MongoDB collection is updated. * Add the ability to listen for when a Watch Change Stream is opened when using Combine. Use `onOpen(event:)` directly after opening a `WatchPublisher` to register a callback to be invoked once the change stream is opened. * Objects with integer primary keys no longer require a separate index for the * primary key column, improving insert performance and slightly reducing file size. ### Compatibility * Realm Studio: 10.0.0 or later. * Carthage release for Swift is built with Xcode 12 ### Internal * Upgraded realm-core from v6.1.4 to v10.0.0 * Upgraded realm-sync from v5.0.29 to v10.0.0 10.0.0-rc.2 Release notes (2020-10-15) ============================================================= ### Enhancements * Add the ability to listen for when a Watch Change Stream is opened when using Combine. Use `onOpen(event:)` directly after opening a `WatchPublisher` to register a callback to be invoked once the change stream is opened. ### Breaking Changes * The insert operations on Mongo collections now report the inserted documents' IDs as BSON rather than ObjectId. * Embedded objects can no longer form cycles at the schema level. For example, type A can no longer have an object property of type A, or an object property of type B if type B links to type A. This was always rejected by the server, but previously was allowed in non-synchronized Realms. * Primary key properties are once again marked as being indexed. This reflects an internal change to how primary keys are handled that should not have any other visible effects. * Change paired return types from Swift completion handlers to return `Result`. * Adjust how RealmSwift.Object is defined to add support for Swift Library Evolution mode. This should normally not have any effect, but you may need to add `override` to initializers of object subclasses. * Add `.null` type to AnyBSON. This creates a distinction between null values and properly absent BSON types. ### Fixed * Set the precision correctly when serializing doubles in extended json. * Reading the `objectTypes` array from a Realm Configuration would not include the embedded object types which were set in the array. * Reject loops in embedded objects as part of local schema validation rather than as a server error. * Although MongoClient is obtained from a User, it was actually using the User's App's current user rather than the User it was obtained from to make requests. This release also contains the following changes from 5.4.7 - 5.5.0 ### Enhancements * Add the ability to capture a NotificationToken when using a Combine publisher that observes a Realm Object or Collection. The user will call `saveToken(on:at:)` directly after invoking the publisher to use the feature. ### Fixed * When using `Realm.write(withoutNotifying:)` there was a chance that the supplied observation blocks would not be skipped when in a write transaction. ([Object Store #1103](https://github.com/realm/realm-object-store/pull/1103)) * Comparing two identical unmanaged `List<>`/`RLMArray` objects would fail. ([#5665](https://github.com/realm/realm-swift/issues/5665)). * Case-insensitive equality queries on indexed string properties failed to clear some internal state when rerunning the query. This could manifest as duplicate results or "key not found" errors. ([#6830](https://github.com/realm/realm-swift/issues/6830), [#6694](https://github.com/realm/realm-swift/issues/6694), since 5.0.0). * Equality queries on indexed string properties would sometimes throw "key not found" exceptions if the hash of the string happened to have bit 62 set. ([.NET #2025](https://github.com/realm/realm-dotnet/issues/2025), since v5.0.0). * Queries comparing non-optional int properties to nil would behave as if they were comparing against zero instead (since v5.0.0). ### Compatibility * File format: Generates Realms with format v12 (Reads and upgrades all previous formats) * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v10.0.0-beta.9 to v10.0.0 * Upgraded realm-sync from v10.0.0-beta.14 to v10.0.0 10.0.0-rc.1 Release notes (2020-10-01) ============================================================= ### Breaking Changes * Change the following methods on RLMUser to properties: - `[RLMUser emailPasswordAuth]` => `RLMUser.emailPasswordAuth` - `[RLMUser identities]` => `RLMUser.identities` - `[RLMUser allSessions]` => `RLMUser.allSessions` - `[RLMUser apiKeysAuth]` => `RLMUser.apiKeysAuth` * Other changes to RLMUser: - `nullable` has been removed from `RLMUser.identifier` - `nullable` has been removed from `RLMUser.customData` * Change the following methods on RLMApp to properties: - `[RLMApp allUsers]` => `RLMApp.allUsers` - `[RLMApp currentUser]` => `RLMApp.currentUser` - `[RLMApp emailPasswordAuth]` => `RLMApp.emailPasswordAuth` * Define `RealmSwift.Credentials` as an enum instead of a `typealias`. Example usage has changed from `Credentials(googleAuthCode: "token")` to `Credentials.google(serverAuthCode: "serverAuthCode")`, and `Credentials(facebookToken: "token")` to `Credentials.facebook(accessToken: "accessToken")`, etc. * Remove error parameter and redefine payload in `+ (instancetype)credentialsWithFunctionPayload:(NSDictionary *)payload error:(NSError **)error;`. It is now defined as `+ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload;` ### Compatibility * File format: Generates Realms with format v12 (Reads and upgrades all previous formats) * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 10.x.y series. * Carthage release for Swift is built with Xcode 12. 10.0.0-beta.6 Release notes (2020-09-30) ============================================================= ### Breaking Changes * Change Google Credential parameter names to better reflect the required auth code: - `Credentials(googleToken:)` => `Credentials(googleAuthCode:)` - `[RLMCredentials credentialsWithGoogleToken:]` => `[RLMCredentials credentialsWithGoogleAuthCode:]` * Rename Realm.Publishers to RealmPublishers to avoid confusion with Combine.Publishers ### Fixed * Deleting objects could sometimes change the ObjectId remaining objects from null to ObjectId("deaddeaddeaddeaddeaddead") when there are more than 1000 objects. (Since v10.0.0-alpha.1) * Fixed an assertion failure when adding an index to a nullable ObjectId property that contains nulls. (since v10.0.0-alpha.1). This release also contains the following changes from 5.4.0 - 5.4.6: ### Enhancements * Add prebuilt binary for Xcode 11.7 to the release package. * Add prebuilt binary for Xcode 12 to the release package. * Improve the asymtotic performance of NOT IN queries on indexed properties. It is now O(Number of Rows) rather than O(Number of Rows \* Number of values in IN clause.) * Slightly (<5%) improve the performance of most operations which involve reading from a Realm file. ### Fixed * Upgrading pre-5.x files with string primary keys would result in a file where `realm.object(ofType:forPrimaryKey:)` would fail to find the object. ([#6716](https://github.com/realm/realm-swift/issues/6716), since 5.2.0) * A write transaction which modifies an object with more than 16 managed properties and causes the Realm file to grow larger than 2 GB could cause an assertion failure mentioning "m_has_refs". ([JS #3194](https://github.com/realm/realm-js/issues/3194), since 5.0.0). * Objects with more than 32 properties could corrupt the Realm file and result in a variety of crashes. ([Java #7057](https://github.com/realm/realm-java/issues/7057), since 5.0.0). * Fix deadlocks when opening a Realm file in both the iOS simulator and Realm Studio ([#6743](https://github.com/realm/realm-swift/issues/6743), since 5.3.6). * Fix Springboard deadlocking when an app is unsuspended while it has an open Realm file which is stored in an app group on iOS 10-12 ([#6749](https://github.com/realm/realm-swift/issues/6749), since 5.3.6). * If you use encryption your application cound crash with a message like "Opening Realm files of format version 0 is not supported by this version of Realm". ([#6889](https://github.com/realm/realm-java/issues/6889) among others, since 5.0.0) * Confining a Realm to a serial queue would throw an error claiming that the queue was not a serial queue on iOS versions older than 12. ([#6735](https://github.com/realm/realm-swift/issues/6735), since 5.0.0). * Results would sometimes give stale results inside a write transaction if a write which should have updated the Results was made before the first access of a pre-existing Results object. ([#6721](https://github.com/realm/realm-swift/issues/6721), since 5.0.0) * Fix Archiving the Realm and RealmSwift frameworks with Xcode 12. ([#6774](https://github.com/realm/realm-swift/issues/6774)) * Fix compilation via Carthage when using Xcode 12 ([#6717](https://github.com/realm/realm-swift/issues/6717)). * Fix a crash inside `realm::Array(Type)::init_from_mem()` which would sometimes occur when running a query over links immediately after creating objects of the queried type. ([#6789](https://github.com/realm/realm-swift/issues/6789) and possibly others, since 5.0.0). * Possibly fix problems when changing the type of the primary key of an object from optional to non-optional. * Rerunning a equality query on an indexed string property would give incorrect results if a previous run of the query matched multiple objects and it now matches one object. This could manifest as either finding a non-matching object or a "key not found" exception being thrown. ([#6536](https://github.com/realm/realm-swift/issues/6536), since 5.0.0). ### Compatibility * File format: Generates Realms with format v12 (Reads and upgrades all previous formats) * Realm Studio: 10.0.0 or later. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v10.0.0-beta.7 to v10.0.0-beta.9 * Upgraded realm-sync from v10.0.0-beta.11 to v10.0.0-beta.14 10.0.0-beta.5 Release notes (2020-09-15) ============================================================= ### Enhancements * Add `User.loggedIn`. * Add support for multiple Realm Apps. * Remove `[RLMSyncManager shared]`. This is now instatiated as a property on the app itself. * Add Combine support for: * PushClient * APIKeyAuth * User * MongoCollection * EmailPasswordAuth * App.login ### Fixed * Fix `MongoCollection.watch` to consistently deliver events on a given queue. * Fix `[RLMUser logOutWithCompletion]` and `User.logOut` to now log out the correct user. * Fix crash on startup on iOS versions older than 13 (since v10.0.0-beta.3). ### Breaking Changes * `RLMSyncManager.pinnedCertificatePaths` has been removed. * Classes `RLMUserAccountInfo` & `RLMUserInfo` (swift: `UserInfo`, `UserAccountInfo`) have been removed. * The following functionality has been renamed to align Cocoa with the other Realm SDKs: | Old API | New API | |:-------------------------------------------------------------|:---------------------------------------------------------------| | `RLMUser.identity` | `RLMUser.identifier` | | `User.identity` | `User.id` | | `-[RLMCredentials credentialsWithUsername:password:]` | `-[RLMCredentials credentialsWithEmail:password:]` | | `Credentials(username:password:)` | `Credentials(email:password:)` | | -`[RLMUser apiKeyAuth]` | `-[RLMUser apiKeysAuth]` | | `User.apiKeyAuth()` | `User.apiKeysAuth()` | | `-[RLMEmailPasswordAuth registerEmail:password:completion:]` | `-[RLMEmailPasswordAuth registerUserWithEmail:password:completion:]` | | `App.emailPasswordAuth().registerEmail(email:password:)` | `App.emailPasswordAuth().registerUser(email:password:)` | ### Compatibility * File format: Generates Realms with format v12 (Reads and upgrades all previous formats) * Realm Studio: 10.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v10.0.0-beta.6 to v10.0.0-beta.7 * Upgraded realm-sync from v10.0.0-beta.10 to v10.0.0-beta.11 10.0.0-beta.4 Release notes (2020-08-28) ============================================================= ### Enhancements * Add support for the 64-bit watchOS simulator added in Xcode 12. * Add ability to stream change events on a remote MongoDB collection with `[RLMMongoCollection watch:delegate:delegateQueue]`, `MongoCollection.watch(delegate)`. When calling `watch(delegate)` you will be given a `RLMChangeStream` (`ChangeStream`), this will be used to invalidate and stop the streaming session by calling `[RLMChangeStream close]` (`ChangeStream.close()`) when needed. * Add `MongoCollection.watch`, which is a Combine publisher that will stream change events each time the remote MongoDB collection is updated. * Add ability to open a synced Realm with a `nil` partition value. ### Fixed * Realm.Configuration.objectTypes now accepts embedded objects * Ports fixes from 5.3.5 ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the v10.0.0-beta.x series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v10.0.0-beta.1 to v10.0.0-beta.6 * Upgraded realm-sync from v10.0.0-beta.2 to v10.0.0-beta.10 10.0.0-beta.3 Release notes (2020-08-17) ============================================================= This release also contains all changes from 5.3.2. ### Breaking Changes * The following classes & aliases have been renamed to align Cocoa with the other Realm SDKs: | Old API | New API | |:------------------------------------------------------------|:---------------------------------------------------------------| | `RLMSyncUser` | `RLMUser` | | `SyncUser` | `User` | | `RLMAppCredential` | `RLMCredential` | | `AppCredential` | `Credential` | | `RealmApp` | `App` | | `RLMUserAPIKeyProviderClient` | `RLMAPIKeyAuth` | | `RLMUsernamePasswordProviderClient` | `RLMEmailPasswordAuth` | | `UsernamePasswordProviderClient` | `EmailPasswordAuth` | | `UserAPIKeyProviderClient` | `APIKeyAuth` | * The following functionality has also moved to the User | Old API | New API | |:-------------------------------------------------------------|:--------------------------------------------------------------| | `[RLMApp callFunctionNamed:]` | `[RLMUser callFunctionNamed:]` | | `App.functions` | `User.functions` | | `[RLMApp mongoClientWithServiceName:]` | `[RLMUser mongoClientWithServiceName:]` | | `App.mongoClient(serviceName)` | `User.mongoClient(serviceName)` | | `[RLMApp userAPIKeyProviderClient]` | `[RLMUser apiKeyAuth]` | | `App.userAPIKeyProviderClient` | `App.apiKeyAuth()` | | `[RLMApp logOut:]` | `[RLMUser logOut]` | | `App.logOut(user)` | `User.logOut()` | | `[RLMApp removeUser:]` | `[RLMUser remove]` | | `App.remove(user)` | `User.remove()` | | `[RLMApp linkUser:credentials:]` | `[RLMUser linkWithCredentials:]` | | `App.linkUser(user, credentials)` | `User.link(credentials)` | * `refreshCustomData()` on User now returns void and passes the custom data to the callback on success. ### Compatibility * This release introduces breaking changes w.r.t some sync classes and MongoDB Realm Cloud functionality. (See the breaking changes section for the full list) * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Studio: 10.0.0 or later. * Carthage release for Swift is built with Xcode 11.5. 10.0.0-beta.2 Release notes (2020-06-09) ============================================================= Xcode 11.3 and iOS 9 are now the minimum supported versions. ### Enhancements * Add support for building with Xcode 12 beta 1. watchOS currently requires removing x86_64 from the supported architectures. Support for the new 64-bit watch simulator will come in a future release. ### Fixed * Opening a SyncSession with LOCAL app deployments would not use the correct endpoints. * Linking from embedded objects to top-level objects was incorrectly disallowed. * Opening a Realm file in file format v6 (created by Realm Cocoa versions between 2.4 and 2.10) would crash. (Since 5.0.0, [Core #3764](https://github.com/realm/realm-core/issues/3764)). * Upgrading v9 (pre-5.0) Realm files would create a redundant search index for primary key properties. This index would then be removed the next time the Realm was opened, resulting in some extra i/o in the upgrade process. (Since 5.0.0, [Core #3787](https://github.com/realm/realm-core/issues/3787)). * Fixed a performance issue with upgrading v9 files with search indexes on non-primary-key properties. (Since 5.0.0, [Core #3767](https://github.com/realm/realm-core/issues/3767)). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * MongoDB Realm: 84893c5 or later. * APIs are backwards compatible with all previous releases in the 10.0.0-alpha series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.3 to v10.0.0-beta.1 * Upgraded realm-sync from v5.0.1 to v10.0.0-beta.2 10.0.0-beta.1 Release notes (2020-06-08) ============================================================= NOTE: This version bumps the Realm file format to version 11. It is not possible to downgrade to earlier versions. Older files will automatically be upgraded to the new file format. Only [Realm Studio 10.0.0](https://github.com/realm/realm-studio/releases/tag/v10.0.0-beta.1) or later will be able to open the new file format. ### Enhancements * Add support for next generation sync. Support for syncing to MongoDB instead of Realm Object Server. Applications must be created at realm.mongodb.com * The memory mapping scheme for Realm files has changed to better support opening very large files. * Add support for the ObjectId data type. This is an automatically-generated unique identifier similar to a GUID or a UUID. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for the Decimal128 data type. This is a 128-bit IEEE 754 decimal floating point number similar to NSDecimalNumber. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for embedded objects. Embedded objects are objects which are 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 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 to define and execute server-side logic for your application. Functions are written in modern JavaScript (ES6+) and execute in a serverless manner. When you call a function, you can dynamically access components of the current application as well as information about the request to execute the function and the logged in user that sent the request. * Add `-[RLMApp mongoClientWithServiceName]`/`RealmApp.mongoClient`. This is the entry point for calling your Remote MongoDB Service. The read operations are `-[RLMMongoCollection findWhere:completion:]`, `-[RLMMongoCollection countWhere:completion:]`and `-[RLMMongoCollection aggregateWithPipeline:completion:]`. The write operations are `-[RLMMongoCollection insertOneDocument:completion:]`, `-[RLMMongoCollection insertManyDocuments:completion:]`, `-[RLMMongoCollection updateOneDocument:completion:]`, `-[RLMMongoCollection updateManyDocuments:completion:]`, `-[RLMMongoCollection deleteOneDocument:completion:]`, and `-[RLMMongoCollection deleteManyDocuments:completion:]`. If you are already familiar with MongoDB drivers, it is important to understand that the remote MongoCollection only provides access to the operations available in MongoDB Realm. * Change `[RLMSyncUser configurationWithPartitionValue:]`/`SyncUser.configuration(with:)` to accept all BSON types. Partition values can currently be of types `String`, `Int`, or `ObjectId`. Opening a realm by partition value is the equivalent of previously opening a realm by URL. In this case, partitions are meant to be more closely associated with your data. E.g., if you are running a `Dog` kennel, and have a field `breed` that acts as your partition key, you could open up realms based on the breed of the dogs. ### Breaking Changes * We no longer support Realm Cloud (legacy), but instead the new "MongoDB Realm" Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. * Remove support for Query-based sync, including the configuration parameters and the `RLMSyncSubscription` and `SyncSubscription` types ([#6437](https://github.com/realm/realm-swift/pull/6437)). * Primary key properties are no longer marked as being indexed. This reflects an internal change to how primary keys are handled that should not have any other visible effects. ([#6440](https://github.com/realm/realm-swift/pull/6440)). * Remove everything related to sync permissions, including both the path-based permission system and the object-level privileges for query-based sync. ([#6445](https://github.com/realm/realm-swift/pulls/6445)) * Primary key uniqueness is now enforced when creating new objects during migrations, rather than only at the end of migrations. Previously new objects could be created with duplicate primary keys during a migration as long as the property was changed to a unique value before the end of the migration, but now a unique value must be supplied when creating the object. * Remove support for Realm Object Server. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * MongoDB Realm: 84893c5 or later. * APIs are backwards compatible with all previous releases in the 10.0.0-alpha series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.3 to v10.0.0-beta.1 * Upgraded realm-sync from v5.0.1 to v10.0.0-beta.2 5.5.0 Release notes (2020-10-12) ============================================================= ### Enhancements * Add the ability to capture a NotificationToken when using a Combine publisher that observes a Realm Object or Collection. The user will call `saveToken(on:at:)` directly after invoking the publisher to use the feature. ### Fixed * When using `Realm.write(withoutNotifying:)` there was a chance that the supplied observation blocks would not be skipped when in a write transaction. ([Object Store #1103](https://github.com/realm/realm-object-store/pull/1103)) * Comparing two identical unmanaged `List<>`/`RLMArray` objects would fail. ([#5665](https://github.com/realm/realm-swift/issues/5665)). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. 5.4.8 Release notes (2020-10-05) ============================================================= ### Fixed * Case-insensitive equality queries on indexed string properties failed to clear some internal state when rerunning the query. This could manifest as duplicate results or "key not found" errors. ([#6830](https://github.com/realm/realm-swift/issues/6830), [#6694](https://github.com/realm/realm-swift/issues/6694), since 5.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v6.1.3 to v6.1.4 * Upgraded realm-sync from v5.0.28 to v5.0.29 5.4.7 Release notes (2020-09-30) ============================================================= ### Fixed * Equality queries on indexed string properties would sometimes throw "key not found" exceptions if the hash of the string happened to have bit 62 set. ([.NET #2025](https://github.com/realm/realm-dotnet/issues/2025), since v5.0.0). * Queries comparing non-optional int properties to nil would behave as if they were comparing against zero instead (since v5.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v6.1.2 to v6.1.3 * Upgraded realm-sync from v5.0.27 to v5.0.28 5.4.6 Release notes (2020-09-29) ============================================================= 5.4.5 failed to actually update the core version for installation methods other than SPM. All changes listed there actually happened in this version for non-SPM installation methods. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-sync from v5.0.26 to v5.0.27 5.4.5 Release notes (2020-09-28) ============================================================= ### Enhancements * Slightly (<5%) improve the performance of most operations which involve reading from a Realm file. ### Fixed * Rerunning a equality query on an indexed string property would give incorrect results if a previous run of the query matched multiple objects and it now matches one object. This could manifest as either finding a non-matching object or a "key not found" exception being thrown. ([#6536](https://github.com/realm/realm-swift/issues/6536), since 5.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v6.1.1 to v6.1.2 * Upgraded realm-sync from v5.0.25 to v5.0.26 5.4.4 Release notes (2020-09-25) ============================================================= ### Enhancements * Improve the asymtotic performance of NOT IN queries on indexed properties. It is now O(Number of Rows) rather than O(Number of Rows \* Number of values in IN clause.) ### Fixed * Fix a crash inside `realm::Array(Type)::init_from_mem()` which would sometimes occur when running a query over links immediately after creating objects of the queried type. ([#6789](https://github.com/realm/realm-swift/issues/6789) and possibly others, since 5.0.0). * Possibly fix problems when changing the type of the primary key of an object from optional to non-optional. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 5.0.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. ### Internal * Upgraded realm-core from v6.0.26 to v6.1.1 * Upgraded realm-sync from v5.0.23 to v5.0.25 5.4.3 Release notes (2020-09-21) ============================================================= ### Fixed * Fix compilation via Carthage when using Xcode 12 ([#6717](https://github.com/realm/realm-swift/issues/6717)). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.12 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. 5.4.2 Release notes (2020-09-17) ============================================================= ### Enhancements * Add prebuilt binary for Xcode 12 to the release package. ### Fixed * Fix Archiving the Realm and RealmSwift frameworks with Xcode 12. ([#6774](https://github.com/realm/realm-swift/issues/6774)) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.12 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 12. 5.4.1 Release notes (2020-09-16) ============================================================= ### Enhancements * Add prebuilt binary for Xcode 11.7 to the release package. ### Fixed * Fix deadlocks when opening a Realm file in both the iOS simulator and Realm Studio ([#6743](https://github.com/realm/realm-swift/issues/6743), since 5.3.6). * Fix Springboard deadlocking when an app is unsuspended while it has an open Realm file which is stored in an app group on iOS 10-12 ([#6749](https://github.com/realm/realm-swift/issues/6749), since 5.3.6). * If you use encryption your application cound crash with a message like "Opening Realm files of format version 0 is not supported by this version of Realm". ([#6889](https://github.com/realm/realm-java/issues/6889) among others, since 5.0.0) * Confining a Realm to a serial queue would throw an error claiming that the queue was not a serial queue on iOS versions older than 12. ([#6735](https://github.com/realm/realm-swift/issues/6735), since 5.0.0). * Results would sometimes give stale results inside a write transaction if a write which should have updated the Results was made before the first access of a pre-existing Results object. ([#6721](https://github.com/realm/realm-swift/issues/6721), since 5.0.0) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.12 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.7. ### Internal * Upgraded realm-core from v6.0.25 to v6.0.26 * Upgraded realm-sync from v5.0.22 to v5.0.23 5.4.0 Release notes (2020-09-09) ============================================================= This version bumps the Realm file format version. This means that older versions of Realm will be unable to open Realm files written by this version, and a new version of Realm Studio will be required. There are no actual format changes and the version bump is just to force a re-migration of incorrectly upgraded Realms. ### Fixed * Upgrading pre-5.x files with string primary keys would result in a file where `realm.object(ofType:forPrimaryKey:)` would fail to find the object. ([#6716](https://github.com/realm/realm-swift/issues/6716), since 5.2.0) * A write transaction which modifies an object with more than 16 managed properties and causes the Realm file to grow larger than 2 GB could cause an assertion failure mentioning "m_has_refs". ([JS #3194](https://github.com/realm/realm-js/issues/3194), since 5.0.0). * Objects with more than 32 properties could corrupt the Realm file and result in a variety of crashes. ([Java #7057](https://github.com/realm/realm-java/issues/7057), since 5.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.12 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.23 to v6.0.25 * Upgraded realm-sync from v5.0.20 to v5.0.22 5.3.6 Release notes (2020-09-02) ============================================================= ### Fixed * Work around iOS 14 no longer allowing the use of file locks in shared containers, which resulted in the OS killing an app which entered the background while a Realm was open ([#6671](https://github.com/realm/realm-swift/issues/6671)). * If an attempt to upgrade a realm has ended with a crash with "migrate_links()" in the call stack, the realm was left in an invalid state. The migration logic now handles this state and can complete upgrading files which were incompletely upgraded by pre-5.3.4 versions. * Fix deadlocks when writing to a Realm file on an exFAT partition from macOS. ([#6691](https://github.com/realm/realm-swift/issues/6691)). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.19 to v6.0.23 * Upgraded realm-sync from v5.0.16 to v5.0.20 5.3.5 Release notes (2020-08-20) ============================================================= ### Fixed * Opening Realms on background threads could produce spurious Incorrect Thread exceptions when a cached Realm existed for a previously existing thread with the same thread ID as the current thread. ([#6659](https://github.com/realm/realm-swift/issues/6659), [#6689](https://github.com/realm/realm-swift/issues/6689), [#6712](https://github.com/realm/realm-swift/issues/6712), since 5.0.0). * Upgrading a table with incoming links but no properties would crash. This was probably not possible to hit in practice as we reject object types with no properties. * Upgrading a non-nullable List which nonetheless contained null values would crash. This was possible due to missing error-checking in some older versions of Realm. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.18 to v6.0.19 * Upgraded realm-sync from v5.0.15 to v5.0.16 5.3.4 Release notes (2020-08-17) ============================================================= ### Fixed * Accessing a Realm after calling `deleteAll()` would sometimes throw an exception with the reason 'ConstIterator copy failed'. ([#6597](https://github.com/realm/realm-swift/issues/6597), since 5.0.0). * Fix an assertion failure inside the `migrate_links()` function when upgrading a pre-5.0 Realm file. * Fix a bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. ([Core #3838](https://github.com/realm/realm-core/pull/3838), since v5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.14 to v6.0.18 * Upgraded realm-sync from v5.0.14 to v5.0.15 5.3.3 Release notes (2020-07-30) ============================================================= ### Enhancements * Add support for the x86_64 watchOS simulator added in Xcode 12. ### Fixed * (RLM)Results objects would incorrectly pin old read transaction versions until they were accessed after a Realm was refreshed, resulting in the Realm file growing to large sizes if a Results was retained but not accessed after every write. ([#6677](https://github.com/realm/realm-swift/issues/6677), since 5.0.0). * Fix linker errors when using SwiftUI previews with Xcode 12 when Realm was installed via Swift Package Manager. ([#6625](https://github.com/realm/realm-swift/issues/6625)) ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.12 to v6.0.14 * Upgraded realm-sync from v5.0.12 to v5.0.14 5.3.2 Release notes (2020-07-21) ============================================================= ### Fixed * Fix a file format upgrade bug when opening older Realm files. Could cause assertions like "Assertion failed: ref != 0" during opning of a Realm. ([Core #6644](https://github.com/realm/realm-swift/issues/6644), since 5.2.0) * A use-after-free would occur if a Realm was compacted, opened on multiple threads prior to the first write, then written to while reads were happening on other threads. This could result in a variety of crashes, often inside realm::util::EncryptedFileMapping::read_barrier. (Since v5.0.0, [#6626](https://github.com/realm/realm-swift/issues/6626), [#6628](https://github.com/realm/realm-swift/issues/6628), [#6652](https://github.com/realm/realm-swift/issues/6652), [#6655](https://github.com/realm/realm-swift/issues/6555), [#6656](https://github.com/realm/realm-swift/issues/6656)). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.11 to v6.0.12 * Upgraded realm-sync from v5.0.11 to v5.0.12 5.3.1 Release notes (2020-07-17) ============================================================= ### Enhancements * Add prebuilt binary for Xcode 11.6 to the release package. ### Fixed * Creating an object inside migration which changed that object type's primary key would hit an assertion failure mentioning primary_key_col ([#6613](https://github.com/realm/realm-swift/issues/6613), since 5.0.0). * Modifying the value of a string primary key property inside a migration with a Realm file which was upgraded from pre-5.0 would corrupt the property's index, typically resulting in crashes. ([Core #3765](https://github.com/realm/realm-core/issues/3765), since 5.0.0). * Some Realm files which hit assertion failures when upgrading from the pre-5.0 file format should now upgrade correctly (Since 5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.6. ### Internal * Upgraded realm-core from v6.0.9 to v6.0.11 * Upgraded realm-sync from v5.0.8 to v5.0.11 5.3.0 Release notes (2020-07-14) ============================================================= ### Enhancements * Add `Realm.objectWillChange`, which is a Combine publisher that will emit a notification each time the Realm is refreshed or a write transaction is commited. ### Fixed * Fix the spelling of `ObjectKeyIdentifiable`. The old spelling is available and deprecated for compatibility. * Rename `RealmCollection.publisher` to `RealmCollection.collectionPublisher`. The old name interacted with the `publisher` defined by `Sequence` in very confusing ways, so we need to use a different name. The `publisher` name is still available for compatibility. ([#6516](https://github.com/realm/realm-swift/issues/6516)) * Work around "xcodebuild timed out while trying to read SwiftPackageManagerExample.xcodeproj" errors when installing Realm via Carthage. ([#6549](https://github.com/realm/realm-swift/issues/6549)). * Fix a performance regression when using change notifications. (Since 5.0.0, [#6629](https://github.com/realm/realm-swift/issues/6629)). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.8 to v6.0.9 * Upgraded realm-sync from v5.0.7 to v5.0.8 5.2.0 Release notes (2020-06-30) ============================================================= ### Fixed * Opening a SyncSession with LOCAL app deployments would not use the correct endpoints. This release also contains all changes from 5.0.3 and 5.1.0. ### Breaking Changes * The following classes & aliases have been renamed to align Cocoa with the other Realm SDKs: | Old API | New API | |:------------------------------------------------------------|:---------------------------------------------------------------| | `RLMSyncUser` | `RLMUser` | | `SyncUser` | `User` | | `RLMAppCredential` | `RLMCredential` | | `AppCredential` | `Credential` | | `RealmApp` | `App` | | `RLMUserAPIKeyProviderClient` | `RLMAPIKeyAuth` | | `RLMUsernamePasswordProviderClient` | `RLMEmailPasswordAuth` | | `UsernamePasswordProviderClient` | `EmailPasswordAuth` | | `UserAPIKeyProviderClient` | `APIKeyAuth` | * The following functionality has also moved to the User: | Old API | New API | |:-------------------------------------------------------------|:--------------------------------------------------------------| | `[RLMApp callFunctionNamed:]` | `[RLMUser callFunctionNamed:]` | | `App.functions` | `User.functions` | | `[RLMApp mongoClientWithServiceName:]` | `[RLMUser mongoClientWithServiceName:]` | | `App.mongoClient(serviceName)` | `User.mongoClient(serviceName)` | | `[RLMApp userAPIKeyProviderClient]` | `[RLMUser apiKeyAuth]` | | `App.userAPIKeyProviderClient` | `App.apiKeyAuth()` | | `[RLMApp logOut:]` | `[RLMUser logOut]` | | `App.logOut(user)` | `User.logOut()` | | `[RLMApp removeUser:]` | `[RLMUser remove]` | | `App.remove(user)` | `User.remove()` | | `[RLMApp linkUser:credentials:]` | `[RLMUser linkWithCredentials:]` | | `App.linkUser(user, credentials)` | `User.link(credentials)` | * The argument labels in Swift have changed for several methods: | Old API | New API | |:-------------------------------------------------------------|:--------------------------------------------------------------| | `APIKeyAuth.createApiKey(withName:completion:)` | `APIKeyAuth.createApiKey(named:completion:)` | | `App.login(withCredential:completion:) | `App.login(credentials:completion:)` | | `App.pushClient(withServiceName:)` | `App.pushClient(serviceName:)` | | `MongoClient.database(withName:)` | `MongoClient.database(named:)` | * `refreshCustomData()` on User now returns void and passes the custom data to the callback on success. ### Compatibility * This release introduces breaking changes w.r.t some sync classes and MongoDB Realm Cloud functionality. (See the breaking changes section for the full list) * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * MongoDB Realm: 84893c5 or later. * APIs are backwards compatible with all previous releases in the 10.0.0-alpha series. * Realm Studio: 10.0.0 or later. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.3 to v10.0.0-beta.1 * Upgraded realm-sync from v5.0.1 to v10.0.0-beta.2 10.0.0-beta.2 Release notes (2020-06-09) ============================================================= Xcode 11.3 and iOS 9 are now the minimum supported versions. ### Enhancements * Add support for building with Xcode 12 beta 1. watchOS currently requires removing x86_64 from the supported architectures. Support for the new 64-bit watch simulator will come in a future release. ### Fixed * Opening a SyncSession with LOCAL app deployments would not use the correct endpoints. * Linking from embedded objects to top-level objects was incorrectly disallowed. * Opening a Realm file in file format v6 (created by Realm Cocoa versions between 2.4 and 2.10) would crash. (Since 5.0.0, [Core #3764](https://github.com/realm/realm-core/issues/3764)). * Upgrading v9 (pre-5.0) Realm files would create a redundant search index for primary key properties. This index would then be removed the next time the Realm was opened, resulting in some extra i/o in the upgrade process. (Since 5.0.0, [Core #3787](https://github.com/realm/realm-core/issues/3787)). * Fixed a performance issue with upgrading v9 files with search indexes on non-primary-key properties. (Since 5.0.0, [Core #3767](https://github.com/realm/realm-core/issues/3767)). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * MongoDB Realm: 84893c5 or later. * APIs are backwards compatible with all previous releases in the 10.0.0-alpha series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.3 to v10.0.0-beta.1 * Upgraded realm-sync from v5.0.1 to v10.0.0-beta.2 10.0.0-beta.1 Release notes (2020-06-08) ============================================================= NOTE: This version bumps the Realm file format to version 11. It is not possible to downgrade to earlier versions. Older files will automatically be upgraded to the new file format. Only [Realm Studio 10.0.0](https://github.com/realm/realm-studio/releases/tag/v10.0.0-beta.1) or later will be able to open the new file format. ### Enhancements * Add support for next generation sync. Support for syncing to MongoDB instead of Realm Object Server. Applications must be created at realm.mongodb.com * The memory mapping scheme for Realm files has changed to better support opening very large files. * Add support for the ObjectId data type. This is an automatically-generated unique identifier similar to a GUID or a UUID. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for the Decimal128 data type. This is a 128-bit IEEE 754 decimal floating point number similar to NSDecimalNumber. ([PR #6450](https://github.com/realm/realm-swift/pull/6450)). * Add support for embedded objects. Embedded objects are objects which are 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 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 to define and execute server-side logic for your application. Functions are written in modern JavaScript (ES6+) and execute in a serverless manner. When you call a function, you can dynamically access components of the current application as well as information about the request to execute the function and the logged in user that sent the request. * Add `-[RLMApp mongoClientWithServiceName]`/`RealmApp.mongoClient`. This is the entry point for calling your Remote MongoDB Service. The read operations are `-[RLMMongoCollection findWhere:completion:]`, `-[RLMMongoCollection countWhere:completion:]`and `-[RLMMongoCollection aggregateWithPipeline:completion:]`. The write operations are `-[RLMMongoCollection insertOneDocument:completion:]`, `-[RLMMongoCollection insertManyDocuments:completion:]`, `-[RLMMongoCollection updateOneDocument:completion:]`, `-[RLMMongoCollection updateManyDocuments:completion:]`, `-[RLMMongoCollection deleteOneDocument:completion:]`, and `-[RLMMongoCollection deleteManyDocuments:completion:]`. If you are already familiar with MongoDB drivers, it is important to understand that the remote MongoCollection only provides access to the operations available in MongoDB Realm. * Change `[RLMSyncUser configurationWithPartitionValue:]`/`SyncUser.configuration(with:)` to accept all BSON types. Partition values can currently be of types `String`, `Int`, or `ObjectId`. Opening a realm by partition value is the equivalent of previously opening a realm by URL. In this case, partitions are meant to be more closely associated with your data. E.g., if you are running a `Dog` kennel, and have a field `breed` that acts as your partition key, you could open up realms based on the breed of the dogs. ### Breaking Changes * We no longer support Realm Cloud (legacy), but instead the new "MongoDB Realm" Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. * Remove support for Query-based sync, including the configuration parameters and the `RLMSyncSubscription` and `SyncSubscription` types ([#6437](https://github.com/realm/realm-swift/pull/6437)). * Primary key properties are no longer marked as being indexed. This reflects an internal change to how primary keys are handled that should not have any other visible effects. ([#6440](https://github.com/realm/realm-swift/pull/6440)). * Remove everything related to sync permissions, including both the path-based permission system and the object-level privileges for query-based sync. ([#6445](https://github.com/realm/realm-swift/pulls/6445)) * Primary key uniqueness is now enforced when creating new objects during migrations, rather than only at the end of migrations. Previously new objects could be created with duplicate primary keys during a migration as long as the property was changed to a unique value before the end of the migration, but now a unique value must be supplied when creating the object. * Remove support for Realm Object Server. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats) * MongoDB Realm: 84893c5 or later. * APIs are backwards compatible with all previous releases in the 10.0.0-alpha series. * `List.index(of:)` would give incorrect results if it was the very first thing called on that List after a Realm was refreshed following a write which modified the List. (Since 5.0.0, [#6606](https://github.com/realm/realm-swift/issues/6606)). * If a ThreadSafeReference was the only remaining reference to a Realm, multiple copies of the file could end up mapped into memory at once. This probably did not have any symptoms other than increased memory usage. (Since 5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.3 to v10.0.0-beta.1 * Upgraded realm-sync from v5.0.1 to v10.0.0-beta.2 * Upgraded realm-core from v6.0.6 to v6.0.7 * Upgraded realm-sync from v5.0.5 to v5.0.6 * Upgraded realm-core from v6.0.6 to v6.0.8 * Upgraded realm-sync from v5.0.5 to v5.0.7 5.1.0 Release notes (2020-06-22) ============================================================= ### Enhancements * Allow opening full-sync Realms in read-only mode. This disables local schema initialization, which makes it possible to open a Realm which the user does not have write access to without using asyncOpen. In addition, it will report errors immediately when an operation would require writing to the Realm rather than reporting it via the sync error handler only after the server rejects the write. ### Fixed * Opening a Realm using a configuration object read from an existing Realm would incorrectly bind the new Realm to the original Realm's thread/queue, resulting in "Realm accessed from incorrect thread." exceptions. ([#6574](https://github.com/realm/realm-swift/issues/6574), [#6559](https://github.com/realm/realm-swift/issues/6559), since 5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. 5.0.3 Release notes (2020-06-10) ============================================================= ### Fixed * `-[RLMObject isFrozen]` always returned false. ([#6568](https://github.com/realm/realm-swift/issues/6568), since 5.0.0). * Freezing an object within the write transaction that the object was created in now throws an exception rather than crashing when the object is first used. * The schema for frozen Realms was not properly initialized, leading to crashes when accessing a RLMLinkingObjects property. ([#6568](https://github.com/realm/realm-swift/issues/6568), since 5.0.0). * Observing `Object.isInvalidated` via a keypath literal would produce a warning in Swift 5.2 due to the property not being marked as @objc. ([#6554](https://github.com/realm/realm-swift/issues/6554)) ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. 5.0.2 Release notes (2020-06-02) ============================================================= ### Fixed * Fix errSecDuplicateItem (-25299) errors when opening a synchronized Realm when upgrading from pre-5.0 versions of Realm. ([#6538](https://github.com/realm/realm-swift/issues/6538), [#6494](https://github.com/realm/realm-swift/issues/6494), since 5.0.0). * Opening Realms stored on filesystems which do not support preallocation (such as ExFAT) would give "Operation not supported" exceptions. ([#6508](https://github.com/realm/realm-swift/issues/6508), since 3.2.0). * 'NoSuchTable' exceptions would sometimes be thrown after upgrading a Relam file to the v10 format. ([Core #3701](https://github.com/realm/realm-core/issues/3701), since 5.0.0) * If the upgrade process was interrupted/killed for various reasons, the following run could stop with some assertions failing. No instances of this happening were reported to us. (Since 5.0.0). * Queries filtering a `List` where the query was on an indexed property over a link would sometimes give incomplete results. ([#6540](https://github.com/realm/realm-swift/issues/6540), since 4.1.0 but more common since 5.0.0) * Opening a file in read-only mode would attempt to make a spurious write to the file, causing errors if the file was in read-only storage (since 5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. ### Internal * Upgraded realm-core from v6.0.4 to v6.0.6 * Upgraded realm-sync from v5.0.3 to v5.0.5 5.0.1 Release notes (2020-05-27) ============================================================= ### Enhancements * Add prebuilt binary for Xcode 11.5 to the release package. ### Fixed * Fix linker error when building a xcframework for Catalyst. ([#6511](https://github.com/realm/realm-swift/issues/6511), since 4.3.1). * Fix building for iOS devices when using Swift Package Manager ([#6522](https://github.com/realm/realm-swift/issues/6522), since 5.0.0). * `List` and `RealmOptional` properties on frozen objects were not initialized correctly and would always report `nil` or an empty list. ([#6527](https://github.com/realm/realm-swift/issues/6527), since 5.0.0). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.5. 5.0.0 Release notes (2020-05-15) ============================================================= 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 [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. ### Enhancements * Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. * Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. * Add support for frozen objects. `Realm`, `Results`, `List` and `Object` now have `freeze()` methods which return a frozen copy of the object. These objects behave similarly to creating unmanaged deep copies of the source objects. They can be read from any thread and do not update when writes are made to the Realm, but creating frozen objects does not actually copy data out of the Realm and so can be much faster and use less memory. Frozen objects cannot be mutated or observed for changes (as they never change). ([PR #6427](https://github.com/realm/realm-swift/pull/6427)). * Add the `isFrozen` property to `Realm`, `Results`, `List` and `Object`. * Add `Realm.Configuration.maxNumberOfActiveVersions`. Each time a write transaction is performed, a new version is created inside the Realm, and then any versions which are no longer in use are cleaned up. If too many versions are kept alive while performing writes (either due to a background thread performing a long operation that doesn't let the Realm on that thread refresh, or due to holding onto frozen versions for a long time) the Realm file will grow in size, potentially to the point where it is too large to be opened. Setting this configuration option will make write transactions which would cause the live version count to exceed the limit to instead fail. * Add support for queue-confined Realms. Rather than being bound to a specific thread, queue-confined Realms are bound to a serial dispatch queue and can be used within blocks dispatched to that queue regardless of what thread they happen to run on. In addition, change notifications will be delivered to that queue rather than the thread's run loop. ([PR #6478](https://github.com/realm/realm-swift/pull/6478)). * Add an option to deliver object and collection notifications to a specific serial queue rather than the current thread. ([PR #6478](https://github.com/realm/realm-swift/pull/6478)). * Add Combine publishers for Realm types. Realm collections have a `.publisher` property which publishes the collection each time it changes, and a `.changesetPublisher` which publishes a `RealmCollectionChange` each time the collection changes. Corresponding publishers for Realm Objects can be obtained with the `publisher()` and `changesetPublisher()` global functions. * Extend Combine publishers which output Realm types with a `.freeze()` function which will make the publisher instead output frozen objects. * String primary keys no longer require a separate index, improving insertion and deletion performance without hurting lookup performance. * Reduce the encrypted page reclaimer's impact on battery life when encryption is used. ([Core #3461](https://github.com/realm/realm-core/pull/3461)). ### Fixed * The uploaded bytes in sync progress notifications was sometimes incorrect and wouldn't exactly equal the uploadable bytes when the uploaded completed. * macOS binaries were built with the incorrect deployment target (10.14 rather than 10.9), resulting in linker warnings. ([#6299](https://github.com/realm/realm-swift/issues/6299), since 3.18.0). * An internal datastructure for List properties could be double-deleted if the last reference was released from a thread other than the one which the List was created on at the wrong time. This would typically manifest as "pthread_mutex_destroy() failed", but could also result in other kinds of crashes. ([#6333](https://github.com/realm/realm-swift/issues/6333)). * Sorting on float or double properties containing NaN values had inconsistent results and would sometimes crash due to out-of-bounds memory accesses. ([#6357](https://github.com/realm/realm-swift/issues/6357)). ### Breaking Changes * The ObjectChange type in Swift is now generic and includes a reference to the object which changed. When using `observe(on:)` to receive notifications on a dispatch queue, the object will be confined to that queue. * The Realm instance passed in the callback to asyncOpen() is now confined to the callback queue passed to asyncOpen() rather than the thread which the callback happens to be called on. This means that the Realm instance may be stored and reused in further blocks dispatched to that queue, but the queue must now be a serial queue. * Files containing Date properties written by version of Realm prior to 1.0 can no longer be opened. * Files containing Any properties can no longer be opened. This property type was never documented and was deprecated in 1.0. * Deleting objects now preserves the order of objects reported by unsorted Results rather than performing a swap operation before the delete. Note that it is still not safe to assume that the order of objects in an unsorted Results is the order that the objects were created in. * The minimum supported deployment target for iOS when using Swift Package Manager to install Realm is now iOS 11. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Realm Studio: 3.11 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.4.1. ### Internal * Upgraded realm-core from v5.23.8 to v6.0.4 * Upgraded realm-sync from v4.9.5 to v5.0.3 5.0.0-beta.6 Release notes (2020-05-08) ============================================================= ### Enhancements * Add support for queue-confined Realms. Rather than being bound to a specific thread, queue-confined Realms are bound to a serial dispatch queue and can be used within blocks dispatched to that queue regardless of what thread they happen to run on. In addition, change notifications will be delivered to that queue rather than the thread's run loop. ([PR #6478](https://github.com/realm/realm-swift/pull/6478)). * Add an option to deliver object and collection notifications to a specific serial queue rather than the current thread. ([PR #6478](https://github.com/realm/realm-swift/pull/6478)). ### Fixed * The uploaded bytes in sync progress notifications was sometimes incorrect and wouldn't exactly equal the uploadable bytes when the uploaded completed. ### Breaking Changes * The Realm instance passed in the callback to asyncOpen() is now confined to the callback queue passed to asyncOpen() rather than the thread which the callback happens to be called on. This means that the Realm instance may be stored and reused in further blocks dispatched to that queue, but the queue must now be a serial queue. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.4.1. ### Internal * Upgraded realm-core from v6.0.3 to v6.0.4 * Upgraded realm-sync from v5.0.1 to v5.0.3 4.4.1 Release notes (2020-04-16) ============================================================= ### Enhancements * Upgrade Xcode 11.4 binaries to Xcode 11.4.1. ### Fixed * Fix a "previous <= m_schema_transaction_version_max" assertion failure caused by a race condition that could occur after performing a migration. (Since 3.0.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.4.1. 5.0.0-beta.3 Release notes (2020-02-26) ============================================================= Based on 4.3.2 and also includes all changes since 4.3.0. ### Enhancements * Add support for frozen objects. `Realm`, `Results`, `List` and `Object` now have `freeze()` methods which return a frozen copy of the object. These objects behave similarly to creating unmanaged deep copies of the source objects. They can be read from any thread and do not update when writes are made to the Realm, but creating frozen objects does not actually copy data out of the Realm and so can be much faster and use less memory. Frozen objects cannot be mutated or observed for changes (as they never change). ([PR #6427](https://github.com/realm/realm-swift/pull/6427)). * Add the `isFrozen` property to `Realm`, `Results`, `List` and `Object`. * Add `Realm.Configuration.maxNumberOfActiveVersions`. Each time a write transaction is performed, a new version is created inside the Realm, and then any versions which are no longer in use are cleaned up. If too many versions are kept alive while performing writes (either due to a background thread performing a long operation that doesn't let the Realm on that thread refresh, or due to holding onto frozen versions for a long time) the Realm file will grow in size, potentially to the point where it is too large to be opened. Setting this configuration option will make write transactions which would cause the live version count to exceed the limit to instead fail. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-core from v6.0.0-beta.3 to v6.0.3 * Upgraded realm-sync from v5.0.0-beta.2 to v5.0.1 5.0.0-beta.2 Release notes (2020-01-13) ============================================================= Based on 4.3.0 and also includes all changes since 4.1.1. ### Fixed * Fix compilation when using CocoaPods targeting iOS versions older than 11 (since 5.0.0-alpha). ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-core from v6.0.0-beta.2 to v6.0.0-beta.3 * Upgraded realm-sync from v5.0.0-beta.1 to v5.0.0-beta.2 5.0.0-beta.1 Release notes (2019-12-13) ============================================================= Based on 4.1.1 and also includes all changes since 4.1.0. 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. ### Enhancements * String primary keys no longer require a separate index, improving insertion and deletion performance without hurting lookup performance. * Reduce the encrypted page reclaimer's impact on battery life when encryption is used. ([Core #3461](https://github.com/realm/realm-core/pull/3461)). ### Fixed * Fix an error when a table-backed Results was accessed immediately after deleting the object previously at the index being accessed (since 5.0.0-alpha.1). * macOS binaries were built with the incorrect deployment target (10.14 rather than 10.9), resulting in linker warnings. ([#6299](https://github.com/realm/realm-swift/issues/6299), since 3.18.0). * An internal datastructure for List properties could be double-deleted if the last reference was released from a thread other than the one which the List was created on at the wrong time. This would typically manifest as "pthread_mutex_destroy() failed", but could also result in other kinds of crashes. ([#6333](https://github.com/realm/realm-swift/issues/6333)). * Sorting on float or double properties containing NaN values had inconsistent results and would sometimes crash due to out-of-bounds memory accesses. ([#6357](https://github.com/realm/realm-swift/issues/6357)). ### Known Issues * Changing which property of an object is the primary key in a migration will break incoming links to objects of that type. * Changing the primary key of an object with Data properties in a migration will crash. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * APIs are backwards compatible with all previous releases in the 5.x.y series. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-core from v6.0.0-alpha.24 to v6.0.0-beta.2 * Upgraded realm-sync from 4.7.1-core6.5 to v5.0.0-beta.1 5.0.0-alpha.1 Release notes (2019-11-14) ============================================================= Based on 4.1.0. ### Enhancements * Add `-[RLMRealm fileExistsForConfiguration:]`/`Realm.fileExists(for:)`, which checks if a local Realm file exists for the given configuration. * Add `-[RLMRealm deleteFilesForConfiguration:]`/`Realm.deleteFiles(for:)` to delete the Realm file and all auxiliary files for the given configuration. * Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. * Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. 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. This automatic upgrade process is not yet well tested. Do not open Realm files with data you care about with this alpha version. ### Breaking Changes * Files containing Date properties written by version of Realm prior to 1.0 can no longer be opened. * Files containing Any properties can no longer be opened. This property type was never documented and was deprecated in 1.0. ### Compatibility * File format: Generates Realms with format v10 (Reads and upgrades v9) * Realm Object Server: 3.21.0 or later. * APIs are backwards compatible with all previous releases in the 4.x.y series. * Carthage release for Swift is built with Xcode 11.3. * Carthage release for Swift is built with Xcode 11.2.1. ### Internal * Upgraded realm-core from 5.23.6 to v6.0.0-alpha.24. * Upgraded realm-sync from 4.8.2 to 4.7.1-core6.5. 4.4.0 Release notes (2020-03-26) ============================================================= Swift 4.0 and Xcode 10.3 are now the minimum supported versions. ### Enhancements * Allow setting the `fileUrl` for synchronized Realms. An appropriate local path based on the sync URL will still be used if it is not overridden. ([PR #6454](https://github.com/realm/realm-swift/pull/6454)). * Add Xcode 11.4 binaries to the release package. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.4. 4.3.2 Release notes (2020-02-06) ============================================================= ### Enhancements * Similar to `autoreleasepool()`, `realm.write()` now returns the value which the block passed to it returns. Returning `Void` from the block is still allowed. ### Fixed * Fix a memory leak attributed to `property_copyAttributeList` the first time a Realm is opened when using Realm Swift. ([#6409](https://github.com/realm/realm-swift/issues/6409), since 4.0.0). * Connecting to a `realms:` sync URL would crash at runtime on iOS 11 (and no other iOS versions) inside the SSL validation code. (Since 4.3.1). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-sync from 4.9.4 to 4.9.5. 4.3.1 Release notes (2020-01-16) ============================================================= ### Enhancements * Reduce the encrypted page reclaimer's impact on battery life when encryption is used. ([Core #3461](https://github.com/realm/realm-core/pull/3461)). ### Fixed * macOS binaries were built with the incorrect deployment target (10.14 rather than 10.9), resulting in linker warnings. ([#6299](https://github.com/realm/realm-swift/issues/6299), since 3.18.0). * An internal datastructure for List properties could be double-deleted if the last reference was released from a thread other than the one which the List was created on at the wrong time. This would typically manifest as "pthread_mutex_destroy() failed", but could also result in other kinds of crashes. ([#6333](https://github.com/realm/realm-swift/issues/6333)). * Sorting on float or double properties containing NaN values had inconsistent results and would sometimes crash due to out-of-bounds memory accesses. ([#6357](https://github.com/realm/realm-swift/issues/6357)). * A NOT query on a `List` which happened to have the objects in a different order than the underlying table would sometimes include the object immediately before an object which matches the query. ([#6289](https://github.com/realm/realm-swift/issues/6289), since 0.90.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-core from 5.23.6 to 5.23.8. * Upgraded realm-sync from 4.9.0 to 4.9.4. 4.3.0 Release notes (2019-12-19) ============================================================= ### Enhancements * Add the ability to set a custom logger function on `RLMSyncManager` which is called instead of the default NSLog-based logger. * Expose configuration options for the various types of sync connection timeouts and heartbeat intervals on `RLMSyncManager`. * Add an option to have `Realm.asyncOpen()` report an error if the connection times out rather than swallowing the error and attempting to reconnect until it succeeds. ### Fixed * Fix a crash when using value(forKey:) on a LinkingObjects property (including when doing so indirectly, such as by querying on that property). ([#6366](https://github.com/realm/realm-swift/issues/6366), since 4.0.0). * Fix a rare crash in `ClientHistoryImpl::integrate_server_changesets()` which would only happen in Debug builds (since v3.0.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.3. ### Internal * Upgraded realm-sync from 4.8.2 to 4.9.0. 4.2.0 Release notes (2019-12-16) ============================================================= ### Enhancements * Add `-[RLMRealm fileExistsForConfiguration:]`/`Realm.fileExists(for:)`, which checks if a local Realm file exists for the given configuration. * Add `-[RLMRealm deleteFilesForConfiguration:]`/`Realm.deleteFiles(for:)` to delete the Realm file and all auxiliary files for the given configuration. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.3. 4.1.1 Release notes (2019-11-18) ============================================================= ### Fixed * The UpdatePolicy passed to `realm.add()` or `realm.create()` was not properly propagated when adding objects within a `List`, which could result in spurious change notifications when using `.modified`. ([#6321](https://github.com/realm/realm-swift/issues/6321), since v3.16.0) * Fix a rare deadlock when a Realm collection or object was observed, then `refresh()` was explicitly called, and then the NotificationToken from the observation was destroyed on a different thread (since 0.98.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.2. 4.1.0 Release notes (2019-11-13) ============================================================= ### Enhancements * Improve performance of queries over a link where the final target property has an index. * Restore support for storing `@objc enum` properties on RealmSwift.Object subclasses (broken in 4.0.0), and add support for storing them in RealmOptional properties. ### Fixed * The sync client would fail to reconnect after failing to integrate a changeset. The bug would lead to further corruption of the client’s Realm file. ([RSYNC-48](https://jira.mongodb.org/browse/RSYNC-48), since v3.2.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.2. ### Internal * Upgraded realm-core from 5.23.5 to 5.23.6. * Upgraded realm-sync from 4.7.11 to 4.8.2 4.0.0 Release notes (2019-11-08) ============================================================= ### Breaking Changes * All previously deprecated functionality has now been removed entirely. * The schema discovery logic for RealmSwift.Object subclasses has been rewritten in Swift. This should not have any effect on valid class definitions, but there may be types of invalid definitions which previously worked by coincidence and no longer do. * `SyncSubscription` no longer has a generic type parameter, as the type was not actually used for anything. * The following Swift types have changed from `final class` to `struct`: - AnyRealmCollection - LinkingObjects - ObjectiveCSupport - Realm - Results - SyncSubscription - ThreadSafeReference There is no intended change in semantics from this, but certain edge cases may behave differently. * The designated initializers defined by RLMObject and Object other than zero-argument `init` have been replaced with convenience initializers. * The implementation of the path-based permissions API has been redesigned to accomodate changes to the server. This should be mostly a transparent change, with two main exceptions: 1. SyncPermission objects are no longer live Realm objects, and retrieving permissions gives an Array rather than Results. Getting up-to-date permissions now requires calling retrievePermissions() again rather than observing the permissions. 2. The error codes for permissions functions have changed. Rather than a separate error type and set of error codes, permission functions now produce SyncAuthErrors. ### Enhancements * Improve performance of initializing Realm objects with List properties. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.2. 3.21.0 Release notes (2019-11-04) ============================================================= ### Enhancements * Add prebuilt binaries for Xcode 11.2. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.2. 3.20.0 Release notes (2019-10-21) ============================================================= ### Enhancements * Add support for custom refresh token authentication. This allows a user to be authorized with an externally-issued refresh token when ROS is configured to recognize the external issuer as a refresh token validator. ([PR #6311](https://github.com/realm/realm-swift/pull/6311)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11. 3.19.1 Release notes (2019-10-17) ============================================================= ### Enhancements * Improve performance of sync changeset integration. Transactions involving a very large number of objects and cheap operations on each object are as much as 20% faster. ### Fixed * Fix a crash when a RLMArray/List of primitives was observed and then the containing object was deleted before the first time that the background notifier could run. ([Issue #6234](https://github.com/realm/realm-swift/issues/6234, since 3.0.0)). * Remove an incorrect assertion that would cause crashes inside `TableInfoCache::get_table_info()`, with messages like "Assertion failed: info.object_id_index == 0 [3, 0]". (Since 3.18.0, [#6268](https://github.com/realm/realm-swift/issues/6268) and [#6257](https://github.com/realm/realm-swift/issues/6257)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.0. ### Internal * Upgrade to REALM_SYNC_VERSION=4.7.11 3.19.0 Release notes (2019-09-27) ============================================================= ### Enhancements * Expose ObjectSchema.objectClass in Swift as looking up the class via NSClassFromString() can be complicated for Swift types. ([PR #6244](https://github.com/realm/realm-swift/pull/6244)). * Add support for suppressing notifications using closure-based write/transaction methods. ([PR #6252](https://github.com/realm/realm-swift/pull/6252)). ### Fixed * IN or chained OR equals queries on an unindexed string column would fail to match some results if any of the strings were 64 bytes or longer. ([Core #3386](https://github.com/realm/realm-core/pull/3386), since 3.14.2). * Query Based Sync subscriptions for queries involving a null timestamp were not sent to the server correctly and would match no objects. ([Core #3389](https://github.com/realm/realm-core/pull/3388), since 3.17.3). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.0. ### Internal * Upgrade to REALM_CORE_VERSION=5.23.5 * Upgrade to REALM_SYNC_VERSION=4.7.8 3.18.0 Release notes (2019-09-13) ============================================================= The file format for synchronized Realms has changed. Old Realms will be automatically upgraded when they are opened. Once upgraded, the files will not be openable by older versions of Realm. The upgrade should not take a significant amount of time to run or run any risk of errors. This does not effect non-synchronized Realms. ### Enhancements * Improve performance of queries on Date properties ([Core #3344](https://github.com/realm/realm-core/pull/3344), [Core #3351](https://github.com/realm/realm-core/pull/3351)). * Syncronized Realms are now more aggressive about trimming local history that is no longer needed. This should reduce file size growth in write-heavy workloads. ([Sync #3007](https://github.com/realm/realm-sync/issues/3007)). * Add support for building Realm as an xcframework. ([PR #6238](https://github.com/realm/realm-swift/pull/6238)). * Add prebuilt libraries for Xcode 11 to the release package. ([PR #6248](https://github.com/realm/realm-swift/pull/6248)). * Add a prebuilt library for Catalyst/UIKit For Mac to the release package ([PR #6248](https://github.com/realm/realm-swift/pull/6248)). ### Fixed * If a signal interrupted a msync() call, Realm would throw an exception and the write transaction would fail. This behavior has new been changed to retry the system call instead. ([Core #3352](https://github.com/realm/realm-core/issues/3352)) * Queries on the sum or average of an integer property would sometimes give incorrect results. ([Core #3356](https://github.com/realm/realm-core/pull/3356)). * Opening query-based synchronized Realms with a small number of subscriptions performed an unneccesary write transaction. ([ObjectStore #815](https://github.com/realm/realm-object-store/pull/815)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 11.0 ### Deprecations * `RLMIdentityProviderNickname` has been deprecated in favor of `RLMIdentityProviderUsernamePassword`. * `+[RLMIdentityProvider credentialsWithNickname]` has been deprecated in favor of `+[RLMIdentityProvider credentialsWithUsername]`. * `Sync.nickname(String, Bool)` has been deprecated in favor of `Sync.usernamePassword(String, String, Bool)`. 3.17.3 Release notes (2019-07-24) ============================================================= ### Enhancements * Add Xcode 10.3 binaries to the release package. Remove the Xcode 9.2 and 9.3 binaries. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.3. 3.17.1 Release notes (2019-07-10) ============================================================= ### Enhancements * Add support for canceling asynchronous opens using a new AsyncOpenTask returned from the asyncOpen() call. ([PR #6193](https://github.com/realm/realm-swift/pull/6193)). * Importing the Realm SPM package can now be done by pinning to a version rather than a branch. ### Fixed * Queries on a List/RLMArray which checked an indexed int property would sometimes give incorrect results. ([#6154](https://github.com/realm/realm-swift/issues/6154)), since v3.15.0) * Queries involving an indexed int property had a memory leak if run multiple times. ([#6186](https://github.com/realm/realm-swift/issues/6186)), since v3.15.0) * Creating a subscription with `includeLinkingObjects:` performed unneccesary comparisons, making it extremely slow when large numbers of objects were involved. ([Core #3311](https://github.com/realm/realm-core/issues/3311), since v3.15.0) ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.17.0 Release notes (2019-06-28) ============================================================= ### Enhancements * Add support for including Realm via Swift Package Manager. This currently requires depending on the branch "master" rather than pinning to a version (i.e. `.package(url: "https://github.com/realm/realm-swift", .branch("master"))`). ([#6187](https://github.com/realm/realm-swift/pull/6187)). * Add Codable conformance to RealmOptional and List, and Encodable conformance to Results. ([PR #6172](https://github.com/realm/realm-swift/pull/6172)). ### Fixed * Attempting to observe an unmanaged LinkingObjects object crashed rather than throwing an approriate exception (since v0.100.0). * Opening an encrypted Realm could potentially report that a valid file was corrupted if the system was low on free memory. (since 3.14.0, [Core #3267](https://github.com/realm/realm-core/issues/3267)) * Calling `Realm.asyncOpen()` on multiple Realms at once would sometimes crash due to a `FileNotFound` exception being thrown on a background worker thread. (since 3.16.0, [ObjectStore #806](https://github.com/realm/realm-object-store/pull/806)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.16.2 Release notes (2019-06-14) ============================================================= ### Enhancements * Add support for Xcode 11 Beta 1. Xcode betas are only supported when building from source, and not when using a prebuilt framework. ([PR #6164](https://github.com/realm/realm-swift/pull/6164)). ### Fixed * Using asyncOpen on query-based Realms which didn't already exist on the local device would fail with error 214. ([#6178](https://github.com/realm/realm-swift/issues/6178), since 3.16.0). * asyncOpen on query-based Realms did not wait for the server-created permission objects to be downloaded, resulting in crashes if modifications to the permissions were made before creating a subscription for the first time (since 3.0.0). * EINTR was not handled correctly in the notification worker, which may have resulted in inconsistent and rare assertion failures in `ExternalCommitHelper::listen()` when building with assertions enabled. (PR: [#804](https://github.com/realm/realm-object-store/pull/804), since 0.91.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.16.1 Release notes (2019-05-31) ============================================================= ### Fixed * The static type passed at compile time to `realm.create()` was checked for a primary key rather than the actual type passed at runtime, resulting in exceptions like "''RealmSwiftObject' does not have a primary key and can not be updated'" being thrown even if the object type being created has a primary key. (since 3.16.0, [#6159](https://github.com/realm/realm-swift/issues/6159)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.16.0 Release notes (2019-05-29) ============================================================= ### Enhancements * Add an option to only set the properties which have values different from the existing ones when updating an existing object with `Realm.create()`/`-[RLMObject createOrUpdateInRealm:withValue:]`. This makes notifications report only the properties which have actually changed, and improves Object Server performance by reducing the number of operations to merge. (Issue: [#5970](https://github.com/realm/realm-swift/issues/5970), PR: [#6149](https://github.com/realm/realm-swift/pull/6149)). * Using `-[RLMRealm asyncOpenWithConfiguration:callbackQueue:]`/`Realm.asyncOpen()` to open a synchronized Realm which does not exist on the local device now uses an optimized transfer method to download the initial data for the Realm, greatly speeding up the first start time for applications which use full synchronization. This is currently not applicable to query-based synchronization. (PR: [#6106](https://github.com/realm/realm-swift/pull/6106)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.15.0 Release notes (2019-05-06) ============================================================= The minimum version of Realm Object Server has been increased to 3.21.0 and attempting to connect to older versions will produce protocol mismatch errors. Realm Cloud has already been upgraded to this version, and users using that do not need to worry about this. ### Enhancements * Add `createdAt`, `updatedAt`, `expiresAt` and `timeToLive` properties to `RLMSyncSubscription`/`SyncSubscription`. These properties will be `nil` for subscriptions created with older versions of Realm, but will be automatically populated for newly-created subscriptions. * Add support for transient subscriptions by setting the `timeToLive` when creating the subscription. The next time a subscription is created or updated after that time has elapsed the subscription will be automatically removed. * Add support for updating existing subscriptions with a new query or limit. This is done by passing `update: true` (in swift) or setting `options.overwriteExisting = YES` (in obj-c) when creating the subscription, which will make it update the existing subscription with the same name rather than failing if one already exists with that name. * Add an option to include the objects from `RLMLinkingObjects`/`LinkingObjects` properties in sync subscriptions, similarly to how `RLMArray`/`List` automatically pull in the contained objects. * Improve query performance for chains of OR conditions (or an IN condition) on an unindexed integer or string property. ([Core PR #2888](https://github.com/realm/realm-core/pull/2888) and [Core PR #3250](https://github.com/realm/realm-core/pull/3250)). * Improve query performance for equality conditions on indexed integer properties. ([Core PR #3272](https://github.com/realm/realm-core/pull/3272)). * Adjust the file allocation algorithm to reduce fragmentation caused by large numbers of small blocks. * Improve file allocator logic to reduce fragmentation and improve commit performance after many writes. ([Core PR #3278](https://github.com/realm/realm-core/pull/3278)). ### Fixed * Making a query that compares two integer properties could cause a segmentation fault on x86 (i.e. macOS only). ([Core PR #3253](https://github.com/realm/realm-core/pull/3256)). * The `downloadable_bytes` parameter passed to sync progress callbacks reported a value which correlated to the amount of data left to download, but not actually the number of bytes which would be downloaded. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.21.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.14.2 Release notes (2019-04-25) ============================================================= ### Enhancements * Updating `RLMSyncManager.customRequestHeaders` will immediately update all currently active sync session with the new headers rather than requiring manually closing the Realm and reopening it. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. * Carthage release for Swift is built with Xcode 10.2.1. 3.14.1 Release notes (2019-04-04) ============================================================= ### Fixed * Fix "Cannot find interface declaration for 'RealmSwiftObject', superclass of 'MyRealmObjectClass'" errors when building for a simulator with Xcode 10.2 with "Install Objective-C Compatibility Header" enabled. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. * Carthage release for Swift is built with Xcode 10.2. 3.14.0 Release notes (2019-03-27) ============================================================= ### Enhancements * Reduce memory usage when committing write transactions. * Improve performance of compacting encrypted Realm files. ([PR #3221](https://github.com/realm/realm-core/pull/3221)). * Add a Xcode 10.2 build to the release package. ### Fixed * Fix a memory leak whenever Realm makes a HTTP(s) request to the Realm Object Server (Issue [#6058](https://github.com/realm/realm-swift/issues/6058), since 3.8.0). * Fix an assertion failure when creating an object in a synchronized Realm after creating an object with a null int primary key in the same write transaction. ([PR #3227](https://github.com/realm/realm-core/pull/3227)). * Fix some new warnings when building with Xcode 10.2 beta. * Properly clean up sync sessions when the last Realm object using the session is deallocated while the session is explicitly suspended (since 3.9.0). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. * Carthage release for Swift is built with Xcode 10.2. ### Internal * Throw an exception rather than crashing with an assertion failure in more cases when opening invalid Realm files. * Upgrade to REALM_CORE_VERSION=5.14.0 * Upgrade to REALM_SYNC_VERSION=3.15.1 3.13.1 Release notes (2019-01-03) ============================================================= ### Fixed * Fix a crash when iterating over `Realm.subscriptions()` using for-in. (Since 3.13.0, PR [#6050](https://github.com/realm/realm-swift/pull/6050)). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. 3.13.0 Release notes (2018-12-14) ============================================================= ### Enhancements * Add `Realm.subscriptions()`/`-[RLMRealm subscriptions]` and `Realm.subscription(named:)`/`-[RLMRealm subscriptionWithName:]` to enable looking up existing query-based sync subscriptions. (PR: https://github.com/realm/realm-swift/pull/6029). ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. 3.12.0 Release notes (2018-11-26) ============================================================= ### Enhancements * Add a User-Agent header to HTTP requests made to the Realm Object Server. By default, this contains information about the Realm library version and your app's bundle ID. The application identifier can be customized by setting `RLMSyncManager.sharedManager.userAgent`/`SyncManager.shared.userAgent` prior to opening a synchronized Realm. (PR: https://github.com/realm/realm-swift/pull/6007). * Add Xcode 10.1 binary to the prebuilt package. ### Fixed * None. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. ### Internal * None. 3.11.2 Release notes (2018-11-15) ============================================================= ### Enhancements * Improve the performance of the merge algorithm used for integrating remote changes from the server. In particular, changesets involving many objects which all link to a single object should be greatly improved. ### Fixed * Fix a memory leak when removing notification blocks from collections. PR: [#702](https://github.com/realm/realm-object-store/pull/702), since 1.1.0. * Fix re-sorting or distincting an already-sorted Results using values from linked objects. Previously the unsorted order was used to read the values from the linked objects. PR [#3102](https://github.com/realm/realm-core/pull/3102), since 3.1.0. * Fix a set of bugs which could lead to bad changeset assertions when using sync. The assertions would look something like the following: `[realm-core-5.10.0] Assertion failed: ndx < size() with (ndx, size()) = [742, 742]`. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. ### Internal * None. 3.11.1 Release notes (2018-10-19) ============================================================= ### Enhancements * None. ### Fixed * Fix `SyncUser.requestEmailConfirmation` not triggering the email confirmation flow on ROS. (PR [#5953](https://github.com/realm/realm-swift/pull/5953), since 3.5.0) * Add some missing validation in the getters and setters of properties on managed Realm objects, which would sometimes result in an application crashing with a segfault rather than the appropriate exception being thrown when trying to write to an object which has been deleted. (PR [#5952](https://github.com/realm/realm-swift/pull/5952), since 2.8.0) ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * Realm Object Server: 3.11.0 or later. ### Internal * None. 3.11.0 Release notes (2018-10-04) ============================================================= ### Enhancements * Reduce memory usage when integrating synchronized changes sent by ROS. * Devices will now report download progress for read-only Realms, allowing the server to compact Realms more aggressively and reducing the amount of server-side storage space required. ### Fixed * Fix a crash when adding an object with a non-`@objc` `String?` property which has not been explicitly ignored to a Realm on watchOS 5 (and possibly other platforms when building with Xcode 10). (Issue: [5929](https://github.com/realm/realm-swift/issues/5929)). * Fix some merge algorithm bugs which could result in `BadChangesetError` being thrown when integrating changes sent by the server. ### Compatibility * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * **NOTE!!! You will need to upgrade your Realm Object Server to at least version 3.11.0 or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or previous, you will see an error like `Wrong protocol version in Sync HTTP request, client protocol version = 25, server protocol version = 24`.** ### Internal * Update to Sync 3.12.2. 3.10.0 Release notes (2018-09-19) ============================================================= Prebuilt binaries are now built for Xcode 9.2, 9.3, 9.4 and 10.0. Older versions of Xcode are still supported when building from source, but you should be migrating to at least Xcode 9.2 as soon as possible. ### Enhancements * Add support for Watch Series 4 by adding an arm64_32 slice to the library. 3.9.0 Release notes (2018-09-10) ============================================================= ### Enhancements * Expose RLMSyncUser.refreshToken publicly so that it can be used for custom HTTP requests to Realm Object Server. * Add RLMSyncSession.connectionState, which reports whether the session is currently connected to the Realm Object Server or if it is offline. * Add `-suspend` and `-resume` methods to `RLMSyncSession` to enable manually pausing data synchronization. * Add support for limiting the number of objects matched by a query-based sync subscription. This requires a server running ROS 3.10.1 or newer. ### Bugfixes * Fix crash when getting the description of a `MigrationObject` which has `List` properties. * Fix crash when calling `dynamicList()` on a `MigrationObject`. 3.8.0 Release notes (2018-09-05) ============================================================= ### Enhancements * Remove some old and no longer applicable migration logic which created an unencrypted file in the sync metadata directory containing a list of ROS URLs connected to. * Add support for pinning SSL certificates used for https and realms connections by setting `RLMSyncManager.sharedManager.pinnedCertificatePaths` in obj-c and `SyncManager.shared.pinnedCertificatePaths` in Swift. ### Bugfixes * Fix warnings when building Realm as a static framework with CocoaPods. 3.7.6 Release notes (2018-08-08) ============================================================= ### Enhancements * Speed up the actual compaction when using compact-on-launch. * Reduce memory usage when locally merging changes from sync. * When first connecting to a server, wait to begin uploading changes until after all changes have been downloaded to reduce the server-side load for query-based sync. 3.7.5 Release notes (2018-07-23) ============================================================= ### Enhancements * Improve performance of applying remote changesets from sync. * Improve performance of creating objects with string primary keys. * Improve performance of large write transactions. * Adjust file space allocation strategy to reduce fragmentation, producing smaller Realm files and typically better performance. * Close network connections immediately when a sync session is destroyed. * Report more information in `InvalidDatabase` exceptions. ### Bugfixes * Fix permission denied errors for RLMPlatform.h when building with CocoaPods and Xcode 10 beta 3. * Fix a use-after-free when canceling a write transaction which could result in incorrect "before" values in KVO observations (typically `nil` when a non-nil value is expected). * Fix several bugs in the merge algorithm that could lead to memory corruption and crashes with errors like "bad changeset" and "unreachable code". 3.7.4 Release notes (2018-06-19) ============================================================= ### Bugfixes * Fix a bug which could potentially flood Realm Object Server with PING messages after a client device comes back online. 3.7.3 Release notes (2018-06-18) ============================================================= ### Enhancements * Avoid performing potentially large amounts of pointless background work for LinkingObjects instances which are accessed and then not immediate deallocated. ### Bugfixes * Fix crashes which could result from extremely fragmented Realm files. * Fix a bug that could result in a crash with the message "bad changeset error" when merging changesets from the server. 3.7.2 Release notes (2018-06-13) ============================================================= ### Enhancements * Add some additional consistency checks that will hopefully produce better errors when the "prev_ref + prev_size <= ref" assertion failure occurs. ### Bugfixes * Fix a problem in the changeset indexing algorithm that would sometimes cause "bad permission object" and "bad changeset" errors. * Fix a large number of linking warnings about symbol visibility by aligning compiler flags used. * Fix large increase in size of files produced by `Realm.writeCopy()` introduced in 3.6.0. 3.7.1 Release notes (2018-06-07) ============================================================= * Add support for compiling Realm Swift with Xcode 10 beta 1. 3.7.0 Release notes (2018-06-06) ============================================================= The feature known as Partial Sync has been renamed to Query-based Synchronization. This has impacted a number of API's. See below for the details. ### Deprecations * `+[RLMSyncConfiguration initWithUser] has been deprecated in favor of `-[RLMSyncUser configurationWithURL:url]. * `+[RLMSyncConfiguration automaticConfiguration] has been deprecated in favor of `-[RLMSyncUser configuration]. * `+[RLMSyncConfiguration automaticConfigurationForUser] has been deprecated in favor of `-[RLMSyncUser configuration]. * `-[RLMSyncConfiguration isPartial] has been deprecated in favor of `-[RLMSyncConfiguration fullSynchronization]`. ### Enhancements * Add `-[RLMRealm syncSession]` and `Realm.syncSession` to obtain the session used for a synchronized Realm. * Add `-[RLMSyncUser configuration]`. Query-based sync is the default sync mode for this configuration. * Add `-[RLMSyncUser configurationWithURL:url]`. Query-based sync is the default sync mode for this configuration. 3.6.0 Release notes (2018-05-29) ============================================================= ### Enhancements * Improve performance of sync metadata operations and resolving thread-safe references. * `shouldCompactOnLaunch` is now supported for compacting the local data of synchronized Realms. ### Bugfixes * Fix a potential deadlock when a sync session progress callback held the last strong reference to the sync session. * Fix some cases where comparisons to `nil` in queries were not properly serialized when subscribing to a query. * Don't delete objects added during a migration after a call to `-[RLMMigration deleteDataForClassName:]`. * Fix incorrect results and/or crashes when multiple `-[RLMMigration enumerateObjects:block:]` blocks deleted objects of the same type. * Fix some edge-cases where `-[RLMMigration enumerateObjects:block:]` enumerated the incorrect objects following deletions. * Restore the pre-3.5.0 behavior for Swift optional properties missing an ivar rather than crashing. 3.5.0 Release notes (2018-04-25) ============================================================= ### Enhancements * Add wrapper functions for email confirmation and password reset to `SyncUser`. ### Bugfixes * Fix incorrect results when using optional chaining to access a RealmOptional property in Release builds, or otherwise interacting with a RealmOptional object after the owning Object has been deallocated. 3.4.0 Release notes (2018-04-19) ============================================================= The prebuilt binary for Carthage is now built for Swift 4.1. ### Enhancements * Expose `RLMSyncManager.authorizationHeaderName`/`SyncManager.authorizationHeaderName` as a way to override the transport header for Realm Object Server authorization. * Expose `RLMSyncManager.customRequestHeaders`/`SyncManager.customRequestHeaders` which allows custom HTTP headers to be appended on requests to the Realm Object Server. * Expose `RLMSSyncConfiguration.urlPrefix`/`SyncConfiguration.urlPrefix` as a mechanism to replace the default path prefix in Realm Sync WebSocket requests. 3.3.2 Release notes (2018-04-03) ============================================================= Add a prebuilt binary for Xcode 9.3. 3.3.1 Release notes (2018-03-28) ============================================================= Realm Object Server v3.0.0 or newer is required when using synchronized Realms. ### Enhancements * Expose `RLMObject.object(forPrimaryKey:)` as a factory method for Swift so that it is callable with recent versions of Swift. ### Bugfixes * Exclude the RLMObject-derived Permissions classes from the types repored by `Realm.Configuration.defaultConfiguration.objectTypes` to avoid a failed cast. * Cancel pending `Realm.asyncOpen()` calls when authentication fails with a non-transient error such as missing the Realm path in the URL. * Fix "fcntl() inside prealloc()" errors on APFS. 3.3.0 Release notes (2018-03-19) ============================================================= Realm Object Server v3.0.0 or newer is required when using synchronized Realms. ### Enhancements * Add `Realm.permissions`, `Realm.permissions(forType:)`, and `Realm.permissions(forClassNamed:)` as convenience methods for accessing the permissions of the Realm or a type. ### Bugfixes * Fix `+[RLMClassPermission objectInRealm:forClass:]` to work for classes that are part of the permissions API, such as `RLMPermissionRole`. * Fix runtime errors when applications define an `Object` subclass with the same name as one of the Permissions object types. 3.2.0 Release notes (2018-03-15) ============================================================= Realm Object Server v3.0.0 or newer is required when using synchronized Realms. ### Enhancements * Added an improved API for adding subscriptions in partially-synchronized Realms. `Results.subscribe()` can be used to subscribe to any result set, and the returned `SyncSubscription` object can be used to observe the state of the subscription and ultimately to remove the subscription. See the documentation for more information (). * Added a fine-grained permissions system for use with partially-synchronized Realms. This allows permissions to be defined at the level of individual objects or classes. See the documentation for more information (). * Added `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(user:)`. These methods return a `Realm.Configuration` appropriate for syncing with the default synced Realm for the current (or specified) user. These should be considered the preferred methods for accessing synced Realms going forwards. * Added `+[RLMSyncSession sessionForRealm:]` to retrieve the sync session corresponding to a `RLMRealm`. ### Bugfixes * Fix incorrect initalization of `RLMSyncManager` that made it impossible to set `errorHandler`. * Fix compiler warnings when building with Xcode 9.3. * Fix some warnings when running with UBsan. 3.2.0-rc.1 Release notes (2018-03-14) ============================================================= Realm Object Server v3.0.0-rc.1 or newer is required when using synchronized Realms. ### Enhancements * Added `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(user:)`. These methods return a `Realm.Configuration` appropriate for syncing with the default synced Realm for the current (or specified). These should be considered the preferred methods for accessing synced Realms going forwards. * A role is now automatically created for each user with that user as its only member. This simplifies the common use case of restricting access to specific objects to a single user. This role can be accessed at `PermissionUser.role`. * Improved error reporting when the server rejects a schema change due to a lack of permissions. ### Bugfixes * Fix incorrect initalization of `RLMSyncManager` that made it impossible to set `errorHandler`. * Fix compiler warnings when building with Xcode 9.3. 3.2.0-beta.3 Release notes (2018-03-01) ============================================================= Realm Object Server v3.0.0-alpha.9 or newer is required when using synchronized Realms. ### Bugfixes * Fix a crash that would occur when using partial sync with Realm Object Server v3.0.0-alpha.9. 3.2.0-beta.2 Release notes (2018-02-28) ============================================================= Realm Object Server v3.0.0-alpha.8 or newer is required when using synchronized Realms. ### Enhancements * Added `findOrCreate(forRoleNamed:)` and `findOrCreate(forRole:)` to `List` to simplify the process of adding permissions for a role. * Added `+permissionForRoleNamed:inArray:`, `+permissionForRoleNamed:onRealm:`, `+permissionForRoleNamed:onClass:realm:`, `+permissionForRoleNamed:onClassNamed:realm:`, and `+permissionForRoleNamed:onObject:` to `RLMSyncPermission` to simplify the process of adding permissions for a role. * Added `+[RLMSyncSession sessionForRealm:]` to retrieve the sync session corresponding to a `RLMRealm`. ### Bugfixes * `PermissionRole.users` and `PermissionUser.roles` are now public as intended. * Fixed the handling of `setPermissions` in `-[RLMRealm privilegesForRealm]` and related methods. 3.2.0-beta.1 Release notes (2018-02-19) ============================================================= ### Enhancements * Added an improved API for adding subscriptions in partially-synchronized Realms. `Results.subscribe()` can be used to subscribe to any result set, and the returned `SyncSubscription` object can be used to observe the state of the subscription and ultimately to remove the subscription. * Added a fine-grained permissions system for use with partially-synchronized Realms. This allows permissions to be defined at the level of individual objects or classes. See `Permission` and related types for more information. ### Bugfixes * Fix some warnings when running with UBsan. 3.1.1 Release notes (2018-02-03) ============================================================= Prebuilt Swift frameworks for Carthage are now built with Xcode 9.2. ### Bugfixes * Fix a memory leak when opening Realms with an explicit `objectTypes` array from Swift. 3.1.0 Release notes (2018-01-16) ============================================================= * Prebuilt frameworks are now included for Swift 3.2.3 and 4.0.3. * Prebuilt frameworks are no longer included for Swift 3.0.x. * Building from source with Xcode versions prior to Xcode 8.3 is no longer supported. ### Enhancements * Add `Results.distinct(by:)` / `-[RLMResults distinctResultsUsingKeyPaths:]`, which return a `Results` containing only objects with unique values at the given key paths. * Improve performance of change checking for notifications in certain cases. * Realm Object Server errors not explicitly recognized by the client are now reported to the application regardless. * Add support for JSON Web Token as a sync credential source. * Add support for Nickname and Anonymous Auth as a sync credential source. * Improve allocator performance when writing to a highly fragmented file. This should significantly improve performance when inserting large numbers of objects which have indexed properties. * Improve write performance for complex object graphs involving many classes linking to each other. ### Bugfixes * Add a missing check for a run loop in the permission API methods which require one. * Fix some cases where non-fatal sync errors were being treated as fatal errors. 3.0.2 Release notes (2017-11-08) ============================================================= Prebuilt frameworks are now included for Swift 3.2.2 and 4.0.2. ### Bugfixes * Fix a crash when a linking objects property is retrieved from a model object instance via Swift subscripting. * Fix incorrect behavior if a call to `posix_fallocate` is interrupted. 3.0.1 Release notes (2017-10-26) ============================================================= ### Bugfixes * Explicitly exclude KVO-generated object subclasses from the schema. * Fix regression where the type of a Realm model class is not properly determined, causing crashes when a type value derived at runtime by `type(of:)` is passed into certain APIs. * Fix a crash when an `Object` subclass has implicitly ignored `let` properties. * Fix several cases where adding a notification block from within a notification callback could produce incorrect results. 3.0.0 Release notes (2017-10-16) ============================================================= ### Breaking Changes * iOS 7 is no longer supported. * Synchronized Realms require a server running Realm Object Server v2.0 or higher. * Computed properties on Realm object types are detected and no longer added to the automatically generated schema. * The Objective-C and Swift `create(_:, value: update:)` APIs now correctly nil out nullable properties when updating an existing object when the `value` argument specifies nil or `NSNull` for the property value. * `-[RLMRealm addOrUpdateObjects:]` and `-[RLMRealm deleteObjects:]` now require their argument to conform to `NSFastEnumeration`, to match similar APIs that also take collections. * The way interactive sync errors (client reset and permission denied) are delivered to the user has been changed. Instead of a block which can be invoked to immediately delete the offending Realm file, an opaque token object of type `RLMSyncErrorActionToken` will be returned in the error object's `userInfo` dictionary. This error object can be passed into the new `+[RLMSyncSession immediatelyHandleError:]` API to delete the files. * The return types of the `SyncError.clientResetInfo()` and `SyncError.deleteRealmUserInfo()` APIs have been changed. They now return `RLMSyncErrorActionToken`s or `SyncError.ActionToken`s instead of closures. * The class methods `Object.className()`, `Object.objectUtilClass()`, and the property `Object.isInvalidated` can no longer be overriden. * The callback which runs when a sync user login succeeds or fails now runs on the main queue by default, or can be explicitly specified by a new `callbackQueue` parameter on the `{RLM}SyncUser.logIn(...)` API. * Fix empty strings, binary data, and null on the right side of `BEGINSWITH`, `ENDSWITH` and `CONTAINS` operators in predicates to match Foundation's semantics of never matching any strings or data. * Swift `Object` comparison and hashing behavior now works the same way as that of `RLMObject` (objects are now only considered equatable if their model class defines a primary key). * Fix the way the hash property works on `Object` when the object model has no primary key. * Fix an issue where if a Swift model class defined non-generic managed properties after generic Realm properties (like `List`), the schema would be constructed incorrectly. Fixes an issue where creating such models from an array could fail. * Loosen `RLMArray` and `RLMResults`'s generic constraint from `RLMObject` to `NSObject`. This may result in having to add some casts to disambiguate types. * Remove `RLMSyncPermissionResults`. `RLMSyncPermission`s are now vended out using a `RLMResults`. This results collection supports all normal collection operations except for setting values using key-value coding (since `RLMSyncPermission`s are immutable) and the property aggregation operations. * `RLMSyncUserInfo` has been significantly enhanced. It now contains metadata about a user stored on the Realm Object Server, as well as a list of all user account data associated with that user. * Starting with Swift 4, `List` now conforms to `MutableCollection` instead of `RangeReplaceableCollection`. For Swift 4, the empty collection initializer has been removed, and default implementations of range replaceable collection methods that make sense for `List` have been added. * `List.removeLast()` now throws an exception if the list is empty, to more closely match the behavior of the standard library's `Collection.removeLast()` implementation. * `RealmCollection`'s associated type `Element` has been renamed `ElementType`. * The following APIs have been renamed: | Old API | New API | |:------------------------------------------------------------|:---------------------------------------------------------------| | `NotificationToken.stop()` | `NotificationToken.invalidate()` | | `-[RLMNotificationToken stop]` | `-[RLMNotificationToken invalidate]` | | `RealmCollection.addNotificationBlock(_:)` | `RealmCollection.observe(_:)` | | `RLMSyncProgress` | `RLMSyncProgressMode` | | `List.remove(objectAtIndex:)` | `List.remove(at:)` | | `List.swap(_:_:)` | `List.swapAt(_:_:)` | | `SyncPermissionValue` | `SyncPermission` | | `RLMSyncPermissionValue` | `RLMSyncPermission` | | `-[RLMSyncPermission initWithRealmPath:userID:accessLevel]` | `-[RLMSyncPermission initWithRealmPath:identity:accessLevel:]` | | `RLMSyncPermission.userId` | `RLMSyncPermission.identity` | | `-[RLMRealm addOrUpdateObjectsInArray:]` | `-[RLMRealm addOrUpdateObjects:]` | * The following APIs have been removed: | Removed API | Replacement | |:-------------------------------------------------------------|:------------------------------------------------------------------------------------------| | `Object.className` | None, was erroneously present. | | `RLMPropertyTypeArray` | `RLMProperty.array` | | `PropertyType.array` | `Property.array` | | `-[RLMArray sortedResultsUsingProperty:ascending:]` | `-[RLMArray sortedResultsUsingKeyPath:ascending:]` | | `-[RLMCollection sortedResultsUsingProperty:ascending:]` | `-[RLMCollection sortedResultsUsingKeyPath:ascending:]` | | `-[RLMResults sortedResultsUsingProperty:ascending:]` | `-[RLMResults sortedResultsUsingKeyPath:ascending:]` | | `+[RLMSortDescriptor sortDescriptorWithProperty:ascending:]` | `+[RLMSortDescriptor sortDescriptorWithKeyPath:ascending:]` | | `RLMSortDescriptor.property` | `RLMSortDescriptor.keyPath` | | `AnyRealmCollection.sorted(byProperty:ascending:)` | `AnyRealmCollection.sorted(byKeyPath:ascending:)` | | `List.sorted(byProperty:ascending:)` | `List.sorted(byKeyPath:ascending:)` | | `LinkingObjects.sorted(byProperty:ascending:)` | `LinkingObjects.sorted(byKeyPath:ascending:)` | | `Results.sorted(byProperty:ascending:)` | `Results.sorted(byKeyPath:ascending:)` | | `SortDescriptor.init(property:ascending:)` | `SortDescriptor.init(keyPath:ascending:)` | | `SortDescriptor.property` | `SortDescriptor.keyPath` | | `+[RLMRealm migrateRealm:configuration:]` | `+[RLMRealm performMigrationForConfiguration:error:]` | | `RLMSyncManager.disableSSLValidation` | `RLMSyncConfiguration.enableSSLValidation` | | `SyncManager.disableSSLValidation` | `SyncConfiguration.enableSSLValidation` | | `RLMSyncErrorBadResponse` | `RLMSyncAuthErrorBadResponse` | | `RLMSyncPermissionResults` | `RLMResults` | | `SyncPermissionResults` | `Results` | | `RLMSyncPermissionChange` | `-[RLMSyncUser applyPermission:callback]` / `-[RLMSyncUser deletePermission:callback:]` | | `-[RLMSyncUser permissionRealmWithError:]` | `-[RLMSyncUser retrievePermissionsWithCallback:]` | | `RLMSyncPermissionOffer` | `-[RLMSyncUser createOfferForRealmAtURL:accessLevel:expiration:callback:]` | | `RLMSyncPermissionOfferResponse` | `-[RLMSyncUser acceptOfferForToken:callback:]` | | `-[NSError rlmSync_clientResetBlock]` | `-[NSError rlmSync_errorActionToken]` / `-[NSError rlmSync_clientResetBackedUpRealmPath]` | | `-[NSError rlmSync_deleteRealmBlock]` | `-[NSError rlmSync_errorActionToken]` | ### Enhancements * `List` can now contain values of types `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `String`, `Data`, and `Date` (and optional versions of all of these) in addition to `Object` subclasses. Querying `List`s containing values other than `Object` subclasses is not yet implemented. * `RLMArray` can now be constrained with the protocols `RLMBool`, `RLMInt`, `RLMFloat`, `RLMDouble`, `RLMString`, `RLMData`, and `RLMDate` in addition to protocols defined with `RLM_ARRAY_TYPE`. By default `RLMArray`s of non-`RLMObject` types can contain null. Indicating that the property is required (by overriding `+requiredProperties:`) will instead make the values within the array required. Querying `RLMArray`s containing values other than `RLMObject` subclasses is not yet implemented. * Add a new error code to denote 'permission denied' errors when working with synchronized Realms, as well as an accompanying block that can be called to inform the binding that the offending Realm's files should be deleted immediately. This allows recovering from 'permission denied' errors in a more robust manner. See the documentation for `RLMSyncErrorPermissionDeniedError` for more information. * Add Swift `Object.isSameObject(as:_)` API to perform the same function as the existing Objective-C API `-[RLMObject isEqualToObject:]`. * Opening a synced Realm whose local copy was created with an older version of Realm Mobile Platfrom when a migration is not possible to the current version will result in an `RLMErrorIncompatibleSyncedFile` / `incompatibleSyncedFile` error. When such an error occurs, the original file is moved to a backup location, and future attempts to open the synchronized Realm will result in a new file being created. If you wish to migrate any data from the backup Realm you can open it using the backup Realm configuration available on the error object. * Add a preview of partial synchronization. Partial synchronization allows a synchronized Realm to be opened in such a way that only objects requested by the user are synchronized to the device. You can use it by setting the `isPartial` property on a `SyncConfiguration`, opening the Realm, and then calling `Realm.subscribe(to:where:callback:)` with the type of object you're interested in, a string containing a query determining which objects you want to subscribe to, and a callback which will report the results. You may add as many subscriptions to a synced Realm as necessary. ### Bugfixes * Realm no longer throws an "unsupported instruction" exception in some cases when opening a synced Realm asynchronously. * Realm Swift APIs that filter or look up the index of an object based on a format string now properly handle optional arguments in their variadic argument list. * `-[RLMResults indexOfObject:]` now properly accounts for access level. * Fix a race condition that could lead to a crash accessing to the freed configuration object if a default configuration was set from a different thread. * Fixed an issue that crash when enumerating after clearing data during migration. * Fix a bug where a synced Realm couldn't be reopened even after a successful client reset in some cases. * Fix a bug where the sync subsystem waited too long in certain cases to reconnect to the server. * Fix a bug where encrypted sync-related metadata was incorrectly deleted from upgrading users, resulting in all users being logged out. * Fix a bug where permission-related data continued to be synced to a client even after the user that data belonged to logged out. * Fix an issue where collection notifications might be delivered inconsistently if a notification callback was added within another callback for the same collection. 3.0.0-rc.2 Release notes (2017-10-14) ============================================================= ### Enhancements * Reinstate `RLMSyncPermissionSortPropertyUserID` to allow users to sort permissions to their own Realms they've granted to others. ### Bugfixes * `-[RLMResults indexOfObject:]` now properly accounts for access level. * Fix a race condition that could lead to a crash accessing to the freed configuration object if a default configuration was set from a different thread. * Fixed an issue that crash when enumerating after clearing data during migration. * Fix a bug where a synced Realm couldn't be reopened even after a successful client reset in some cases. * Fix a bug where the sync subsystem waited too long in certain cases to reconnect to the server. * Fix a bug where encrypted sync-related metadata was incorrectly deleted from upgrading users, resulting in all users being logged out. * Fix a bug where permission-related data continued to be synced to a client even after the user that data belonged to logged out. * Fix an issue where collection notifications might be delivered inconsistently if a notification callback was added within another callback for the same collection. 3.0.0-rc.1 Release notes (2017-10-03) ============================================================= ### Breaking Changes * Remove `RLMSyncPermissionSortPropertyUserID` to reflect changes in how the Realm Object Server reports permissions for a user. * Remove `RLMSyncPermissionOffer` and `RLMSyncPermissionOfferResponse` classes and associated helper methods and functions. Use the `-[RLMSyncUser createOfferForRealmAtURL:accessLevel:expiration:callback:]` and `-[RLMSyncUser acceptOfferForToken:callback:]` methods instead. ### Bugfixes * The keychain item name used by Realm to manage the encryption keys for sync-related metadata is now set to a per-app name based on the bundle identifier. Keys that were previously stored within the single, shared Realm keychain item will be transparently migrated to the per-application keychain item. * Fix downloading of the Realm core binaries when Xcode's command-line tools are set as the active developer directory for command-line interactions. * Fix a crash that could occur when resolving a ThreadSafeReference to a `List` whose parent object had since been deleted. 3.0.0-beta.4 Release notes (2017-09-22) ============================================================= ### Breaking Changes * Rename `List.remove(objectAtIndex:)` to `List.remove(at:)` to match the name used by 'RangeReplaceableCollection'. * Rename `List.swap()` to `List.swapAt()` to match the name used by 'Array'. * Loosen `RLMArray` and `RLMResults`'s generic constraint from `RLMObject` to `NSObject`. This may result in having to add some casts to disambiguate types. * Remove `RLMPropertyTypeArray` in favor of a separate bool `array` property on `RLMProperty`/`Property`. * Remove `RLMSyncPermissionResults`. `RLMSyncPermission`s are now vended out using a `RLMResults`. This results collection supports all normal collection operations except for setting values using KVO (since `RLMSyncPermission`s are immutable) and the property aggregation operations. * `RealmCollection`'s associated type `Element` has been renamed `ElementType`. * Realm Swift collection types (`List`, `Results`, `AnyRealmCollection`, and `LinkingObjects` have had their generic type parameter changed from `T` to `Element`). * `RealmOptional`'s generic type parameter has been changed from `T` to `Value`. * `RLMSyncUserInfo` has been significantly enhanced. It now contains metadata about a user stored on the Realm Object Server, as well as a list of all user account data associated with that user. * Starting with Swift 4, `List` now conforms to `MutableCollection` instead of `RangeReplaceableCollection`. For Swift 4, the empty collection initializer has been removed, and default implementations of range replaceable collection methods that make sense for `List` have been added. * `List.removeLast()` now throws an exception if the list is empty, to more closely match the behavior of the standard library's `Collection.removeLast()` implementation. ### Enhancements * `List` can now contain values of types `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `String`, `Data`, and `Date` (and optional versions of all of these) in addition to `Object` subclasses. Querying `List`s containing values other than `Object` subclasses is not yet implemented. * `RLMArray` can now be constrained with the protocols `RLMBool`, `RLMInt`, `RLMFloat`, `RLMDouble`, `RLMString`, `RLMData`, and `RLMDate` in addition to protocols defined with `RLM_ARRAY_TYPE`. By default `RLMArray`s of non-`RLMObject` types can contain null. Indicating that the property is required (by overriding `+requiredProperties:`) will instead make the values within the array required. Querying `RLMArray`s containing values other than `RLMObject` subclasses is not yet implemented. * Opening a synced Realm whose local copy was created with an older version of Realm Mobile Platfrom when a migration is not possible to the current version will result in an `RLMErrorIncompatibleSyncedFile` / `incompatibleSyncedFile` error. When such an error occurs, the original file is moved to a backup location, and future attempts to open the synchronized Realm will result in a new file being created. If you wish to migrate any data from the backup Realm you can open it using the backup Realm configuration available on the error object. * Add preview support for partial synchronization. Partial synchronization is allows a synchronized Realm to be opened in such a way that only objects requested by the user are synchronized to the device. You can use it by setting the `isPartial` property on a `SyncConfiguration`, opening the Realm, and then calling `Realm.subscribe(to:where:callback:)` with the type of object you're interested in, a string containing a query determining which objects you want to subscribe to, and a callback which will report the results. You may add as many subscriptions to a synced Realm as necessary. ### Bugfixes * Realm Swift APIs that filter or look up the index of an object based on a format string now properly handle optional arguments in their variadic argument list. 3.0.0-beta.3 Release notes (2017-08-23) ============================================================= ### Breaking Changes * iOS 7 is no longer supported. * Computed properties on Realm object types are detected and no longer added to the automatically generated schema. * `-[RLMRealm addOrUpdateObjectsInArray:]` has been renamed to `-[RLMRealm addOrUpdateObjects:]` for consistency with similar methods that add or delete objects. * `-[RLMRealm addOrUpdateObjects:]` and `-[RLMRealm deleteObjects:]` now require their argument to conform to `NSFastEnumeration`, to match similar APIs that also take collections. * Remove deprecated `{RLM}SyncPermission` and `{RLM}SyncPermissionChange` classes. * `{RLM}SyncPermissionValue` has been renamed to just `{RLM}SyncPermission`. Its `userId` property has been renamed `identity`, and its `-initWithRealmPath:userID:accessLevel:` initializer has been renamed `-initWithRealmPath:identity:accessLevel:`. * Remove deprecated `-[RLMSyncUser permissionRealmWithError:]` and `SyncUser.permissionRealm()` APIs. Use the new permissions system. * Remove deprecated error `RLMSyncErrorBadResponse`. Use `RLMSyncAuthErrorBadResponse` instead. * The way interactive sync errors (client reset and permission denied) are delivered to the user has been changed. Instead of a block which can be invoked to immediately delete the offending Realm file, an opaque token object of type `RLMSyncErrorActionToken` will be returned in the error object's `userInfo` dictionary. This error object can be passed into the new `+[RLMSyncSession immediatelyHandleError:]` API to delete the files. * Remove `-[NSError rlmSync_clientResetBlock]` and `-[NSError rlmSync_deleteRealmBlock]` APIs. * The return types of the `SyncError.clientResetInfo()` and `SyncError.deleteRealmUserInfo()` APIs have been changed. They now return `RLMSyncErrorActionToken`s or `SyncError.ActionToken`s instead of closures. * The (erroneously added) instance property `Object.className` has been removed. * The class methods `Object.className()`, `Object.objectUtilClass()`, and the property `Object.isInvalidated` can no longer be overriden. * The callback which runs when a sync user login succeeds or fails now runs on the main queue by default, or can be explicitly specified by a new `callbackQueue` parameter on the `{RLM}SyncUser.logIn(...)` API. * Rename `{RLM}NotificationToken.stop()` to `invalidate()` and `{RealmCollection,SyncPermissionResults}.addNotificationBlock(_:)` to `observe(_:)` to mirror Foundation's new KVO APIs. * The `RLMSyncProgress` enum has been renamed `RLMSyncProgressMode`. * Remove deprecated `{RLM}SyncManager.disableSSLValidation` property. Disable SSL validation on a per-Realm basis by setting the `enableSSLValidation` property on `{RLM}SyncConfiguration` instead. * Fix empty strings, binary data, and null on the right side of `BEGINSWITH`, `ENDSWITH` and `CONTAINS` operators in predicates to match Foundation's semantics of never matching any strings or data. * Swift `Object` comparison and hashing behavior now works the same way as that of `RLMObject` (objects are now only considered equatable if their model class defines a primary key). * Fix the way the hash property works on `Object` when the object model has no primary key. * Fix an issue where if a Swift model class defined non-generic managed properties after generic Realm properties (like `List`), the schema would be constructed incorrectly. Fixes an issue where creating such models from an array could fail. ### Enhancements * Add Swift `Object.isSameObject(as:_)` API to perform the same function as the existing Objective-C API `-[RLMObject isEqualToObject:]`. * Expose additional authentication-related errors that might be reported by a Realm Object Server. * An error handler can now be registered on `{RLM}SyncUser`s in order to report authentication-related errors that affect the user. ### Bugfixes * Sync users are now automatically logged out upon receiving certain types of errors that indicate they are no longer logged into the server. For example, users who are authenticated using third-party credentials will find themselves logged out of the Realm Object Server if the third-party identity service indicates that their credential is no longer valid. * Address high CPU usage and hangs in certain cases when processing collection notifications in highly-connected object graphs. 3.0.0-beta.2 Release notes (2017-07-26) ============================================================= ### Breaking Changes * Remove the following deprecated Objective-C APIs: `-[RLMArray sortedResultsUsingProperty:ascending:]`, `-[RLMCollection sortedResultsUsingProperty:ascending:]`, `-[RLMResults sortedResultsUsingProperty:ascending:]`, `+[RLMSortDescriptor sortDescriptorWithProperty:ascending:]`, `RLMSortDescriptor.property`. These APIs have been superseded by equivalent APIs that take or return key paths instead of property names. * Remove the following deprecated Objective-C API: `+[RLMRealm migrateRealm:configuration:]`. Please use `+[RLMRealm performMigrationForConfiguration:error:]` instead. * Remove the following deprecated Swift APIs: `AnyRealmCollection.sorted(byProperty:, ascending:)`, `LinkingObjects.sorted(byProperty:, ascending:)`, `List.sorted(byProperty:, ascending:)`, `Results.sorted(byProperty:, ascending:)`, `SortDescriptor.init(property:, ascending:)`, `SortDescriptor.property`. These APIs have been superseded by equivalent APIs that take or return key paths instead of property names. * The Objective-C and Swift `create(_:, value: update:)` APIs now correctly nil out nullable properties when updating an existing object when the `value` argument specifies nil or `NSNull` for the property value. ### Enhancements * It is now possible to create and log in multiple Realm Object Server users with the same identity if they originate from different servers. Note that if the URLs are different aliases for the same authentication server each user will still be treated as separate (e.g. they will have their own copy of each synchronized Realm opened using them). It is highly encouraged that users defined using the access token credential type be logged in with an authentication server URL specified; this parameter will become mandatory in a future version of the SDK. * Add `-[RLMSyncUser retrieveInfoForUser:identityProvider:completion:]` API allowing administrator users to retrieve information about a user based on their provider identity (for example, a username). Requires any edition of the Realm Object Server 1.8.2 or later. ### Bugfixes * Realm no longer throws an "unsupported instruction" exception in some cases when opening a synced Realm asynchronously. 3.0.0-beta Release notes (2017-07-14) ============================================================= ### Breaking Changes * Synchronized Realms require a server running Realm Object Server v2.0 or higher. ### Enhancements * Add a new error code to denote 'permission denied' errors when working with synchronized Realms, as well as an accompanying block that can be called to inform the binding that the offending Realm's files should be deleted immediately. This allows recovering from 'permission denied' errors in a more robust manner. See the documentation for `RLMSyncErrorPermissionDeniedError` for more information. * Add `-[RLMSyncPermissionValue initWithRealmPath:username:accessLevel:]` API allowing permissions to be applied to a user based on their username (usually, an email address). Requires any edition of the Realm Object Server 1.6.0 or later. * Improve performance of creating Swift objects which contain at least one List property. ### Bugfixes * `List.description` now reports the correct types for nested lists. * Fix unmanaged object initialization when a nested property type returned `false` from `Object.shouldIncludeInDefaultSchema()`. * Don't clear RLMArrays on self-assignment. 2.10.2 Release notes (2017-09-27) ============================================================= ### Bugfixes * The keychain item name used by Realm to manage the encryption keys for sync-related metadata is now set to a per-app name based on the bundle identifier. Keys that were previously stored within the single, shared Realm keychain item will be transparently migrated to the per-application keychain item. * Fix downloading of the Realm core binaries when Xcode's command-line tools are set as the active developer directory for command-line interactions. * Fix a crash that could occur when resolving a ThreadSafeReference to a `List` whose parent object had since been deleted. 2.10.1 Release notes (2017-09-14) ============================================================= Swift binaries are now produced for Swift 3.0, 3.0.1, 3.0.2, 3.1, 3.2 and 4.0. ### Enhancements * Auxiliary files are excluded from backup by default. ### Bugfixes * Fix more cases where assigning an RLMArray property to itself would clear the RLMArray. 2.10.0 Release notes (2017-08-21) ============================================================= ### API Breaking Changes * None. ### Enhancements * Expose additional authentication-related errors that might be reported by a Realm Object Server. * An error handler can now be registered on `{RLM}SyncUser`s in order to report authentication-related errors that affect the user. ### Bugfixes * Sorting Realm collection types no longer throws an exception on iOS 7. * Sync users are now automatically logged out upon receiving certain types of errors that indicate they are no longer logged into the server. For example, users who are authenticated using third-party credentials will find themselves logged out of the Realm Object Server if the third-party identity service indicates that their credential is no longer valid. * Address high CPU usage and hangs in certain cases when processing collection notifications in highly-connected object graphs. 2.9.1 Release notes (2017-08-01) ============================================================= ### API Breaking Changes * None. ### Enhancements * None. ### Bugfixes * The `shouldCompactOnLaunch` block is no longer invoked if the Realm at that path is already open on other threads. * Fix an assertion failure in collection notifications when changes are made to the schema via sync while the notification block is active. 2.9.0 Release notes (2017-07-26) ============================================================= ### API Breaking Changes * None. ### Enhancements * Add a new error code to denote 'permission denied' errors when working with synchronized Realms, as well as an accompanying block that can be called to inform the binding that the offending Realm's files should be deleted immediately. This allows recovering from 'permission denied' errors in a more robust manner. See the documentation for `RLMSyncErrorPermissionDeniedError` for more information. * Add `-[RLMSyncPermissionValue initWithRealmPath:username:accessLevel:]` API allowing permissions to be applied to a user based on their username (usually, an email address). Requires any edition of the Realm Object Server 1.6.0 or later. * Improve performance of creating Swift objects which contain at least one List property. * It is now possible to create and log in multiple Realm Object Server users with the same identity if they originate from different servers. Note that if the URLs are different aliases for the same authentication server each user will still be treated as separate (e.g. they will have their own copy of each synchronized Realm opened using them). It is highly encouraged that users defined using the access token credential type be logged in with an authentication server URL specified; this parameter will become mandatory in a future version of the SDK. * Add `-[RLMSyncUser retrieveInfoForUser:identityProvider:completion:]` API allowing administrator users to retrieve information about a user based on their provider identity (for example, a username). Requires any edition of the Realm Object Server 1.8.2 or later. ### Bugfixes * `List.description` now reports the correct types for nested lists. * Fix unmanaged object initialization when a nested property type returned `false` from `Object.shouldIncludeInDefaultSchema()`. * Don't clear RLMArrays on self-assignment. 2.8.3 Release notes (2017-06-20) ============================================================= ### Bugfixes * Properly update RealmOptional properties when adding an object with `add(update: true)`. * Add some missing quotes in error messages. * Fix a performance regression when creating objects with primary keys. 2.8.2 Release notes (2017-06-16) ============================================================= ### Bugfixes * Fix an issue where synchronized Realms would eventually disconnect from the remote server if the user object used to define their sync configuration was destroyed. * Restore support for changing primary keys in migrations (broken in 2.8.0). * Revert handling of adding objects with nil properties to a Realm to the pre-2.8.0 behavior. 2.8.1 Release notes (2017-06-12) ============================================================= Add support for building with Xcode 9 Beta 1. ### Bugfixes * Fix setting a float property to NaN. * Fix a crash when using compact on launch in combination with collection notifications. 2.8.0 Release notes (2017-06-02) ============================================================= ### API Breaking Changes * None. ### Enhancements * Enable encryption on watchOS. * Add `-[RLMSyncUser changePassword:forUserID:completion:]` API to change an arbitrary user's password if the current user has administrative privileges and using Realm's 'password' authentication provider. Requires any edition of the Realm Object Server 1.6.0 or later. ### Bugfixes * Suppress `-Wdocumentation` warnings in Realm C++ headers when using CocoaPods with Xcode 8.3.2. * Throw an appropriate error rather than crashing when an RLMArray is assigned to an RLMArray property of a different type. * Fix crash in large (>4GB) encrypted Realm files. * Improve accuracy of sync progress notifications. * Fix an issue where synchronized Realms did not connect to the remote server in certain situations, such as when an application was offline when the Realms were opened but later regained network connectivity. 2.7.0 Release notes (2017-05-03) ============================================================= ### API Breaking Changes * None. ### Enhancements * Use reachability API to minimize the reconnection delay if the network connection was lost. * Add `-[RLMSyncUser changePassword:completion:]` API to change the current user's password if using Realm's 'password' authentication provider. Requires any edition of the Realm Object Server 1.4.0 or later. * `{RLM}SyncConfiguration` now has an `enableSSLValidation` property (and default parameter in the Swift initializer) to allow SSL validation to be specified on a per-server basis. * Transactions between a synced Realm and a Realm Object Server can now exceed 16 MB in size. * Add new APIs for changing and retrieving permissions for synchronized Realms. These APIs are intended to replace the existing Realm Object-based permissions system. Requires any edition of the Realm Object Server 1.1.0 or later. ### Bugfixes * Support Realm model classes defined in Swift with overridden Objective-C names (e.g. `@objc(Foo) class SwiftFoo: Object {}`). * Fix `-[RLMMigration enumerateObjects:block:]` returning incorrect `oldObject` objects when enumerating a class name after previously deleting a `newObject`. * Fix an issue where `Realm.asyncOpen(...)` would fail to work when opening a synchronized Realm for which the user only had read permissions. * Using KVC to set a `List` property to `nil` now clears it to match the behavior of `RLMArray` properties. * Fix crash from `!m_awaiting_pong` assertion failure when using synced Realms. * Fix poor performance or hangs when performing case-insensitive queries on indexed string properties that contain many characters that don't differ between upper and lower case (e.g., numbers, punctuation). 2.6.2 Release notes (2017-04-21) ============================================================= ### API Breaking Changes * None. ### Enhancements * None. ### Bugfixes * Fix an issue where calling `Realm.asyncOpen(...)` with a synchronized Realm configuration would fail with an "Operation canceled" error. * Fix initial collection notification sometimes not being delivered for synced Realms. * Fix circular links sometimes resulting in objects not being marked as modified in change notifications. 2.6.1 Release notes (2017-04-18) ============================================================= ### API Breaking Changes * None. ### Enhancements * None. ### Bugfixes * Fix an issue where calling `Realm.asyncOpen(...)` with a synchronized Realm configuration would crash in error cases rather than report the error. This is a small source breaking change if you were relying on the error being reported to be a `Realm.Error`. 2.6.0 Release notes (2017-04-18) ============================================================= ### API Breaking Changes * None. ### Enhancements * Add a `{RLM}SyncUser.isAdmin` property indicating whether a user is a Realm Object Server administrator. * Add an API to asynchronously open a Realm and deliver it to a block on a given queue. This performs all work needed to get the Realm to a usable state (such as running potentially time-consuming migrations) on a background thread before dispatching to the given queue. In addition, synchronized Realms wait for all remote content available at the time the operation began to be downloaded and available locally. * Add `shouldCompactOnLaunch` block property when configuring a Realm to determine if it should be compacted before being returned. * Speed up case-insensitive queries on indexed string properties. * Add RLMResults's collection aggregate methods to RLMArray. * Add support for calling the aggregate methods on unmanaged Lists. ### Bugfixes * Fix a deadlock when multiple processes open a Realm at the same time. * Fix `value(forKey:)`/`value(forKeyPath:)` returning incorrect values for `List` properties. 2.5.1 Release notes (2017-04-05) ============================================================= ### API Breaking Changes * None. ### Enhancements * None. ### Bugfixes * Fix CocoaPods installation with static libraries and multiple platforms. * Fix uncaught "Bad version number" exceptions on the notification worker thread followed by deadlocks when Realms refresh. 2.5.0 Release notes (2017-03-28) ============================================================= Files written by Realm this version cannot be read by earlier versions of Realm. Old files can still be opened and files open in read-only mode will not be modified. If using synchronized Realms, the Realm Object Server must be running version 1.3.0 or later. Swift binaries are now produced for Swift 3.0, 3.0.1, 3.0.2 and 3.1. ### API Breaking Changes * None. ### Enhancements * Add support for multi-level object equality comparisons against `NULL`. * Add support for the `[d]` modifier on string comparison operators to perform diacritic-insensitive comparisons. * Explicitly mark `[[RLMRealm alloc] init]` as unavailable. * Include the name of the problematic class in the error message when an invalid property type is marked as the primary key. ### Bugfixes * Fix incorrect column type assertions which could occur after schemas were merged by sync. * Eliminate an empty write transaction when opening a synced Realm. * Support encrypting synchronized Realms by respecting the `encryptionKey` value of the Realm's configuration. * Fix crash when setting an `{NS}Data` property close to 16MB. * Fix for reading `{NS}Data` properties incorrectly returning `nil`. * Reduce file size growth in cases where Realm versions were pinned while starting write transactions. * Fix an assertion failure when writing to large `RLMArray`/`List` properties. * Fix uncaught `BadTransactLog` exceptions when pulling invalid changesets from synchronized Realms. * Fix an assertion failure when an observed `RLMArray`/`List` is deleted after being modified. 2.4.4 Release notes (2017-03-13) ============================================================= ### API Breaking Changes * None. ### Enhancements * Add `(RLM)SyncPermission` class to allow reviewing access permissions for Realms. Requires any edition of the Realm Object Server 1.1.0 or later. * Further reduce the number of files opened per thread-specific Realm on macOS, iOS and watchOS. ### Bugfixes * Fix a crash that could occur if new Realm instances were created while the application was exiting. * Fix a bug that could lead to bad version number errors when delivering change notifications. * Fix a potential use-after-free bug when checking validity of results. * Fix an issue where a sync session might not close properly if it receives an error while being torn down. * Fix some issues where a sync session might not reconnect to the server properly or get into an inconsistent state if revived after invalidation. * Fix an issue where notifications might not fire when the children of an observed object are changed. * Fix an issue where progress notifications on sync sessions might incorrectly report out-of-date values. * Fix an issue where multiple threads accessing encrypted data could result in corrupted data or crashes. * Fix an issue where certain `LIKE` queries could hang. * Fix an issue where `-[RLMRealm writeCopyToURL:encryptionKey:error]` could create a corrupt Realm file. * Fix an issue where incrementing a synced Realm's schema version without actually changing the schema could cause a crash. 2.4.3 Release notes (2017-02-20) ============================================================= ### API Breaking Changes * None. ### Enhancements * Avoid copying copy-on-write data structures, which can grow the file, when the write does not actually change existing values. * Improve performance of deleting all objects in an RLMResults. * Reduce the number of files opened per thread-specific Realm on macOS. * Improve startup performance with large numbers of `RLMObject`/`Object` subclasses. ### Bugfixes * Fix synchronized Realms not downloading remote changes when an access token expires and there are no local changes to upload. * Fix an issue where values set on a Realm object using `setValue(value:, forKey:)` that were not themselves Realm objects were not properly converted into Realm objects or checked for validity. * Fix an issue where `-[RLMSyncUser sessionForURL:]` could erroneously return a non-nil value when passed in an invalid URL. * `SyncSession.Progress.fractionTransferred` now returns 1 if there are no transferrable bytes. * Fix sync progress notifications registered on background threads by always dispatching on a dedicated background queue. * Fix compilation issues with Xcode 8.3 beta 2. * Fix incorrect sync progress notification values for Realms originally created using a version of Realm prior to 2.3.0. * Fix LLDB integration to be able to display summaries of `RLMResults` once more. * Reject Swift properties with names which cause them to fall in to ARC method families rather than crashing when they are accessed. * Fix sorting by key path when the declared property order doesn't match the order of properties in the Realm file, which can happen when properties are added in different schema versions. 2.4.2 Release notes (2017-01-30) ============================================================= ### Bugfixes * Fix an issue where RLMRealm instances could end up in the autorelease pool for other threads. 2.4.1 Release notes (2017-01-27) ============================================================= ### Bugfixes * Fix an issue where authentication tokens were not properly refreshed automatically before expiring. 2.4.0 Release notes (2017-01-26) ============================================================= This release drops support for compiling with Swift 2.x. Swift 3.0.0 is now the minimum Swift version supported. ### API Breaking Changes * None. ### Enhancements * Add change notifications for individual objects with an API similar to that of collection notifications. ### Bugfixes * Fix Realm Objective-C compilation errors with Xcode 8.3 beta 1. * Fix several error handling issues when renewing expired authentication tokens for synchronized Realms. * Fix a race condition leading to bad_version exceptions being thrown in Realm's background worker thread. 2.3.0 Release notes (2017-01-19) ============================================================= ### Sync Breaking Changes * Make `PermissionChange`'s `id` property a primary key. ### API Breaking Changes * None. ### Enhancements * Add `SyncPermissionOffer` and `SyncPermissionOfferResponse` classes to allow creating and accepting permission change events to synchronized Realms between different users. * Support monitoring sync transfer progress by registering notification blocks on `SyncSession`. Specify the transfer direction (`.upload`/`.download`) and mode (`.reportIndefinitely`/`.forCurrentlyOutstandingWork`) to monitor. ### Bugfixes * Fix a call to `commitWrite(withoutNotifying:)` committing a transaction that would not have triggered a notification incorrectly skipping the next notification. * Fix incorrect results and crashes when conflicting object insertions are merged by the synchronization mechanism when there is a collection notification registered for that object type. 2.2.0 Release notes (2017-01-12) ============================================================= ### Sync Breaking Changes (In Beta) * Sync-related error reporting behavior has been changed. Errors not related to a particular user or session are only reported if they are classed as 'fatal' by the underlying sync engine. * Added `RLMSyncErrorClientResetError` to `RLMSyncError` enum. ### API Breaking Changes * The following Objective-C APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:------------------------------------------------------------|:------------------------------------------------------------| | `-[RLMArray sortedResultsUsingProperty:]` | `-[RLMArray sortedResultsUsingKeyPath:]` | | `-[RLMCollection sortedResultsUsingProperty:]` | `-[RLMCollection sortedResultsUsingKeyPath:]` | | `-[RLMResults sortedResultsUsingProperty:]` | `-[RLMResults sortedResultsUsingKeyPath:]` | | `+[RLMSortDescriptor sortDescriptorWithProperty:ascending]` | `+[RLMSortDescriptor sortDescriptorWithKeyPath:ascending:]` | | `RLMSortDescriptor.property` | `RLMSortDescriptor.keyPath` | * The following Swift APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:------------------------------------------------------|:-------------------------------------------------| | `LinkingObjects.sorted(byProperty:ascending:)` | `LinkingObjects.sorted(byKeyPath:ascending:)` | | `List.sorted(byProperty:ascending:)` | `List.sorted(byKeyPath:ascending:)` | | `RealmCollection.sorted(byProperty:ascending:)` | `RealmCollection.sorted(byKeyPath:ascending:)` | | `Results.sorted(byProperty:ascending:)` | `Results.sorted(byKeyPath:ascending:)` | | `SortDescriptor(property:ascending:)` | `SortDescriptor(keyPath:ascending:)` | | `SortDescriptor.property` | `SortDescriptor.keyPath` | ### Enhancements * Introduce APIs for safely passing objects between threads. Create a thread-safe reference to a thread-confined object by passing it to the `+[RLMThreadSafeReference referenceWithThreadConfined:]`/`ThreadSafeReference(to:)` constructor, which you can then safely pass to another thread to resolve in the new Realm with `-[RLMRealm resolveThreadSafeReference:]`/`Realm.resolve(_:)`. * Realm collections can now be sorted by properties over to-one relationships. * Optimized `CONTAINS` queries to use Boyer-Moore algorithm (around 10x speedup on large datasets). ### Bugfixes * Setting `deleteRealmIfMigrationNeeded` now also deletes the Realm if a file format migration is required, such as when moving from a file last accessed with Realm 0.x to 1.x, or 1.x to 2.x. * Fix queries containing nested `SUBQUERY` expressions. * Fix spurious incorrect thread exceptions when a thread id happens to be reused while an RLMRealm instance from the old thread still exists. * Fixed various bugs in aggregate methods (max, min, avg, sum). 2.1.2 Release notes (2016--12-19) ============================================================= This release adds binary versions of Swift 3.0.2 frameworks built with Xcode 8.2. ### Sync Breaking Changes (In Beta) * Rename occurences of "iCloud" with "CloudKit" in APIs and comments to match naming in the Realm Object Server. ### API Breaking Changes * None. ### Enhancements * Add support for 'LIKE' queries (wildcard matching). ### Bugfixes * Fix authenticating with CloudKit. * Fix linker warning about "Direct access to global weak symbol". 2.1.1 Release notes (2016-12-02) ============================================================= ### Enhancements * Add `RealmSwift.ObjectiveCSupport.convert(object:)` methods to help write code that interoperates between Realm Objective-C and Realm Swift APIs. * Throw exceptions when opening a Realm with an incorrect configuration, like: * `readOnly` set with a sync configuration. * `readOnly` set with a migration block. * migration block set with a sync configuration. * Greatly improve performance of write transactions which make a large number of changes to indexed properties, including the automatic migration when opening files written by Realm 1.x. ### Bugfixes * Reset sync metadata Realm in case of decryption error. * Fix issue preventing using synchronized Realms in Xcode Playgrounds. * Fix assertion failure when migrating a model property from object type to `RLMLinkingObjects` type. * Fix a `LogicError: Bad version number` exception when using `RLMResults` with no notification blocks and explicitly called `-[RLMRealm refresh]` from that thread. * Logged-out users are no longer returned from `+[RLMSyncUser currentUser]` or `+[RLMSyncUser allUsers]`. * Fix several issues which could occur when the 1001st object of a given type was created or added to an RLMArray/List, including crashes when rerunning existing queries and possibly data corruption. * Fix a potential crash when the application exits due to a race condition in the destruction of global static variables. * Fix race conditions when waiting for sync uploads or downloads to complete which could result in crashes or the callback being called too early. 2.1.0 Release notes (2016-11-18) ============================================================= ### Sync Breaking Changes (In Beta) * None. ### API breaking changes * None. ### Enhancements * Add the ability to skip calling specific notification blocks when committing a write transaction. ### Bugfixes * Deliver collection notifications when beginning a write transaction which advances the read version of a Realm (previously only Realm-level notifications were sent). * Fix some scenarios which would lead to inconsistent states when using collection notifications. * Fix several race conditions in the notification functionality. * Don't send Realm change notifications when canceling a write transaction. 2.0.4 Release notes (2016-11-14) ============================================================= ### Sync Breaking Changes (In Beta) * Remove `RLMAuthenticationActions` and replace `+[RLMSyncCredential credentialWithUsername:password:actions:]` with `+[RLMSyncCredential credentialsWithUsername:password:register:]`. * Rename `+[RLMSyncUser authenticateWithCredential:]` to `+[RLMSyncUser logInWithCredentials:]`. * Rename "credential"-related types and methods to `RLMSyncCredentials`/`SyncCredentials` and consistently refer to credentials in the plural form. * Change `+[RLMSyncUser all]` to return a dictionary of identifiers to users and rename to: * `+[RLMSyncUser allUsers]` in Objective-C. * `SyncUser.allUsers()` in Swift 2. * `SyncUser.all` in Swift 3. * Rename `SyncManager.sharedManager()` to `SyncManager.shared` in Swift 3. * Change `Realm.Configuration.syncConfiguration` to take a `SyncConfiguration` struct rather than a named tuple. * `+[RLMSyncUser logInWithCredentials:]` now invokes its callback block on a background queue. ### API breaking changes * None. ### Enhancements * Add `+[RLMSyncUser currentUser]`. * Add the ability to change read, write and management permissions for synchronized Realms using the management Realm obtained via the `-[RLMSyncUser managementRealmWithError:]` API and the `RLMSyncPermissionChange` class. ### Bugfixes * None. 2.0.3 Release notes (2016-10-27) ============================================================= This release adds binary versions of Swift 3.0.1 frameworks built with Xcode 8.1 GM seed. ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a `BadVersion` exception caused by a race condition when delivering collection change notifications. * Fix an assertion failure when additional model classes are added and `deleteRealmIfMigrationNeeded` is enabled. * Fix a `BadTransactLog` exception when deleting an `RLMResults` in a synced Realm. * Fix an assertion failure when a write transaction is in progress at the point of process termination. * Fix a crash that could occur when working with a `RLMLinkingObject` property of an unmanaged object. 2.0.2 Release notes (2016-10-05) ============================================================= This release is not protocol-compatible with previous version of the Realm Mobile Platform. ### API breaking changes * Rename Realm Swift's `User` to `SyncUser` to make clear that it relates to the Realm Mobile Platform, and to avoid potential conflicts with other `User` types. ### Bugfixes * Fix Realm headers to be compatible with pre-C++11 dialects of Objective-C++. * Fix incorrect merging of RLMArray/List changes when objects with the same primary key are created on multiple devices. * Fix bad transaction log errors after deleting objects on a different device. * Fix a BadVersion error when a background worker finishes running while older results from that worker are being delivered to a different thread. 2.0.1 Release notes (2016-09-29) ============================================================= ### Bugfixes * Fix an assertion failure when opening a Realm file written by a 1.x version of Realm which has an indexed nullable int or bool property. 2.0.0 Release notes (2016-09-27) ============================================================= This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. ### API breaking changes * By popular demand, `RealmSwift.Error` has been moved from the top-level namespace into a `Realm` extension and is now `Realm.Error`, so that it no longer conflicts with `Swift.Error`. * Files written by Realm 2.0 cannot be read by 1.x or earlier versions. Old files can still be opened. ### Enhancements * The .log, .log_a and .log_b files no longer exist and the state tracked in them has been moved to the main Realm file. This reduces the number of open files needed by Realm, improves performance of both opening and writing to Realms, and eliminates a small window where committing write transactions would prevent other processes from opening the file. ### Bugfixes * Fix an assertion failure when sorting by zero properties. * Fix a mid-commit crash in one process also crashing all other processes with the same Realm open. * Properly initialize new nullable float and double properties added to existing objects to null rather than 0. * Fix a stack overflow when objects with indexed string properties had very long common prefixes. * Fix a race condition which could lead to crashes when using async queries or collection notifications. * Fix a bug which could lead to incorrect state when an object which links to itself is deleted from the Realm. 1.1.0 Release notes (2016-09-16) ============================================================= This release brings official support for Xcode 8, Swift 2.3 and Swift 3.0. Prebuilt frameworks are now built with Xcode 7.3.1 and Xcode 8.0. ### API breaking changes * Deprecate `migrateRealm:` in favor of new `performMigrationForConfiguration:error:` method that follows Cocoa's NSError conventions. * Fix issue where `RLMResults` used `id `instead of its generic type as the return type of subscript. ### Enhancements * Improve error message when using NSNumber incorrectly in Swift models. * Further reduce the download size of the prebuilt static libraries. * Improve sort performance, especially on non-nullable columns. * Allow partial initialization of object by `initWithValue:`, deferring required property checks until object is added to Realm. ### Bugfixes * Fix incorrect truncation of the constant value for queries of the form `column < value` for `float` and `double` columns. * Fix crash when an aggregate is accessed as an `Int8`, `Int16`, `Int32`, or `Int64`. * Fix a race condition that could lead to a crash if an RLMArray or List was deallocated on a different thread than it was created on. * Fix a crash when the last reference to an observed object is released from within the observation. * Fix a crash when `initWithValue:` is used to create a nested object for a class with an uninitialized schema. * Enforce uniqueness for `RealmOptional` primary keys when using the `value` setter. 1.0.2 Release notes (2016-07-13) ============================================================= ### API breaking changes * Attempting to add an object with no properties to a Realm now throws rather than silently doing nothing. ### Enhancements * Swift: A `write` block may now `throw`, reverting any changes already made in the transaction. * Reduce address space used when committing write transactions. * Significantly reduce the download size of prebuilt binaries and slightly reduce the final size contribution of Realm to applications. * Improve performance of accessing RLMArray properties and creating objects with List properties. ### Bugfixes * Fix a crash when reading the shared schema from an observed Swift object. * Fix crashes or incorrect results when passing an array of values to `createOrUpdate` after reordering the class's properties. * Ensure that the initial call of a Results notification block is always passed .Initial even if there is a write transaction between when the notification is added and when the first notification is delivered. * Fix a crash when deleting all objects in a Realm while fast-enumerating query results from that Realm. * Handle EINTR from flock() rather than crashing. * Fix incorrect behavior following a call to `[RLMRealm compact]`. * Fix live updating and notifications for Results created from a predicate involving an inverse relationship to be triggered when an object at the other end of the relationship is modified. 1.0.1 Release notes (2016-06-12) ============================================================= ### API breaking changes * None. ### Enhancements * Significantly improve performance of opening Realm files, and slightly improve performance of committing write transactions. ### Bugfixes * Swift: Fix an error thrown when trying to create or update `Object` instances via `add(:_update:)` with a primary key property of type `RealmOptional`. * Xcode playground in Swift release zip now runs successfully. * The `key` parameter of `Realm.objectForPrimaryKey(_:key:)`/ `Realm.dynamicObjectForPrimaryKey(_:key:)` is now marked as optional. * Fix a potential memory leak when closing Realms after a Realm file has been opened on multiple threads which are running in active run loops. * Fix notifications breaking on tvOS after a very large number of write transactions have been committed. * Fix a "Destruction of mutex in use" assertion failure after an error while opening a file. * Realm now throws an exception if an `Object` subclass is defined with a managed Swift `lazy` property. Objects with ignored `lazy` properties should now work correctly. * Update the LLDB script to work with recent changes to the implementation of `RLMResults`. * Fix an assertion failure when a Realm file is deleted while it is still open, and then a new Realm is opened at the same path. Note that this is still not a supported scenario, and may break in other ways. 1.0.0 Release notes (2016-05-25) ============================================================= No changes since 0.103.2. 0.103.2 Release notes (2016-05-24) ============================================================= ### API breaking changes * None. ### Enhancements * Improve the error messages when an I/O error occurs in `writeCopyToURL`. ### Bugfixes * Fix an assertion failure which could occur when opening a Realm after opening that Realm failed previously in some specific ways in the same run of the application. * Reading optional integers, floats, and doubles from within a migration block now correctly returns `nil` rather than 0 when the stored value is `nil`. 0.103.1 Release notes (2016-05-19) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a bug that sometimes resulted in a single object's NSData properties changing from `nil` to a zero-length non-`nil` NSData when a different object of the same type was deleted. 0.103.0 Release notes (2016-05-18) ============================================================= ### API breaking changes * All functionality deprecated in previous releases has been removed entirely. * Support for Xcode 6.x & Swift prior to 2.2 has been completely removed. * `RLMResults`/`Results` now become empty when a `RLMArray`/`List` or object they depend on is deleted, rather than throwing an exception when accessed. * Migrations are no longer run when `deleteRealmIfMigrationNeeded` is set, recreating the file instead. ### Enhancements * Added `invalidated` properties to `RLMResults`/`Results`, `RLMLinkingObjects`/`LinkingObjects`, `RealmCollectionType` and `AnyRealmCollection`. These properties report whether the Realm the object is associated with has been invalidated. * Some `NSError`s created by Realm now have more descriptive user info payloads. ### Bugfixes * None. 0.102.1 Release notes (2016-05-13) ============================================================= ### API breaking changes * None. ### Enhancements * Return `RLMErrorSchemaMismatch` error rather than the more generic `RLMErrorFail` when a migration is required. * Improve the performance of allocating instances of `Object` subclasses that have `LinkingObjects` properties. ### Bugfixes * `RLMLinkingObjects` properties declared in Swift subclasses of `RLMObject` now work correctly. * Fix an assertion failure when deleting all objects of a type, inserting more objects, and then deleting some of the newly inserted objects within a single write transaction when there is an active notification block for a different object type which links to the objects being deleted. * Fix crashes and/or incorrect results when querying over multiple levels of `LinkingObjects` properties. * Fix opening read-only Realms on multiple threads at once. * Fix a `BadTransactLog` exception when storing dates before the unix epoch (1970-01-01). 0.102.0 Release notes (2016-05-09) ============================================================= ### API breaking changes * None. ### Enhancements * Add a method to rename properties during migrations: * Swift: `Migration.renamePropertyForClass(_:oldName:newName:)` * Objective-C: `-[RLMMigration renamePropertyForClass:oldName:newName:]` * Add `deleteRealmIfMigrationNeeded` to `RLMRealmConfiguration`/`Realm.Configuration`. When this is set to `true`, the Realm file will be automatically deleted and recreated when there is a schema mismatch rather than migrated to the new schema. ### Bugfixes * Fix `BETWEEN` queries that traverse `RLMArray`/`List` properties to ensure that a single related object satisfies the `BETWEEN` criteria, rather than allowing different objects in the array to satisfy the lower and upper bounds. * Fix a race condition when a Realm is opened on one thread while it is in the middle of being closed on another thread which could result in crashes. * Fix a bug which could result in changes made on one thread being applied incorrectly on other threads when those threads are refreshed. * Fix crash when migrating to the new date format introduced in 0.101.0. * Fix crash when querying inverse relationships when objects are deleted. 0.101.0 Release notes (2016-05-04) ============================================================= ### API breaking changes * Files written by this version of Realm cannot be read by older versions of Realm. Existing files will automatically be upgraded when they are opened. ### Enhancements * Greatly improve performance of collection change calculation for complex object graphs, especially for ones with cycles. * NSDate properties now support nanoseconds precision. * Opening a single Realm file on multiple threads now shares a single memory mapping of the file for all threads, significantly reducing the memory required to work with large files. * Crashing while in the middle of a write transaction no longer blocks other processes from performing write transactions on the same file. * Improve the performance of refreshing a Realm (including via autorefresh) when there are live Results/RLMResults objects for that Realm. ### Bugfixes * Fix an assertion failure of "!more_before || index >= std::prev(it)->second)" in `IndexSet::do_add()`. * Fix a crash when an `RLMArray` or `List` object is destroyed from the wrong thread. 0.100.0 Release notes (2016-04-29) ============================================================= ### API breaking changes * `-[RLMObject linkingObjectsOfClass:forProperty]` and `Object.linkingObjects(_:forProperty:)` are deprecated in favor of properties of type `RLMLinkingObjects` / `LinkingObjects`. ### Enhancements * The automatically-maintained inverse direction of relationships can now be exposed as properties of type `RLMLinkingObjects` / `LinkingObjects`. These properties automatically update to reflect the objects that link to the target object, can be used in queries, and can be filtered like other Realm collection types. * Queries that compare objects for equality now support multi-level key paths. ### Bugfixes * Fix an assertion failure when a second write transaction is committed after a write transaction deleted the object containing an RLMArray/List which had an active notification block. * Queries that compare `RLMArray` / `List` properties using != now give the correct results. 0.99.1 Release notes (2016-04-26) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a scenario that could lead to the assertion failure "m_advancer_sg->get_version_of_current_transaction() == new_notifiers.front()->version()". 0.99.0 Release notes (2016-04-22) ============================================================= ### API breaking changes * Deprecate properties of type `id`/`AnyObject`. This type was rarely used, rarely useful and unsupported in every other Realm binding. * The block for `-[RLMArray addNotificationBlock:]` and `-[RLMResults addNotificationBlock:]` now takes another parameter. * The following Objective-C APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:-------------------------------------------------------|:------------------------------------------------------| | `-[RLMRealm removeNotification:]` | `-[RLMNotificationToken stop]` | | `RLMRealmConfiguration.path` | `RLMRealmConfiguration.fileURL` | | `RLMRealm.path` | `RLMRealmConfiguration.fileURL` | | `RLMRealm.readOnly` | `RLMRealmConfiguration.readOnly` | | `+[RLMRealm realmWithPath:]` | `+[RLMRealm realmWithURL:]` | | `+[RLMRealm writeCopyToPath:error:]` | `+[RLMRealm writeCopyToURL:encryptionKey:error:]` | | `+[RLMRealm writeCopyToPath:encryptionKey:error:]` | `+[RLMRealm writeCopyToURL:encryptionKey:error:]` | | `+[RLMRealm schemaVersionAtPath:error:]` | `+[RLMRealm schemaVersionAtURL:encryptionKey:error:]` | | `+[RLMRealm schemaVersionAtPath:encryptionKey:error:]` | `+[RLMRealm schemaVersionAtURL:encryptionKey:error:]` | * The following Swift APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:----------------------------------------------|:-----------------------------------------| | `Realm.removeNotification(_:)` | `NotificationToken.stop()` | | `Realm.Configuration.path` | `Realm.Configuration.fileURL` | | `Realm.path` | `Realm.Configuration.fileURL` | | `Realm.readOnly` | `Realm.Configuration.readOnly` | | `Realm.writeCopyToPath(_:encryptionKey:)` | `Realm.writeCopyToURL(_:encryptionKey:)` | | `schemaVersionAtPath(_:encryptionKey:error:)` | `schemaVersionAtURL(_:encryptionKey:)` | ### Enhancements * Add information about what rows were added, removed, or modified to the notifications sent to the Realm collections. * Improve error when illegally appending to an `RLMArray` / `List` property from a default value or the standalone initializer (`init()`) before the schema is ready. ### Bugfixes * Fix a use-after-free when an associated object's dealloc method is used to remove observers from an RLMObject. * Fix a small memory leak each time a Realm file is opened. * Return a recoverable `RLMErrorAddressSpaceExhausted` error rather than crash when there is insufficient available address space on Realm initialization or write commit. 0.98.8 Release notes (2016-04-15) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fixed a bug that caused some encrypted files created using `-[RLMRealm writeCopyToPath:encryptionKey:error:]` to fail to open. 0.98.7 Release notes (2016-04-13) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Mark further initializers in Objective-C as NS_DESIGNATED_INITIALIZER to prevent that these aren't correctly defined in Swift Object subclasses, which don't qualify for auto-inheriting the required initializers. * `-[RLMResults indexOfObjectWithPredicate:]` now returns correct results for `RLMResults` instances that were created by filtering an `RLMArray`. * Adjust how RLMObjects are destroyed in order to support using an associated object on an RLMObject to remove KVO observers from that RLMObject. * `-[RLMResults indexOfObjectWithPredicate:]` now returns the index of the first matching object for a sorted `RLMResults`, matching its documented behavior. * Fix a crash when canceling a transaction that set a relationship. * Fix a crash when a query referenced a deleted object. 0.98.6 Release notes (2016-03-25) ============================================================= Prebuilt frameworks are now built with Xcode 7.3. ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix running unit tests on iOS simulators and devices with Xcode 7.3. 0.98.5 Release notes (2016-03-14) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a crash when opening a Realm on 32-bit iOS devices. 0.98.4 Release notes (2016-03-10) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Properly report changes made by adding an object to a Realm with addOrUpdate:/createOrUpdate: to KVO observers for existing objects with that primary key. * Fix crashes and assorted issues when a migration which added object link properties is rolled back due to an error in the migration block. * Fix assertion failures when deleting objects within a migration block of a type which had an object link property added in that migration. * Fix an assertion failure in `Query::apply_patch` when updating certain kinds of queries after a write transaction is committed. 0.98.3 Release notes (2016-02-26) ============================================================= ### Enhancements * Initializing the shared schema is 3x faster. ### Bugfixes * Using Realm Objective-C from Swift while having Realm Swift linked no longer causes that the declared `ignoredProperties` are not taken into account. * Fix assertion failures when rolling back a migration which added Object link properties to a class. * Fix potential errors when cancelling a write transaction which modified multiple `RLMArray`/`List` properties. * Report the correct value for inWriteTransaction after attempting to commit a write transaction fails. * Support CocoaPods 1.0 beginning from prerelease 1.0.0.beta.4 while retaining backwards compatibility with 0.39. 0.98.2 Release notes (2016-02-18) ============================================================= ### API breaking changes * None. ### Enhancements * Aggregate operations (`ANY`, `NONE`, `@count`, `SUBQUERY`, etc.) are now supported for key paths that begin with an object relationship so long as there is a `RLMArray`/`List` property at some point in a key path. * Predicates of the form `%@ IN arrayProperty` are now supported. ### Bugfixes * Use of KVC collection operators on Swift collection types no longer throws an exception. * Fix reporting of inWriteTransaction in notifications triggered by `beginWriteTransaction`. * The contents of `List` and `Optional` properties are now correctly preserved when copying a Swift object from one Realm to another, and performing other operations that result in a Swift object graph being recursively traversed from Objective-C. * Fix a deadlock when queries are performed within a Realm notification block. * The `ANY` / `SOME` / `NONE` qualifiers are now required in comparisons involving a key path that traverse a `RLMArray`/`List` property. Previously they were only required if the first key in the key path was an `RLMArray`/`List` property. * Fix several scenarios where the default schema would be initialized incorrectly if the first Realm opened used a restricted class subset (via `objectClasses`/`objectTypes`). 0.98.1 Release notes (2016-02-10) ============================================================= ### Bugfixes * Fix crashes when deleting an object containing an `RLMArray`/`List` which had previously been queried. * Fix a crash when deleting an object containing an `RLMArray`/`List` with active notification blocks. * Fix duplicate file warnings when building via CocoaPods. * Fix crash or incorrect results when calling `indexOfObject:` on an `RLMResults` derived from an `RLMArray`. 0.98.0 Release notes (2016-02-04) ============================================================= ### API breaking changes * `+[RLMRealm realmWithPath:]`/`Realm.init(path:)` now inherits from the default configuration. * Swift 1.2 is no longer supported. ### Enhancements * Add `addNotificationBlock` to `RLMResults`, `Results`, `RLMArray`, and `List`, which calls the given block whenever the collection changes. * Do a lot of the work for keeping `RLMResults`/`Results` up-to-date after write transactions on a background thread to help avoid blocking the main thread. * `NSPredicate`'s `SUBQUERY` operator is now supported. It has the following limitations: * `@count` is the only operator that may be applied to the `SUBQUERY` expression. * The `SUBQUERY(…).@count` expression must be compared with a constant. * Correlated subqueries are not yet supported. ### Bugfixes * None. 0.97.1 Release notes (2016-01-29) ============================================================= ### API breaking changes * None. ### Enhancements * Swift: Added `Error` enum allowing to catch errors e.g. thrown on initializing `RLMRealm`/`Realm` instances. * Fail with `RLMErrorFileNotFound` instead of the more generic `RLMErrorFileAccess`, if no file was found when a realm was opened as read-only or if the directory part of the specified path was not found when a copy should be written. * Greatly improve performance when deleting objects with one or more indexed properties. * Indexing `BOOL`/`Bool` and `NSDate` properties are now supported. * Swift: Add support for indexing optional properties. ### Bugfixes * Fix incorrect results or crashes when using `-[RLMResults setValue:forKey:]` on an RLMResults which was filtered on the key being set. * Fix crashes when an RLMRealm is deallocated from the wrong thread. * Fix incorrect results from aggregate methods on `Results`/`RLMResults` after objects which were previously in the results are deleted. * Fix a crash when adding a new property to an existing class with over a million objects in the Realm. * Fix errors when opening encrypted Realm files created with writeCopyToPath. * Fix crashes or incorrect results for queries that use relationship equality in cases where the `RLMResults` is kept alive and instances of the target class of the relationship are deleted. 0.97.0 Release notes (2015-12-17) ============================================================= ### API breaking changes * All functionality deprecated in previous releases has been removed entirely. * Add generic type annotations to NSArrays and NSDictionaries in public APIs. * Adding a Realm notification block on a thread not currently running from within a run loop throws an exception rather than silently never calling the notification block. ### Enhancements * Support for tvOS. * Support for building Realm Swift from source when using Carthage. * The block parameter of `-[RLMRealm transactionWithBlock:]`/`Realm.write(_:)` is now marked as `__attribute__((noescape))`/`@noescape`. * Many forms of queries with key paths on both sides of the comparison operator are now supported. * Add support for KVC collection operators in `RLMResults` and `RLMArray`. * Fail instead of deadlocking in `+[RLMRealm sharedSchema]`, if a Swift property is initialized to a computed value, which attempts to open a Realm on its own. ### Bugfixes * Fix poor performance when calling `-[RLMRealm deleteObjects:]` on an `RLMResults` which filtered the objects when there are other classes linking to the type of the deleted objects. * An exception is now thrown when defining `Object` properties of an unsupported type. 0.96.3 Release notes (2015-12-04) ============================================================= ### Enhancements * Queries are no longer limited to 16 levels of grouping. * Rework the implementation of encrypted Realms to no longer interfere with debuggers. ### Bugfixes * Fix crash when trying to retrieve object instances via `dynamicObjects`. * Throw an exception when querying on a link providing objects, which are from a different Realm. * Return empty results when querying on a link providing an unattached object. * Fix crashes or incorrect results when calling `-[RLMRealm refresh]` during fast enumeration. * Add `Int8` support for `RealmOptional`, `MinMaxType` and `AddableType`. * Set the default value for newly added non-optional NSData properties to a zero-byte NSData rather than nil. * Fix a potential crash when deleting all objects of a class. * Fix performance problems when creating large numbers of objects with `RLMArray`/`List` properties. * Fix memory leak when using Object(value:) for subclasses with `List` or `RealmOptional` properties. * Fix a crash when computing the average of an optional integer property. * Fix incorrect search results for some queries on integer properties. * Add error-checking for nil realm parameters in many methods such as `+[RLMObject allObjectsInRealm:]`. * Fix a race condition between commits and opening Realm files on new threads that could lead to a crash. * Fix several crashes when opening Realm files. * `-[RLMObject createInRealm:withValue:]`, `-[RLMObject createOrUpdateInRealm:withValue:]`, and their variants for the default Realm now always match the contents of an `NSArray` against properties in the same order as they are defined in the model. 0.96.2 Release notes (2015-10-26) ============================================================= Prebuilt frameworks are now built with Xcode 7.1. ### Bugfixes * Fix ignoring optional properties in Swift. * Fix CocoaPods installation on case-sensitive file systems. 0.96.1 Release notes (2015-10-20) ============================================================= ### Bugfixes * Support assigning `Results` to `List` properties via KVC. * Honor the schema version set in the configuration in `+[RLMRealm migrateRealm:]`. * Fix crash when using optional Int16/Int32/Int64 properties in Swift. 0.96.0 Release notes (2015-10-14) ============================================================= * No functional changes since beta2. 0.96.0-beta2 Release notes (2015-10-08) ============================================================= ### Bugfixes * Add RLMOptionalBase.h to the podspec. 0.96.0-beta Release notes (2015-10-07) ============================================================= ### API breaking changes * CocoaPods v0.38 or greater is now required to install Realm and RealmSwift as pods. ### Enhancements * Functionality common to both `List` and `Results` is now declared in a `RealmCollectionType` protocol that both types conform to. * `Results.realm` now returns an `Optional` in order to conform to `RealmCollectionType`, but will always return `.Some()` since a `Results` cannot exist independently from a `Realm`. * Aggregate operations are now available on `List`: `min`, `max`, `sum`, `average`. * Committing write transactions (via `commitWrite` / `commitWriteTransaction` and `write` / `transactionWithBlock`) now optionally allow for handling errors when the disk is out of space. * Added `isEmpty` property on `RLMRealm`/`Realm` to indicate if it contains any objects. * The `@count`, `@min`, `@max`, `@sum` and `@avg` collection operators are now supported in queries. ### Bugfixes * Fix assertion failure when inserting NSData between 8MB and 16MB in size. * Fix assertion failure when rolling back a migration which removed an object link or `RLMArray`/`List` property. * Add the path of the file being opened to file open errors. * Fix a crash that could be triggered by rapidly opening and closing a Realm many times on multiple threads at once. * Fix several places where exception messages included the name of the wrong function which failed. 0.95.3 Release notes (2015-10-05) ============================================================= ### Bugfixes * Compile iOS Simulator framework architectures with `-fembed-bitcode-marker`. * Fix crashes when the first Realm opened uses a class subset and later Realms opened do not. * Fix inconsistent errors when `Object(value: ...)` is used to initialize the default value of a property of an `Object` subclass. * Throw an exception when a class subset has objects with array or object properties of a type that are not part of the class subset. 0.95.2 Release notes (2015-09-24) ============================================================= * Enable bitcode for iOS and watchOS frameworks. * Build libraries with Xcode 7 final rather than the GM. 0.95.1 Release notes (2015-09-23) ============================================================= ### Enhancements * Add missing KVO handling for moving and exchanging objects in `RLMArray` and `List`. ### Bugfixes * Setting the primary key property on persisted `RLMObject`s / `Object`s via subscripting or key-value coding will cause an exception to be thrown. * Fix crash due to race condition in `RLMRealmConfiguration` where the default configuration was in the process of being copied in one thread, while released in another. * Fix crash when a migration which removed an object or array property is rolled back due to an error. 0.95.0 Release notes (2015-08-25) ============================================================= ### API breaking changes * The following APIs have been deprecated in favor of the new `RLMRealmConfiguration` class in Realm Objective-C: | Deprecated API | New API | |:------------------------------------------------------------------|:---------------------------------------------------------------------------------| | `+[RLMRealm realmWithPath:readOnly:error:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm realmWithPath:encryptionKey:readOnly:error:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm setEncryptionKey:forRealmsAtPath:]` | `-[RLMRealmConfiguration setEncryptionKey:]` | | `+[RLMRealm inMemoryRealmWithIdentifier:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm defaultRealmPath]` | `+[RLMRealmConfiguration defaultConfiguration]` | | `+[RLMRealm setDefaultRealmPath:]` | `+[RLMRealmConfiguration setDefaultConfiguration:]` | | `+[RLMRealm setDefaultRealmSchemaVersion:withMigrationBlock]` | `RLMRealmConfiguration.schemaVersion` and `RLMRealmConfiguration.migrationBlock` | | `+[RLMRealm setSchemaVersion:forRealmAtPath:withMigrationBlock:]` | `RLMRealmConfiguration.schemaVersion` and `RLMRealmConfiguration.migrationBlock` | | `+[RLMRealm migrateRealmAtPath:]` | `+[RLMRealm migrateRealm:]` | | `+[RLMRealm migrateRealmAtPath:encryptionKey:]` | `+[RLMRealm migrateRealm:]` | * The following APIs have been deprecated in favor of the new `Realm.Configuration` struct in Realm Swift for Swift 1.2: | Deprecated API | New API | |:--------------------------------------------------------------|:-----------------------------------------------------------------------------| | `Realm.defaultPath` | `Realm.Configuration.defaultConfiguration` | | `Realm(path:readOnly:encryptionKey:error:)` | `Realm(configuration:error:)` | | `Realm(inMemoryIdentifier:)` | `Realm(configuration:error:)` | | `Realm.setEncryptionKey(:forPath:)` | `Realm(configuration:error:)` | | `setDefaultRealmSchemaVersion(schemaVersion:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `setSchemaVersion(schemaVersion:realmPath:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `migrateRealm(path:encryptionKey:)` | `migrateRealm(configuration:)` | * The following APIs have been deprecated in favor of the new `Realm.Configuration` struct in Realm Swift for Swift 2.0: | Deprecated API | New API | |:--------------------------------------------------------------|:-----------------------------------------------------------------------------| | `Realm.defaultPath` | `Realm.Configuration.defaultConfiguration` | | `Realm(path:readOnly:encryptionKey:) throws` | `Realm(configuration:) throws` | | `Realm(inMemoryIdentifier:)` | `Realm(configuration:) throws` | | `Realm.setEncryptionKey(:forPath:)` | `Realm(configuration:) throws` | | `setDefaultRealmSchemaVersion(schemaVersion:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `setSchemaVersion(schemaVersion:realmPath:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `migrateRealm(path:encryptionKey:)` | `migrateRealm(configuration:)` | * `List.extend` in Realm Swift for Swift 2.0 has been replaced with `List.appendContentsOf`, mirroring changes to `RangeReplaceableCollectionType`. * Object properties on `Object` subclasses in Realm Swift must be marked as optional, otherwise a runtime exception will be thrown. ### Enhancements * Persisted properties of `RLMObject`/`Object` subclasses are now Key-Value Observing compliant. * The different options used to create Realm instances have been consolidated into a single `RLMRealmConfiguration`/`Realm.Configuration` object. * Enumerating Realm collections (`RLMArray`, `RLMResults`, `List<>`, `Results<>`) now enumerates over a copy of the collection, making it no longer an error to modify a collection during enumeration (either directly, or indirectly by modifying objects to make them no longer match a query). * Improve performance of object insertion in Swift to bring it roughly in line with Objective-C. * Allow specifying a specific list of `RLMObject` / `Object` subclasses to include in a given Realm via `RLMRealmConfiguration.objectClasses` / `Realm.Configuration.objectTypes`. ### Bugfixes * Subscripting on `RLMObject` is now marked as nullable. 0.94.1 Release notes (2015-08-10) ============================================================= ### API breaking changes * Building for watchOS requires Xcode 7 beta 5. ### Enhancements * `Object.className` is now marked as `final`. ### Bugfixes * Fix crash when adding a property to a model without updating the schema version. * Fix unnecessary redownloading of the core library when building from source. * Fix crash when sorting by an integer or floating-point property on iOS 7. 0.94.0 Release notes (2015-07-29) ============================================================= ### API breaking changes * None. ### Enhancements * Reduce the amount of memory used by RLMRealm notification listener threads. * Avoid evaluating results eagerly when filtering and sorting. * Add nullability annotations to the Objective-C API to provide enhanced compiler warnings and bridging to Swift. * Make `RLMResult`s and `RLMArray`s support Objective-C generics. * Add support for building watchOS and bitcode-compatible apps. * Make the exceptions thrown in getters and setters more informative. * Add `-[RLMArray exchangeObjectAtIndex:withObjectAtIndex]` and `List.swap(_:_:)` to allow exchanging the location of two objects in the given `RLMArray` / `List`. * Added anonymous analytics on simulator/debugger runs. * Add `-[RLMArray moveObjectAtIndex:toIndex:]` and `List.move(from:to:)` to allow moving objects in the given `RLMArray` / `List`. ### Bugfixes * Processes crashing due to an uncaught exception inside a write transaction will no longer cause other processes using the same Realm to hang indefinitely. * Fix incorrect results when querying for < or <= on ints that require 64 bits to represent with a CPU that supports SSE 4.2. * An exception will no longer be thrown when attempting to reset the schema version or encryption key on an open Realm to the current value. * Date properties on 32 bit devices will retain 64 bit second precision. * Wrap calls to the block passed to `enumerate` in an autoreleasepool to reduce memory growth when migrating a large amount of objects. * In-memory realms no longer write to the Documents directory on iOS or Application Support on OS X. 0.93.2 Release notes (2015-06-12) ============================================================= ### Bugfixes * Fixed an issue where the packaged OS X Realm.framework was built with `GCC_GENERATE_TEST_COVERAGE_FILES` and `GCC_INSTRUMENT_PROGRAM_FLOW_ARCS` enabled. * Fix a memory leak when constructing standalone Swift objects with NSDate properties. * Throw an exception rather than asserting when an invalidated object is added to an RLMArray. * Fix a case where data loss would occur if a device was hard-powered-off shortly after a write transaction was committed which had to expand the Realm file. 0.93.1 Release notes (2015-05-29) ============================================================= ### Bugfixes * Objects are no longer copied into standalone objects during object creation. This fixes an issue where nested objects with a primary key are sometimes duplicated rather than updated. * Comparison predicates with a constant on the left of the operator and key path on the right now give correct results. An exception is now thrown for predicates that do not yet support this ordering. * Fix some crashes in `index_string.cpp` with int primary keys or indexed int properties. 0.93.0 Release notes (2015-05-27) ============================================================= ### API breaking changes * Schema versions are now represented as `uint64_t` (Objective-C) and `UInt64` (Swift) so that they have the same representation on all architectures. ### Enhancements * Swift: `Results` now conforms to `CVarArgType` so it can now be passed as an argument to `Results.filter(_:...)` and `List.filter(_:...)`. * Swift: Made `SortDescriptor` conform to the `Equatable` and `StringLiteralConvertible` protocols. * Int primary keys are once again automatically indexed. * Improve error reporting when attempting to mark a property of a type that cannot be indexed as indexed. ### Bugfixes * Swift: `RealmSwift.framework` no longer embeds `Realm.framework`, which now allows apps using it to pass iTunes Connect validation. 0.92.4 Release notes (2015-05-22) ============================================================= ### API breaking changes * None. ### Enhancements * Swift: Made `Object.init()` a required initializer. * `RLMObject`, `RLMResults`, `Object` and `Results` can now be safely deallocated (but still not used) from any thread. * Improve performance of `-[RLMArray indexOfObjectWhere:]` and `-[RLMArray indexOfObjectWithPredicate:]`, and implement them for standalone RLMArrays. * Improved performance of most simple queries. ### Bugfixes * The interprocess notification mechanism no longer uses dispatch worker threads, preventing it from starving other GCD clients of the opportunity to execute blocks when dozens of Realms are open at once. 0.92.3 Release notes (2015-05-13) ============================================================= ### API breaking changes * Swift: `Results.average(_:)` now returns an optional, which is `nil` if and only if the results set is empty. ### Enhancements * Swift: Added `List.invalidated`, which returns if the given `List` is no longer safe to be accessed, and is analogous to `-[RLMArray isInvalidated]`. * Assertion messages are automatically logged to Crashlytics if it's loaded into the current process to make it easier to diagnose crashes. ### Bugfixes * Swift: Enumerating through a standalone `List` whose objects themselves have list properties won't crash. * Swift: Using a subclass of `RealmSwift.Object` in an aggregate operator of a predicate no longer throws a spurious type error. * Fix incorrect results for when using OR in a query on a `RLMArray`/`List<>`. * Fix incorrect values from `[RLMResults count]`/`Results.count` when using `!=` on an int property with no other query conditions. * Lower the maximum doubling threshold for Realm file sizes from 128MB to 16MB to reduce the amount of wasted space. 0.92.2 Release notes (2015-05-08) ============================================================= ### API breaking changes * None. ### Enhancements * Exceptions raised when incorrect object types are used with predicates now contain more detailed information. * Added `-[RLMMigration deleteDataForClassName:]` and `Migration.deleteData(_:)` to enable cleaning up after removing object subclasses ### Bugfixes * Prevent debugging of an application using an encrypted Realm to work around frequent LLDB hangs. Until the underlying issue is addressed you may set REALM_DISABLE_ENCRYPTION=YES in your application's environment variables to have requests to open an encrypted Realm treated as a request for an unencrypted Realm. * Linked objects are properly updated in `createOrUpdateInRealm:withValue:`. * List properties on Objects are now properly initialized during fast enumeration. 0.92.1 Release notes (2015-05-06) ============================================================= ### API breaking changes * None. ### Enhancements * `-[RLMRealm inWriteTransaction]` is now public. * Realm Swift is now available on CoocaPods. ### Bugfixes * Force code re-signing after stripping architectures in `strip-frameworks.sh`. 0.92.0 Release notes (2015-05-05) ============================================================= ### API breaking changes * Migration blocks are no longer called when a Realm file is first created. * The following APIs have been deprecated in favor of newer method names: | Deprecated API | New API | |:-------------------------------------------------------|:------------------------------------------------------| | `-[RLMMigration createObject:withObject:]` | `-[RLMMigration createObject:withValue:]` | | `-[RLMObject initWithObject:]` | `-[RLMObject initWithValue:]` | | `+[RLMObject createInDefaultRealmWithObject:]` | `+[RLMObject createInDefaultRealmWithValue:]` | | `+[RLMObject createInRealm:withObject:]` | `+[RLMObject createInRealm:withValue:]` | | `+[RLMObject createOrUpdateInDefaultRealmWithObject:]` | `+[RLMObject createOrUpdateInDefaultRealmWithValue:]` | | `+[RLMObject createOrUpdateInRealm:withObject:]` | `+[RLMObject createOrUpdateInRealm:withValue:]` | ### Enhancements * `Int8` properties defined in Swift are now treated as integers, rather than booleans. * NSPredicates created using `+predicateWithValue:` are now supported. ### Bugfixes * Compound AND predicates with no subpredicates now correctly match all objects. 0.91.5 Release notes (2015-04-28) ============================================================= ### Bugfixes * Fix issues with removing search indexes and re-enable it. 0.91.4 Release notes (2015-04-27) ============================================================= ### Bugfixes * Temporarily disable removing indexes from existing columns due to bugs. 0.91.3 Release notes (2015-04-17) ============================================================= ### Bugfixes * Fix `Extra argument 'objectClassName' in call` errors when building via CocoaPods. 0.91.2 Release notes (2015-04-16) ============================================================= * Migration blocks are no longer called when a Realm file is first created. ### Enhancements * `RLMCollection` supports collection KVC operations. * Sorting `RLMResults` is 2-5x faster (typically closer to 2x). * Refreshing `RLMRealm` after a write transaction which inserts or modifies strings or `NSData` is committed on another thread is significantly faster. * Indexes are now added and removed from existing properties when a Realm file is opened, rather than only when properties are first added. ### Bugfixes * `+[RLMSchema dynamicSchemaForRealm:]` now respects search indexes. * `+[RLMProperty isEqualToProperty:]` now checks for equal `indexed` properties. 0.91.1 Release notes (2015-03-12) ============================================================= ### Enhancements * The browser will automatically refresh when the Realm has been modified from another process. * Allow using Realm in an embedded framework by setting `APPLICATION_EXTENSION_API_ONLY` to YES. ### Bugfixes * Fix a crash in CFRunLoopSourceInvalidate. 0.91.0 Release notes (2015-03-10) ============================================================= ### API breaking changes * `attributesForProperty:` has been removed from `RLMObject`. You now specify indexed properties by implementing the `indexedProperties` method. * An exception will be thrown when calling `setEncryptionKey:forRealmsAtPath:`, `setSchemaVersion:forRealmAtPath:withMigrationBlock:`, and `migrateRealmAtPath:` when a Realm at the given path is already open. * Object and array properties of type `RLMObject` will no longer be allowed. ### Enhancements * Add support for sharing Realm files between processes. * The browser will no longer show objects that have no persisted properties. * `RLMSchema`, `RLMObjectSchema`, and `RLMProperty` now have more useful descriptions. * Opening an encrypted Realm while a debugger is attached to the process no longer throws an exception. * `RLMArray` now exposes an `isInvalidated` property to indicate if it can no longer be accessed. ### Bugfixes * An exception will now be thrown when calling `-beginWriteTransaction` from within a notification triggered by calling `-beginWriteTransaction` elsewhere. * When calling `delete:` we now verify that the object being deleted is persisted in the target Realm. * Fix crash when calling `createOrUpdate:inRealm` with nested linked objects. * Use the key from `+[RLMRealm setEncryptionKey:forRealmsAtPath:]` in `-writeCopyToPath:error:` and `+migrateRealmAtPath:`. * Comparing an RLMObject to a non-RLMObject using `-[RLMObject isEqual:]` or `-isEqualToObject:` now returns NO instead of crashing. * Improved error message when an `RLMObject` subclass is defined nested within another Swift declaration. * Fix crash when the process is terminated by the OS on iOS while encrypted realms are open. * Fix crash after large commits to encrypted realms. 0.90.6 Release notes (2015-02-20) ============================================================= ### Enhancements * Improve compatibility of encrypted Realms with third-party crash reporters. ### Bugfixes * Fix incorrect results when using aggregate functions on sorted RLMResults. * Fix data corruption when using writeCopyToPath:encryptionKey:. * Maybe fix some assertion failures. 0.90.5 Release notes (2015-02-04) ============================================================= ### Bugfixes * Fix for crashes when encryption is enabled on 64-bit iOS devices. 0.90.4 Release notes (2015-01-29) ============================================================= ### Bugfixes * Fix bug that resulted in columns being dropped and recreated during migrations. 0.90.3 Release notes (2015-01-27) ============================================================= ### Enhancements * Calling `createInDefaultRealmWithObject:`, `createInRealm:withObject:`, `createOrUpdateInDefaultRealmWithObject:` or `createOrUpdateInRealm:withObject:` is a no-op if the argument is an RLMObject of the same type as the receiver and is already backed by the target realm. ### Bugfixes * Fix incorrect column type assertions when the first Realm file opened is a read-only file that is missing tables. * Throw an exception when adding an invalidated or deleted object as a link. * Throw an exception when calling `createOrUpdateInRealm:withObject:` when the receiver has no primary key defined. 0.90.1 Release notes (2015-01-22) ============================================================= ### Bugfixes * Fix for RLMObject being treated as a model object class and showing up in the browser. * Fix compilation from the podspec. * Fix for crash when calling `objectsWhere:` with grouping in the query on `allObjects`. 0.90.0 Release notes (2015-01-21) ============================================================= ### API breaking changes * Rename `-[RLMRealm encryptedRealmWithPath:key:readOnly:error:]` to `-[RLMRealm realmWithPath:encryptionKey:readOnly:error:]`. * `-[RLMRealm setSchemaVersion:withMigrationBlock]` is no longer global and must be called for each individual Realm path used. You can now call `-[RLMRealm setDefaultRealmSchemaVersion:withMigrationBlock]` for the default Realm and `-[RLMRealm setSchemaVersion:forRealmAtPath:withMigrationBlock:]` for all others; ### Enhancements * Add `-[RLMRealm writeCopyToPath:encryptionKey:error:]`. * Add support for comparing string columns to other string columns in queries. ### Bugfixes * Roll back changes made when an exception is thrown during a migration. * Throw an exception if the number of items in a RLMResults or RLMArray changes while it's being fast-enumerated. * Also encrypt the temporary files used when encryption is enabled for a Realm. * Fixed crash in JSONImport example on OS X with non-en_US locale. * Fixed infinite loop when opening a Realm file in the Browser at the same time as it is open in a 32-bit simulator. * Fixed a crash when adding primary keys to older realm files with no primary keys on any objects. * Fixed a crash when removing a primary key in a migration. * Fixed a crash when multiple write transactions with no changes followed by a write transaction with changes were committed without the main thread RLMRealm getting a chance to refresh. * Fixed incomplete results when querying for non-null relationships. * Improve the error message when a Realm file is opened in multiple processes at once. 0.89.2 Release notes (2015-01-02) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix an assertion failure when invalidating a Realm which is in a write transaction, has already been invalidated, or has never been used. * Fix an assertion failure when sorting an empty RLMArray property. * Fix a bug resulting in the browser never becoming visible on 10.9. * Write UTF-8 when generating class files from a realm file in the Browser. 0.89.1 Release notes (2014-12-22) ============================================================= ### API breaking changes * None. ### Enhancements * Improve the error message when a Realm can't be opened due to lacking write permissions. ### Bugfixes * Fix an assertion failure when inserting rows after calling `deleteAllObjects` on a Realm. * Separate dynamic frameworks are now built for the simulator and devices to work around App Store submission errors due to the simulator version not being automatically stripped from dynamic libraries. 0.89.0 Release notes (2014-12-18) ============================================================= ### API breaking changes * None. ### Enhancements * Add support for encrypting Realm files on disk. * Support using KVC-compliant objects without getters or with custom getter names to initialize RLMObjects with `createObjectInRealm` and friends. ### Bugfixes * Merge native Swift default property values with defaultPropertyValues(). * Don't leave the database schema partially updated when opening a realm fails due to a migration being needed. * Fixed issue where objects with custom getter names couldn't be used to initialize other objects. * Fix a major performance regression on queries on string properties. * Fix a memory leak when circularly linked objects are added to a Realm. 0.88.0 Release notes (2014-12-02) ============================================================= ### API breaking changes * Deallocating an RLMRealm instance in a write transaction lacking an explicit commit/cancel will now be automatically cancelled instead of committed. * `-[RLMObject isDeletedFromRealm]` has been renamed to `-[RLMObject isInvalidated]`. ### Enhancements * Add `-[RLMRealm writeCopyToPath:]` to write a compacted copy of the Realm another file. * Add support for case insensitive, BEGINSWITH, ENDSWITH and CONTAINS string queries on array properties. * Make fast enumeration of `RLMArray` and `RLMResults` ~30% faster and `objectAtIndex:` ~55% faster. * Added a lldb visualizer script for displaying the contents of persisted RLMObjects when debugging. * Added method `-setDefaultRealmPath:` to change the default Realm path. * Add `-[RLMRealm invalidate]` to release data locked by the current thread. ### Bugfixes * Fix for crash when running many simultaneous write transactions on background threads. * Fix for crashes caused by opening Realms at multiple paths simultaneously which have had properties re-ordered during migration. * Don't run the query twice when `firstObject` or `lastObject` are called on an `RLMResults` which has not had its results accessed already. * Fix for bug where schema version is 0 for new Realm created at the latest version. * Fix for error message where no migration block is specified when required. 0.87.4 Release notes (2014-11-07) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix browser location in release zip. 0.87.3 Release notes (2014-11-06) ============================================================= ### API breaking changes * None. ### Enhancements * Added method `-linkingObjectsOfClass:forProperty:` to RLMObject to expose inverse relationships/backlinks. ### Bugfixes * Fix for crash due to missing search index when migrating an object with a string primary key in a database created using an older versions (0.86.3 and earlier). * Throw an exception when passing an array containing a non-RLMObject to -[RLMRealm addObjects:]. * Fix for crash when deleting an object from multiple threads. 0.87.0 Release notes (2014-10-21) ============================================================= ### API breaking changes * RLMArray has been split into two classes, `RLMArray` and `RLMResults`. RLMArray is used for object properties as in previous releases. Moving forward all methods used to enumerate, query, and sort objects return an instance of a new class `RLMResults`. This change was made to support diverging apis and the future addition of change notifications for queries. * The api for migrations has changed. You now call `setSchemaVersion:withMigrationBlock:` to register a global migration block and associated version. This block is applied to Realms as needed when opened for Realms at a previous version. The block can be applied manually if desired by calling `migrateRealmAtPath:`. * `arraySortedByProperty:ascending:` was renamed to `sortedResultsUsingProperty:ascending` * `addObjectsFromArray:` on both `RLMRealm` and `RLMArray` has been renamed to `addObjects:` and now accepts any container class which implements `NSFastEnumeration` * Building with Swift support now requires Xcode 6.1 ### Enhancements * Add support for sorting `RLMArray`s by multiple columns with `sortedResultsUsingDescriptors:` * Added method `deleteAllObjects` on `RLMRealm` to clear a Realm. * Added method `createObject:withObject:` on `RLMMigration` which allows object creation during migrations. * Added method `deleteObject:` on `RLMMigration` which allows object deletion during migrations. * Updating to core library version 0.85.0. * Implement `objectsWhere:` and `objectsWithPredicate:` for array properties. * Add `cancelWriteTransaction` to revert all changes made in a write transaction and end the transaction. * Make creating `RLMRealm` instances on background threads when an instance exists on another thread take a fifth of the time. * Support for partial updates when calling `createOrUpdateWithObject:` and `addOrUpdateObject:` * Re-enable Swift support on OS X ### Bugfixes * Fix exceptions when trying to set `RLMObject` properties after rearranging the properties in a `RLMObject` subclass. * Fix crash on IN query with several thousand items. * Fix crash when querying indexed `NSString` properties. * Fixed an issue which prevented in-memory Realms from being used across multiple threads. * Preserve the sort order when querying a sorted `RLMResults`. * Fixed an issue with migrations where if a Realm file is deleted after a Realm is initialized, the newly created Realm can be initialized with an incorrect schema version. * Fix crash in `RLMSuperSet` when assigning to a `RLMArray` property on a standalone object. * Add an error message when the protocol for an `RLMArray` property is not a valid object type. * Add an error message when an `RLMObject` subclass is defined nested within another Swift class. 0.86.3 Release notes (2014-10-09) ============================================================= ### Enhancements * Add support for != in queries on object relationships. ### Bugfixes * Re-adding an object to its Realm no longer throws an exception and is now a no-op (as it was previously). * Fix another bug which would sometimes result in subclassing RLMObject subclasses not working. 0.86.2 Release notes (2014-10-06) ============================================================= ### Bugfixes * Fixed issues with packaging "Realm Browser.app" for release. 0.86.1 Release notes (2014-10-03) ============================================================= ### Bugfixes * Fix a bug which would sometimes result in subclassing RLMObject subclasses not working. 0.86.0 Release notes (2014-10-03) ============================================================= ### API breaking changes * Xcode 6 is now supported from the main Xcode project `Realm.xcodeproj`. Xcode 5 is no longer supported. ### Enhancements * Support subclassing RLMObject models. Although you can now persist subclasses, polymorphic behavior is not supported (i.e. setting a property to an instance of its subclass). * Add support for sorting RLMArray properties. * Speed up inserting objects with `addObject:` by ~20%. * `readonly` properties are automatically ignored rather than having to be added to `ignoredProperties`. * Updating to core library version 0.83.1. * Return "[deleted object]" rather than throwing an exception when `-description` is called on a deleted RLMObject. * Significantly improve performance of very large queries. * Allow passing any enumerable to IN clauses rather than just NSArray. * Add `objectForPrimaryKey:` and `objectInRealm:forPrimaryKey:` convenience methods to fetch an object by primary key. ### Bugfixes * Fix error about not being able to persist property 'hash' with incompatible type when building for devices with Xcode 6. * Fix spurious notifications of new versions of Realm. * Fix for updating nested objects where some types do not have primary keys. * Fix for inserting objects from JSON with NSNull values when default values should be used. * Trying to add a persisted RLMObject to a different Realm now throws an exception rather than creating an uninitialized object. * Fix validation errors when using IN on array properties. * Fix errors when an IN clause has zero items. * Fix for chained queries ignoring all but the last query's conditions. 0.85.0 Release notes (2014-09-15) ============================================================= ### API breaking changes * Notifications for a refresh being needed (when autorefresh is off) now send the notification type RLMRealmRefreshRequiredNotification rather than RLMRealmDidChangeNotification. ### Enhancements * Updating to core library version 0.83.0. * Support for primary key properties (for int and string columns). Declaring a property to be the primary key ensures uniqueness for that property for all objects of a given type. At the moment indexes on primary keys are not yet supported but this will be added in a future release. * Added methods to update or insert (upsert) for objects with primary keys defined. * `[RLMObject initWithObject:]` and `[RLMObject createInRealmWithObject:]` now support any object type with kvc properties. * The Swift support has been reworked to work around Swift not being supported in Frameworks on iOS 7. * Improve performance when getting the count of items matching a query but not reading any of the objects in the results. * Add a return value to `-[RLMRealm refresh]` that indicates whether or not there was anything to refresh. * Add the class name to the error message when an RLMObject is missing a value for a property without a default. * Add support for opening Realms in read-only mode. * Add an automatic check for updates when using Realm in a simulator (the checker code is not compiled into device builds). This can be disabled by setting the REALM_DISABLE_UPDATE_CHECKER environment variable to any value. * Add support for Int16 and Int64 properties in Swift classes. ### Bugfixes * Realm change notifications when beginning a write transaction are now sent after updating rather than before, to match refresh. * `-isEqual:` now uses the default `NSObject` implementation unless a primary key is specified for an RLMObject. When a primary key is specified, `-isEqual:` calls `-isEqualToObject:` and a corresponding implementation for `-hash` is also implemented. 0.84.0 Release notes (2014-08-28) ============================================================= ### API breaking changes * The timer used to trigger notifications has been removed. Notifications are now only triggered by commits made in other threads, and can not currently be triggered by changes made by other processes. Interprocess notifications will be re-added in a future commit with an improved design. ### Enhancements * Updating to core library version 0.82.2. * Add property `deletedFromRealm` to RLMObject to indicate objects which have been deleted. * Add support for the IN operator in predicates. * Add support for the BETWEEN operator in link queries. * Add support for multi-level link queries in predicates (e.g. `foo.bar.baz = 5`). * Switch to building the SDK from source when using CocoaPods and add a Realm.Headers subspec for use in targets that should not link a copy of Realm (such as test targets). * Allow unregistering from change notifications in the change notification handler block. * Significant performance improvements when holding onto large numbers of RLMObjects. * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta6. * Improved performance during RLMArray iteration, especially when mutating contained objects. ### Bugfixes * Fix crashes and assorted bugs when sorting or querying a RLMArray returned from a query. * Notifications are no longer sent when initializing new RLMRealm instances on background threads. * Handle object cycles in -[RLMObject description] and -[RLMArray description]. * Lowered the deployment target for the Xcode 6 projects and Swift examples to iOS 7.0, as they didn't actually require 8.0. * Support setting model properties starting with the letter 'z' * Fixed crashes that could result from switching between Debug and Relase builds of Realm. 0.83.0 Release notes (2014-08-13) ============================================================= ### API breaking changes * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta5. * Properties to be persisted in Swift classes must be explicitly declared as `dynamic`. * Subclasses of RLMObject subclasses now throw an exception on startup, rather than when added to a Realm. ### Enhancements * Add support for querying for nil object properties. * Improve error message when specifying invalid literals when creating or initializing RLMObjects. * Throw an exception when an RLMObject is used from the incorrect thread rather than crashing in confusing ways. * Speed up RLMRealm instantiation and array property iteration. * Allow array and objection relation properties to be missing or null when creating a RLMObject from a NSDictionary. ### Bugfixes * Fixed a memory leak when querying for objects. * Fixed initializing array properties on standalone Swift RLMObject subclasses. * Fix for queries on 64bit integers. 0.82.0 Release notes (2014-08-05) ============================================================= ### API breaking changes * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta4. ### Enhancements * Updating to core library version 0.80.5. * Now support disabling the `autorefresh` property on RLMRealm instances. * Building Realm-Xcode6 for iOS now builds a universal framework for Simulator & Device. * Using NSNumber properties (unsupported) now throws a more informative exception. * Added `[RLMRealm defaultRealmPath]` * Proper implementation for [RLMArray indexOfObjectWhere:] * The default Realm path on OS X is now ~/Library/Application Support/[bundle identifier]/default.realm rather than ~/Documents * We now check that the correct framework (ios or osx) is used at compile time. ### Bugfixes * Fixed rapid growth of the realm file size. * Fixed a bug which could cause a crash during RLMArray destruction after a query. * Fixed bug related to querying on float properties: `floatProperty = 1.7` now works. * Fixed potential bug related to the handling of array properties (RLMArray). * Fixed bug where array properties accessed the wrong property. * Fixed bug that prevented objects with custom getters to be added to a Realm. * Fixed a bug where initializing a standalone object with an array literal would trigger an exception. * Clarified exception messages when using unsupported NSPredicate operators. * Clarified exception messages when using unsupported property types on RLMObject subclasses. * Fixed a memory leak when breaking out of a for-in loop on RLMArray. * Fixed a memory leak when removing objects from a RLMArray property. * Fixed a memory leak when querying for objects. 0.81.0 Release notes (2014-07-22) ============================================================= ### API breaking changes * None. ### Enhancements * Updating to core library version 0.80.3. * Added support for basic querying of RLMObject and RLMArray properties (one-to-one and one-to-many relationships). e.g. `[Person objectsWhere:@"dog.name == 'Alfonso'"]` or `[Person objectsWhere:@"ANY dogs.name == 'Alfonso'"]` Supports all normal operators for numeric and date types. Does not support NSData properties or `BEGINSWITH`, `ENDSWITH`, `CONTAINS` and other options for string properties. * Added support for querying for object equality in RLMObject and RLMArray properties (one-to-one and one-to-many relationships). e.g. `[Person objectsWhere:@"dog == %@", myDog]` `[Person objectsWhere:@"ANY dogs == %@", myDog]` `[Person objectsWhere:@"ANY friends.dog == %@", dog]` Only supports comparing objects for equality (i.e. ==) * Added a helper method to RLMRealm to perform a block inside a transaction. * OSX framework now supported in CocoaPods. ### Bugfixes * Fixed Unicode support in property names and string contents (Chinese, Russian, etc.). Closing #612 and #604. * Fixed bugs related to migration when properties are removed. * Fixed keyed subscripting for standalone RLMObjects. * Fixed bug related to double clicking on a .realm file to launch the Realm Browser (thanks to Dean Moore). 0.80.0 Release notes (2014-07-15) ============================================================= ### API breaking changes * Rename migration methods to -migrateDefaultRealmWithBlock: and -migrateRealmAtPath:withBlock: * Moved Realm specific query methods from RLMRealm to class methods on RLMObject (-allObjects: to +allObjectsInRealm: ect.) ### Enhancements * Added +createInDefaultRealmWithObject: method to RLMObject. * Added support for array and object literals when calling -createWithObject: and -initWithObject: variants. * Added method -deleteObjects: to batch delete objects from a Realm * Support for defining RLMObject models entirely in Swift (experimental, see known issues). * RLMArrays in Swift support Sequence-style enumeration (for obj in array). * Implemented -indexOfObject: for RLMArray ### Known Issues for Swift-defined models * Properties other than String, NSData and NSDate require a default value in the model. This can be an empty (but typed) array for array properties. * The previous caveat also implies that not all models defined in Objective-C can be used for object properties. Only Objective-C models with only implicit (i.e. primitives) or explicit default values can be used. However, any Objective-C model object can be used in a Swift array property. * Array property accessors don't work until its parent object has been added to a realm. * Realm-Bridging-Header.h is temporarily exposed as a public header. This is temporary and will be private again once rdar://17633863 is fixed. * Does not leverage Swift generics and still uses RLM-prefix everywhere. This is coming in #549. 0.22.0 Release notes ============================================================= ### API breaking changes * Rename schemaForObject: to schemaForClassName: on RLMSchema * Removed -objects:where: and -objects:orderedBy:where: from RLMRealm * Removed -indexOfObjectWhere:, -objectsWhere: and -objectsOrderedBy:where: from RLMArray * Removed +objectsWhere: and +objectsOrderedBy:where: from RLMObject ### Enhancements * New Xcode 6 project for experimental swift support. * New Realm Editor app for reading and editing Realm db files. * Added support for migrations. * Added support for RLMArray properties on objects. * Added support for creating in-memory default Realm. * Added -objectsWithClassName:predicateFormat: and -objectsWithClassName:predicate: to RLMRealm * Added -indexOfObjectWithPredicateFormat:, -indexOfObjectWithPredicate:, -objectsWithPredicateFormat:, -objectsWithPredi * Added +objectsWithPredicateFormat: and +objectsWithPredicate: to RLMObject * Now allows predicates comparing two object properties of the same type. 0.20.0 Release notes (2014-05-28) ============================================================= Completely rewritten to be much more object oriented. ### API breaking changes * Everything ### Enhancements * None. ### Bugfixes * None. 0.11.0 Release notes (not released) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * `RLMTable` objects can only be created with an `RLMRealm` object. * Renamed `RLMContext` to `RLMTransactionManager` * Renamed `RLMContextDidChangeNotification` to `RLMRealmDidChangeNotification` * Renamed `contextWithDefaultPersistence` to `managerForDefaultRealm` * Renamed `contextPersistedAtPath:` to `managerForRealmWithPath:` * Renamed `realmWithDefaultPersistence` to `defaultRealm` * Renamed `realmWithDefaultPersistenceAndInitBlock` to `defaultRealmWithInitBlock` * Renamed `find:` to `firstWhere:` * Renamed `where:` to `allWhere:` * Renamed `where:orderBy:` to `allWhere:orderBy:` ### Enhancements * Added `countWhere:` on `RLMTable` * Added `sumOfColumn:where:` on `RLMTable` * Added `averageOfColumn:where:` on `RLMTable` * Added `minOfProperty:where:` on `RLMTable` * Added `maxOfProperty:where:` on `RLMTable` * Added `toJSONString` on `RLMRealm`, `RLMTable` and `RLMView` * Added support for `NOT` operator in predicates * Added support for default values * Added validation support in `createInRealm:withObject:` ### Bugfixes * None. 0.10.0 Release notes (2014-04-23) ============================================================= TightDB is now Realm! The Objective-C API has been updated and your code will break! ### API breaking changes * All references to TightDB have been changed to Realm. * All prefixes changed from `TDB` to `RLM`. * `TDBTransaction` and `TDBSmartContext` have merged into `RLMRealm`. * Write transactions now take an optional rollback parameter (rather than needing to return a boolean). * `addColumnWithName:` and variant methods now return the index of the newly created column if successful, `NSNotFound` otherwise. ### Enhancements * `createTableWithName:columns:` has been added to `RLMRealm`. * Added keyed subscripting for RLMTable's first column if column is of type RLMPropertyTypeString. * `setRow:atIndex:` has been added to `RLMTable`. * `RLMRealm` constructors now have variants that take an writable initialization block * New object interface - tables created/retrieved using `tableWithName:objectClass:` return custom objects ### Bugfixes * None. 0.6.0 Release notes (2014-04-11) ============================================================= ### API breaking changes * `contextWithPersistenceToFile:error:` renamed to `contextPersistedAtPath:error:` in `TDBContext` * `readWithBlock:` renamed to `readUsingBlock:` in `TDBContext` * `writeWithBlock:error:` renamed to `writeUsingBlock:error:` in `TDBContext` * `readTable:withBlock:` renamed to `readTable:usingBlock:` in `TDBContext` * `writeTable:withBlock:error:` renamed to `writeTable:usingBlock:error:` in `TDBContext` * `findFirstRow` renamed to `indexOfFirstMatchingRow` on `TDBQuery`. * `findFirstRowFromIndex:` renamed to `indexOfFirstMatchingRowFromIndex:` on `TDBQuery`. * Return `NSNotFound` instead of -1 when appropriate. * Renamed `castClass` to `castToTytpedTableClass` on `TDBTable`. * `removeAllRows`, `removeRowAtIndex`, `removeLastRow`, `addRow` and `insertRow` methods on table now return void instead of BOOL. ### Enhancements * A `TDBTable` can now be queried using `where:` and `where:orderBy:` taking `NSPredicate` and `NSSortDescriptor` as arguments. * Added `find:` method on `TDBTable` to find first row matching predicate. * `contextWithDefaultPersistence` class method added to `TDBContext`. Will create a context persisted to a file in app/documents folder. * `renameColumnWithIndex:to:` has been added to `TDBTable`. * `distinctValuesInColumnWithIndex` has been added to `TDBTable`. * `dateIsBetween::`, `doubleIsBetween::`, `floatIsBetween::` and `intIsBetween::` have been added to `TDBQuery`. * Column names in Typed Tables can begin with non-capital letters too. The generated `addX` selector can look odd. For example, a table with one column with name `age`, appending a new row will look like `[table addage:7]`. * Mixed typed values are better validated when rows are added, inserted, or modified as object literals. * `addRow`, `insertRow`, and row updates can be done using objects derived from `NSObject`. * `where` has been added to `TDBView`and `TDBViewProtocol`. * Adding support for "smart" contexts (`TDBSmartContext`). ### Bugfixes * Modifications of a `TDBView` and `TDBQuery` now throw an exception in a readtransaction. 0.5.0 Release notes (2014-04-02) ============================================================= The Objective-C API has been updated and your code will break! Of notable changes a fast interface has been added. This interface includes specific methods to get and set values into Tightdb. To use these methods import ``. ### API breaking changes * `getTableWithName:` renamed to `tableWithName:` in `TDBTransaction`. * `addColumnWithName:andType:` renamed to `addColumnWithName:type:` in `TDBTable`. * `columnTypeOfColumn:` renamed to `columnTypeOfColumnWithIndex` in `TDBTable`. * `columnNameOfColumn:` renamed to `nameOfColumnWithIndex:` in `TDBTable`. * `addColumnWithName:andType:` renamed to `addColumnWithName:type:` in `TDBDescriptor`. * Fast getters and setters moved from `TDBRow.h` to `TDBRowFast.h`. ### Enhancements * Added `minDateInColumnWithIndex` and `maxDateInColumnWithIndex` to `TDBQuery`. * Transactions can now be started directly on named tables. * You can create dynamic tables with initial schema. * `TDBTable` and `TDBView` now have a shared protocol so they can easier be used interchangeably. ### Bugfixes * Fixed bug in 64 bit iOS when inserting BOOL as NSNumber. 0.4.0 Release notes (2014-03-26) ============================================================= ### API breaking changes * Typed interface Cursor has now been renamed to Row. * TDBGroup has been renamed to TDBTransaction. * Header files are renamed so names match class names. * Underscore (_) removed from generated typed table classes. * TDBBinary has been removed; use NSData instead. * Underscope (_) removed from generated typed table classes. * Constructor for TDBContext has been renamed to contextWithPersistenceToFile: * Table findFirstRow and min/max/sum/avg operations has been hidden. * Table.appendRow has been renamed to addRow. * getOrCreateTable on Transaction has been removed. * set*:inColumnWithIndex:atRowIndex: methods have been prefixed with TDB * *:inColumnWithIndex:atRowIndex: methods have been prefixed with TDB * addEmptyRow on table has been removed. Use [table addRow:nil] instead. * TDBMixed removed. Use id and NSObject instead. * insertEmptyRow has been removed from table. Use insertRow:nil atIndex:index instead. #### Enhancements * Added firstRow, lastRow selectors on view. * firstRow and lastRow on table now return nil if table is empty. * getTableWithName selector added on group. * getting and creating table methods on group no longer take error argument. * [TDBQuery parent] and [TDBQuery subtable:] selectors now return self. * createTable method added on Transaction. Throws exception if table with same name already exists. * Experimental support for pinning transactions on Context. * TDBView now has support for object subscripting. ### Bugfixes * None. 0.3.0 Release notes (2014-03-14) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * Most selectors have been renamed in the binding! * Prepend TDB-prefix on all classes and types. ### Enhancements * Return types and parameters changed from size_t to NSUInteger. * Adding setObject to TightdbTable (t[2] = @[@1, @"Hello"] is possible). * Adding insertRow to TightdbTable. * Extending appendRow to accept NSDictionary. ### Bugfixes * None. 0.2.0 Release notes (2014-03-07) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * addRow renamed to addEmptyRow ### Enhancements * Adding a simple class for version numbering. * Adding get-version and set-version targets to build.sh. * tableview now supports sort on column with column type bool, date and int * tableview has method for checking the column type of a specified column * tableview has method for getting the number of columns * Adding methods getVersion, getCoreVersion and isAtLeast. * Adding appendRow to TightdbTable. * Adding object subscripting. * Adding method removeColumn on table. ### Bugfixes * None. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Filing Issues Whether you find a bug, typo or an API call that could be clarified, please [file an issue](https://github.com/realm/realm-swift/issues) on our GitHub repository. When filing an issue, please provide as much of the following information as possible in order to help others fix it: 1. **Goals** 2. **Expected results** 3. **Actual results** 4. **Steps to reproduce** 5. **Code sample that highlights the issue** (full Xcode projects that we can compile ourselves are ideal) 6. **Version of Realm / Xcode / macOS** 7. **Version of involved dependency manager (CocoaPods / Carthage)** If you'd like to send us sensitive sample code to help troubleshoot your issue, you can email directly. ### Speeding things up :runner: You may just copy this little script below and run it directly in your project directory in **Terminal.app**. It will take of compiling a list of relevant data as described in points 6. and 7. in the list above. It copies the list directly to your pasteboard for your convenience, so you can attach it easily when filing a new issue without having to worry about formatting and we may help you faster because we don't have to ask for particular details of your local setup first. ```shell echo "\`\`\` $(sw_vers) $(xcode-select -p) $(xcodebuild -version) $(which pod && pod --version) $(test -e Podfile.lock && cat Podfile.lock | sed -nE 's/^ - (Realm(Swift)? [^:]*):?/\1/p' || echo "(not in use here)") $(which bash && bash -version | head -n1) $(which carthage && carthage version) $(test -e Cartfile.resolved && cat Cartfile.resolved | grep --color=no realm || echo "(not in use here)") $(which git && git --version) \`\`\`" | tee /dev/tty | pbcopy ``` ## Contributing Enhancements We love contributions to Realm! If you'd like to contribute code, documentation, or any other improvements, please [file a Pull Request](https://github.com/realm/realm-swift/pulls) on our GitHub repository. Make sure to accept our [CLA](#cla) and to follow our [style guide](https://github.com/realm/realm-swift/wiki/Objective-C-Style-Guide). ### Commit Messages Although we don’t enforce a strict format for commit messages, we prefer that you follow the guidelines below, which are common among open source projects. Following these guidelines helps with the review process, searching commit logs and documentation of implementation details. At a high level, the contents of the commit message should convey the rationale of the change, without delving into much detail. For example, `setter names were not set right` leaves the reviewer wondering about which bits and why they weren’t “right”. In contrast, `[RLMProperty] Correctly capitalize setterName` conveys almost all there is to the change. Below are some guidelines about the format of the commit message itself: * Separate the commit message into a single-line title and a separate body that describes the change. * Make the title concise to be easily read within a commit log. * Make the body concise, while including the complete reasoning. Unless required to understand the change, additional code examples or other details should be left to the pull request. * If the commit fixes a bug, include the number of the issue in the message. * Use the first person present tense - for example "Fix …" instead of "Fixes …" or "Fixed …". * For text formatting and spelling, follow the same rules as documentation and in-code comments — for example, the use of capitalization and periods. * If the commit is a bug fix on top of another recently committed change, or a revert or reapply of a patch, include the Git revision number of the prior related commit, e.g. `Revert abcd3fg because it caused #1234`. ### CLA Realm welcomes all contributions! The only requirement we have is that, like many other projects, we need to have a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) (CLA) in place before we can accept any external code. Our own CLA is a modified version of the Apache Software Foundation’s CLA. [Please submit your CLA electronically using our Google form](https://docs.google.com/forms/d/e/1FAIpQLSeQ9ROFaTu9pyrmPhXc-dEnLD84DbLuT_-tPNZDOL9J10tOKQ/viewform) so we can accept your submissions. The GitHub username you file there will need to match that of your Pull Requests. If you have any questions or cannot file the CLA electronically, you can email . ================================================ FILE: Configuration/Base.xcconfig ================================================ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = c++20; CLANG_CXX_LIBRARY = libc++; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_ASSIGN_ENUM = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_COMPLETION_HANDLER_MISUSE = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DUPLICATE_METHOD_MATCH = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; // This is Xcode's typo COMBINE_HIDPI_IMAGES = YES; EAGER_LINKING = YES; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = $(REALM_PREFIX_HEADER); GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; OTHER_CFLAGS = -fvisibility-inlines-hidden; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = -Owholemodule; SWIFT_STRICT_CONCURRENCY = complete; WARNING_CFLAGS = -Wmismatched-tags -Wunused-private-field -Wpartial-availability; HEADER_SEARCH_PATHS = $(inherited) core/include; CODE_SIGN_IDENTITY[sdk=iphone*] = iPhone Developer; CODE_SIGNING_REQUIRED[sdk=macosx] = NO; IPHONEOS_DEPLOYMENT_TARGET_1500 = 12.0; IPHONEOS_DEPLOYMENT_TARGET_1600 = 12.0; IPHONEOS_DEPLOYMENT_TARGET_2600 = 12.0; IPHONEOS_DEPLOYMENT_TARGET = $(IPHONEOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR)); MACOSX_DEPLOYMENT_TARGET_1500 = 10.14; MACOSX_DEPLOYMENT_TARGET_1600 = 10.14; MACOSX_DEPLOYMENT_TARGET_2600 = 10.14; MACOSX_DEPLOYMENT_TARGET = $(MACOSX_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR)); WATCHOS_DEPLOYMENT_TARGET_1500 = 4.0; WATCHOS_DEPLOYMENT_TARGET_1600 = 4.0; WATCHOS_DEPLOYMENT_TARGET_2600 = 4.0; WATCHOS_DEPLOYMENT_TARGET = $(WATCHOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR)); TVOS_DEPLOYMENT_TARGET_1500 = 12.0; TVOS_DEPLOYMENT_TARGET_1600 = 12.0; TVOS_DEPLOYMENT_TARGET_2600 = 12.0; TVOS_DEPLOYMENT_TARGET = $(TVOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR)); APPLICATION_EXTENSION_API_ONLY = YES; REALM_MACH_O_TYPE = mh_dylib; SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator watchos watchsimulator appletvos appletvsimulator xros xrsimulator; SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = 1,2,3,4,6,7; SWIFT_VERSION_1500 = 5.7; SWIFT_VERSION_1600 = 5.7; SWIFT_VERSION_2600 = 5; SWIFT_VERSION = $(SWIFT_VERSION_$(XCODE_VERSION_MAJOR)); ================================================ FILE: Configuration/Debug.xcconfig ================================================ #include "Base.xcconfig" COPY_PHASE_STRIP = NO; ENABLE_TESTABILITY = YES; GCC_OPTIMIZATION_LEVEL = 0; ONLY_ACTIVE_ARCH = YES; SWIFT_OPTIMIZATION_LEVEL = -Onone; OTHER_SWIFT_FLAGS = -Xfrontend -enable-actor-data-race-checks; GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 REALM_DEBUG REALM_HAVE_CONFIG __ASSERTMACROS__; ================================================ FILE: Configuration/Realm/PrivateSymbols.txt ================================================ # Anything with a C++ mangled name __Z* # The Bid library used for decimal128 ___bid* ================================================ FILE: Configuration/Realm/Realm.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = @rpath; FRAMEWORK_VERSION = A; HEADER_SEARCH_PATHS = $(inherited) $(DERIVED_FILE_DIR); INFOPLIST_FILE = Realm/Realm-Info.plist; MACH_O_TYPE = $(REALM_MACH_O_TYPE); MODULEMAP_FILE = $(SRCROOT)/Realm/Realm.modulemap; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.${PRODUCT_NAME:rfc1034identifier}; PRODUCT_NAME = Realm; SKIP_INSTALL = YES; LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks; LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks; LINKER_FLAG_MACCATALYST_YES = -framework UIKit; LINKER_FLAG_MACCATALYST_NO = ; OTHER_LDFLAGS = $(REALM_SYMBOLS_FLAGS) -framework UIKit; OTHER_LDFLAGS[sdk=macosx*] = $(REALM_SYMBOLS_FLAGS) $(LINKER_FLAG_MACCATALYST_$(IS_MACCATALYST)); ================================================ FILE: Configuration/Realm/Tests.xcconfig ================================================ #include "../TestBase.xcconfig" INFOPLIST_FILE = Realm/Tests/RealmTests-Info.plist; OTHER_CFLAGS = -fobjc-arc-exceptions; OTHER_LDFLAGS = -ObjC; SWIFT_OBJC_BRIDGING_HEADER = Realm/Tests/Swift/Swift-Tests-Bridging-Header.h; SWIFT_OPTIMIZATION_LEVEL = -Onone; EXCLUDED_SOURCE_FILE_NAMES[sdk=iphone*] = InterprocessTests.m SwiftSchemaTests.swift; EXCLUDED_SOURCE_FILE_NAMES[sdk=appletv*] = EncryptionTests.mm InterprocessTests.m SwiftSchemaTests.swift; EXCLUDED_SOURCE_FILE_NAMES[sdk=watch*] = *; ================================================ FILE: Configuration/RealmSwift/RealmSwift.xcconfig ================================================ SKIP_INSTALL = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = @rpath; INFOPLIST_FILE = RealmSwift/RealmSwift-Info.plist; LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks; MACH_O_TYPE = $(REALM_MACH_O_TYPE); PRODUCT_BUNDLE_IDENTIFIER = io.realm.RealmSwit; PRODUCT_NAME = RealmSwift; REALM_BUILD_LIBRARY_FOR_DISTRIBUTION = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = $(REALM_BUILD_LIBRARY_FOR_DISTRIBUTION); ================================================ FILE: Configuration/RealmSwift/Tests.xcconfig ================================================ #include "../TestBase.xcconfig" INFOPLIST_FILE = RealmSwift/Tests/RealmSwiftTests-Info.plist; OTHER_LDFLAGS = -ObjC; SWIFT_OBJC_BRIDGING_HEADER = RealmSwift/Tests/RealmSwiftTests-BridgingHeader.h SWIFT_OPTIMIZATION_LEVEL = -Onone; SWIFT_STRICT_CONCURRENCY = targeted; EXCLUDED_SOURCE_FILE_NAMES[sdk=iphone*] = build/osx/*; EXCLUDED_SOURCE_FILE_NAMES[sdk=appletv*] = build/osx/*; EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] = build/ios/*; ================================================ FILE: Configuration/Release.xcconfig ================================================ #include "Base.xcconfig" // As of beta 1 dSYM generation fails with the unhelpful error "warning: could // not find referenced DIE" (which seems to not actually be just a warning?) DEBUG_INFORMATION_FORMAT_1400 = dwarf-with-dsym; DEBUG_INFORMATION_FORMAT_1500 = dwarf; DEBUG_INFORMATION_FORMAT = $(DEBUG_INFORMATION_FORMAT_$(XCODE_VERSION_MAJOR)); ENABLE_NS_ASSERTIONS = NO; GCC_PREPROCESSOR_DEFINITIONS = REALM_HAVE_CONFIG __ASSERTMACROS__; LLVM_LTO = YES_THIN; VALIDATE_PRODUCT = YES; REALM_HIDE_SYMBOLS = NO; REALM_SYMBOLS_FLAGS_NO = ; REALM_SYMBOLS_FLAGS_YES = -Xlinker -unexported_symbols_list -Xlinker Configuration/Realm/PrivateSymbols.txt; REALM_SYMBOLS_FLAGS = $(REALM_SYMBOLS_FLAGS_$(REALM_HIDE_SYMBOLS)); ================================================ FILE: Configuration/Static.xcconfig ================================================ #include "Release.xcconfig" REALM_MACH_O_TYPE = staticlib; LLVM_LTO = NO; GCC_PREPROCESSOR_DEFINITIONS = REALM_STATIC_FRAMEWORK REALM_HAVE_CONFIG __ASSERTMACROS__; ================================================ FILE: Configuration/SwiftUITestHost.xcconfig ================================================ #include "TestHost.xcconfig" INFOPLIST_FILE = Realm/Tests/SwiftUITestHost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_BUNDLE_IDENTIFIER = io.realm.SwiftUITestHost; SUPPORTED_PLATFORMS = iphonesimulator iphoneos; ================================================ FILE: Configuration/SwiftUITests.xcconfig ================================================ #include "SwiftUITestHost.xcconfig" SDKROOT = iphoneos; TEST_TARGET_NAME = SwiftUITestHost; TEST_HOST[sdk=appletv*] = ; TEST_HOST[sdk=iphone*] = ; ================================================ FILE: Configuration/TestBase.xcconfig ================================================ SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos appletvos appletvsimulator xros xrsimulator; SKIP_INSTALL = YES; PRODUCT_NAME = $(TARGET_NAME); PRODUCT_BUNDLE_IDENTIFIER = io.Realm.${PRODUCT_NAME:rfc1034identifier}; LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks; LD_RUNPATH_SEARCH_PATHS[sdk=appletv*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks; LD_RUNPATH_SEARCH_PATHS[sdk=xr*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks; LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks; TEST_HOST[sdk=iphone*] = $(BUILT_PRODUCTS_DIR)/TestHost.app/TestHost; TEST_HOST[sdk=appletv*] = $(BUILT_PRODUCTS_DIR)/TestHost.app/TestHost; TEST_HOST[sdk=xr*] = $(BUILT_PRODUCTS_DIR)/TestHost.app/TestHost; MACOSX_DEPLOYMENT_TARGET = 13.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; TVOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0; ================================================ FILE: Configuration/TestHost.xcconfig ================================================ #include "TestBase.xcconfig" ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_MODULES_AUTOLINK = NO; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; INFOPLIST_FILE = Realm/Tests/TestHost/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks; PRODUCT_NAME = $(TARGET_NAME); REALM_UI_FRAMEWORK_ = Cocoa; REALM_UI_FRAMEWORK_uikit = UIKit; OTHER_LDFLAGS[sdk=iphone*] = -framework UIKit; OTHER_LDFLAGS[sdk=appletv*] = -framework UIKit; OTHER_LDFLAGS[sdk=xr*] = -framework UIKit; OTHER_LDFLAGS[sdk=macosx*] = -framework $(REALM_UI_FRAMEWORK_$(RESOURCES_UI_FRAMEWORK_FAMILY)); PRINCIPAL_CLASS[sdk=iphone*] = UIApplication; PRINCIPAL_CLASS[sdk=appletv*] = UIApplication; PRINCIPAL_CLASS[sdk=xr*] = UIApplication; PRINCIPAL_CLASS[sdk=macosx*] = NSApplication; ================================================ FILE: Gemfile ================================================ source "https://rubygems.org" gem 'cocoapods' gem 'fileutils' gem 'jazzy' gem 'jwt' gem 'octokit' gem 'pathname', '0.3.0' ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: Package.swift ================================================ // swift-tools-version:5.10 import PackageDescription import Foundation let coreVersion = Version("20.1.4") let cocoaVersion = Version("20.0.4") #if compiler(>=6) let swiftVersion = [SwiftVersion.version("6")] #else let swiftVersion = [SwiftVersion.v5] #endif let cxxSettings: [CXXSetting] = [ .headerSearchPath("."), .headerSearchPath("include"), .define("REALM_SPM", to: "1"), .define("REALM_COCOA_VERSION", to: "@\"\(cocoaVersion)\""), .define("REALM_VERSION", to: "\"\(coreVersion)\""), .define("REALM_DEBUG", .when(configuration: .debug)), .define("REALM_NO_CONFIG"), .define("REALM_INSTALL_LIBEXECDIR", to: ""), .define("REALM_ENABLE_ASSERTIONS", to: "1"), .define("REALM_ENABLE_ENCRYPTION", to: "1"), .define("REALM_VERSION_MAJOR", to: String(coreVersion.major)), .define("REALM_VERSION_MINOR", to: String(coreVersion.minor)), .define("REALM_VERSION_PATCH", to: String(coreVersion.patch)), .define("REALM_VERSION_EXTRA", to: "\"\(coreVersion.prereleaseIdentifiers.first ?? "")\""), .define("REALM_VERSION_STRING", to: "\"\(coreVersion)\""), .define("REALM_ENABLE_GEOSPATIAL", to: "1"), ] let testCxxSettings: [CXXSetting] = cxxSettings + [ // Command-line `swift build` resolves header search paths // relative to the package root, while Xcode resolves them // relative to the target root, so we need both. .headerSearchPath("Realm"), .headerSearchPath(".."), ] let package = Package( name: "Realm", platforms: [ .macOS(.v10_13), .iOS(.v12), .tvOS(.v12), .watchOS(.v4) ], products: [ .library( name: "Realm", type: .dynamic, targets: ["Realm"]), .library( name: "RealmSwift", type: .dynamic, targets: ["RealmSwift"]), ], dependencies: [ .package(url: "https://github.com/realm/realm-core.git", exact: coreVersion) ], targets: [ .target( name: "Realm", dependencies: [.product(name: "RealmCore", package: "realm-core")], path: ".", exclude: [ "CHANGELOG.md", "CONTRIBUTING.md", "Carthage", "Configuration", "LICENSE", "Package.swift", "README.md", "Realm.podspec", "Realm.xcodeproj", "Realm/Realm-Info.plist", "Realm/Swift/RLMSupport.swift", "Realm/TestUtils", "Realm/Tests", "RealmSwift", "RealmSwift.podspec", "SUPPORT.md", "build.sh", "contrib", "dependencies.list", "docs", "examples", "include", "logo.png", "plugin", "scripts", ], sources: [ "Realm/RLMAccessor.mm", "Realm/RLMArray.mm", "Realm/RLMAsyncTask.mm", "Realm/RLMClassInfo.mm", "Realm/RLMCollection.mm", "Realm/RLMConstants.m", "Realm/RLMDecimal128.mm", "Realm/RLMDictionary.mm", "Realm/RLMEmbeddedObject.mm", "Realm/RLMError.mm", "Realm/RLMGeospatial.mm", "Realm/RLMLogger.mm", "Realm/RLMManagedArray.mm", "Realm/RLMManagedDictionary.mm", "Realm/RLMManagedSet.mm", "Realm/RLMMigration.mm", "Realm/RLMObject.mm", "Realm/RLMObjectBase.mm", "Realm/RLMObjectId.mm", "Realm/RLMObjectSchema.mm", "Realm/RLMObjectStore.mm", "Realm/RLMObservation.mm", "Realm/RLMPredicateUtil.mm", "Realm/RLMProperty.mm", "Realm/RLMQueryUtil.mm", "Realm/RLMRealm.mm", "Realm/RLMRealmConfiguration.mm", "Realm/RLMRealmUtil.mm", "Realm/RLMResults.mm", "Realm/RLMScheduler.mm", "Realm/RLMSchema.mm", "Realm/RLMSectionedResults.mm", "Realm/RLMSet.mm", "Realm/RLMSwiftCollectionBase.mm", "Realm/RLMSwiftSupport.m", "Realm/RLMSwiftValueStorage.mm", "Realm/RLMThreadSafeReference.mm", "Realm/RLMUUID.mm", "Realm/RLMUtil.mm", "Realm/RLMValue.mm", ], resources: [ .copy("Realm/PrivacyInfo.xcprivacy") ], publicHeadersPath: "include", cxxSettings: cxxSettings, linkerSettings: [ .linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst, .tvOS, .watchOS])) ] ), .target( name: "RealmSwift", dependencies: ["Realm"], path: "RealmSwift", exclude: [ "RealmSwift-Info.plist", "Tests", ], resources: [ .copy("PrivacyInfo.xcprivacy") ] ), .target( name: "RealmTestSupport", dependencies: ["Realm"], path: "Realm/TestUtils", cxxSettings: testCxxSettings ), .target( name: "RealmSwiftTestSupport", dependencies: ["RealmSwift", "RealmTestSupport"], path: "RealmSwift/Tests", sources: ["TestUtils.swift"] ), .testTarget( name: "RealmTests", dependencies: ["Realm", "RealmTestSupport"], path: "Realm/Tests", exclude: [ "PrimitiveArrayPropertyTests.tpl.m", "PrimitiveDictionaryPropertyTests.tpl.m", "PrimitiveRLMValuePropertyTests.tpl.m", "PrimitiveSetPropertyTests.tpl.m", "RealmTests-Info.plist", "Swift", "SwiftUITestHost", "SwiftUITestHostUITests", "TestHost", "array_tests.py", "dictionary_tests.py", "fileformat-pre-null.realm", "mixed_tests.py", "set_tests.py", ], cxxSettings: testCxxSettings ), .testTarget( name: "RealmObjcSwiftTests", dependencies: ["Realm", "RealmTestSupport"], path: "Realm/Tests/Swift", exclude: ["RealmObjcSwiftTests-Info.plist"] ), .testTarget( name: "RealmSwiftTests", dependencies: ["RealmSwift", "RealmTestSupport", "RealmSwiftTestSupport"], path: "RealmSwift/Tests", exclude: [ "RealmSwiftTests-Info.plist", "QueryTests.swift.gyb", "TestUtils.swift" ] ), ], swiftLanguageVersions: swiftVersion, cxxLanguageStandard: .cxx20 ) ================================================ FILE: README.md ================================================ realm # About Realm Database Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the iOS, macOS, tvOS & watchOS versions of Realm Swift & Realm Objective-C. ## Why Use Realm * **Intuitive to Developers:** Realm’s object-oriented data model is simple to learn, doesn’t need an ORM, and lets you write less code. * **Built for Mobile:** Realm is fully-featured, lightweight, and efficiently uses memory, disk space, and battery life. * **Designed for Offline Use:** Realm’s local database persists data on-disk, so apps work as well offline as they do online. ## Object-Oriented: Streamline Your Code Realm was built for mobile developers, with simplicity in mind. The idiomatic, object-oriented data model can save you thousands of lines of code. ```swift // Define your models like regular Swift classes class Dog: Object { @Persisted var name: String @Persisted var age: Int } class Person: Object { @Persisted(primaryKey: true) var _id: String @Persisted var name: String @Persisted var age: Int // Create relationships by pointing an Object field to another Class @Persisted var dogs: List } // Use them like regular Swift objects let dog = Dog() dog.name = "Rex" dog.age = 1 print("name of dog: \(dog.name)") // Get the default Realm let realm = try! Realm() // Persist your data easily with a write transaction try! realm.write { realm.add(dog) } ``` ## Live Objects: Build Reactive Apps Realm’s live objects mean data updated anywhere is automatically updated everywhere. ```swift // Open the default realm. let realm = try! Realm() var token: NotificationToken? let dog = Dog() dog.name = "Max" // Create a dog in the realm. try! realm.write { realm.add(dog) } // Set up the listener & observe object notifications. token = dog.observe { change in switch change { case .change(let properties): for property in properties { print("Property '\(property.name)' changed to '\(property.newValue!)'"); } case .error(let error): print("An error occurred: (error)") case .deleted: print("The object was deleted.") } } // Update the dog's name to see the effect. try! realm.write { dog.name = "Wolfie" } ``` ### SwiftUI Realm integrates directly with SwiftUI, updating your views so you don't have to. ```swift struct ContactsView: View { @ObservedResults(Person.self) var persons var body: some View { List { ForEach(persons) { person in Text(person.name) } .onMove(perform: $persons.move) .onDelete(perform: $persons.remove) }.navigationBarItems(trailing: Button("Add") { $persons.append(Person()) } ) } } ``` ## Fully Encrypted Data can be encrypted in-flight and at-rest, keeping even the most sensitive data secure. ```swift // Generate a random encryption key var key = Data(count: 64) _ = key.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in guard let baseAddress = pointer.baseAddress else { fatalError("Failed to obtain base address") } SecRandomCopyBytes(kSecRandomDefault, 64, baseAddress) } // Add the encryption key to the config and open the realm let config = Realm.Configuration(encryptionKey: key) let realm = try Realm(configuration: config) // Use the Realm as normal let dogs = realm.objects(Dog.self).filter("name contains 'Fido'") ``` ## Getting Started We support installing Realm via Swift Package Manager, CocoaPods, Carthage, or by importing a dynamic XCFramework. For more information, see our [Quick Start](docs/guides/quick-start.md). ## Documentation The documentation can be found in the [docs/](docs/README.md) directory. The API reference can be generated from source using [jazzy](https://github.com/realm/jazzy/) by running `sh build.sh docs` from the root of this repository. ## Getting Help - **Need help with your code?**: Look for previous questions with the[`realm` tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) on Stack Overflow or [ask a new question](https://stackoverflow.com/questions/ask?tags=realm). For general discussion that might be considered too broad for Stack Overflow, use the [Community Forum](https://developer.mongodb.com/community/forums/tags/c/realm-sdks/58/swift/). - **Have a bug to report?** [Open a GitHub issue](https://github.com/realm/realm-swift/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. - **Have a feature request?** [Open a GitHub issue](https://github.com/realm/realm-swift/issues/new). Tell us what the feature should do and why you want the feature. ## Building Realm In case you don't want to use the precompiled version, you can build Realm yourself from source. Prerequisites: * Building Realm requires Xcode 15.3 or newer. * Building Realm documentation requires [jazzy](https://github.com/realm/jazzy) Once you have all the necessary prerequisites, building Realm just takes a single command: `sh build.sh build`. You'll need an internet connection the first time you build Realm to download the core binary. This will produce Realm.xcframework and RealmSwift.xcframework in `build/Release/`. Run `sh build.sh help` to see all the actions you can perform (build ios/osx, generate docs, test, etc.). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for more details! ## Code of Conduct This project adheres to the [MongoDB Code of Conduct](https://www.mongodb.com/community-code-of-conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to [community-conduct@mongodb.com](mailto:community-conduct@mongodb.com). ## License Realm Objective-C & Realm Swift are published under the Apache 2.0 license. Realm Core is also published under the Apache 2.0 license and is available [here](https://github.com/realm/realm-core). ## Feedback **_If you use Realm and are happy with it, please consider sending out a tweet mentioning [@realm](https://twitter.com/realm) to share your thoughts!_** **_And if you don't like it, please let us know what you would like improved, so we can fix it!_** ================================================ FILE: Realm/PrivacyInfo.xcprivacy ================================================ NSPrivacyTrackingDomains NSPrivacyCollectedDataTypes NSPrivacyAccessedAPITypes NSPrivacyAccessedAPITypeReasons C617.1 NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons E174.1 NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryDiskSpace NSPrivacyTracking ================================================ FILE: Realm/RLMAccessor.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @class RLMObjectSchema, RLMProperty, RLMObjectBase; RLM_HEADER_AUDIT_BEGIN(nullability) // // Accessors Class Creation/Caching // // get accessor classes for an object class - generates classes if not cached Class RLMManagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, const char *name); Class RLMUnmanagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema); // // Dynamic getters/setters // FOUNDATION_EXTERN void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id __nullable val); FOUNDATION_EXTERN id __nullable RLMDynamicGet(RLMObjectBase *obj, RLMProperty *prop); FOUNDATION_EXTERN id __nullable RLMDynamicGetByName(RLMObjectBase *obj, NSString *propName); // by property/column void RLMDynamicSet(RLMObjectBase *obj, RLMProperty *prop, id val); // // Class modification // // Replace className method for the given class void RLMReplaceClassNameMethod(Class accessorClass, NSString *className); // Replace sharedSchema method for the given class void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema * __nullable schema); RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMAccessor.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMAccessor.h" #import "RLMClassInfo.hpp" #import "RLMDecimal128_Private.hpp" #import "RLMObjectId_Private.hpp" #import "RLMUUID_Private.hpp" #import "RLMUtil.hpp" #import @class RLMRealm; class RLMClassInfo; class RLMObservationTracker; typedef NS_ENUM(NSUInteger, RLMUpdatePolicy); RLM_HIDDEN_BEGIN // std::optional doesn't work because Objective-C types can't // be members of unions with ARC, so this covers the subset of Optional that we // actually need. struct RLMOptionalId { id value; RLMOptionalId(id value) : value(value) { } explicit operator bool() const noexcept { return value; } id operator*() const noexcept { return value; } }; // The subset of RLMAccessorContext which does not require any member variables. // Use this if you require to box/unbox types and you do not have access to the // parent object or realm. struct RLMStatelessAccessorContext { static id box(bool v) { return @(v); } static id box(double v) { return @(v); } static id box(float v) { return @(v); } static id box(long long v) { return @(v); } static id box(realm::StringData v) { return RLMStringDataToNSString(v) ?: NSNull.null; } static id box(realm::BinaryData v) { return RLMBinaryDataToNSData(v) ?: NSNull.null; } static id box(realm::Timestamp v) { return RLMTimestampToNSDate(v) ?: NSNull.null; } static id box(realm::Decimal128 v) { return v.is_null() ? NSNull.null : [[RLMDecimal128 alloc] initWithDecimal128:v]; } static id box(realm::ObjectId v) { return [[RLMObjectId alloc] initWithValue:v]; } static id box(realm::UUID v) { return [[NSUUID alloc] initWithRealmUUID:v]; } static id box(std::optional v) { return v ? @(*v) : NSNull.null; } static id box(std::optional v) { return v ? @(*v) : NSNull.null; } static id box(std::optional v) { return v ? @(*v) : NSNull.null; } static id box(std::optional v) { return v ? @(*v) : NSNull.null; } static id box(std::optional v) { return v ? box(*v) : NSNull.null; } static id box(std::optional v) { return v ? box(*v) : NSNull.null; } template static T unbox(id v); template static void enumerate_collection(__unsafe_unretained const id v, Func&& func) { id enumerable = RLMAsFastEnumeration(v) ?: v; for (id value in enumerable) { func(value); } } template static void enumerate_dictionary(__unsafe_unretained const id v, Func&& func) { id enumerable = RLMAsFastEnumeration(v) ?: v; for (id key in enumerable) { func(unbox(key), v[key]); } } static bool is_null(id v) noexcept { return v == NSNull.null; } static id null_value() noexcept { return NSNull.null; } static id no_value() noexcept { return nil; } static bool allow_missing(id v) noexcept { return [v isKindOfClass:[NSArray class]]; } static bool is_same_list(realm::List const& list, id v) noexcept; static bool is_same_dictionary(realm::object_store::Dictionary const&, id) noexcept; static bool is_same_set(realm::object_store::Set const&, id) noexcept; static std::string print(id obj) { return [obj description].UTF8String; } }; class RLMAccessorContext : public RLMStatelessAccessorContext { public: ~RLMAccessorContext(); // Accessor context interface RLMAccessorContext(RLMAccessorContext& parent, realm::Obj const& parent_obj, realm::Property const& property); using RLMStatelessAccessorContext::box; id box(realm::List&&); id box(realm::Results&&); id box(realm::Object&&); id box(realm::Obj&&); id box(realm::object_store::Dictionary&&); id box(realm::object_store::Set&&); id box(realm::Mixed); void will_change(realm::Obj const&, realm::Property const&); void will_change(realm::Object& obj, realm::Property const& prop) { will_change(obj.get_obj(), prop); } void did_change(); RLMOptionalId value_for_property(id dict, realm::Property const&, size_t prop_index); RLMOptionalId default_value_for_property(realm::ObjectSchema const&, realm::Property const& prop); template T unbox(__unsafe_unretained id const v, realm::CreatePolicy = realm::CreatePolicy::Skip, realm::ObjKey = {}) { return RLMStatelessAccessorContext::unbox(v); } template<> realm::Obj unbox(id v, realm::CreatePolicy, realm::ObjKey); template<> realm::Mixed unbox(id v, realm::CreatePolicy, realm::ObjKey); realm::Obj create_embedded_object(); // Internal API RLMAccessorContext(RLMObjectBase *parentObject, const realm::Property *property = nullptr); RLMAccessorContext(RLMObjectBase *parentObject, realm::ColKey); RLMAccessorContext(RLMClassInfo& info); RLMAccessorContext(RLMClassInfo& parentInfo, RLMClassInfo& info, RLMProperty *property); // The property currently being accessed; needed for KVO things for boxing // List and Results RLMProperty *currentProperty; std::pair createObject(id value, realm::CreatePolicy policy, bool forceCreate=false, realm::ObjKey existingKey={}); private: __unsafe_unretained RLMRealm *const _realm; RLMClassInfo& _info; realm::Obj _parentObject; RLMClassInfo* _parentObjectInfo = nullptr; realm::ColKey _colKey; // Cached default values dictionary to avoid having to call the class method // for every property NSDictionary *_defaultValues; std::unique_ptr _observationHelper; id defaultValue(NSString *key); id propertyValue(id obj, size_t propIndex, __unsafe_unretained RLMProperty *const prop); }; RLM_HIDDEN_END ================================================ FILE: Realm/RLMAccessor.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMAccessor.hpp" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMObjectId_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMRealm_Private.hpp" #import "RLMResults_Private.hpp" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftProperty.h" #import "RLMUUID_Private.hpp" #import "RLMUtil.hpp" #import "RLMValue.h" #import #import #import #import #import #pragma mark Helper functions using realm::ColKey; namespace realm { template<> Obj Obj::get(ColKey col) const { ObjKey key = get(col); return key ? get_target_table(col)->get_object(key) : Obj(); } } // namespace realm namespace { realm::Property const& getProperty(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { return obj->_info->objectSchema->persisted_properties[index]; } realm::Property const& getProperty(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop) { if (prop.linkOriginPropertyName) { return obj->_info->objectSchema->computed_properties[prop.index]; } return obj->_info->objectSchema->persisted_properties[prop.index]; } template bool isNull(T const& v) { return !v; } template<> bool isNull(realm::Timestamp const& v) { return v.is_null(); } template<> bool isNull(realm::ObjectId const&) { return false; } template<> bool isNull(realm::Decimal128 const& v) { return v.is_null(); } template<> bool isNull(realm::Mixed const& v) { return v.is_null(); } template<> bool isNull(realm::UUID const&) { return false; } template T get(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { RLMVerifyAttached(obj); return obj->_row.get(getProperty(obj, index).column_key); } template id getBoxed(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { RLMVerifyAttached(obj); auto& prop = getProperty(obj, index); RLMAccessorContext ctx(obj, &prop); auto value = obj->_row.get(prop.column_key); return isNull(value) ? nil : ctx.box(std::move(value)); } template T getOptional(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, bool *gotValue) { auto ret = get>(obj, key); if (ret) { *gotValue = true; } return ret.value_or(T{}); } template void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, T val) { obj->_row.set(key, val); } template void setValueOrNull(__unsafe_unretained RLMObjectBase *const obj, ColKey col, __unsafe_unretained id const value) { RLMVerifyInWriteTransaction(obj); RLMTranslateError([&] { if (value) { RLMStatelessAccessorContext ctx; obj->_row.set(col, ctx.unbox(value)); } else { obj->_row.set_null(col); } }); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSDate *const date) { setValueOrNull(obj, key, date); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSData *const value) { setValueOrNull(obj, key, value); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSString *const value) { setValueOrNull(obj, key, value); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained RLMObjectBase *const val) { if (!val) { obj->_row.set(key, realm::null()); return; } if (!val->_row) { RLMAccessorContext{obj, key}.createObject(val, {.create = true}, false, {}); } // make sure it is the correct type auto table = val->_row.get_table(); if (table != obj->_row.get_table()->get_link_target(key)) { @throw RLMException(@"Can't set object of type '%@' to property of type '%@'", val->_objectSchema.className, obj->_info->propertyForTableColumn(key).objectClassName); } if (!table->is_embedded()) { obj->_row.set(key, val->_row.get_key()); } else if (obj->_row.get_linked_object(key).get_key() != val->_row.get_key()) { @throw RLMException(@"Can't set link to existing managed embedded object"); } } id RLMCollectionClassForProperty(RLMProperty *prop, bool isManaged) { Class cls = nil; if (prop.array) { cls = isManaged ? [RLMManagedArray class] : [RLMArray class]; } else if (prop.set) { cls = isManaged ? [RLMManagedSet class] : [RLMSet class]; } else if (prop.dictionary) { cls = isManaged ? [RLMManagedDictionary class] : [RLMDictionary class]; } else { @throw RLMException(@"Invalid collection '%@' for class '%@'.", prop.name, prop.objectClassName); } return cls; } // collection getter/setter id getCollection(__unsafe_unretained RLMObjectBase *const obj, NSUInteger propIndex) { RLMVerifyAttached(obj); auto prop = obj->_info->rlmObjectSchema.properties[propIndex]; Class cls = RLMCollectionClassForProperty(prop, true); return [[cls alloc] initWithParent:obj property:prop]; } template void assignValue(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop, ColKey key, __unsafe_unretained id const value) { auto info = obj->_info; Collection collection(obj->_realm->_realm, obj->_row, key); if (collection.get_type() == realm::PropertyType::Object) { info = &obj->_info->linkTargetType(prop.index); } RLMAccessorContext ctx(*info); RLMTranslateError([&] { collection.assign(ctx, value, realm::CreatePolicy::ForceCreate); }); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained id const value) { auto prop = obj->_info->propertyForTableColumn(key); RLMValidateValueForProperty(value, obj->_info->rlmObjectSchema, prop, true); if (prop.array) { assignValue(obj, prop, key, value); } else if (prop.set) { assignValue(obj, prop, key, value); } else if (prop.dictionary) { assignValue(obj, prop, key, value); } } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSNumber *const intObject) { setValueOrNull(obj, key, intObject); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSNumber *const floatObject) { setValueOrNull(obj, key, floatObject); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSNumber *const doubleObject) { setValueOrNull(obj, key, doubleObject); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSNumber *const boolObject) { setValueOrNull(obj, key, boolObject); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained RLMDecimal128 *const value) { setValueOrNull(obj, key, value); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained RLMObjectId *const value) { setValueOrNull(obj, key, value); } void setValue(__unsafe_unretained RLMObjectBase *const obj, ColKey key, __unsafe_unretained NSUUID *const value) { setValueOrNull(obj, key, value); } void setValue(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const property, __unsafe_unretained id const value) { realm::Object o(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row); RLMAccessorContext ctx(obj); o.set_property_value(ctx, getProperty(obj, property), value ?: NSNull.null); } RLMLinkingObjects *getLinkingObjects(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const property) { RLMVerifyAttached(obj); auto& objectInfo = obj->_realm->_info[property.objectClassName]; auto& linkOrigin = obj->_info->objectSchema->computed_properties[property.index].link_origin_property_name; auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin); auto backlinkView = obj->_row.get_backlink_view(objectInfo.table(), linkingProperty->column_key); realm::Results results(obj->_realm->_realm, std::move(backlinkView)); return [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)]; } // any getter/setter template id makeGetter(NSUInteger index) { return ^(__unsafe_unretained RLMObjectBase *const obj) { return static_cast(get(obj, index)); }; } template id makeBoxedGetter(NSUInteger index) { return ^(__unsafe_unretained RLMObjectBase *const obj) { return getBoxed(obj, index); }; } template id makeOptionalGetter(NSUInteger index) { return ^(__unsafe_unretained RLMObjectBase *const obj) { return getBoxed>(obj, index); }; } template id makeNumberGetter(NSUInteger index, bool boxed, bool optional) { if (optional) { return makeOptionalGetter(index); } if (boxed) { return makeBoxedGetter(index); } return makeGetter(index); } template id makeWrapperGetter(NSUInteger index, bool optional) { if (optional) { return makeOptionalGetter(index); } return makeBoxedGetter(index); } // dynamic getter with column closure id managedGetter(RLMProperty *prop, const char *type) { NSUInteger index = prop.index; if (prop.collection && prop.type != RLMPropertyTypeLinkingObjects) { return ^id(__unsafe_unretained RLMObjectBase *const obj) { return getCollection(obj, index); }; } bool boxed = *type == '@'; switch (prop.type) { case RLMPropertyTypeInt: if (prop.optional || boxed) { return makeNumberGetter(index, boxed, prop.optional); } switch (*type) { case 'c': return makeGetter(index); case 's': return makeGetter(index); case 'i': return makeGetter(index); case 'l': return makeGetter(index); case 'q': return makeGetter(index); default: @throw RLMException(@"Unexpected property type for Objective-C type code"); } case RLMPropertyTypeFloat: return makeNumberGetter(index, boxed, prop.optional); case RLMPropertyTypeDouble: return makeNumberGetter(index, boxed, prop.optional); case RLMPropertyTypeBool: return makeNumberGetter(index, boxed, prop.optional); case RLMPropertyTypeString: return makeBoxedGetter(index); case RLMPropertyTypeDate: return makeBoxedGetter(index); case RLMPropertyTypeData: return makeBoxedGetter(index); case RLMPropertyTypeObject: return makeBoxedGetter(index); case RLMPropertyTypeDecimal128: return makeBoxedGetter(index); case RLMPropertyTypeObjectId: return makeWrapperGetter(index, prop.optional); case RLMPropertyTypeAny: // Mixed is represented as optional in Core, // but not in Cocoa. We use `makeBoxedGetter` over // `makeWrapperGetter` becuase Mixed can box a `null` representation. return makeBoxedGetter(index); case RLMPropertyTypeLinkingObjects: return ^(__unsafe_unretained RLMObjectBase *const obj) { return getLinkingObjects(obj, prop); }; case RLMPropertyTypeUUID: return makeWrapperGetter(index, prop.optional); } } static realm::ColKey willChange(RLMObservationTracker& tracker, __unsafe_unretained RLMObjectBase *const obj, NSUInteger index) { auto& prop = getProperty(obj, index); if (prop.is_primary) { @throw RLMException(@"Primary key can't be changed after an object is inserted."); } tracker.willChange(RLMGetObservationInfo(obj->_observationInfo, obj->_row.get_key(), *obj->_info), obj->_objectSchema.properties[index].name); return prop.column_key; } template void kvoSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index, ArgType value) { RLMVerifyInWriteTransaction(obj); RLMObservationTracker tracker(obj->_realm); auto key = willChange(tracker, obj, index); if constexpr (std::is_same_v) { tracker.trackDeletions(); } setValue(obj, key, static_cast(value)); } template<> void kvoSetValue>(__unsafe_unretained RLMObjectBase *const obj, NSUInteger index, id value) { RLMVerifyInWriteTransaction(obj); auto& prop = getProperty(obj, index); setValue(obj, obj->_info->propertyForTableColumn(prop.column_key), static_cast>(value)); } template id makeSetter(__unsafe_unretained RLMProperty *const prop) { if (prop.isPrimary) { return ^(__unused RLMObjectBase *obj, __unused ArgType val) { @throw RLMException(@"Primary key can't be changed after an object is inserted."); }; } NSUInteger index = prop.index; return ^(__unsafe_unretained RLMObjectBase *const obj, ArgType val) { kvoSetValue(obj, index, val); }; } // dynamic setter with column closure id managedSetter(RLMProperty *prop, const char *type) { if (prop.collection && prop.type != RLMPropertyTypeLinkingObjects) { return makeSetter>(prop); } bool boxed = prop.optional || *type == '@'; switch (prop.type) { case RLMPropertyTypeInt: if (boxed) { return makeSetter *>(prop); } switch (*type) { case 'c': return makeSetter(prop); case 's': return makeSetter(prop); case 'i': return makeSetter(prop); case 'l': return makeSetter(prop); case 'q': return makeSetter(prop); default: @throw RLMException(@"Unexpected property type for Objective-C type code"); } case RLMPropertyTypeFloat: return boxed ? makeSetter *>(prop) : makeSetter(prop); case RLMPropertyTypeDouble: return boxed ? makeSetter *>(prop) : makeSetter(prop); case RLMPropertyTypeBool: return boxed ? makeSetter *>(prop) : makeSetter(prop); case RLMPropertyTypeString: return makeSetter(prop); case RLMPropertyTypeDate: return makeSetter(prop); case RLMPropertyTypeData: return makeSetter(prop); case RLMPropertyTypeAny: return makeSetter>(prop); case RLMPropertyTypeLinkingObjects: return nil; case RLMPropertyTypeObject: return makeSetter(prop); case RLMPropertyTypeObjectId: return makeSetter(prop); case RLMPropertyTypeDecimal128: return makeSetter(prop); case RLMPropertyTypeUUID: return makeSetter(prop); } } // call getter for superclass for property at key id superGet(RLMObjectBase *obj, NSString *propName) { typedef id (*getter_type)(RLMObjectBase *, SEL); RLMProperty *prop = obj->_objectSchema[propName]; Class superClass = class_getSuperclass(obj.class); getter_type superGetter = (getter_type)[superClass instanceMethodForSelector:prop.getterSel]; return superGetter(obj, prop.getterSel); } // call setter for superclass for property at key void superSet(RLMObjectBase *obj, NSString *propName, id val) { typedef void (*setter_type)(RLMObjectBase *, SEL, id collection); RLMProperty *prop = obj->_objectSchema[propName]; Class superClass = class_getSuperclass(obj.class); setter_type superSetter = (setter_type)[superClass instanceMethodForSelector:prop.setterSel]; superSetter(obj, prop.setterSel, val); } // getter/setter for unmanaged object id unmanagedGetter(RLMProperty *prop, const char *) { // only override getters for RLMCollection and linking objects properties if (prop.type == RLMPropertyTypeLinkingObjects) { return ^(RLMObjectBase *) { return [RLMResults emptyDetachedResults]; }; } if (prop.collection) { NSString *propName = prop.name; Class cls = RLMCollectionClassForProperty(prop, false); if (prop.type == RLMPropertyTypeObject) { NSString *objectClassName = prop.objectClassName; RLMPropertyType keyType = prop.dictionaryKeyType; return ^(RLMObjectBase *obj) { id val = superGet(obj, propName); if (!val) { val = [[cls alloc] initWithObjectClassName:objectClassName keyType:keyType]; superSet(obj, propName, val); } return val; }; } auto type = prop.type; auto optional = prop.optional; auto dictionaryKeyType = prop.dictionaryKeyType; return ^(RLMObjectBase *obj) { id val = superGet(obj, propName); if (!val) { val = [[cls alloc] initWithObjectType:type optional:optional keyType:dictionaryKeyType]; superSet(obj, propName, val); } return val; }; } return nil; } id unmanagedSetter(RLMProperty *prop, const char *) { // Only RLMCollection types need special handling for the unmanaged setter if (!prop.collection) { return nil; } NSString *propName = prop.name; return ^(RLMObjectBase *obj, id values) { auto prop = obj->_objectSchema[propName]; RLMValidateValueForProperty(values, obj->_objectSchema, prop, true); Class cls = RLMCollectionClassForProperty(prop, false); id collection; // make copy when setting (as is the case for all other variants) if (prop.type == RLMPropertyTypeObject) { collection = [[cls alloc] initWithObjectClassName:prop.objectClassName keyType:prop.dictionaryKeyType]; } else { collection = [[cls alloc] initWithObjectType:prop.type optional:prop.optional keyType:prop.dictionaryKeyType]; } if (prop.dictionary) [collection addEntriesFromDictionary:(id)values]; else [collection addObjects:values]; superSet(obj, propName, collection); }; } void addMethod(Class cls, __unsafe_unretained RLMProperty *const prop, id (*getter)(RLMProperty *, const char *), id (*setter)(RLMProperty *, const char *)) { SEL sel = prop.getterSel; if (!sel) { return; } auto getterMethod = class_getInstanceMethod(cls, sel); if (!getterMethod) { return; } const char *getterType = method_getTypeEncoding(getterMethod); if (id block = getter(prop, getterType)) { class_addMethod(cls, sel, imp_implementationWithBlock(block), getterType); } if (!(sel = prop.setterSel)) { return; } auto setterMethod = class_getInstanceMethod(cls, sel); if (!setterMethod) { return; } if (id block = setter(prop, getterType)) { // note: deliberately getterType as it's easier to grab the relevant type from class_addMethod(cls, sel, imp_implementationWithBlock(block), method_getTypeEncoding(setterMethod)); } } Class createAccessorClass(Class objectClass, RLMObjectSchema *schema, const char *accessorClassName, id (*getterGetter)(RLMProperty *, const char *), id (*setterGetter)(RLMProperty *, const char *)) { REALM_ASSERT_DEBUG(RLMIsObjectOrSubclass(objectClass)); // create and register proxy class which derives from object class Class accClass = objc_allocateClassPair(objectClass, accessorClassName, 0); if (!accClass) { // Class with that name already exists, so just return the pre-existing one // This should only happen for our standalone "accessors" return objc_lookUpClass(accessorClassName); } // override getters/setters for each propery for (RLMProperty *prop in schema.properties) { addMethod(accClass, prop, getterGetter, setterGetter); } for (RLMProperty *prop in schema.computedProperties) { addMethod(accClass, prop, getterGetter, setterGetter); } objc_registerClassPair(accClass); return accClass; } bool requiresUnmanagedAccessor(RLMObjectSchema *schema) { for (RLMProperty *prop in schema.properties) { if (prop.collection && !prop.swiftIvar) { return true; } } for (RLMProperty *prop in schema.computedProperties) { if (prop.collection && !prop.swiftIvar) { return true; } } return false; } } // anonymous namespace #pragma mark - Public Interface Class RLMManagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, const char *name) { return createAccessorClass(objectClass, schema, name, managedGetter, managedSetter); } Class RLMUnmanagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema) { if (!requiresUnmanagedAccessor(schema)) { return objectClass; } return createAccessorClass(objectClass, schema, [@"RLM:Unmanaged " stringByAppendingString:schema.className].UTF8String, unmanagedGetter, unmanagedSetter); } // implement the class method className on accessors to return the className of the // base object void RLMReplaceClassNameMethod(Class accessorClass, NSString *className) { Class metaClass = object_getClass(accessorClass); IMP imp = imp_implementationWithBlock(^(Class) { return className; }); class_addMethod(metaClass, @selector(className), imp, "@@:"); } // implement the shared schema method void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema *schema) { REALM_ASSERT(accessorClass != [RealmSwiftObject class]); Class metaClass = object_getClass(accessorClass); IMP imp = imp_implementationWithBlock(^(Class cls) { if (cls == accessorClass) { return schema; } // If we aren't being called directly on the class this was overridden // for, the class is either a subclass which we haven't initialized yet, // or it's a runtime-generated class which should use the parent's // schema. We check for the latter by checking if the immediate // descendent of the desired class is a class generated by us (there // may be further subclasses not generated by us for things like KVO). Class parent = class_getSuperclass(cls); while (parent != accessorClass) { cls = parent; parent = class_getSuperclass(cls); } static const char accessorClassPrefix[] = "RLM:"; if (!strncmp(class_getName(cls), accessorClassPrefix, sizeof(accessorClassPrefix) - 1)) { return schema; } return [RLMSchema sharedSchemaForClass:cls]; }); class_addMethod(metaClass, @selector(sharedSchema), imp, "@@:"); } void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id val) { RLMVerifyAttached(obj); RLMObjectSchema *schema = obj->_objectSchema; RLMProperty *prop = schema[propName]; if (!prop) { @throw RLMException(@"Invalid property name '%@' for class '%@'.", propName, obj->_objectSchema.className); } if (prop.isPrimary) { @throw RLMException(@"Primary key can't be changed to '%@' after an object is inserted.", val); } // Because embedded objects cannot be created directly, we accept anything // that can be converted to an embedded object for dynamic link set operations. bool is_embedded = prop.type == RLMPropertyTypeObject && obj->_info->linkTargetType(prop.index).rlmObjectSchema.isEmbedded; RLMValidateValueForProperty(val, schema, prop, !is_embedded); RLMDynamicSet(obj, prop, RLMCoerceToNil(val)); } // Precondition: the property is not a primary key void RLMDynamicSet(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop, __unsafe_unretained id const val) { REALM_ASSERT_DEBUG(!prop.isPrimary); realm::Object o(obj->_info->realm->_realm, *obj->_info->objectSchema, obj->_row); RLMAccessorContext c(obj); RLMTranslateError([&] { o.set_property_value(c, getProperty(obj, prop).name, val ?: NSNull.null); }); } id RLMDynamicGet(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop) { if (auto accessor = prop.swiftAccessor; accessor && [obj isKindOfClass:obj->_objectSchema.objectClass]) { return RLMCoerceToNil([accessor get:prop on:obj]); } if (!obj->_realm) { return [obj valueForKey:prop.name]; } realm::Object o(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row); RLMAccessorContext c(obj); c.currentProperty = prop; return RLMTranslateError([&] { return RLMCoerceToNil(o.get_property_value(c, getProperty(obj, prop))); }); } id RLMDynamicGetByName(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained NSString *const propName) { RLMProperty *prop = obj->_objectSchema[propName]; if (!prop) { @throw RLMException(@"Invalid property name '%@' for class '%@'.", propName, obj->_objectSchema.className); } return RLMDynamicGet(obj, prop); } #pragma mark - Swift property getters and setter #define REALM_SWIFT_PROPERTY_ACCESSOR(objc, swift, rlmtype) \ objc RLMGetSwiftProperty##swift(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { \ return get(obj, key); \ } \ objc RLMGetSwiftProperty##swift##Optional(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, bool *gotValue) { \ return getOptional(obj, key, gotValue); \ } \ void RLMSetSwiftProperty##swift(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, objc value) { \ RLMVerifyAttached(obj); \ kvoSetValue(obj, key, value); \ } REALM_FOR_EACH_SWIFT_PRIMITIVE_TYPE(REALM_SWIFT_PROPERTY_ACCESSOR) #undef REALM_SWIFT_PROPERTY_ACCESSOR #define REALM_SWIFT_PROPERTY_ACCESSOR(objc, swift, rlmtype) \ void RLMSetSwiftProperty##swift(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, objc *value) { \ RLMVerifyAttached(obj); \ kvoSetValue(obj, key, value); \ } REALM_FOR_EACH_SWIFT_OBJECT_TYPE(REALM_SWIFT_PROPERTY_ACCESSOR) #undef REALM_SWIFT_PROPERTY_ACCESSOR NSString *RLMGetSwiftPropertyString(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } NSData *RLMGetSwiftPropertyData(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } NSDate *RLMGetSwiftPropertyDate(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } NSUUID *RLMGetSwiftPropertyUUID(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed>(obj, key); } RLMObjectId *RLMGetSwiftPropertyObjectId(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed>(obj, key); } RLMDecimal128 *RLMGetSwiftPropertyDecimal128(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } RLMArray *RLMGetSwiftPropertyArray(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return (RLMArray *)getCollection(obj, key); } RLMSet *RLMGetSwiftPropertySet(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getCollection(obj, key); } RLMDictionary *RLMGetSwiftPropertyMap(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return (RLMDictionary *)getCollection(obj, key); } void RLMSetSwiftPropertyNil(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { RLMVerifyInWriteTransaction(obj); if (getProperty(obj, key).type == realm::PropertyType::Object) { kvoSetValue(obj, key, (RLMObjectBase *)nil); } else { // The type used here is arbitrary; it simply needs to be any non-object type kvoSetValue(obj, key, (NSNumber *)nil); } } void RLMSetSwiftPropertyObject(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, __unsafe_unretained RLMObjectBase *const target) { kvoSetValue(obj, key, target); } RLMObjectBase *RLMGetSwiftPropertyObject(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } void RLMSetSwiftPropertyAny(__unsafe_unretained RLMObjectBase *const obj, uint16_t key, __unsafe_unretained id const value) { kvoSetValue(obj, key, value); } id RLMGetSwiftPropertyAny(__unsafe_unretained RLMObjectBase *const obj, uint16_t key) { return getBoxed(obj, key); } #pragma mark - RLMAccessorContext RLMAccessorContext::~RLMAccessorContext() = default; RLMAccessorContext::RLMAccessorContext(RLMAccessorContext& parent, realm::Obj const& obj, realm::Property const& property) : _realm(parent._realm) , _info(property.type == realm::PropertyType::Object ? parent._info.linkTargetType(property) : parent._info) , _parentObject(obj) , _parentObjectInfo(&parent._info) , _colKey(property.column_key) { } RLMAccessorContext::RLMAccessorContext(__unsafe_unretained RLMObjectBase *const parent, const realm::Property *prop) : _realm(parent->_realm) , _info(prop && prop->type == realm::PropertyType::Object ? parent->_info->linkTargetType(*prop) : *parent->_info) , _parentObject(parent->_row) , _parentObjectInfo(parent->_info) , _colKey(prop ? prop->column_key : ColKey{}) { } RLMAccessorContext::RLMAccessorContext(__unsafe_unretained RLMObjectBase *const parent, realm::ColKey col) : _realm(parent->_realm) , _info(_realm->_info[parent->_info->propertyForTableColumn(col).objectClassName]) , _parentObject(parent->_row) , _parentObjectInfo(parent->_info) , _colKey(col) { } RLMAccessorContext::RLMAccessorContext(RLMClassInfo& info) : _realm(info.realm), _info(info) { } RLMAccessorContext::RLMAccessorContext(RLMClassInfo& parentInfo, RLMClassInfo& info, __unsafe_unretained RLMProperty *const property) : _realm(info.realm) , _info(info) , _parentObjectInfo(&parentInfo) , currentProperty(property) { } id RLMAccessorContext::defaultValue(__unsafe_unretained NSString *const key) { if (!_defaultValues) { _defaultValues = RLMDefaultValuesForObjectSchema(_info.rlmObjectSchema); } return _defaultValues[key]; } id RLMAccessorContext::propertyValue(id obj, size_t propIndex, __unsafe_unretained RLMProperty *const prop) { obj = RLMBridgeSwiftValue(obj) ?: obj; // Property value from an NSArray if ([obj respondsToSelector:@selector(objectAtIndex:)]) { return propIndex < [obj count] ? [obj objectAtIndex:propIndex] : nil; } // Property value from an NSDictionary if ([obj respondsToSelector:@selector(objectForKey:)]) { return [obj objectForKey:prop.name]; } // Property value from an instance of this object type if ([obj isKindOfClass:_info.rlmObjectSchema.objectClass] && prop.swiftAccessor) { return [prop.swiftAccessor get:prop on:obj]; } // Property value from some object that's KVC-compatible id value = RLMValidatedValueForProperty(obj, [obj respondsToSelector:prop.getterSel] ? prop.getterName : prop.name, _info.rlmObjectSchema.className); return value ?: NSNull.null; } realm::Obj RLMAccessorContext::create_embedded_object() { if (!_parentObject) { @throw RLMException(@"Embedded objects cannot be created directly"); } return _parentObject.create_and_set_linked_object(_colKey); } id RLMAccessorContext::box(realm::Mixed v) { auto property = currentProperty ?: _info.propertyForTableColumn(_colKey); // Property and ParentObject are only passed for List and Dictionary boxing return RLMMixedToObjc(v, _realm, &_info, property, _parentObject); } id RLMAccessorContext::box(realm::List&& l) { REALM_ASSERT(_parentObjectInfo); auto property = currentProperty ?: _info.propertyForTableColumn(_colKey); REALM_ASSERT(property); return [[RLMManagedArray alloc] initWithBackingCollection:std::move(l) parentInfo:_parentObjectInfo property:property]; } id RLMAccessorContext::box(realm::object_store::Set&& s) { REALM_ASSERT(_parentObjectInfo); REALM_ASSERT(currentProperty); return [[RLMManagedSet alloc] initWithBackingCollection:std::move(s) parentInfo:_parentObjectInfo property:currentProperty]; } id RLMAccessorContext::box(realm::object_store::Dictionary&& d) { REALM_ASSERT(_parentObjectInfo); auto property = currentProperty ? currentProperty : _info.propertyForTableColumn(_colKey); REALM_ASSERT(property); return [[RLMManagedDictionary alloc] initWithBackingCollection:std::move(d) parentInfo:_parentObjectInfo property:property]; } id RLMAccessorContext::box(realm::Object&& o) { REALM_ASSERT(currentProperty); return RLMCreateObjectAccessor(_info.linkTargetType(currentProperty.index), o.get_obj()); } id RLMAccessorContext::box(realm::Obj&& r) { if (!currentProperty) { // If currentProperty is set, then we're reading from a Collection and // that reported an audit read for us. If not, we need to report the // audit read. This happens automatically when creating a // `realm::Object`, but our object accessors don't wrap that type. realm::Object(_realm->_realm, *_info.objectSchema, r, _parentObject, _colKey); } return RLMCreateObjectAccessor(_info, std::move(r)); } id RLMAccessorContext::box(realm::Results&& r) { REALM_ASSERT(currentProperty); return [RLMResults resultsWithObjectInfo:_realm->_info[currentProperty.objectClassName] results:std::move(r)]; } using realm::ObjKey; using realm::CreatePolicy; template static T *bridged(__unsafe_unretained id const value) { return [value isKindOfClass:[T class]] ? value : RLMBridgeSwiftValue(value); } template<> realm::Timestamp RLMStatelessAccessorContext::unbox(__unsafe_unretained id const value) { id v = RLMCoerceToNil(value); return RLMTimestampForNSDate(bridged(v)); } template<> bool RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return [bridged(v) boolValue]; } template<> double RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return [bridged(v) doubleValue]; } template<> float RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return [bridged(v) floatValue]; } template<> long long RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return [bridged(v) longLongValue]; } template<> realm::BinaryData RLMStatelessAccessorContext::unbox(id v) { v = RLMCoerceToNil(v); return RLMBinaryDataForNSData(bridged(v)); } template<> realm::StringData RLMStatelessAccessorContext::unbox(id v) { v = RLMCoerceToNil(v); return RLMStringDataWithNSString(bridged(v)); } template<> realm::Decimal128 RLMStatelessAccessorContext::unbox(id v) { return RLMObjcToDecimal128(v); } template<> realm::ObjectId RLMStatelessAccessorContext::unbox(id v) { return bridged(v).value; } template<> realm::UUID RLMStatelessAccessorContext::unbox(id v) { return RLMObjcToUUID(bridged(v)); } template<> realm::Mixed RLMAccessorContext::unbox(__unsafe_unretained id v, CreatePolicy p, ObjKey) { return RLMObjcToMixed(v, _realm, p); } template static auto toOptional(__unsafe_unretained id const value) { id v = RLMCoerceToNil(value); return v ? realm::util::make_optional(RLMStatelessAccessorContext::unbox(v)) : realm::util::none; } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } template<> std::optional RLMStatelessAccessorContext::unbox(__unsafe_unretained id const v) { return toOptional(v); } std::pair RLMAccessorContext::createObject(id value, realm::CreatePolicy policy, bool forceCreate, ObjKey existingKey) { if (!value || value == NSNull.null) { @throw RLMException(@"Must provide a non-nil value."); } if ([value isKindOfClass:[NSArray class]] && [value count] > _info.objectSchema->persisted_properties.size()) { @throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).", (unsigned long long)[value count], (unsigned long long)_info.objectSchema->persisted_properties.size()); } RLMObjectBase *objBase = RLMDynamicCast(value); realm::Obj obj, *outObj = nullptr; bool requiresSwiftUIObservers = false; if (objBase) { if (objBase.isInvalidated) { if (policy.create && !policy.copy) { @throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted"); } else { @throw RLMException(@"Object has been deleted or invalidated."); } } if (policy.copy) { if (policy.update || !forceCreate) { // create(update: true) is a no-op when given an object already in // the Realm which is of the correct type if (objBase->_realm == _realm && objBase->_row.get_table() == _info.table() && !_info.table()->is_embedded()) { return {objBase->_row, true}; } } // Otherwise we copy the object objBase = nil; } else { outObj = &objBase->_row; // add() on an object already managed by this Realm is a no-op if (objBase->_realm == _realm) { return {objBase->_row, true}; } if (!policy.create) { return {realm::Obj(), false}; } if (objBase->_realm) { @throw RLMException(@"Object is already managed by another Realm. Use create instead to copy it into this Realm."); } if (objBase->_observationInfo && objBase->_observationInfo->hasObservers()) { requiresSwiftUIObservers = [RLMSwiftUIKVO removeObserversFromObject:objBase]; if (!requiresSwiftUIObservers) { @throw RLMException(@"Cannot add an object with observers to a Realm"); } } REALM_ASSERT([objBase->_objectSchema.className isEqualToString:_info.rlmObjectSchema.className]); REALM_ASSERT([objBase isKindOfClass:_info.rlmObjectSchema.unmanagedClass]); objBase->_info = &_info; objBase->_realm = _realm; objBase->_objectSchema = _info.rlmObjectSchema; } } if (!policy.create) { return {realm::Obj(), false}; } if (!outObj) { outObj = &obj; } try { realm::Object::create(*this, _realm->_realm, *_info.objectSchema, (id)value, policy, existingKey, outObj); } catch (std::exception const& e) { @throw RLMException(e); } if (objBase) { for (RLMProperty *prop in _info.rlmObjectSchema.properties) { // set the ivars for object and array properties to nil as otherwise the // accessors retain objects that are no longer accessible via the properties // this is mainly an issue when the object graph being added has cycles, // as it's not obvious that the user has to set the *ivars* to nil to // avoid leaking memory if (prop.type == RLMPropertyTypeObject && !prop.swiftIvar) { ((void(*)(id, SEL, id))objc_msgSend)(objBase, prop.setterSel, nil); } } object_setClass(objBase, _info.rlmObjectSchema.accessorClass); RLMInitializeSwiftAccessor(objBase, true); } if (requiresSwiftUIObservers) { [RLMSwiftUIKVO addObserversToObject:objBase]; } return {*outObj, false}; } template<> realm::Obj RLMAccessorContext::unbox(__unsafe_unretained id const v, CreatePolicy policy, ObjKey key) { return createObject(v, policy, false, key).first; } void RLMAccessorContext::will_change(realm::Obj const& row, realm::Property const& prop) { auto obsInfo = RLMGetObservationInfo(nullptr, row.get_key(), _info); if (!_observationHelper) { if (obsInfo || prop.type == realm::PropertyType::Object) { _observationHelper = std::make_unique(_info.realm); } } if (_observationHelper) { _observationHelper->willChange(obsInfo, _info.propertyForTableColumn(prop.column_key).name); if (prop.type == realm::PropertyType::Object) { _observationHelper->trackDeletions(); } } } void RLMAccessorContext::did_change() { if (_observationHelper) { _observationHelper->didChange(); } } RLMOptionalId RLMAccessorContext::value_for_property(__unsafe_unretained id const obj, realm::Property const&, size_t propIndex) { auto prop = _info.rlmObjectSchema.properties[propIndex]; id value = propertyValue(obj, propIndex, prop); if (value) { RLMValidateValueForProperty(value, _info.rlmObjectSchema, prop); } return RLMOptionalId{value}; } RLMOptionalId RLMAccessorContext::default_value_for_property(realm::ObjectSchema const&, realm::Property const& prop) { return RLMOptionalId{defaultValue(@(prop.name.c_str()))}; } bool RLMStatelessAccessorContext::is_same_list(realm::List const& list, __unsafe_unretained id const v) noexcept { return [v respondsToSelector:@selector(isBackedByList:)] && [v isBackedByList:list]; } bool RLMStatelessAccessorContext::is_same_set(realm::object_store::Set const& set, __unsafe_unretained id const v) noexcept { return [v respondsToSelector:@selector(isBackedBySet:)] && [v isBackedBySet:set]; } bool RLMStatelessAccessorContext::is_same_dictionary(realm::object_store::Dictionary const& dict, __unsafe_unretained id const v) noexcept { return [v respondsToSelector:@selector(isBackedByDictionary:)] && [v isBackedByDictionary:dict]; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation RLMManagedPropertyAccessor // Most types don't need to distinguish between promote and init so provide a default + (void)promote:(RLMProperty *)property on:(RLMObjectBase *)parent { [self initialize:property on:parent]; } @end #pragma clang diagnostic pop ================================================ FILE: Realm/RLMArray.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObject, RLMResults; /** `RLMArray` is the container type in Realm used to define to-many relationships. Unlike an `NSArray`, `RLMArray`s hold a single type, specified by the `objectClassName` property. This is referred to in these docs as the “type” of the array. When declaring an `RLMArray` property, the type must be marked as conforming to a protocol by the same name as the objects it should contain (see the `RLM_COLLECTION_TYPE` macro). In addition, the property can be declared using Objective-C generics for better compile-time type safety. RLM_COLLECTION_TYPE(ObjectType) ... @property RLMArray *arrayOfObjectTypes; `RLMArray`s can be queried with the same predicates as `RLMObject` and `RLMResult`s. `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed, or can be obtained by querying a Realm. ### Key-Value Observing `RLMArray` supports array key-value observing on `RLMArray` properties on `RLMObject` subclasses, and the `invalidated` property on `RLMArray` instances themselves is key-value observing compliant when the `RLMArray` is attached to a managed `RLMObject` (`RLMArray`s on unmanaged `RLMObject`s will never become invalidated). Because `RLMArray`s are attached to the object which they are a property of, they do not require using the mutable collection proxy objects from `-mutableArrayValueForKey:` or KVC-compatible mutation methods on the containing object. Instead, you can call the mutation methods on the `RLMArray` directly. */ @interface RLMArray : NSObject #pragma mark - Properties /** The number of objects in the array. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The type of the objects in the array. */ @property (nonatomic, readonly, assign) RLMPropertyType type; /** Indicates whether the objects in the collection can be `nil`. */ @property (nonatomic, readonly, getter = isOptional) BOOL optional; /** The class name of the objects contained in the array. Will be `nil` if `type` is not RLMPropertyTypeObject. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** The Realm which manages the array. Returns `nil` for unmanaged arrays. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** Indicates if the array can no longer be accessed. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; /** Indicates if the array is frozen. Frozen arrays are immutable and can be accessed from any thread. Frozen arrays are created by calling `-freeze` on a managed live array. Unmanaged arrays are never frozen. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Accessing Objects from an Array /** Returns the object at the index specified. @param index The index to look up. @return An object of the type contained in the array. */ - (RLMObjectType)objectAtIndex:(NSUInteger)index; /** Returns an array containing the objects in the array at the indexes specified by a given index set. `nil` will be returned if the index set contains an index out of the arrays bounds. @param indexes The indexes in the array to retrieve objects from. @return The objects at the specified indexes. */ - (nullable NSArray *)objectsAtIndexes:(NSIndexSet *)indexes; /** Returns the first object in the array. Returns `nil` if called on an empty array. @return An object of the type contained in the array. */ - (nullable RLMObjectType)firstObject; /** Returns the last object in the array. Returns `nil` if called on an empty array. @return An object of the type contained in the array. */ - (nullable RLMObjectType)lastObject; #pragma mark - Adding, Removing, and Replacing Objects in an Array /** Adds an object to the end of the array. @warning This method may only be called during a write transaction. @param object An object of the type contained in the array. */ - (void)addObject:(RLMObjectType)object; /** Adds an array of objects to the end of the array. @warning This method may only be called during a write transaction. @param objects An enumerable object such as `NSArray` or `RLMResults` which contains objects of the same class as the array. */ - (void)addObjects:(id)objects; /** Inserts an object at the given index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param anObject An object of the type contained in the array. @param index The index at which to insert the object. */ - (void)insertObject:(RLMObjectType)anObject atIndex:(NSUInteger)index; /** Removes an object at the given index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index The array index identifying the object to be removed. */ - (void)removeObjectAtIndex:(NSUInteger)index; /** Removes the last object in the array. This is a no-op if the array is already empty. @warning This method may only be called during a write transaction. */ - (void)removeLastObject; /** Removes all objects from the array. @warning This method may only be called during a write transaction. */ - (void)removeAllObjects; /** Replaces an object at the given index with a new object. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index The index of the object to be replaced. @param anObject An object (of the same type as returned from the `objectClassName` selector). */ - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(RLMObjectType)anObject; /** Moves the object at the given source index to the given destination index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param sourceIndex The index of the object to be moved. @param destinationIndex The index to which the object at `sourceIndex` should be moved. */ - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex; /** Exchanges the objects in the array at given indices. Throws an exception if either index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index1 The index of the object which should replace the object at index `index2`. @param index2 The index of the object which should replace the object at index `index1`. */ - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2; #pragma mark - Querying an Array /** Returns the index of an object in the array. Returns `NSNotFound` if the object is not found in the array. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(RLMObjectType)object; /** Returns the index of the first object in the array matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the array. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the array matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the array. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; /** Returns all the objects matching the given predicate in the array. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all the objects matching the given predicate in the array. @param predicate The predicate with which to filter the objects. @return An `RLMResults` of objects that match the given predicate */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from the array. @param keyPath The key path to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from the array. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /** Returns a distinct `RLMResults` from the array. @param keyPaths The key paths to distinct on. @return An `RLMResults` with the distinct values of the keypath(s). */ - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; /// :nodoc: - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; /// :nodoc: - (void)setObject:(RLMObjectType)newValue atIndexedSubscript:(NSUInteger)index; #pragma mark - Sectioning an Array /** Sorts and sections this collection from a given property key path, returning the result as an instance of `RLMSectionedResults`. @param keyPath The property key path to sort on. @param ascending The direction to sort in. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; /** Sorts and sections this collection from a given array of sort descriptors, returning the result as an instance of `RLMSectionedResults`. @param sortDescriptors An array of `RLMSortDescriptor`s to sort by. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @note The primary sort descriptor must be responsible for determining the section key. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; #pragma mark - Notifications /** Registers a block to be called each time the array changes. The block will be asynchronously called with the initial array, and then called again after each write transaction which changes any of the objects in the array, which objects are in the results, or the order of the objects in the array. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the array were added, removed or modified. If a write transaction did not modify any objects in the array, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. Person *person = [[Person allObjectsInRealm:realm] firstObject]; NSLog(@"person.dogs.count: %zu", person.dogs.count); // => 0 self.token = [person.dogs addNotificationBlock(RLMArray *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count) // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [person.dogs addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a non-frozen managed array. @param block The block to be called each time the array changes. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *_Nullable array, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the array changes. The block will be asynchronously called with the initial array, and then called again after each write transaction which changes any of the objects in the array, which objects are in the results, or the order of the objects in the array. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the array were added, removed or modified. If a write transaction did not modify any objects in the array, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *_Nullable array, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the array changes. The block will be asynchronously called with the initial array, and then called again after each write transaction which changes any of the objects in the array, which objects are in the results, or the order of the objects in the array. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the array were added, removed or modified. If a write transaction did not modify any objects in the array, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *_Nullable array, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the array changes. The block will be asynchronously called with the initial array, and then called again after each write transaction which changes any of the objects in the array, which objects are in the results, or the order of the objects in the array. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the array were added, removed or modified. If a write transaction did not modify any objects in the array, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *_Nullable array, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths __attribute__((warn_unused_result)); #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the objects in the array. NSNumber *min = [object.arrayProperty minOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The minimum value of the property, or `nil` if the array is empty. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects in the array. NSNumber *max = [object.arrayProperty maxOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose maximum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The maximum value of the property, or `nil` if the array is empty. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of the values of a given property over all the objects in the array. NSNumber *sum = [object.arrayProperty sumOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose values should be summed. Only properties of types `int`, `float`, and `double` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects in the array. NSNumber *average = [object.arrayProperty averageOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose average value should be calculated. Only properties of types `int`, `float`, and `double` are supported. @return The average value of the given property, or `nil` if the array is empty. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of this array. The frozen copy is an immutable array which contains the same data as this array currently contains, but will not update when writes are made to the containing Realm. Unlike live arrays, frozen arrays can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a managed array. @warning Holding onto a frozen array for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; #pragma mark - Unavailable Methods /** `-[RLMArray init]` is not available because `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed. */ - (instancetype)init __attribute__((unavailable("RLMArrays cannot be created directly"))); /** `+[RLMArray new]` is not available because `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed. */ + (instancetype)new __attribute__((unavailable("RLMArrays cannot be created directly"))); @end /// :nodoc: @interface RLMArray (Swift) // for use only in Swift class definitions - (instancetype)initWithObjectClassName:(NSString *)objectClassName; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMArray.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMArray_Private.hpp" #import "RLMObjectSchema.h" #import "RLMObjectStore.h" #import "RLMObject_Private.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMSchema_Private.h" #import "RLMSwiftSupport.h" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" @interface RLMArray () @end @implementation RLMArray { // Backing array when this instance is unmanaged @public NSMutableArray *_backingCollection; } #pragma mark - Initializers - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName keyType:(__unused RLMPropertyType)keyType { return [self initWithObjectClassName:objectClassName]; } - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional keyType:(__unused RLMPropertyType)keyType { return [self initWithObjectType:type optional:optional]; } - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName { REALM_ASSERT([objectClassName length] > 0); self = [super init]; if (self) { _objectClassName = objectClassName; _type = RLMPropertyTypeObject; } return self; } - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional { REALM_ASSERT(type != RLMPropertyTypeObject); self = [super init]; if (self) { _type = type; _optional = optional; } return self; } - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property { _parentObject = parentObject; _property = property; _isLegacyProperty = property.isLegacy; } #pragma mark - Convenience wrappers used for all RLMArray types - (void)addObjects:(id)objects { for (id obj in objects) { [self addObject:obj]; } } - (void)addObject:(id)object { [self insertObject:object atIndex:self.count]; } - (void)removeLastObject { NSUInteger count = self.count; if (count) { [self removeObjectAtIndex:count-1]; } } - (id)objectAtIndexedSubscript:(NSUInteger)index { return [self objectAtIndex:index]; } - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index { [self replaceObjectAtIndex:index withObject:newValue]; } - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; } - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args]; va_end(args); return index; } - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args { return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } #pragma clang diagnostic pop #pragma mark - Unmanaged RLMArray implementation - (RLMRealm *)realm { return nil; } - (id)firstObject { if (self.count) { return [self objectAtIndex:0]; } return nil; } - (id)lastObject { NSUInteger count = self.count; if (count) { return [self objectAtIndex:count-1]; } return nil; } - (id)objectAtIndex:(NSUInteger)index { validateArrayBounds(self, index); return [_backingCollection objectAtIndex:index]; } - (NSUInteger)count { return _backingCollection.count; } - (BOOL)isInvalidated { return NO; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(__unused NSUInteger)len { return RLMUnmanagedFastEnumerate(_backingCollection, state); } template static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) { if (!ar->_backingCollection) { ar->_backingCollection = [NSMutableArray new]; } if (RLMObjectBase *parent = ar->_parentObject) { NSIndexSet *indexes = is(); [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_property.name]; f(); [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_property.name]; } else { f(); } } static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; }); } static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; }); } static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return is; }); } void RLMArrayValidateMatchingObjectType(__unsafe_unretained RLMArray *const array, __unsafe_unretained id const value) { if (!value && !array->_optional) { @throw RLMException(@"Invalid nil value for array of '%@'.", array->_objectClassName ?: RLMTypeToString(array->_type)); } if (array->_type != RLMPropertyTypeObject) { if (!RLMValidateValue(value, array->_type, array->_optional, false, nil)) { @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.", value, [value class], RLMTypeToString(array->_type), array->_optional ? "?" : ""); } return; } auto object = RLMDynamicCast(value); if (!object) { return; } if (!object->_objectSchema) { @throw RLMException(@"Object cannot be inserted unless the schema is initialized. " "This can happen if you try to insert objects into a RLMArray / List from a default value or from an overriden unmanaged initializer (`init()`)."); } if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) { @throw RLMException(@"Object of type '%@' does not match RLMArray type '%@'.", object->_objectSchema.className, array->_objectClassName); } } static void validateArrayBounds(__unsafe_unretained RLMArray *const ar, NSUInteger index, bool allowOnePastEnd=false) { NSUInteger max = ar->_backingCollection.count + allowOnePastEnd; if (index >= max) { @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).", (unsigned long long)index, (unsigned long long)max); } } - (void)addObjectsFromArray:(NSArray *)array { for (id obj in array) { RLMArrayValidateMatchingObjectType(self, obj); } changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingCollection.count, array.count), ^{ [_backingCollection addObjectsFromArray:array]; }); } - (void)insertObject:(id)anObject atIndex:(NSUInteger)index { RLMArrayValidateMatchingObjectType(self, anObject); validateArrayBounds(self, index, true); changeArray(self, NSKeyValueChangeInsertion, index, ^{ [_backingCollection insertObject:anObject atIndex:index]; }); } - (void)insertObjects:(id)objects atIndexes:(NSIndexSet *)indexes { changeArray(self, NSKeyValueChangeInsertion, indexes, ^{ NSUInteger currentIndex = [indexes firstIndex]; for (RLMObject *obj in objects) { RLMArrayValidateMatchingObjectType(self, obj); [_backingCollection insertObject:obj atIndex:currentIndex]; currentIndex = [indexes indexGreaterThanIndex:currentIndex]; } }); } - (void)removeObjectAtIndex:(NSUInteger)index { validateArrayBounds(self, index); changeArray(self, NSKeyValueChangeRemoval, index, ^{ [_backingCollection removeObjectAtIndex:index]; }); } - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes { changeArray(self, NSKeyValueChangeRemoval, indexes, ^{ [_backingCollection removeObjectsAtIndexes:indexes]; }); } - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { RLMArrayValidateMatchingObjectType(self, anObject); validateArrayBounds(self, index); changeArray(self, NSKeyValueChangeReplacement, index, ^{ [_backingCollection replaceObjectAtIndex:index withObject:anObject]; }); } - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex { validateArrayBounds(self, sourceIndex); validateArrayBounds(self, destinationIndex); id original = _backingCollection[sourceIndex]; auto start = std::min(sourceIndex, destinationIndex); auto len = std::max(sourceIndex, destinationIndex) - start + 1; changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{ [_backingCollection removeObjectAtIndex:sourceIndex]; [_backingCollection insertObject:original atIndex:destinationIndex]; }); } - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 { validateArrayBounds(self, index1); validateArrayBounds(self, index2); changeArray(self, NSKeyValueChangeReplacement, ^{ [_backingCollection exchangeObjectAtIndex:index1 withObjectAtIndex:index2]; }, [=] { NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1]; [set addIndex:index2]; return set; }); } - (NSUInteger)indexOfObject:(id)object { RLMArrayValidateMatchingObjectType(self, object); if (!_backingCollection) { return NSNotFound; } if (_type != RLMPropertyTypeObject) { return [_backingCollection indexOfObject:object]; } NSUInteger index = 0; for (RLMObjectBase *cmp in _backingCollection) { if (RLMObjectBaseAreEqual(object, cmp)) { return index; } index++; } return NSNotFound; } - (void)removeAllObjects { changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingCollection.count), ^{ [_backingCollection removeAllObjects]; }); } - (void)replaceAllObjectsWithObjects:(NSArray *)objects { if (_backingCollection.count) { changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingCollection.count), ^{ [_backingCollection removeAllObjects]; }); } if (![objects respondsToSelector:@selector(count)] || !objects.count) { return; } changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(0, objects.count), ^{ for (id object in objects) { // object should always be non-nil since it's a value stored in a // NSArray, but as of Xcode 16.3 [Decimal128?] sometimes ends up // with nil instead of NSNull when bridged from Swift. [_backingCollection addObject:object ?: NSNull.null]; } }); } - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsWhere:predicateFormat args:args]; va_end(args); return results; } - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (RLMPropertyType)typeForProperty:(NSString *)propertyName { if ([propertyName isEqualToString:@"self"]) { return _type; } RLMObjectSchema *objectSchema; if (_backingCollection.count) { objectSchema = [_backingCollection[0] objectSchema]; } else { objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName]; } return RLMValidatedProperty(objectSchema, propertyName).type; } - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel { // Although delegating to valueForKeyPath: here would allow to support // nested key paths as well, limiting functionality gives consistency // between unmanaged and managed arrays. if ([key rangeOfString:@"."].location != NSNotFound) { @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); } bool allowDate = false; bool sum = false; if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) { allowDate = true; } else if ([op isEqualToString:@"@sum"]) { sum = true; } else if (![op isEqualToString:@"@avg"]) { // Just delegate to NSArray for all other operators return [_backingCollection valueForKeyPath:[op stringByAppendingPathExtension:key]]; } RLMPropertyType type = [self typeForProperty:key]; if (!canAggregate(type, allowDate)) { NSString *method = sel ? NSStringFromSelector(sel) : op; if (_type == RLMPropertyTypeObject) { @throw RLMException(@"%@: is not supported for %@ property '%@.%@'", method, RLMTypeToString(type), _objectClassName, key); } else { @throw RLMException(@"%@ is not supported for %@%s array", method, RLMTypeToString(_type), _optional ? "?" : ""); } } NSArray *values = [key isEqualToString:@"self"] ? _backingCollection : [_backingCollection valueForKey:key]; if (_optional) { // Filter out NSNull values to match our behavior on managed arrays NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { return obj != NSNull.null; }]; if (nonnull.count < values.count) { values = [values objectsAtIndexes:nonnull]; } } id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]]; return sum && !result ? @0 : result; } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath characterAtIndex:0] != '@') { return _backingCollection ? [_backingCollection valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath]; } if (!_backingCollection) { _backingCollection = [NSMutableArray new]; } NSUInteger dot = [keyPath rangeOfString:@"."].location; if (dot == NSNotFound) { return [_backingCollection valueForKeyPath:keyPath]; } NSString *op = [keyPath substringToIndex:dot]; NSString *key = [keyPath substringFromIndex:dot + 1]; return [self aggregateProperty:key operation:op method:nil]; } - (id)valueForKey:(NSString *)key { if ([key isEqualToString:RLMInvalidatedKey]) { return @NO; // Unmanaged arrays are never invalidated } if (!_backingCollection) { _backingCollection = [NSMutableArray new]; } return [_backingCollection valueForKey:key]; } - (void)setValue:(id)value forKey:(NSString *)key { if ([key isEqualToString:@"self"]) { RLMArrayValidateMatchingObjectType(self, value); for (NSUInteger i = 0, count = _backingCollection.count; i < count; ++i) { _backingCollection[i] = value; } return; } else if (_type == RLMPropertyTypeObject) { [_backingCollection setValue:value forKey:key]; } else { [self setValue:value forUndefinedKey:key]; } } - (id)minOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@min" method:_cmd]; } - (id)maxOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@max" method:_cmd]; } - (id)sumOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@sum" method:_cmd]; } - (id)averageOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@avg" method:_cmd]; } - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { if (!_backingCollection) { return NSNotFound; } return [_backingCollection indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { return [predicate evaluateWithObject:obj]; }]; } - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { if ([indexes indexGreaterThanOrEqualToIndex:self.count] != NSNotFound) { return nil; } return [_backingCollection objectsAtIndexes:indexes] ?: @[]; } - (BOOL)isEqual:(id)object { if (auto array = RLMDynamicCast(object)) { if (array.realm) { return NO; } NSArray *otherCollection = array->_backingCollection; return (_backingCollection.count == 0 && otherCollection.count == 0) || [_backingCollection isEqual:otherCollection]; } return NO; } - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMValidateArrayObservationKey(keyPath, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } #pragma mark - Methods unsupported on unmanaged RLMArray instances #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (instancetype)freeze { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } - (instancetype)thaw { @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm"); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); } - (id)objectiveCMetadata { REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(id)metadata realm:(RLMRealm *)realm { REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`"); } #pragma clang diagnostic pop // unused parameter warning #pragma mark - Superclass Overrides - (NSString *)description { return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; } - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth); } #pragma mark - Key Path Strings - (NSString *)propertyKey { return _property.name; } @end @implementation RLMSortDescriptor + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init]; desc->_keyPath = keyPath; desc->_ascending = ascending; return desc; } - (instancetype)reversedSortDescriptor { return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending]; } @end ================================================ FILE: Realm/RLMArray_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import @class RLMObjectBase, RLMProperty; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMArray () - (instancetype)initWithObjectClassName:(NSString *)objectClassName; - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional; - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth; - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; - (void)replaceAllObjectsWithObjects:(NSArray *)objects; // YES if the property is declared with old property syntax. @property (nonatomic, readonly) BOOL isLegacyProperty; // The name of the property which this collection represents @property (nonatomic, readonly) NSString *propertyKey; @end @interface RLMManagedArray : RLMArray - (instancetype)initWithParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; @end void RLMArrayValidateMatchingObjectType(RLMArray *array, id value); RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMArray_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMArray_Private.h" #import "RLMCollection_Private.hpp" #import "RLMResults_Private.hpp" #import namespace realm { class Results; } @class RLMObjectBase, RLMObjectSchema, RLMProperty; class RLMClassInfo; class RLMObservationInfo; @interface RLMArray () { @protected NSString *_objectClassName; BOOL _optional; @public // The property which this RLMArray represents RLMProperty *_property; __weak RLMObjectBase *_parentObject; } @end @interface RLMManagedArray () - (RLMManagedArray *)initWithBackingCollection:(realm::List)list parentInfo:(RLMClassInfo *)parentInfo property:(RLMProperty *)property; - (RLMManagedArray *)initWithParent:(realm::Obj)parent property:(RLMProperty *)property parentInfo:(RLMClassInfo&)info; - (bool)isBackedByList:(realm::List const&)list; // deletes all objects in the RLMArray from their containing realms - (void)deleteObjectsFromRealm; @end void RLMValidateArrayObservationKey(NSString *keyPath, RLMArray *array); // Initialize the observation info for an array if needed void RLMEnsureArrayObservationInfo(std::unique_ptr& info, NSString *keyPath, RLMArray *array, id observed); ================================================ FILE: Realm/RLMAsyncTask.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** A task object which can be used to observe or cancel an async open. When a synchronized Realm is opened asynchronously, the latest state of the Realm is downloaded from the server before the completion callback is invoked. This task object can be used to observe the state of the download or to cancel it. This should be used instead of trying to observe the download via the sync session as the sync session itself is created asynchronously, and may not exist yet when -[RLMRealm asyncOpenWithConfiguration:completion:] returns. */ NS_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe @interface RLMAsyncOpenTask : NSObject /** Cancel the asynchronous open. Any download in progress will be cancelled, and the completion block for this async open will never be called. If multiple async opens on the same Realm are happening concurrently, all other opens will fail with the error "operation cancelled". */ - (void)cancel; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMAsyncTask.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 "RLMAsyncTask_Private.h" #import "RLMError_Private.hpp" #import "RLMRealm_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMScheduler.h" #import "RLMUtil.hpp" #import #import static dispatch_queue_t s_async_open_queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue", DISPATCH_QUEUE_CONCURRENT); void RLMSetAsyncOpenQueue(dispatch_queue_t queue) { s_async_open_queue = queue; } static NSError *s_canceledError = [NSError errorWithDomain:NSPOSIXErrorDomain code:ECANCELED userInfo:@{ NSLocalizedDescriptionKey: @"Operation canceled" }]; __attribute__((objc_direct_members)) @implementation RLMAsyncOpenTask { RLMUnfairMutex _mutex; bool _cancel; RLMRealmConfiguration *_configuration; RLMScheduler *_scheduler; void (^_completion)(NSError *); RLMRealm *_backgroundRealm; } - (void)cancel { std::lock_guard lock(_mutex); _cancel = true; } - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)scheduler { if (!(self = [super init])) { return self; } // Copying the configuration here as the user could potentially modify // the config after calling async open _configuration = configuration.copy; _scheduler = scheduler; return self; } - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)confinement completion:(RLMAsyncOpenRealmCallback)completion { self = [self initWithConfiguration:configuration confinedTo:confinement]; [self waitForOpen:completion]; return self; } - (void)waitForOpen:(RLMAsyncOpenRealmCallback)completion { __weak auto weakSelf = self; [self waitWithCompletion:^(NSError *error) { RLMRealm *realm; if (auto self = weakSelf) { realm = self->_localRealm; self->_localRealm = nil; } completion(realm, error); }]; } - (void)waitWithCompletion:(void (^)(NSError *))completion { std::lock_guard lock(_mutex); _completion = completion; if (_cancel) { return [self reportError:s_canceledError]; } dispatch_async(s_async_open_queue, ^{ @autoreleasepool { [self startAsyncOpen]; } }); } - (void)startAsyncOpen { std::unique_lock lock(_mutex); if ([self checkCancellation]) { return; } NSError *error; @autoreleasepool { // Holding onto the Realm so that opening the final Realm on the target // scheduler can hit the fast path _backgroundRealm = [RLMRealm realmWithConfiguration:_configuration confinedTo:RLMScheduler.currentRunLoop error:&error]; if (error) { return [self reportError:error]; } } if ([self checkCancellation]) { return; } [_scheduler invoke:^{ [self openFinalRealmAndCallCompletion]; }]; } - (void)openFinalRealmAndCallCompletion { std::unique_lock lock(_mutex); @autoreleasepool { if ([self checkCancellation]) { return; } if (!_completion) { return; } NSError *error; auto completion = _completion; // It should not actually be possible for this to fail _localRealm = [RLMRealm realmWithConfiguration:_configuration confinedTo:_scheduler error:&error]; [self releaseResources]; lock.unlock(); completion(error); } } - (bool)checkCancellation { if (_cancel && _completion) { [self reportError:s_canceledError]; } return _cancel; } - (void)reportException:(std::exception_ptr const&)err { try { std::rethrow_exception(err); } catch (realm::Exception const& e) { if (e.code() == realm::ErrorCodes::OperationAborted) { return [self reportError:s_canceledError]; } [self reportError:makeError(e)]; } catch (...) { NSError *error; RLMRealmTranslateException(&error); [self reportError:error]; } } - (void)reportError:(NSError *)error { if (!_completion || !_scheduler) { return; } auto completion = _completion; auto scheduler = _scheduler; [self releaseResources]; [scheduler invoke:^{ completion(error); }]; } - (void)releaseResources { _backgroundRealm = nil; _configuration = nil; _scheduler = nil; _completion = nil; } @end __attribute__((objc_direct_members)) @implementation RLMAsyncRefreshTask { RLMUnfairMutex _mutex; void (^_completion)(bool); bool _complete; bool _didRefresh; } - (void)complete:(bool)didRefresh { void (^completion)(bool); { std::lock_guard lock(_mutex); std::swap(completion, _completion); _complete = true; // If we're both cancelled and did complete a refresh then continue // to report true _didRefresh = _didRefresh || didRefresh; } if (completion) { completion(didRefresh); } } - (void)wait:(void (^)(bool))completion { bool didRefresh; { std::lock_guard lock(_mutex); if (!_complete) { _completion = completion; return; } didRefresh = _didRefresh; } completion(didRefresh); } + (RLMAsyncRefreshTask *)completedRefresh { static RLMAsyncRefreshTask *shared = [] { auto refresh = [[RLMAsyncRefreshTask alloc] init]; refresh->_complete = true; refresh->_didRefresh = true; return refresh; }(); return shared; } @end @implementation RLMAsyncWriteTask { // Mutex guards _realm and _completion RLMUnfairMutex _mutex; // _realm is non-nil only while waiting for an async write to begin. It is // set to `nil` when it either completes or is cancelled. RLMRealm *_realm; dispatch_block_t _completion; RLMAsyncTransactionId _id; } // No locking needed for these two as they have to be called before the // cancellation handler is set up - (instancetype)initWithRealm:(RLMRealm *)realm { if (self = [super init]) { _realm = realm; } return self; } - (void)setTransactionId:(RLMAsyncTransactionId)transactionID { _id = transactionID; } - (void)complete:(bool)cancel { // The swap-under-lock pattern is used to avoid invoking the callback with // a lock held dispatch_block_t completion; { std::lock_guard lock(_mutex); std::swap(completion, _completion); if (cancel) { // This is a no-op if cancellation is coming after the wait completed [_realm cancelAsyncTransaction:_id]; } _realm = nil; } if (completion) { completion(); } } - (void)wait:(void (^)())completion { { std::lock_guard lock(_mutex); // `_realm` being non-nil means it's neither completed nor been cancelled if (_realm) { _completion = completion; return; } } // It has either been completed or cancelled, so call the callback immediately completion(); } @end ================================================ FILE: Realm/RLMAsyncTask_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 #import "RLMRealm_Private.h" RLM_HEADER_AUDIT_BEGIN(nullability) @interface RLMAsyncOpenTask () @property (nonatomic, nullable) RLMRealm *localRealm; - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)confinement completion:(RLMAsyncOpenRealmCallback)completion __attribute__((objc_direct)); - (instancetype)initWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)confinement; - (void)waitWithCompletion:(void (^)(NSError *_Nullable))completion; - (void)waitForOpen:(RLMAsyncOpenRealmCallback)completion __attribute__((objc_direct)); @end // A cancellable task for beginning an async write NS_SWIFT_SENDABLE @interface RLMAsyncWriteTask : NSObject // Must only be called from within the Actor - (instancetype)initWithRealm:(RLMRealm *)realm; - (void)setTransactionId:(RLMAsyncTransactionId)transactionID; - (void)complete:(bool)cancel; // Can be called from any thread - (void)wait:(void (^)(void))completion; @end typedef void (^RLMAsyncRefreshCompletion)(bool); // A cancellable task for refreshing a Realm NS_SWIFT_SENDABLE @interface RLMAsyncRefreshTask : NSObject - (void)complete:(bool)didRefresh; - (void)wait:(RLMAsyncRefreshCompletion)completion; + (RLMAsyncRefreshTask *)completedRefresh; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMClassInfo.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 #import #import #import #import namespace realm { class ObjectSchema; class Schema; struct Property; struct ColKey; struct TableKey; } class RLMObservationInfo; @class RLMRealm, RLMSchema, RLMObjectSchema, RLMProperty; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) namespace std { // Add specializations so that NSString can be used as the key for hash containers template<> struct hash { size_t operator()(__unsafe_unretained NSString *const str) const { return [str hash]; } }; template<> struct equal_to { bool operator()(__unsafe_unretained NSString * lhs, __unsafe_unretained NSString *rhs) const { return [lhs isEqualToString:rhs]; } }; } // The per-RLMRealm object schema information which stores the cached table // reference, handles table column lookups, and tracks observed objects class RLMClassInfo { public: RLMClassInfo(RLMRealm *, RLMObjectSchema *, const realm::ObjectSchema *); RLMClassInfo(RLMRealm *realm, RLMObjectSchema *rlmObjectSchema, std::unique_ptr objectSchema); __unsafe_unretained RLMRealm *const realm; __unsafe_unretained RLMObjectSchema *const rlmObjectSchema; const realm::ObjectSchema *const objectSchema; // Storage for the functionality in RLMObservation for handling indirect // changes to KVO-observed things std::vector observedObjects; // Get the table for this object type. Will return nullptr only if it's a // read-only Realm that is missing the table entirely. realm::TableRef table() const; // Get the RLMProperty for a given table column, or `nil` if it is a column // not used by the current schema RLMProperty *_Nullable propertyForTableColumn(realm::ColKey) const noexcept; // Get the RLMProperty that's used as the primary key, or `nil` if there is // no primary key for the current schema RLMProperty *_Nullable propertyForPrimaryKey() const noexcept; // Get the table column for the given property. The property must be a valid // persisted property. realm::ColKey tableColumn(NSString *propertyName) const; realm::ColKey tableColumn(RLMProperty *property) const; // Get the table column key for the given computed property. The property // must be a valid computed property. // Subscripting a `realm::ObjectSchema->computed_properties[property.index]` // does not return a valid colKey, unlike subscripting persisted_properties. // This method retrieves a valid column key for computed properties by // getting the opposite table column of the origin's "forward" link. realm::ColKey computedTableColumn(RLMProperty *property) const; // Get the info for the target of the link at the given property index. RLMClassInfo &linkTargetType(size_t propertyIndex); // Get the info for the target of the given property RLMClassInfo &linkTargetType(realm::Property const& property); // Get the corresponding ClassInfo for the given Realm RLMClassInfo &resolve(RLMRealm *); // Return true if the RLMObjectSchema is for a Swift class bool isSwiftClass() const noexcept; // Returns true if this was a dynamically added type bool isDynamic() const noexcept; // KeyPathFromString converts a string keypath to a vector of key // pairs to be used for deep change checking across links. std::optional>>> keyPathArrayFromStringArray(NSArray *keyPaths) const; private: // If the ObjectSchema is not owned by the realm instance // we need to manually manage the ownership of the object. std::unique_ptr dynamicObjectSchema; [[maybe_unused]] RLMObjectSchema *_Nullable dynamicRLMObjectSchema; }; // A per-RLMRealm object schema map which stores RLMClassInfo keyed on the name class RLMSchemaInfo { using impl = std::unordered_map; public: RLMSchemaInfo() = default; RLMSchemaInfo(RLMRealm *realm); RLMSchemaInfo clone(realm::Schema const& source_schema, RLMRealm *target_realm); // Look up by name, throwing if it's not present RLMClassInfo& operator[](NSString *name); // Look up by table key, return none if its not present. RLMClassInfo* operator[](realm::TableKey tableKey); // Emplaces a locally derived object schema into RLMSchemaInfo. This is used // when creating objects dynamically that are not registered in the Cocoa schema. // Note: `RLMClassInfo` assumes ownership of `schema`. void appendDynamicObjectSchema(std::unique_ptr schema, RLMObjectSchema *objectSchema, RLMRealm *const target_realm); impl::iterator begin() noexcept; impl::iterator end() noexcept; impl::const_iterator begin() const noexcept; impl::const_iterator end() const noexcept; private: std::unordered_map m_objects; }; RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMClassInfo.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMClassInfo.hpp" #import "RLMRealm_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMSchema.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMUtil.hpp" #import #import #import #import #import using namespace realm; RLMClassInfo::RLMClassInfo(__unsafe_unretained RLMRealm *const realm, __unsafe_unretained RLMObjectSchema *const rlmObjectSchema, const realm::ObjectSchema *objectSchema) : realm(realm), rlmObjectSchema(rlmObjectSchema), objectSchema(objectSchema) { } RLMClassInfo::RLMClassInfo(RLMRealm *realm, RLMObjectSchema *rlmObjectSchema, std::unique_ptr schema) : realm(realm) , rlmObjectSchema(rlmObjectSchema) , objectSchema(&*schema) , dynamicObjectSchema(std::move(schema)) , dynamicRLMObjectSchema(rlmObjectSchema) { } realm::TableRef RLMClassInfo::table() const { if (auto key = objectSchema->table_key) { return realm.group.get_table(objectSchema->table_key); } return nullptr; } RLMProperty *RLMClassInfo::propertyForTableColumn(ColKey col) const noexcept { auto const& props = objectSchema->persisted_properties; for (size_t i = 0; i < props.size(); ++i) { if (props[i].column_key == col) { return rlmObjectSchema.properties[i]; } } return nil; } RLMProperty *RLMClassInfo::propertyForPrimaryKey() const noexcept { return rlmObjectSchema.primaryKeyProperty; } realm::ColKey RLMClassInfo::tableColumn(NSString *propertyName) const { return tableColumn(RLMValidatedProperty(rlmObjectSchema, propertyName)); } realm::ColKey RLMClassInfo::tableColumn(RLMProperty *property) const { return objectSchema->persisted_properties[property.index].column_key; } realm::ColKey RLMClassInfo::computedTableColumn(RLMProperty *property) const { // Retrieve the table key and class info for the origin property // that corresponds to the target property. RLMClassInfo& originInfo = realm->_info[property.objectClassName]; TableKey originTableKey = originInfo.objectSchema->table_key; TableRef originTable = realm.group.get_table(originTableKey); // Get the column key for origin's forward link that links to the property on the target. ColKey forwardLinkKey = originInfo.tableColumn(property.linkOriginPropertyName); // The column key opposite of the origin's forward link is the target's backlink property. return originTable->get_opposite_column(forwardLinkKey); } RLMClassInfo &RLMClassInfo::linkTargetType(size_t propertyIndex) { return realm->_info[rlmObjectSchema.properties[propertyIndex].objectClassName]; } RLMClassInfo &RLMClassInfo::linkTargetType(realm::Property const& property) { REALM_ASSERT(property.type == PropertyType::Object); return linkTargetType(&property - &objectSchema->persisted_properties[0]); } RLMClassInfo &RLMClassInfo::resolve(__unsafe_unretained RLMRealm *const realm) { return realm->_info[rlmObjectSchema.className]; } bool RLMClassInfo::isSwiftClass() const noexcept { return rlmObjectSchema.isSwiftClass; } bool RLMClassInfo::isDynamic() const noexcept { return !!dynamicObjectSchema; } static KeyPath keyPathFromString(RLMRealm *realm, RLMSchema *schema, const RLMClassInfo *info, RLMObjectSchema *rlmObjectSchema, NSString *keyPath) { KeyPath keyPairs; for (NSString *component in [keyPath componentsSeparatedByString:@"."]) { RLMProperty *property = rlmObjectSchema[component]; if (!property) { throw RLMException(@"Invalid property name: property '%@' not found in object of type '%@'", component, rlmObjectSchema.className); } TableKey tk = info->objectSchema->table_key; ColKey ck; if (property.type == RLMPropertyTypeObject) { ck = info->tableColumn(property.name); info = &realm->_info[property.objectClassName]; rlmObjectSchema = schema[property.objectClassName]; } else if (property.type == RLMPropertyTypeLinkingObjects) { ck = info->computedTableColumn(property); info = &realm->_info[property.objectClassName]; rlmObjectSchema = schema[property.objectClassName]; } else { ck = info->tableColumn(property.name); } keyPairs.emplace_back(tk, ck); } return keyPairs; } std::optional RLMClassInfo::keyPathArrayFromStringArray(NSArray *keyPaths) const { std::optional keyPathArray; if (keyPaths) { keyPathArray.emplace(); for (NSString *keyPath in keyPaths) { keyPathArray->push_back(keyPathFromString(realm, realm.schema, this, rlmObjectSchema, keyPath)); } } return keyPathArray; } RLMSchemaInfo::impl::iterator RLMSchemaInfo::begin() noexcept { return m_objects.begin(); } RLMSchemaInfo::impl::iterator RLMSchemaInfo::end() noexcept { return m_objects.end(); } RLMSchemaInfo::impl::const_iterator RLMSchemaInfo::begin() const noexcept { return m_objects.begin(); } RLMSchemaInfo::impl::const_iterator RLMSchemaInfo::end() const noexcept { return m_objects.end(); } RLMClassInfo& RLMSchemaInfo::operator[](NSString *name) { auto it = m_objects.find(name); if (it == m_objects.end()) { @throw RLMException(@"Object type '%@' is not managed by the Realm. " @"If using a custom `objectClasses` / `objectTypes` array in your configuration, " @"add `%@` to the list of `objectClasses` / `objectTypes`.", name, name); } return *&it->second; } RLMClassInfo* RLMSchemaInfo::operator[](realm::TableKey key) { for (auto& [name, info] : m_objects) { if (info.objectSchema->table_key == key) return &info; } return nullptr; } RLMSchemaInfo::RLMSchemaInfo(RLMRealm *realm) { RLMSchema *rlmSchema = realm.schema; realm::Schema const& schema = realm->_realm->schema(); // rlmSchema can be larger due to multiple classes backed by one table REALM_ASSERT(rlmSchema.objectSchema.count >= schema.size()); m_objects.reserve(schema.size()); for (RLMObjectSchema *rlmObjectSchema in rlmSchema.objectSchema) { auto it = schema.find(rlmObjectSchema.objectStoreName); if (it == schema.end()) { continue; } m_objects.emplace(std::piecewise_construct, std::forward_as_tuple(rlmObjectSchema.className), std::forward_as_tuple(realm, rlmObjectSchema, &*it)); } } RLMSchemaInfo RLMSchemaInfo::clone(realm::Schema const& source_schema, __unsafe_unretained RLMRealm *const target_realm) { RLMSchemaInfo info; info.m_objects.reserve(m_objects.size()); auto& schema = target_realm->_realm->schema(); REALM_ASSERT_DEBUG(schema == source_schema); for (auto& [name, class_info] : m_objects) { if (class_info.isDynamic()) { continue; } size_t idx = class_info.objectSchema - &*source_schema.begin(); info.m_objects.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(target_realm, class_info.rlmObjectSchema, &*schema.begin() + idx)); } return info; } void RLMSchemaInfo::appendDynamicObjectSchema(std::unique_ptr schema, RLMObjectSchema *objectSchema, __unsafe_unretained RLMRealm *const target_realm) { m_objects.emplace(std::piecewise_construct, std::forward_as_tuple(objectSchema.className), std::forward_as_tuple(target_realm, objectSchema, std::move(schema))); } ================================================ FILE: Realm/RLMCollection.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RLMValue; @class RLMRealm, RLMResults, RLMSortDescriptor, RLMNotificationToken, RLMCollectionChange, RLMSectionedResults; typedef NS_CLOSED_ENUM(int32_t, RLMPropertyType); /// A callback which is invoked on each element in the Results collection which returns the section key. typedef id _Nullable(^RLMSectionedResultsKeyBlock)(id); /** A homogenous collection of Realm-managed objects. Examples of conforming types include `RLMArray`, `RLMSet`, `RLMResults`, and `RLMLinkingObjects`. */ @protocol RLMCollection #pragma mark - Properties /** The number of objects in the collection. */ @property (nonatomic, readonly) NSUInteger count; /** The type of the objects in the collection. */ @property (nonatomic, readonly) RLMPropertyType type; /** Indicates whether the objects in the collection can be `nil`. */ @property (nonatomic, readonly, getter = isOptional) BOOL optional; /** The class name of the objects contained in the collection. Will be `nil` if `type` is not RLMPropertyTypeObject. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** The Realm which manages this collection, if any. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** Indicates if the collection is no longer valid. The collection becomes invalid if `invalidate` is called on the managing Realm. Unmanaged collections are never invalidated. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Accessing Objects from a Collection /** Returns the object at the index specified. @param index The index to look up. @return An object of the type contained in the collection. */ - (id)objectAtIndex:(NSUInteger)index; @optional /** Returns an array containing the objects in the collection at the indexes specified by a given index set. `nil` will be returned if the index set contains an index out of the collections bounds. @param indexes The indexes in the collection to retrieve objects from. @return The objects at the specified indexes. */ - (nullable NSArray *)objectsAtIndexes:(NSIndexSet *)indexes; /** Returns the first object in the collection. RLMSet is not ordered, and so for sets this will return an arbitrary object in the set. It is not guaraneed to be a different object from what `lastObject` gives even if the set has multiple objects in it. Returns `nil` if called on an empty collection. @return An object of the type contained in the collection. */ - (nullable id)firstObject; /** Returns the last object in the collection. RLMSet is not ordered, and so for sets this will return an arbitrary object in the set. It is not guaraneed to be a different object from what `firstObject` gives even if the set has multiple objects in it. Returns `nil` if called on an empty collection. @return An object of the type contained in the collection. */ - (nullable id)lastObject; /// :nodoc: - (id)objectAtIndexedSubscript:(NSUInteger)index; /** Returns the index of an object in the collection. Returns `NSNotFound` if the object is not found in the collection. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(id)object; /** Returns the index of the first object in the collection matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the collection. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the collection matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the collection. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; @required #pragma mark - Querying a Collection /** Returns all objects matching the given predicate in the collection. This is only supported for managed collections. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects matching the given predicate in the collection. This is only supported for managed collections. @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing objects that match the given predicate. */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from the collection. This is only supported for managed collections. @param keyPath The keyPath to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from the collection. This is only supported for managed collections. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /** Returns a distinct `RLMResults` from the collection. This is only supported for managed collections. @param keyPaths The key paths used produce distinct results @return An `RLMResults` made distinct based on the specified key paths */ - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; /** Returns an `NSArray` containing the results of invoking `valueForKey:` using `key` on each of the collection's objects. @param key The name of the property. @return An `NSArray` containing results. */ - (nullable id)valueForKey:(NSString *)key; /** Returns the value for the derived property identified by a given key path. @param keyPath A key path of the form relationship.property (with one or more relationships). @return The value for the derived property identified by keyPath. */ - (nullable id)valueForKeyPath:(NSString *)keyPath; /** Invokes `setValue:forKey:` on each of the collection's objects using the specified `value` and `key`. @warning This method may only be called during a write transaction. @param value The object value. @param key The name of the property. */ - (void)setValue:(nullable id)value forKey:(NSString *)key; #pragma mark - Notifications /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the collection object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; NSLog(@"dogs.count: %zu", dogs.count); // => 0 self.token = [results addNotificationBlock:^(RLMResults *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only or frozen. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the collection object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the collection object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); #pragma mark - Sectioned Results /** Sorts and sections this collection from a given property key path, returning the result as an instance of `RLMSectionedResults`. @param keyPath The property key path to sort on. @param ascending The direction to sort in. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; /** Sorts and sections this collection from a given array of sort descriptors, returning the result as an instance of `RLMSectionedResults`. @param sortDescriptors An array of `RLMSortDescriptor`s to sort by. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @note The primary sort descriptor must be responsible for determining the section key. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the objects in the collection. NSNumber *min = [results minOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The minimum value of the property, or `nil` if the Results are empty. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects in the collection. NSNumber *max = [results maxOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose maximum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The maximum value of the property, or `nil` if the Results are empty. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of the values of a given property over all the objects in the collection. NSNumber *sum = [results sumOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose values should be summed. Only properties of types `int`, `float`, and `double` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects in the collection. NSNumber *average = [results averageOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose average value should be calculated. Only properties of types `int`, `float`, and `double` are supported. @return The average value of the given property, or `nil` if the Results are empty. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; #pragma mark - Freeze /** Indicates if the collection is frozen. Frozen collections are immutable and can be accessed from any thread. The objects read from a frozen collection will also be frozen. */ @property (nonatomic, readonly, getter=isFrozen) BOOL frozen; /** Returns a frozen (immutable) snapshot of this collection. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live collections, frozen collections can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; @end /** An `RLMSortDescriptor` stores a property name and a sort order for use with `sortedResultsUsingDescriptors:`. It is similar to `NSSortDescriptor`, but supports only the subset of functionality which can be efficiently run by Realm's query engine. `RLMSortDescriptor` instances are immutable. */ NS_SWIFT_SENDABLE RLM_FINAL @interface RLMSortDescriptor : NSObject #pragma mark - Properties /** The key path which the sort descriptor orders results by. */ @property (nonatomic, readonly) NSString *keyPath; /** Whether the descriptor sorts in ascending or descending order. */ @property (nonatomic, readonly) BOOL ascending; #pragma mark - Methods /** Returns a new sort descriptor for the given key path and sort direction. */ + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a copy of the receiver with the sort direction reversed. */ - (instancetype)reversedSortDescriptor; @end /** A `RLMCollectionChange` object encapsulates information about changes to collections that are reported by Realm notifications. `RLMCollectionChange` is passed to the notification blocks registered with `-addNotificationBlock` on `RLMArray` and `RLMResults`, and reports what rows in the collection changed since the last time the notification block was called. The change information is available in two formats: a simple array of row indices in the collection for each type of change, and an array of index paths in a requested section suitable for passing directly to `UITableView`'s batch update methods. A complete example of updating a `UITableView` named `tv`: [tv beginUpdates]; [tv deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv endUpdates]; All of the arrays in an `RLMCollectionChange` are always sorted in ascending order. */ @interface RLMCollectionChange : NSObject /// The indices of objects in the previous version of the collection which have /// been removed from this one. @property (nonatomic, readonly) NSArray *deletions; /// The indices in the new version of the collection which were newly inserted. @property (nonatomic, readonly) NSArray *insertions; /** The indices in the new version of the collection which were modified. For `RLMResults`, this means that one or more of the properties of the object at that index were modified (or an object linked to by that object was modified). For `RLMArray`, the array itself being modified to contain a different object at that index will also be reported as a modification. */ @property (nonatomic, readonly) NSArray *modifications; /// Returns the index paths of the deletion indices in the given section. - (NSArray *)deletionsInSection:(NSUInteger)section; /// Returns the index paths of the insertion indices in the given section. - (NSArray *)insertionsInSection:(NSUInteger)section; /// Returns the index paths of the modification indices in the given section. - (NSArray *)modificationsInSection:(NSUInteger)section; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMCollection.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMCollection_Private.hpp" #import "RLMAccessor.hpp" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftCollectionBase.h" #import #import #import #import static const int RLMEnumerationBufferSize = 16; @implementation RLMFastEnumerator { // The buffer supplied by fast enumeration does not retain the objects given // to it, but because we create objects on-demand and don't want them // autoreleased (a table can have more rows than the device has memory for // accessor objects) we need a thing to retain them. id _strongBuffer[RLMEnumerationBufferSize]; RLMRealm *_realm; RLMClassInfo *_info; RLMClassInfo *_parentInfo; RLMProperty *_property; // A pointer to either _snapshot or a Results from the source collection, // to avoid having to copy the Results when not in a write transaction realm::Results *_results; realm::Results _snapshot; // A strong reference to the collection being enumerated to ensure it stays // alive when we're holding a pointer to a member in it id _collection; } - (instancetype)initWithBackingCollection:(realm::object_store::Collection const&)backingCollection collection:(id)collection classInfo:(RLMClassInfo *)info parentInfo:(RLMClassInfo *)parentInfo property:(RLMProperty *)property { self = [super init]; if (self) { _info = info; _realm = _info->realm; _parentInfo = parentInfo; _property = property; if (_realm.inWriteTransaction) { _snapshot = backingCollection.as_results().snapshot(); } else { _snapshot = backingCollection.as_results(); _collection = collection; [_realm registerEnumerator:self]; } _results = &_snapshot; } return self; } - (instancetype)initWithBackingDictionary:(realm::object_store::Dictionary const&)backingDictionary dictionary:(RLMManagedDictionary *)dictionary classInfo:(RLMClassInfo *)info parentInfo:(RLMClassInfo *)parentInfo property:(RLMProperty *)property { self = [super init]; if (self) { _info = info; _realm = _info->realm; _parentInfo = parentInfo; _property = property; if (_realm.inWriteTransaction) { _snapshot = backingDictionary.get_keys().snapshot(); } else { _snapshot = backingDictionary.get_keys(); _collection = dictionary; [_realm registerEnumerator:self]; } _results = &_snapshot; } return self; } - (instancetype)initWithResults:(realm::Results&)results collection:(id)collection classInfo:(RLMClassInfo&)info { self = [super init]; if (self) { _info = &info; _realm = _info->realm; if (_realm.inWriteTransaction) { _snapshot = results.snapshot(); _results = &_snapshot; } else { _results = &results; _collection = collection; [_realm registerEnumerator:self]; } } return self; } - (void)dealloc { if (_collection) { [_realm unregisterEnumerator:self]; } } - (void)detach { _snapshot = _results->snapshot(); _results = &_snapshot; _collection = nil; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state count:(NSUInteger)len { [_realm verifyThread]; if (!_results->is_valid()) { @throw RLMException(@"Collection is no longer valid"); } // The fast enumeration buffer size is currently a hardcoded number in the // compiler so this can't actually happen, but just in case it changes in // the future... if (len > RLMEnumerationBufferSize) { len = RLMEnumerationBufferSize; } NSUInteger batchCount = 0, count = state->extra[1]; @autoreleasepool { auto ctx = _parentInfo ? RLMAccessorContext(*_parentInfo, *_info, _property) : RLMAccessorContext(*_info); for (NSUInteger index = state->state; index < count && batchCount < len; ++index) { _strongBuffer[batchCount] = _results->get(ctx, index); batchCount++; } } for (NSUInteger i = batchCount; i < len; ++i) { _strongBuffer[i] = nil; } if (batchCount == 0) { // Release our data if we're done, as we're autoreleased and so may // stick around for a while if (_collection) { _collection = nil; [_realm unregisterEnumerator:self]; } _snapshot = {}; } state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer; state->state += batchCount; state->mutationsPtr = state->extra+1; return batchCount; } @end NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id collection) { __autoreleasing RLMFastEnumerator *enumerator; if (state->state == 0) { enumerator = collection.fastEnumerator; state->extra[0] = (long)enumerator; state->extra[1] = collection.count; } else { enumerator = (__bridge id)(void *)state->extra[0]; } return [enumerator countByEnumeratingWithState:state count:len]; } @interface RLMArrayHolder : NSObject @end @implementation RLMArrayHolder { std::unique_ptr items; } NSUInteger RLMUnmanagedFastEnumerate(id collection, NSFastEnumerationState *state) { if (state->state != 0) { return 0; } // We need to enumerate a copy of the backing array so that it doesn't // reflect changes made during enumeration. This copy has to be autoreleased // (since there's nowhere for us to store a strong reference), and uses // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee // that it'll use a single contiguous block of memory, and if it doesn't // we'd need to forward multiple calls to this method to the same NSArray, // which would require holding a reference to it somewhere. __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init]; copy->items = std::make_unique([collection count]); NSUInteger i = 0; for (id object in collection) { copy->items.get()[i++] = object; } state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get(); // needs to point to something valid, but the whole point of this is so // that it can't be changed state->mutationsPtr = state->extra; state->state = i; return i; } @end template NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info) { size_t count = collection.size(); if (count == 0) { return @[]; } NSMutableArray *array = [NSMutableArray arrayWithCapacity:count]; if ([key isEqualToString:@"self"]) { RLMAccessorContext context(info); for (size_t i = 0; i < count; ++i) { [array addObject:collection.get(context, i) ?: NSNull.null]; } return array; } if (collection.get_type() != realm::PropertyType::Object) { RLMAccessorContext context(info); for (size_t i = 0; i < count; ++i) { [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null]; } return array; } RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); auto prop = info.rlmObjectSchema[key]; // Collection properties need to be handled specially since we need to create // a new collection each time if (info.rlmObjectSchema.isSwiftClass) { if (prop.collection && prop.swiftAccessor) { // Grab the actual class for the generic collection from an instance of it // so that we can make instances of the collection without creating a new // object accessor each time Class cls = [[prop.swiftAccessor get:prop on:accessor] class]; for (size_t i = 0; i < count; ++i) { RLMSwiftCollectionBase *base = [[cls alloc] init]; base._rlmCollection = [[[cls _backingCollectionType] alloc] initWithParent:collection.get(i) property:prop parentInfo:info]; [array addObject:base]; } return array; } } auto swiftAccessor = prop.swiftAccessor; for (size_t i = 0; i < count; i++) { accessor->_row = collection.get(i); if (swiftAccessor) { [swiftAccessor initialize:prop on:accessor]; } [array addObject:[accessor valueForKey:key] ?: NSNull.null]; } return array; } realm::ColKey columnForProperty(NSString *propertyName, realm::object_store::Collection const& backingCollection, RLMClassInfo *objectInfo, RLMPropertyType propertyType, RLMCollectionType collectionType) { if (backingCollection.get_type() == realm::PropertyType::Object) { return objectInfo->tableColumn(propertyName); } if (![propertyName isEqualToString:@"self"]) { NSString *collectionTypeName; switch (collectionType) { case RLMCollectionTypeArray: collectionTypeName = @"Arrays"; break; case RLMCollectionTypeSet: collectionTypeName = @"Sets"; break; case RLMCollectionTypeDictionary: collectionTypeName = @"Dictionaries"; break; } @throw RLMException(@"%@ of '%@' can only be aggregated on \"self\"", collectionTypeName, RLMTypeToString(propertyType)); } return {}; } template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMClassInfo&); template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMClassInfo&); template NSArray *RLMCollectionValueForKey(realm::object_store::Set&, NSString *, RLMClassInfo&); void RLMCollectionSetValueForKey(id collection, NSString *key, id value) { realm::TableView tv = [collection tableView]; if (tv.size() == 0) { return; } RLMClassInfo *info = collection.objectInfo; RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, info); for (size_t i = 0; i < tv.size(); i++) { accessor->_row = tv[i]; RLMInitializeSwiftAccessor(accessor, false); [accessor setValue:value forKey:key]; } } void RLMAssignToCollection(id collection, id value) { [(id)collection replaceAllObjectsWithObjects:value]; } NSString *RLMDescriptionWithMaxDepth(NSString *name, id collection, NSUInteger depth) { if (depth == 0) { return @""; } const NSUInteger maxObjects = 100; auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name, [collection objectClassName] ?: RLMTypeToString([collection type]), (void *)collection]; size_t index = 0, skipped = 0; for (id obj in collection) { NSString *sub; if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) { sub = [obj descriptionWithMaxDepth:depth - 1]; } else { sub = [obj description]; } // Indent child objects NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription]; if (index >= maxObjects) { skipped = collection.count - maxObjects; break; } } // Remove last comma and newline characters if (collection.count > 0) { [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)]; } if (skipped) { [str appendFormat:@"\n\t... %zu objects skipped.", skipped]; } [str appendFormat:@"\n)"]; return str; } std::vector> RLMSortDescriptorsToKeypathArray(NSArray *properties) { std::vector> keypaths; keypaths.reserve(properties.count); for (RLMSortDescriptor *desc in properties) { if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) { @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath); } keypaths.push_back({desc.keyPath.UTF8String, desc.ascending}); } return keypaths; } @implementation RLMCollectionChange { realm::CollectionChangeSet _indices; } - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices { self = [super init]; if (self) { _indices = std::move(indices); } return self; } static NSArray *toArray(realm::IndexSet const& set) { NSMutableArray *ret = [NSMutableArray new]; for (auto index : set.as_indexes()) { [ret addObject:@(index)]; } return ret; } - (NSArray *)insertions { return toArray(_indices.insertions); } - (NSArray *)deletions { return toArray(_indices.deletions); } - (NSArray *)modifications { return toArray(_indices.modifications); } - (NSArray *)deletionsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.deletions, section); } - (NSArray *)insertionsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.insertions, section); } - (NSArray *)modificationsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.modifications, section); } - (NSString *)description { return [NSString stringWithFormat:@" insertions: %@, deletions: %@, modifications: %@", (__bridge void *)self, self.insertions, self.deletions, self.modifications]; } @end namespace { struct CollectionCallbackWrapper { void (^block)(id, id, NSError *); id collection; bool ignoreChangesInInitialNotification; void operator()(realm::CollectionChangeSet const& changes) { if (ignoreChangesInInitialNotification) { ignoreChangesInInitialNotification = false; block(collection, nil, nil); } else if (changes.empty()) { block(collection, nil, nil); } else if (!changes.collection_root_was_deleted || !changes.deletions.empty()) { block(collection, [[RLMCollectionChange alloc] initWithChanges:changes], nil); } } }; } // anonymous namespace @interface RLMCancellationToken : RLMNotificationToken @end RLM_HIDDEN @implementation RLMCancellationToken { __unsafe_unretained RLMRealm *_realm; realm::NotificationToken _token; RLMUnfairMutex _mutex; } - (RLMRealm *)realm { std::lock_guard lock(_mutex); return _realm; } - (void)suppressNextNotification { std::lock_guard lock(_mutex); if (_realm) { _token.suppress_next(); } } - (bool)invalidate { std::lock_guard lock(_mutex); if (_realm) { _token = {}; _realm = nil; return true; } return false; } RLMNotificationToken *RLMAddNotificationBlock(id c, id block, NSArray *keyPaths, dispatch_queue_t queue) { id collection = c; RLMRealm *realm = collection.realm; if (!realm) { @throw RLMException(@"Change notifications are only supported on managed collections."); } auto token = [[RLMCancellationToken alloc] init]; token->_realm = realm; RLMClassInfo *info = collection.objectInfo; if (!queue) { [realm verifyNotificationsAreSupported:true]; try { token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)]; } catch (const realm::Exception& e) { @throw RLMException(e); } return token; } RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:collection]; RLMRealmConfiguration *config = realm.configurationSharingSchema; dispatch_async(queue, ^{ std::lock_guard lock(token->_mutex); if (!token->_realm) { return; } RLMRealm *realm = [RLMRealm realmWithConfiguration:config queue:queue error:nil]; token->_realm = realm; id collection = [realm resolveThreadSafeReference:tsr]; token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)]; }); return token; } realm::CollectionChangeCallback RLMWrapCollectionChangeCallback(void (^block)(id, id, NSError *), id collection, bool skipFirst) { return CollectionCallbackWrapper{block, collection, skipFirst}; } @end NSArray *RLMToIndexPathArray(realm::IndexSet const& set, NSUInteger section) { NSMutableArray *ret = [NSMutableArray new]; NSUInteger path[2] = {section, 0}; for (auto index : set.as_indexes()) { path[1] = index; [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]]; } return ret; } ================================================ FILE: Realm/RLMCollection_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 @protocol RLMCollectionPrivate; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) NSUInteger RLMUnmanagedFastEnumerate(id collection, NSFastEnumerationState *); void RLMCollectionSetValueForKey(id collection, NSString *key, id _Nullable value); FOUNDATION_EXTERN NSString *RLMDescriptionWithMaxDepth(NSString *name, id collection, NSUInteger depth); FOUNDATION_EXTERN void RLMAssignToCollection(id collection, id value); FOUNDATION_EXTERN void RLMSetSwiftBridgeCallback(id _Nullable (*_Nonnull)(id)); FOUNDATION_EXTERN RLMNotificationToken *RLMAddNotificationBlock(id collection, id block, NSArray *_Nullable keyPaths, dispatch_queue_t _Nullable queue); typedef NS_CLOSED_ENUM(int32_t, RLMCollectionType) { RLMCollectionTypeArray = 0, RLMCollectionTypeSet = 1, RLMCollectionTypeDictionary = 2 }; RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMCollection_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 #import #import #import #import #import namespace realm { class CollectionChangeCallback; class List; class Obj; class Results; class TableView; struct CollectionChangeSet; struct ColKey; namespace object_store { class Collection; class Dictionary; class Set; } } class RLMClassInfo; @class RLMFastEnumerator, RLMManagedArray, RLMManagedSet, RLMManagedDictionary, RLMProperty, RLMObjectBase; RLM_HIDDEN_BEGIN @protocol RLMCollectionPrivate @property (nonatomic, readonly) RLMRealm *realm; @property (nonatomic, readonly) RLMClassInfo *objectInfo; @property (nonatomic, readonly) NSUInteger count; - (realm::TableView)tableView; - (RLMFastEnumerator *)fastEnumerator; - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths; @end // An object which encapsulates the shared logic for fast-enumerating RLMArray // RLMSet and RLMResults, and has a buffer to store strong references to the current // set of enumerated items RLM_DIRECT_MEMBERS @interface RLMFastEnumerator : NSObject - (instancetype)initWithBackingCollection:(realm::object_store::Collection const&)backingCollection collection:(id)collection classInfo:(RLMClassInfo *)info parentInfo:(RLMClassInfo *)parentInfo property:(RLMProperty *)property; - (instancetype)initWithBackingDictionary:(realm::object_store::Dictionary const&)backingDictionary dictionary:(RLMManagedDictionary *)dictionary classInfo:(RLMClassInfo *)info parentInfo:(RLMClassInfo *)parentInfo property:(RLMProperty *)property; - (instancetype)initWithResults:(realm::Results&)results collection:(id)collection classInfo:(RLMClassInfo&)info; // Detach this enumerator from the source collection. Must be called before the // source collection is changed. - (void)detach; - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state count:(NSUInteger)len; @end NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id collection); @interface RLMNotificationToken () - (void)suppressNextNotification; - (RLMRealm *)realm; @end @interface RLMCollectionChange () - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices; @end realm::CollectionChangeCallback RLMWrapCollectionChangeCallback(void (^block)(id, id, NSError *), id collection, bool skipFirst); template NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info); std::vector> RLMSortDescriptorsToKeypathArray(NSArray *properties); realm::ColKey columnForProperty(NSString *propertyName, realm::object_store::Collection const& backingCollection, RLMClassInfo *objectInfo, RLMPropertyType propertyType, RLMCollectionType collectionType); static inline bool canAggregate(RLMPropertyType type, bool allowDate) { switch (type) { case RLMPropertyTypeInt: case RLMPropertyTypeFloat: case RLMPropertyTypeDouble: case RLMPropertyTypeDecimal128: case RLMPropertyTypeAny: return true; case RLMPropertyTypeDate: return allowDate; default: return false; } } NSArray *RLMToIndexPathArray(realm::IndexSet const& set, NSUInteger section); RLM_HIDDEN_END ================================================ FILE: Realm/RLMConstants.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #define RLM_HEADER_AUDIT_BEGIN NS_HEADER_AUDIT_BEGIN #define RLM_HEADER_AUDIT_END NS_HEADER_AUDIT_END #define RLM_FINAL __attribute__((objc_subclassing_restricted)) RLM_HEADER_AUDIT_BEGIN(nullability, sendability) #if __has_attribute(ns_error_domain) && (!defined(__cplusplus) || !__cplusplus || __cplusplus >= 201103L) #define RLM_ERROR_ENUM(type, name, domain) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wignored-attributes\"") \ NS_ENUM(type, __attribute__((ns_error_domain(domain))) name) \ _Pragma("clang diagnostic pop") #else #define RLM_ERROR_ENUM(type, name, domain) NS_ENUM(type, name) #endif #define RLM_HIDDEN __attribute__((visibility("hidden"))) #define RLM_VISIBLE __attribute__((visibility("default"))) #define RLM_HIDDEN_BEGIN _Pragma("GCC visibility push(hidden)") #define RLM_HIDDEN_END _Pragma("GCC visibility pop") #define RLM_DIRECT __attribute__((objc_direct)) #define RLM_DIRECT_MEMBERS __attribute__((objc_direct_members)) #pragma mark - Enums /** `RLMPropertyType` is an enumeration describing all property types supported in Realm models. For more information, see [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/object-models/). */ typedef NS_CLOSED_ENUM(int32_t, RLMPropertyType) { #pragma mark - Primitive types /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ RLMPropertyTypeInt = 0, /** Booleans: `BOOL`, `bool`, `Bool` (Swift) */ RLMPropertyTypeBool = 1, /** Floating-point numbers: `float`, `Float` (Swift) */ RLMPropertyTypeFloat = 5, /** Double-precision floating-point numbers: `double`, `Double` (Swift) */ RLMPropertyTypeDouble = 6, /** NSUUID, UUID */ RLMPropertyTypeUUID = 12, #pragma mark - Object types /** Strings: `NSString`, `String` (Swift) */ RLMPropertyTypeString = 2, /** Binary data: `NSData` */ RLMPropertyTypeData = 3, /** Any type: `id`, `AnyRealmValue` (Swift) */ RLMPropertyTypeAny = 9, /** Dates: `NSDate` */ RLMPropertyTypeDate = 4, RLMPropertyTypeObjectId = 10, RLMPropertyTypeDecimal128 = 11, #pragma mark - Linked object types /** Realm model objects. See [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/object-models/) for more information. */ RLMPropertyTypeObject = 7, /** Realm linking objects. See [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/#define-an-inverse-relationship-property) for more information. */ RLMPropertyTypeLinkingObjects = 8, }; /** `RLMAnyValueType` is an enumeration describing all property types supported by RLMValue (AnyRealmValue). For more information, see [Realm Models](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/supported-types/#std-label-ios-anyrealmvalue-data-type). */ typedef NS_CLOSED_ENUM(int32_t, RLMAnyValueType) { #pragma mark - Primitive types /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ RLMAnyValueTypeInt = 0, /** Booleans: `BOOL`, `bool`, `Bool` (Swift) */ RLMAnyValueTypeBool = 1, /** Floating-point numbers: `float`, `Float` (Swift) */ RLMAnyValueTypeFloat = 5, /** Double-precision floating-point numbers: `double`, `Double` (Swift) */ RLMAnyValueTypeDouble = 6, /** NSUUID, UUID */ RLMAnyValueTypeUUID = 12, #pragma mark - Object types /** Strings: `NSString`, `String` (Swift) */ RLMAnyValueTypeString = 2, /** Binary data: `NSData` */ RLMAnyValueTypeData = 3, /** Any type: `id`, `AnyRealmValue` (Swift) */ RLMAnyValueTypeAny = 9, /** Dates: `NSDate` */ RLMAnyValueTypeDate = 4, RLMAnyValueTypeObjectId = 10, RLMAnyValueTypeDecimal128 = 11, #pragma mark - Linked object types /** Realm model objects. See [Realm Models](https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/object-models-and-schemas/) for more information. */ RLMAnyValueTypeObject = 7, /** Realm linking objects. See [Realm Models](https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/relationships/#inverse-relationship) for more information. */ RLMAnyValueTypeLinkingObjects = 8, /** Dictionary: `RLMDictionary`, `Map` (Swift) */ RLMAnyValueTypeDictionary = 512, /** Set: `RLMArray`, `List` (Swift) */ RLMAnyValueTypeList = 128, }; #pragma mark - Notification Constants /** A notification indicating that changes were made to a Realm. */ typedef NSString * RLMNotification NS_EXTENSIBLE_STRING_ENUM; /** This notification is posted when a write transaction has been committed to a Realm on a different thread for the same file. It is not posted if `autorefresh` is enabled, or if the Realm is refreshed before the notification has a chance to run. Realms with autorefresh disabled should normally install a handler for this notification which calls `-[RLMRealm refresh]` after doing some work. Refreshing the Realm is optional, but not refreshing the Realm may lead to large Realm files. This is because an extra copy of the data must be kept for the stale Realm. */ extern RLMNotification const RLMRealmRefreshRequiredNotification NS_SWIFT_NAME(RefreshRequired); /** This notification is posted by a Realm when a write transaction has been committed to a Realm on a different thread for the same file. It is not posted if `-[RLMRealm autorefresh]` is enabled, or if the Realm is refreshed before the notification has a chance to run. Realms with autorefresh disabled should normally install a handler for this notification which calls `-[RLMRealm refresh]` after doing some work. Refreshing the Realm is optional, but not refreshing the Realm may lead to large Realm files. This is because Realm must keep an extra copy of the data for the stale Realm. */ extern RLMNotification const RLMRealmDidChangeNotification NS_SWIFT_NAME(DidChange); #pragma mark - Error keys /** Key to identify the associated backup Realm configuration in an error's `userInfo` dictionary */ extern NSString * const RLMBackupRealmConfigurationErrorKey; #pragma mark - Other Constants /** The schema version used for uninitialized Realms */ extern const uint64_t RLMNotVersioned; /** The corresponding value is the name of an exception thrown by Realm. */ extern NSString * const RLMExceptionName; /** The corresponding value is a Realm file version. */ extern NSString * const RLMRealmVersionKey; /** The corresponding key is the version of the underlying database engine. */ extern NSString * const RLMRealmCoreVersionKey; /** The corresponding key is the Realm invalidated property name. */ extern NSString * const RLMInvalidatedKey; RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMConstants.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLMNotification const RLMRealmRefreshRequiredNotification = @"RLMRealmRefreshRequiredNotification"; RLMNotification const RLMRealmDidChangeNotification = @"RLMRealmDidChangeNotification"; NSString * const RLMExceptionName = @"RLMException"; NSString * const RLMRealmVersionKey = @"RLMRealmVersion"; NSString * const RLMRealmCoreVersionKey = @"RLMRealmCoreVersion"; NSString * const RLMInvalidatedKey = @"invalidated"; NSString * const RLMBackupRealmConfigurationErrorKey = @"RLMBackupRealmConfiguration"; ================================================ FILE: Realm/RLMDecimal128.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** A 128-bit IEEE 754-2008 decimal floating point number. This type is similar to Swift's built-in Decimal type, but allocates bits differently, resulting in a different representable range. (NS)Decimal stores a significand of up to 38 digits long and an exponent from -128 to 127, while this type stores up to 34 digits of significand and an exponent from -6143 to 6144. */ NS_SWIFT_SENDABLE // immutable @interface RLMDecimal128 : NSObject /// Creates a new zero-initialized decimal128. - (instancetype)init; /// Converts the given value to a RLMDecimal128. /// /// The following types can be converted to RLMDecimal128: /// - NSNumber /// - NSString /// - NSDecimalNumber /// /// Passing a value with a type not in this list is a fatal error. Passing a /// string which cannot be parsed as a valid Decimal128 is a fatal error. - (instancetype)initWithValue:(id)value; /// Converts the given number to a RLMDecimal128. - (instancetype)initWithNumber:(NSNumber *)number; /// Parses the given string to a RLMDecimal128. /// /// Returns a decimal where `isNaN` is `YES` if the string cannot be parsed as a decimal. - (instancetype)initWithString:(NSString *)string; /// Converts the given number to a RLMDecimal128. + (instancetype)decimalWithNumber:(NSNumber *)number; /// The minimum value for RLMDecimal128. @property (class, readonly, copy) RLMDecimal128 *minimumDecimalNumber NS_REFINED_FOR_SWIFT; /// The maximum value for RLMDecimal128. @property (class, readonly, copy) RLMDecimal128 *maximumDecimalNumber NS_REFINED_FOR_SWIFT; /// Convert this value to a double. This is a lossy conversion. @property (nonatomic, readonly) double doubleValue; /// Convert this value to a NSDecimal. This may be a lossy conversion. @property (nonatomic, readonly) NSDecimal decimalValue; /// Convert this value to a string. @property (nonatomic, readonly) NSString *stringValue; /// Gets if this Decimal128 represents a NaN value. @property (nonatomic, readonly) BOOL isNaN; /// The magnitude of this RLMDecimal128. @property (nonatomic, readonly) RLMDecimal128 *magnitude NS_REFINED_FOR_SWIFT; /// Replaces this RLMDecimal128 value with its additive inverse. - (void)negate; /// Adds the right hand side to the current value and returns the result. - (RLMDecimal128 *)decimalNumberByAdding:(RLMDecimal128 *)decimalNumber; /// Divides the right hand side to the current value and returns the result. - (RLMDecimal128 *)decimalNumberByDividingBy:(RLMDecimal128 *)decimalNumber; /// Subtracts the right hand side to the current value and returns the result. - (RLMDecimal128 *)decimalNumberBySubtracting:(RLMDecimal128 *)decimalNumber; /// Multiply the right hand side to the current value and returns the result. - (RLMDecimal128 *)decimalNumberByMultiplyingBy:(RLMDecimal128 *)decimalNumber; /// Comparision operator to check if the right hand side is greater than the current value. - (BOOL)isGreaterThan:(nullable RLMDecimal128 *)decimalNumber; /// Comparision operator to check if the right hand side is greater than or equal to the current value. - (BOOL)isGreaterThanOrEqualTo:(nullable RLMDecimal128 *)decimalNumber; /// Comparision operator to check if the right hand side is less than the current value. - (BOOL)isLessThan:(nullable RLMDecimal128 *)decimalNumber; /// Comparision operator to check if the right hand side is less than or equal to the current value. - (BOOL)isLessThanOrEqualTo:(nullable RLMDecimal128 *)decimalNumber; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMDecimal128.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMDecimal128_Private.hpp" #import "RLMUtil.hpp" #import // Swift's obj-c bridging does not support making an obj-c defined class conform // to Decodable, so we need a Swift-defined subclass for that. This means that // when Realm Swift is being used, we need to produce objects of that type rather // than our obj-c defined type. objc_runtime_visible marks the type as being // visible only to the obj-c runtime and not the linker, which means that it'll // be `nil` at runtime rather than being a linker error if it's not defined, and // valid if it happens to be defined by some other library (i.e. Realm Swift). // // At the point where the objects are being allocated we generally don't have // any good way of knowing whether or not it's going to end up being used by // Swift, so we just switch to the subclass unconditionally if the subclass // exists. This shouldn't have any impact on obj-c code other than a small // performance hit. [[clang::objc_runtime_visible]] @interface RealmSwiftDecimal128 : RLMDecimal128 @end @implementation RLMDecimal128 { realm::Decimal128 _value; } - (instancetype)init { if (self = [super init]) { if (auto cls = [RealmSwiftDecimal128 class]; cls && cls != self.class) { object_setClass(self, cls); } } return self; } - (instancetype)initWithDecimal128:(realm::Decimal128)value { if ((self = [self init])) { _value = value; } return self; } - (instancetype)initWithValue:(id)value { if ((self = [self init])) { _value = RLMObjcToDecimal128(value); } return self; } - (instancetype)initWithNumber:(NSNumber *)number { if ((self = [self init])) { _value = RLMObjcToDecimal128(number); } return self; } - (instancetype)initWithString:(NSString *)string { if ((self = [self init])) { _value = realm::Decimal128(string.UTF8String); } return self; } + (instancetype)decimalWithNumber:(NSNumber *)number { return [[self alloc] initWithNumber:number]; } + (instancetype)decimalWithNSDecimal:(NSDecimalNumber *)number { return [[self alloc] initWithString:number.stringValue]; } - (id)copyWithZone:(NSZone *)zone { RLMDecimal128 *copy = [[self.class allocWithZone:zone] init]; copy->_value = _value; return copy; } - (realm::Decimal128)decimal128Value { return _value; } - (BOOL)isEqual:(id)object { if (auto decimal128 = RLMDynamicCast(object)) { return _value == decimal128->_value; } if (auto number = RLMDynamicCast(object)) { return _value == RLMObjcToDecimal128(number); } return NO; } - (NSUInteger)hash { return @(self.doubleValue).hash; } - (NSString *)description { return self.stringValue; } - (NSComparisonResult)compare:(RLMDecimal128 *)other { return static_cast(_value.compare(other->_value)); } - (double)doubleValue { return [NSDecimalNumber decimalNumberWithDecimal:self.decimalValue].doubleValue; } - (NSDecimal)decimalValue { NSDecimal ret; [[[NSScanner alloc] initWithString:@(_value.to_string().c_str())] scanDecimal:&ret]; return ret; } - (NSString *)stringValue { auto str = _value.to_string(); // If there's a decimal point, trim trailing zeroes auto decimal_pos = str.find('.'); if (decimal_pos != std::string::npos) { // Look specifically at the range between the decimal point and the E // if it's present, and the rest of the string if not std::string_view sv = str; auto e_pos = str.find('E', decimal_pos); if (e_pos != std::string::npos) { sv = sv.substr(0, e_pos); } // Remove everything between the character after the final non-zero // and the end of the string (or the E) auto final_non_zero = sv.find_last_not_of('0'); REALM_ASSERT(final_non_zero != std::string::npos); if (final_non_zero == decimal_pos) { // Also drop the decimal if there's no non-zero digits after it --final_non_zero; } str.erase(final_non_zero + 1, sv.size() - final_non_zero - 1); } return @(str.c_str()); } - (BOOL)isNaN { return _value.is_nan(); } - (RLMDecimal128 *)magnitude { auto result = realm::Decimal128(abs(self.doubleValue)); return [[RLMDecimal128 alloc] initWithDecimal128:result]; } - (void)negate { _value = realm::Decimal128(-self.doubleValue); } + (RLMDecimal128 *)minimumDecimalNumber { return [[RLMDecimal128 alloc] initWithDecimal128:std::numeric_limits::lowest()]; } + (RLMDecimal128 *)maximumDecimalNumber { return [[RLMDecimal128 alloc] initWithDecimal128:std::numeric_limits::max()]; } - (RLMDecimal128 *)decimalNumberByAdding:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return [[RLMDecimal128 alloc] initWithDecimal128:_value+rhs]; } - (RLMDecimal128 *)decimalNumberByDividingBy:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return [[RLMDecimal128 alloc] initWithDecimal128:_value/rhs]; } - (RLMDecimal128 *)decimalNumberBySubtracting:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return [[RLMDecimal128 alloc] initWithDecimal128:_value-rhs]; } - (RLMDecimal128 *)decimalNumberByMultiplyingBy:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return [[RLMDecimal128 alloc] initWithDecimal128:_value*rhs]; } - (BOOL)isGreaterThan:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return _value > rhs; } - (BOOL)isGreaterThanOrEqualTo:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return _value >= rhs; } - (BOOL)isLessThan:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return _value < rhs; } - (BOOL)isLessThanOrEqualTo:(RLMDecimal128 *)decimalNumber { auto rhs = RLMObjcToDecimal128(decimalNumber); return _value <= rhs; } @end ================================================ FILE: Realm/RLMDecimal128_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 namespace realm { class Decimal128; } RLM_DIRECT_MEMBERS @interface RLMDecimal128 () - (instancetype)initWithDecimal128:(realm::Decimal128)value; - (realm::Decimal128)decimal128Value; @end ================================================ FILE: Realm/RLMDictionary.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObject, RLMResults, RLMDictionaryChange; /** `RLMDictionary` is a container type in Realm representing a dynamic collection of key-value pairs. Unlike `NSDictionary`, `RLMDictionary`s hold a single key and value type. This is referred to in these docs as the “type” and “keyType” of the dictionary. When declaring an `RLMDictionary` property, the object type and keyType must be marked as conforming to a protocol by the same name as the objects it should contain. RLM_COLLECTION_TYPE(ObjectType) ... @property RLMDictionary *objectTypeDictionary; `RLMDictionary`s can be queried with the same predicates as `RLMObject` and `RLMResult`s. `RLMDictionary`s cannot be created directly. `RLMDictionary` properties on `RLMObject`s are lazily created when accessed, or can be obtained by querying a Realm. `RLMDictionary` only supports `NSString` as a key. Realm disallows the use of `.` or `$` characters within a dictionary key. ### Key-Value Observing `RLMDictionary` supports dictionary key-value observing on `RLMDictionary` properties on `RLMObject` subclasses, and the `invalidated` property on `RLMDictionary` instances themselves is key-value observing compliant when the `RLMDictionary` is attached to a managed `RLMObject` (`RLMDictionary`s on unmanaged `RLMObject`s will never become invalidated). */ @interface RLMDictionary: NSObject #pragma mark - Properties /** The number of entries in the dictionary. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The type of the objects in the dictionary. */ @property (nonatomic, readonly, assign) RLMPropertyType type; /** The type of the key used in this dictionary. */ @property (nonatomic, readonly, assign) RLMPropertyType keyType; /** Indicates whether the objects in the collection can be `nil`. */ @property (nonatomic, readonly, getter = isOptional) BOOL optional; /** The class name of the objects contained in the dictionary. Will be `nil` if `type` is not RLMPropertyTypeObject. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** The Realm which manages the dictionary. Returns `nil` for unmanaged dictionary. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** Indicates if the dictionary can no longer be accessed. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; /** Indicates if the dictionary is frozen. Frozen dictionaries are immutable and can be accessed from any thread. Frozen dictionaries are created by calling `-freeze` on a managed live dictionary. Unmanaged dictionaries are never frozen. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Accessing Objects from a Dictionary /** Returns the value associated with a given key. @param key The name of the property. @discussion If key does not start with “@”, invokes object(forKey:). If key does start with “@”, strips the “@” and invokes [super valueForKey:] with the rest of the key. @return A value associated with a given key or `nil`. */ - (nullable id)valueForKey:(nonnull RLMKeyType)key; /** Returns an array containing the dictionary’s keys. @note The order of the elements in the array is not defined. */ @property(readonly, copy) NSArray *allKeys; /** Returns an array containing the dictionary’s values. @note The order of the elements in the array is not defined. */ @property(readonly, copy) NSArray *allValues; /** Returns the value associated with a given key. @note `nil` will be returned if no value is associated with a given key. NSNull will be returned where null is associated with the key. @param key The key for which to return the corresponding value. @return The value associated with key. */ - (nullable RLMObjectType)objectForKey:(nonnull RLMKeyType)key; /** Returns the value associated with a given key. @note `nil` will be returned if no value is associated with a given key. NSNull will be returned where null is associated with the key. @param key The key for which to return the corresponding value. @return The value associated with key. */ - (nullable RLMObjectType)objectForKeyedSubscript:(RLMKeyType)key; /** Applies a given block object to the each key-value pair of the dictionary. @param block A block object to operate on entries in the dictionary. @note If the block sets *stop to YES, the enumeration stops. */ - (void)enumerateKeysAndObjectsUsingBlock:(void (^)(RLMKeyType key, RLMObjectType obj, BOOL *stop))block; #pragma mark - Adding, Removing, and Replacing Objects in a Dictionary /** Replace the contents of a dictionary with the contents of another dictionary - NSDictionary or RLMDictionary. This will remove all elements in this dictionary and then apply each element from the given dictionary. @warning This method may only be called during a write transaction. @warning If otherDictionary is self this will result in an empty dictionary. */ - (void)setDictionary:(id)otherDictionary; /** Removes all contents in the dictionary. @warning This method may only be called during a write transaction. */ - (void)removeAllObjects; /** Removes from the dictionary entries specified by elements in a given array. If a given key does not exist, no mutation will happen for that key. @warning This method may only be called during a write transaction. */ - (void)removeObjectsForKeys:(NSArray *)keyArray; /** Removes a given key and its associated value from the dictionary. If the key does not exist the dictionary will not be modified. @warning This method may only be called during a write transaction. */ - (void)removeObjectForKey:(RLMKeyType)key; /** Adds a given key-value pair to the dictionary if the key is not present, or updates the value for the given key if the key already present. @warning This method may only be called during a write transaction. */ - (void)setObject:(nullable RLMObjectType)obj forKeyedSubscript:(RLMKeyType)key; /** Adds a given key-value pair to the dictionary if the key is not present, or updates the value for the given key if the key already present. @warning This method may only be called during a write transaction. */ - (void)setObject:(nullable RLMObjectType)anObject forKey:(RLMKeyType)aKey; /** Adds to the receiving dictionary the entries from another dictionary. @note If the receiving dictionary contains the same key(s) as the otherDictionary, then the receiving dictionary will update each key-value pair for the matching key. @warning This method may only be called during a write transaction. @param otherDictionary An enumerable object such as `NSDictionary` or `RLMDictionary` which contains objects of the same type as the receiving dictionary. */ - (void)addEntriesFromDictionary:(id )otherDictionary; #pragma mark - Querying a Dictionary /** Returns all the values matching the given predicate in the dictionary. @note The keys in the dictionary are ignored when quering values, and they will not be returned in the `RLMResults`. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all the values matching the given predicate in the dictionary. @note The keys in the dictionary are ignored when quering values, and they will not be returned in the `RLMResults`. @param predicate The predicate with which to filter the objects. @return An `RLMResults` of objects that match the given predicate */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted RLMResults of all values in the dictionary. @note The keys in the dictionary are ignored when sorting values, and they will not be returned in the `RLMResults`. @param keyPath The key path to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted RLMResults of all values in the dictionary. @note The keys in the dictionary are ignored when sorting values, and they will not be returned in the `RLMResults`. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /** Returns a distinct `RLMResults` from all values in the dictionary. @note The keys in the dictionary are ignored, and they will not be returned in the `RLMResults`. @param keyPaths The key paths to distinct on. @return An `RLMResults` with the distinct values of the keypath(s). */ - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the values in the dictionary. NSNumber *min = [object.dictionaryProperty minOfProperty:@"age"]; @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, `NSDate`, `RLMValue` and `RLMDecimal128` are supported. @return The minimum value of the property, or `nil` if the dictionary is empty. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects in the dictionary. NSNumber *max = [object.dictionaryProperty maxOfProperty:@"age"]; @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, `NSDate`, `RLMValue` and `RLMDecimal128` are supported. @return The maximum value of the property, or `nil` if the dictionary is empty. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of distinct values of a given property over all the objects in the dictionary. NSNumber *sum = [object.dictionaryProperty sumOfProperty:@"age"]; @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, `RLMValue` and `RLMDecimal128` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects in the dictionary. NSNumber *average = [object.dictionaryProperty averageOfProperty:@"age"]; @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, `NSDate`, `RLMValue` and `RLMDecimal128` are supported. @return The average value of the given property, or `nil` if the dictionary is empty. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; #pragma mark - Notifications /** Registers a block to be called each time the dictionary changes. The block will be asynchronously called with the initial dictionary, and then called again after each write transaction which changes any of the keys or values within the dictionary. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which keys in the dictionary were added, modified or deleted. If a write transaction did not modify any keys or values in the dictionary, the block is not called at all. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. Person *person = [[Person allObjectsInRealm:realm] firstObject]; NSLog(@"person.dogs.count: %zu", person.dogs.count); // => 0 self.token = [person.dogs addNotificationBlock(RLMDictionary *dogs, RLMDictionaryChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; person.dogs[@"frenchBulldog"] = dog; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a non-frozen managed dictionary. @param block The block to be called each time the dictionary changes. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *_Nullable dictionary, RLMDictionaryChange *_Nullable changes, NSError *_Nullable error))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the dictionary changes. The block will be asynchronously called with the initial dictionary, and then called again after each write transaction which changes any of the key-value in the dictionary or which objects are in the results. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which keys in the dictionary were added or modified. If a write transaction did not modify any objects in the dictionary, the block is not called at all. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *_Nullable dictionary, RLMDictionaryChange *_Nullable changes, NSError *_Nullable error))block queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the dictionary changes. The block will be asynchronously called with the initial dictionary, and then called again after each write transaction which changes any of the key-value in the dictionary or which objects are in the results. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which keys in the dictionary were added or modified. If a write transaction did not modify any objects in the dictionary, the block is not called at all. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *_Nullable dictionary, RLMDictionaryChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the dictionary changes. The block will be asynchronously called with the initial dictionary, and then called again after each write transaction which changes any of the key-value in the dictionary or which objects are in the results. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which keys in the dictionary were added or modified. If a write transaction did not modify any objects in the dictionary, the block is not called at all. The error parameter is present only for backwards compatibility and will always be `nil`. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *_Nullable dictionary, RLMDictionaryChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths __attribute__((warn_unused_result)); #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of a dictionary. The frozen copy is an immutable dictionary which contains the same data as this dictionary currently contains, but will not update when writes are made to the containing Realm. Unlike live dictionaries, frozen dictionaries can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a managed dictionary. @warning Holding onto a frozen dictionary for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; #pragma mark - Unavailable Methods /** `-[RLMDictionary init]` is not available because `RLMDictionary`s cannot be created directly. `RLMDictionary` properties on `RLMObject`s are lazily created when accessed. */ - (instancetype)init __attribute__((unavailable("RLMDictionary cannot be created directly"))); /** `+[RLMDictionary new]` is not available because `RLMDictionary`s cannot be created directly. `RLMDictionary` properties on `RLMObject`s are lazily created when accessed. */ + (instancetype)new __attribute__((unavailable("RLMDictionary cannot be created directly"))); @end /** A `RLMDictionaryChange` object encapsulates information about changes to dictionaries that are reported by Realm notifications. `RLMDictionaryChange` is passed to the notification blocks registered with `-addNotificationBlock` on `RLMDictionary`, and reports what keys in the dictionary changed since the last time the notification block was called. */ @interface RLMDictionaryChange : NSObject /// The keys in the new version of the dictionary which were newly inserted. @property (nonatomic, readonly) NSArray *insertions; /// The keys in the new version of the dictionary which were modified. @property (nonatomic, readonly) NSArray *modifications; /// The keys which were deleted from the old version. @property (nonatomic, readonly) NSArray *deletions; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMDictionary.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMDictionary_Private.hpp" #import "RLMObject_Private.h" #import "RLMObjectSchema.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMSchema_Private.h" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" @interface RLMDictionary () @end @implementation NSString (RLMDictionaryKey) @end @implementation RLMDictionary { @public // Backing dictionary when this instance is unmanaged NSMutableDictionary *_backingCollection; } #pragma mark Initializers - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName keyType:(RLMPropertyType)keyType { REALM_ASSERT([objectClassName length] > 0); REALM_ASSERT(RLMValidateKeyType(keyType)); self = [super init]; if (self) { _objectClassName = objectClassName; _type = RLMPropertyTypeObject; _keyType = keyType; _optional = YES; } return self; } - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional keyType:(RLMPropertyType)keyType { REALM_ASSERT(RLMValidateKeyType(keyType)); REALM_ASSERT(type != RLMPropertyTypeObject); self = [super init]; if (self) { _type = type; _keyType = keyType; _optional = optional; } return self; } - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property { _parentObject = parentObject; _property = property; _isLegacyProperty = property.isLegacy; } static bool RLMValidateKeyType(RLMPropertyType keyType) { switch (keyType) { case RLMPropertyTypeString: return true; default: return false; } } id RLMDictionaryKey(__unsafe_unretained RLMDictionary *const dictionary, __unsafe_unretained id const key) { if (!key) { @throw RLMException(@"Invalid nil key for dictionary expecting key of type '%@'.", dictionary->_objectClassName ?: RLMTypeToString(dictionary.keyType)); } id validated = RLMValidateValue(key, dictionary.keyType, false, false, nil); if (!validated) { @throw RLMException(@"Invalid key '%@' of type '%@' for expected type '%@'.", key, [key class], RLMTypeToString(dictionary.keyType)); } return validated; } id RLMDictionaryValue(__unsafe_unretained RLMDictionary *const dictionary, __unsafe_unretained id const value) { if (!value) { return value; } if (dictionary->_type != RLMPropertyTypeObject) { id validated = RLMValidateValue(value, dictionary->_type, dictionary->_optional, false, nil); if (!validated) { @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.", value, [value class], RLMTypeToString(dictionary->_type), dictionary->_optional ? "?" : ""); } return validated; } if (auto valueObject = RLMDynamicCast(value)) { if (!valueObject->_objectSchema) { @throw RLMException(@"Object cannot be inserted unless the schema is initialized. " "This can happen if you try to insert objects into a RLMDictionary / Map from a default value or from an overridden unmanaged initializer (`init()`) or if the key is uninitialized."); } if (![dictionary->_objectClassName isEqualToString:valueObject->_objectSchema.className]) { @throw RLMException(@"Value of type '%@' does not match RLMDictionary value type '%@'.", valueObject->_objectSchema.className, dictionary->_objectClassName); } } else if (![value isKindOfClass:NSNull.class]) { @throw RLMException(@"Value of type '%@' does not match RLMDictionary value type '%@'.", [value className], dictionary->_objectClassName); } return value; } static void changeDictionary(__unsafe_unretained RLMDictionary *const dictionary, dispatch_block_t f) { if (!dictionary->_backingCollection) { dictionary->_backingCollection = [NSMutableDictionary new]; } if (RLMObjectBase *parent = dictionary->_parentObject) { [parent willChangeValueForKey:dictionary->_property.name]; f(); [parent didChangeValueForKey:dictionary->_property.name]; } else { f(); } } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *, RLMDictionaryChange *, NSError *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *, RLMDictionaryChange *, NSError *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *, RLMDictionaryChange *, NSError *))block keyPaths:(nullable NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMDictionary *, RLMDictionaryChange *, NSError *))block keyPaths:(nullable NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths, nil); } #pragma clang diagnostic pop #pragma mark - Unmanaged RLMDictionary implementation - (RLMRealm *)realm { return nil; } - (NSUInteger)count { return _backingCollection.count; } - (NSArray *)allKeys { return _backingCollection.allKeys ?: @[]; } - (NSArray *)allValues { return _backingCollection.allValues ?: @[]; } - (nullable id)objectForKey:(id)key { if (!_backingCollection) { _backingCollection = [NSMutableDictionary new]; } return [_backingCollection objectForKey:key]; } - (nullable id)objectForKeyedSubscript:(id)key { return [self objectForKey:key]; } - (BOOL)isInvalidated { return NO; } - (void)setValue:(nullable id)value forKey:(nonnull NSString *)key { [self setObject:value forKeyedSubscript:key]; } - (void)setDictionary:(id)dictionary { if (!dictionary || dictionary == NSNull.null) { return [self removeAllObjects]; } if (![dictionary respondsToSelector:@selector(enumerateKeysAndObjectsUsingBlock:)]) { @throw RLMException(@"Cannot set dictionary to object of class '%@'", [dictionary className]); } changeDictionary(self, ^{ [_backingCollection removeAllObjects]; [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) { [_backingCollection setObject:RLMDictionaryValue(self, value) forKey:RLMDictionaryKey(self, key)]; }]; }); } - (void)setObject:(id)obj forKeyedSubscript:(id)key { if (obj) { [self setObject:obj forKey:key]; } else { [self removeObjectForKey:key]; } } - (void)setObject:(id)obj forKey:(id)key { changeDictionary(self, ^{ [_backingCollection setObject:RLMDictionaryValue(self, obj) forKey:RLMDictionaryKey(self, key)]; }); } - (void)removeAllObjects { changeDictionary(self, ^{ [_backingCollection removeAllObjects]; }); } - (void)removeObjectsForKeys:(NSArray *)keyArray { changeDictionary(self, ^{ [_backingCollection removeObjectsForKeys:keyArray]; }); } - (void)removeObjectForKey:(id)key { changeDictionary(self, ^{ [_backingCollection removeObjectForKey:key]; }); } - (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block { [_backingCollection enumerateKeysAndObjectsUsingBlock:block]; } - (nullable id)valueForKey:(nonnull NSString *)key { if ([key isEqualToString:RLMInvalidatedKey]) { return @NO; // Unmanaged dictionaries are never invalidated } if (!_backingCollection) { _backingCollection = [NSMutableDictionary new]; } return [_backingCollection valueForKey:key]; } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath characterAtIndex:0] != '@') { return _backingCollection ? [_backingCollection valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath]; } if (!_backingCollection) { _backingCollection = [NSMutableDictionary new]; } NSUInteger dot = [keyPath rangeOfString:@"."].location; if (dot == NSNotFound) { return [_backingCollection valueForKeyPath:keyPath]; } NSString *op = [keyPath substringToIndex:dot]; NSString *key = [keyPath substringFromIndex:dot + 1]; return [self aggregateProperty:key operation:op method:nil]; } - (void)addEntriesFromDictionary:(id)otherDictionary { if (!otherDictionary) { return; } if (![otherDictionary respondsToSelector:@selector(enumerateKeysAndObjectsUsingBlock:)]) { @throw RLMException(@"Cannot add entries from object of class '%@'", [otherDictionary className]); } changeDictionary(self, ^{ [otherDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) { _backingCollection[RLMDictionaryKey(self, key)] = RLMDictionaryValue(self, value); }]; }); } - (NSUInteger)countByEnumeratingWithState:(nonnull NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nullable * _Nonnull)buffer count:(NSUInteger)len { return RLMUnmanagedFastEnumerate(_backingCollection.allKeys, state); } #pragma mark - Aggregate operations - (RLMPropertyType)typeForProperty:(NSString *)propertyName { if ([propertyName isEqualToString:@"self"]) { return _type; } RLMObjectSchema *objectSchema; if (_backingCollection.count) { objectSchema = [_backingCollection.allValues[0] objectSchema]; } else { objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName]; } return RLMValidatedProperty(objectSchema, propertyName).type; } - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel { // Although delegating to valueForKeyPath: here would allow to support // nested key paths as well, limiting functionality gives consistency // between unmanaged and managed arrays. if ([key rangeOfString:@"."].location != NSNotFound) { @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); } if ([op isEqualToString:@"@distinctUnionOfObjects"]) { @throw RLMException(@"this class does not implement the distinctUnionOfObjects"); } bool allowDate = false; bool sum = false; if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) { allowDate = true; } else if ([op isEqualToString:@"@sum"]) { sum = true; } else if (![op isEqualToString:@"@avg"]) { // Just delegate to NSDictionary for all other operators return [_backingCollection valueForKeyPath:[op stringByAppendingPathExtension:key]]; } RLMPropertyType type = [self typeForProperty:key]; if (!canAggregate(type, allowDate)) { NSString *method = sel ? NSStringFromSelector(sel) : op; if (_type == RLMPropertyTypeObject) { @throw RLMException(@"%@: is not supported for %@ property '%@.%@'", method, RLMTypeToString(type), _objectClassName, key); } else { @throw RLMException(@"%@ is not supported for %@%s dictionary", method, RLMTypeToString(_type), _optional ? "?" : ""); } } NSArray *values = [key isEqualToString:@"self"] ? _backingCollection.allValues : [_backingCollection.allValues valueForKey:key]; if (_optional) { // Filter out NSNull values to match our behavior on managed arrays NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { return obj != NSNull.null; }]; if (nonnull.count < values.count) { values = [values objectsAtIndexes:nonnull]; } } id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]]; return sum && !result ? @0 : result; } - (id)minOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@min" method:_cmd]; } - (id)maxOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@max" method:_cmd]; } - (id)sumOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@sum" method:_cmd]; } - (id)averageOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@avg" method:_cmd]; } - (nonnull RLMResults *)objectsWhere:(nonnull NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsWhere:predicateFormat args:args]; va_end(args); return results; } - (nonnull RLMResults *)objectsWhere:(nonnull NSString *)predicateFormat args:(va_list)args { return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (BOOL)isEqual:(id)object { if (auto dictionary = RLMDynamicCast(object)) { return !dictionary.realm && ((_backingCollection.count == 0 && dictionary->_backingCollection.count == 0) || [_backingCollection isEqual:dictionary->_backingCollection]); } return NO; } - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMDictionaryValidateObservationKey(keyPath, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } #pragma mark - Key Path Strings - (NSString *)propertyKey { return _property.name; } #pragma mark - Methods unsupported on unmanaged RLMDictionary instances #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" - (nonnull RLMResults *)objectsWithPredicate:(nonnull NSPredicate *)predicate { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (RLMResults *)sortedResultsUsingDescriptors:(nonnull NSArray *)properties { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (RLMResults *)sortedResultsUsingKeyPath:(nonnull NSString *)keyPath ascending:(BOOL)ascending { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (instancetype)freeze { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (instancetype)thaw { @throw RLMException(@"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); } - (NSUInteger)indexOfObject:(id)value { @throw RLMException(@"This method is not available on RLMDictionary."); } - (id)objectAtIndex:(NSUInteger)index { @throw RLMException(@"This method is not available on RLMDictionary."); } - (nullable NSArray *)objectsAtIndexes:(nonnull NSIndexSet *)indexes { @throw RLMException(@"This method is not available on RLMDictionary."); } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method is not available on RLMDictionary."); } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method is not available on RLMDictionary."); } #pragma clang diagnostic pop // unused parameter warning #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { REALM_TERMINATE("Unexpected handover of unmanaged `RLMDictionary`"); } - (id)objectiveCMetadata { REALM_TERMINATE("Unexpected handover of unmanaged `RLMDictionary`"); } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(id)metadata realm:(RLMRealm *)realm { REALM_TERMINATE("Unexpected handover of unmanaged `RLMDictionary`"); } #pragma mark - Superclass Overrides - (NSString *)description { return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; } - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { return RLMDictionaryDescriptionWithMaxDepth(@"RLMDictionary", self, depth); } NSString *RLMDictionaryDescriptionWithMaxDepth(NSString *name, RLMDictionary *dictionary, NSUInteger depth) { if (depth == 0) { return @""; } const NSUInteger maxObjects = 100; auto str = [NSMutableString stringWithFormat:@"%@<%@, %@> <%p> (\n", name, RLMTypeToString([dictionary keyType]), [dictionary objectClassName] ?: RLMTypeToString([dictionary type]), (void *)dictionary]; size_t index = 0, skipped = 0; for (id key in dictionary) { id value = dictionary[key]; NSString *keyDesc; if ([key respondsToSelector:@selector(descriptionWithMaxDepth:)]) { keyDesc = [key descriptionWithMaxDepth:depth - 1]; } else { keyDesc = [key description]; } NSString *valDesc; if ([value respondsToSelector:@selector(descriptionWithMaxDepth:)]) { valDesc = [value descriptionWithMaxDepth:depth - 1]; } else { valDesc = [value description]; } // Indent child objects NSString *sub = [NSString stringWithFormat:@"[%@]: %@", keyDesc, valDesc]; NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; [str appendFormat:@"%@,\n", objDescription]; if (index >= maxObjects) { skipped = dictionary.count - maxObjects; break; } } // Remove last comma and newline characters if (dictionary.count > 0) { [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)]; } if (skipped) { [str appendFormat:@"\n\t... %zu objects skipped.", skipped]; } [str appendFormat:@"\n)"]; return str; } @end ================================================ FILE: Realm/RLMDictionary_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 @class RLMObjectBase, RLMProperty; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMDictionary () - (instancetype)initWithObjectClassName:(NSString *)objectClassName keyType:(RLMPropertyType)keyType; - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional keyType:(RLMPropertyType)keyType; - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth; - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; // YES if the property is declared with old property syntax. @property (nonatomic, readonly) BOOL isLegacyProperty; // The name of the property which this collection represents @property (nonatomic, readonly) NSString *propertyKey; @end @interface RLMManagedDictionary : RLMDictionary - (instancetype)initWithParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; @end FOUNDATION_EXTERN NSString *RLMDictionaryDescriptionWithMaxDepth(NSString *name, RLMDictionary *dictionary, NSUInteger depth); id RLMDictionaryKey(RLMDictionary *dictionary, id key) RLM_HIDDEN; id RLMDictionaryValue(RLMDictionary *dictionary, id value) RLM_HIDDEN; RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMDictionary_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMDictionary_Private.h" #import "RLMCollection_Private.hpp" #import "RLMResults_Private.hpp" #import namespace realm { class Results; } @class RLMObjectBase, RLMObjectSchema, RLMProperty; class RLMClassInfo; class RLMObservationInfo; @interface RLMDictionary () { @protected NSString *_objectClassName; BOOL _optional; @public // The property which this RLMDictionary represents RLMProperty *_property; __weak RLMObjectBase *_parentObject; } @end @interface RLMManagedDictionary () - (RLMManagedDictionary *)initWithBackingCollection:(realm::object_store::Dictionary)dictionary parentInfo:(RLMClassInfo *)parentInfo property:(__unsafe_unretained RLMProperty *const)property; - (RLMManagedDictionary *)initWithParent:(realm::Obj)parent property:(RLMProperty *)property parentInfo:(RLMClassInfo&)info; - (bool)isBackedByDictionary:(realm::object_store::Dictionary const&)dictionary; // deletes all objects in the RLMDictionary from their containing realms - (void)deleteObjectsFromRealm; @end void RLMDictionaryValidateObservationKey(__unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMDictionary *const collection); // Initialize the observation info for an dictionary if needed void RLMEnsureDictionaryObservationInfo(std::unique_ptr& info, NSString *keyPath, RLMDictionary *array, id observed); ================================================ FILE: Realm/RLMEmbeddedObject.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObjectSchema, RLMPropertyDescriptor, RLMRealm, RLMNotificationToken, RLMPropertyChange; typedef void (^RLMObjectChangeBlock)(BOOL deleted, NSArray *_Nullable changes, NSError *_Nullable error); /** `RLMEmbeddedObject` is a base class used to define Realm model objects. Embedded objects work similarly to normal objects, but are owned by a single parent Object (which itself may be embedded). Unlike normal top-level objects, embedded objects cannot be directly created in or added to a Realm. Instead, they can only be created as part of a parent object, or by assigning an unmanaged object to a parent object's property. Embedded objects are automatically deleted when the parent object is deleted or when the parent is modified to no longer point at the embedded object, either by reassigning an RLMObject property or by removing the embedded object from the array containing it. Embedded objects can only ever have a single parent object which links to them, and attempting to link to an existing managed embedded object will throw an exception. The property types supported on `RLMEmbeddedObject` are the same as for `RLMObject`, except for that embedded objects cannot link to top-level objects, so `RLMObject` and `RLMArray` properties are not supported (`RLMEmbeddedObject` and `RLMArray` *are*). Embedded objects cannot have primary keys or indexed properties. */ @interface RLMEmbeddedObject : RLMObjectBase #pragma mark - Creating & Initializing Objects /** Creates an unmanaged instance of a Realm object. Unmanaged embedded objects can be added to a Realm by assigning them to an object property of a managed Realm object or by adding them to a managed RLMArray. */ - (instancetype)init NS_DESIGNATED_INITIALIZER; /** Creates an unmanaged instance of a Realm object. Pass in an `NSArray` or `NSDictionary` instance to set the values of the object's properties. Unmanaged embedded objects can be added to a Realm by assigning them to an object property of a managed Realm object or by adding them to a managed RLMArray. */ - (instancetype)initWithValue:(id)value; /** Returns the class name for a Realm object subclass. @warning Do not override. Realm relies on this method returning the exact class name. @return The class name for the model class. */ + (NSString *)className; #pragma mark - Properties /** The Realm which manages the object, or `nil` if the object is unmanaged. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** The object schema which lists the managed properties for the object. */ @property (nonatomic, readonly) RLMObjectSchema *objectSchema; /** Indicates if the object can no longer be accessed because it is now invalid. An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if `invalidate` is called on that Realm. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; /** Indicates if this object is frozen. @see `-[RLMEmbeddedObject freeze]` */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Customizing your Objects /** Override this method to specify the default values to be used for each property. @return A dictionary mapping property names to their default values. */ + (nullable NSDictionary *)defaultPropertyValues; /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. @return An array of property names to ignore. */ + (nullable NSArray *)ignoredProperties; /** Override this method to specify the names of properties that are non-optional (i.e. cannot be assigned a `nil` value). By default, all properties of a type whose values can be set to `nil` are considered optional properties. To require that an object in a Realm always store a non-`nil` value for a property, add the name of the property to the array returned from this method. Properties of `RLMEmbeddedObject` type cannot be non-optional. Array and `NSNumber` properties can be non-optional, but there is no reason to do so: arrays do not support storing nil, and if you want a non-optional number you should instead use the primitive type. @return An array of property names that are required. */ + (NSArray *)requiredProperties; /** Override this method to provide information related to properties containing linking objects. Each property of type `RLMLinkingObjects` must have a key in the dictionary returned by this method consisting of the property name. The corresponding value must be an instance of `RLMPropertyDescriptor` that describes the class and property that the property is linked to. return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Owner.class propertyName:@"dogs"] }; @return A dictionary mapping property names to `RLMPropertyDescriptor` instances. */ + (NSDictionary *)linkingObjectsProperties; #pragma mark - Notifications /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in differen processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block queue:(dispatch_queue_t)queue; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths; #pragma mark - Other Instance Methods /** Returns YES if another Realm object instance points to the same object as the receiver in the Realm managing the receiver. For frozen objects and object types with a primary key, `isEqual:` is overridden to use the same logic as this method (along with a corresponding implementation for `hash`). Non-frozen objects without primary keys use pointer identity for `isEqual:` and `hash`. @param object The object to compare the receiver to. @return Whether the object represents the same object as the receiver. */ - (BOOL)isEqualToObject:(RLMEmbeddedObject *)object; /** Returns a frozen (immutable) snapshot of this object. The frozen copy is an immutable object which contains the same data as this object currently contains, but will not update when writes are made to the containing Realm. Unlike live objects, frozen objects can be accessed from any thread. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. - warning: This method can only be called on a managed object. */ - (instancetype)freeze NS_RETURNS_RETAINED; /** Returns a live (mutable) reference of this object. This method creates a managed accessor to a live copy of the same frozen object. Will return self if called on an already live object. */ - (instancetype)thaw; #pragma mark - Dynamic Accessors /// :nodoc: - (nullable id)objectForKeyedSubscript:(NSString *)key; /// :nodoc: - (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMEmbeddedObject.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMEmbeddedObject.h" #import "RLMObject_Private.hpp" #import "RLMSchema_Private.h" @implementation RLMEmbeddedObject // synthesized in RLMObjectBase but redeclared here for documentation purposes @dynamic invalidated, realm, objectSchema; #pragma mark - Designated Initializers - (instancetype)init { return [super init]; } #pragma mark - Convenience Initializers - (instancetype)initWithValue:(id)value { if (!(self = [self init])) { return nil; } RLMInitializeWithValue(self, value, RLMSchema.partialPrivateSharedSchema); return self; } #pragma mark - Subscripting - (id)objectForKeyedSubscript:(NSString *)key { return RLMObjectBaseObjectForKeyedSubscript(self, key); } - (void)setObject:(id)obj forKeyedSubscript:(NSString *)key { RLMObjectBaseSetObjectForKeyedSubscript(self, key, obj); } #pragma mark - Other Instance Methods - (BOOL)isEqualToObject:(RLMObjectBase *)object { return [object isKindOfClass:RLMObjectBase.class] && RLMObjectBaseAreEqual(self, object); } - (instancetype)freeze { return RLMObjectFreeze(self); } - (instancetype)thaw { return RLMObjectThaw(self); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block { return RLMObjectAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block queue:(dispatch_queue_t)queue { return RLMObjectAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths { return RLMObjectAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMObjectAddNotificationBlock(self, block, keyPaths, queue); } + (NSString *)className { return [super className]; } #pragma mark - Default values for schema definition + (NSString *)primaryKey { return nil; } + (NSArray *)indexedProperties { return @[]; } + (NSDictionary *)linkingObjectsProperties { return @{}; } + (NSDictionary *)defaultPropertyValues { return nil; } + (NSArray *)ignoredProperties { return nil; } + (NSArray *)requiredProperties { return @[]; } + (bool)_realmIgnoreClass { return false; } + (bool)isEmbedded { return true; } @end ================================================ FILE: Realm/RLMError.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 @protocol RLMValue; #pragma mark - Error Domains /** Error code is a value from the RLMError enum. */ extern NSString *const RLMErrorDomain; /** An error domain identifying non-specific system errors. */ extern NSString *const RLMUnknownSystemErrorDomain; #pragma mark - RLMError /// A user info key containing the name of the error code. This is for /// debugging purposes only and should not be relied on. extern NSString *const RLMErrorCodeNameKey; /** `RLMError` is an enumeration representing all recoverable errors. It is associated with the Realm error domain specified in `RLMErrorDomain`. */ typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) { /** Denotes a general error that occurred when trying to open a Realm. */ RLMErrorFail = 1, /** Denotes a file I/O error that occurred when trying to open a Realm. */ RLMErrorFileAccess = 2, /** Denotes a file permission error that occurred when trying to open a Realm. This error can occur if the user does not have permission to open or create the specified file in the specified access mode when opening a Realm. */ RLMErrorFilePermissionDenied = 3, /** Denotes an error where a file was to be written to disk, but another file with the same name already exists. */ RLMErrorFileExists = 4, /** Denotes an error that occurs if a file could not be found. This error may occur if a Realm file could not be found on disk when trying to open a Realm as read-only, or if the directory part of the specified path was not found when trying to write a copy. */ RLMErrorFileNotFound = 5, /** Denotes an error that occurs if a file format upgrade is required to open the file, but upgrades were explicitly disabled or the file is being open in read-only mode. */ RLMErrorFileFormatUpgradeRequired = 6, /** Denotes an error that occurs if the database file is currently open in another process which cannot share with the current process due to an architecture mismatch. This error may occur if trying to share a Realm file between an i386 (32-bit) iOS Simulator and the Realm Studio application. In this case, please use the 64-bit version of the iOS Simulator. */ RLMErrorIncompatibleLockFile = 8, /** Denotes an error that occurs when there is insufficient available address space to mmap the Realm file. */ RLMErrorAddressSpaceExhausted = 9, /** Denotes an error that occurs if there is a schema version mismatch and a migration is required. */ RLMErrorSchemaMismatch = 10, /** Denotes an error where an operation was requested which cannot be performed on an open file. */ RLMErrorAlreadyOpen = 12, /// Denotes an error where an input value was invalid. RLMErrorInvalidInput = 13, /// Denotes an error where a write failed due to insufficient disk space. RLMErrorOutOfDiskSpace = 14, /** Denotes an error where a Realm file could not be opened because another process has opened the same file in a way incompatible with inter-process sharing. For example, this can result from opening the backing file for an in-memory Realm in non-in-memory mode. */ RLMErrorIncompatibleSession = 15, /** Denotes an error that occurs if the file is a valid Realm file, but has a file format version which is not supported by this version of Realm. This typically means that the file was written by a newer version of Realm, but may also mean that it is from a pre-1.0 version of Realm (or for synchronized files, pre-10.0). */ RLMErrorUnsupportedFileFormatVersion = 16, /// A subscription was rejected by the server. RLMErrorSubscriptionFailed = 18, /// A file operation failed in a way which does not have a more specific error code. RLMErrorFileOperationFailed = 19, /** Denotes an error that occurs if the file being opened is not a valid Realm file. Some of the possible causes of this are: 1. The file at the given URL simply isn't a Realm file at all. 2. The wrong encryption key was given. 3. The Realm file is encrypted and no encryption key was given. 4. The Realm file isn't encrypted but an encryption key was given. 5. The file on disk has become corrupted. */ RLMErrorInvalidDatabase = 20, /** Denotes an error that occurs if a Realm is opened in the wrong history mode. Typically this means that either a local Realm is being opened as a synchronized Realm or vice versa. */ RLMErrorIncompatibleHistories = 21, }; ================================================ FILE: Realm/RLMError.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 "RLMError_Private.hpp" #import "RLMUtil.hpp" #import NSString *const RLMErrorDomain = @"io.realm"; NSString *const RLMUnknownSystemErrorDomain = @"io.realm.unknown"; NSString *const RLMErrorCodeKey = @"Error Code"; NSString *const RLMErrorCodeNameKey = @"Error Name"; namespace { NSInteger translateFileError(realm::ErrorCodes::Error code) { using ec = realm::ErrorCodes::Error; switch (code) { // Local errors case ec::AddressSpaceExhausted: return RLMErrorAddressSpaceExhausted; case ec::DeleteOnOpenRealm: return RLMErrorAlreadyOpen; case ec::FileAlreadyExists: return RLMErrorFileExists; case ec::FileFormatUpgradeRequired: return RLMErrorFileFormatUpgradeRequired; case ec::FileNotFound: return RLMErrorFileNotFound; case ec::FileOperationFailed: return RLMErrorFileOperationFailed; case ec::IncompatibleHistories: return RLMErrorIncompatibleHistories; case ec::IncompatibleLockFile: return RLMErrorIncompatibleLockFile; case ec::IncompatibleSession: return RLMErrorIncompatibleSession; case ec::InvalidDatabase: return RLMErrorInvalidDatabase; case ec::OutOfDiskSpace: return RLMErrorOutOfDiskSpace; case ec::PermissionDenied: return RLMErrorFilePermissionDenied; case ec::SchemaMismatch: return RLMErrorSchemaMismatch; case ec::UnsupportedFileFormatVersion: return RLMErrorUnsupportedFileFormatVersion; default: { auto category = realm::ErrorCodes::error_categories(code); if (category.test(realm::ErrorCategory::file_access)) { return RLMErrorFileAccess; } return RLMErrorFail; } } } NSString *errorString(realm::ErrorCodes::Error error) { return RLMStringViewToNSString(realm::ErrorCodes::error_string(error)); } NSError *translateSystemError(std::error_code ec, const char *msg) { int code = ec.value(); BOOL isGenericCategoryError = ec.category() == std::generic_category() || ec.category() == realm::util::error::basic_system_error_category(); NSString *errorDomain = isGenericCategoryError ? NSPOSIXErrorDomain : RLMUnknownSystemErrorDomain; NSMutableDictionary *userInfo = [NSMutableDictionary new]; userInfo[NSLocalizedDescriptionKey] = @(msg); return [NSError errorWithDomain:errorDomain code:code userInfo:userInfo.copy]; } } // anonymous namespace NSError *makeError(realm::Status const& status) { if (status.is_ok()) { return nil; } auto code = translateFileError(status.code()); return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(status.reason().c_str()), RLMErrorCodeNameKey: errorString(status.code())}]; } NSError *makeError(realm::Exception const& exception) { NSInteger code = translateFileError(exception.code()); return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), RLMErrorCodeNameKey: errorString(exception.code())}]; } NSError *makeError(realm::FileAccessError const& exception) { NSInteger code = translateFileError(exception.code()); return [NSError errorWithDomain:RLMErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(exception.what()), NSFilePathErrorKey: @(exception.get_path().data()), RLMErrorCodeNameKey: errorString(exception.code())}]; } NSError *makeError(std::exception const& exception) { return [NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey: @(exception.what())}]; } NSError *makeError(std::system_error const& exception) { return translateSystemError(exception.code(), exception.what()); } ================================================ FILE: Realm/RLMError_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 #import #import RLM_HIDDEN_BEGIN NSError *makeError(realm::Status const& status); template NSError *makeError(realm::StatusWith const& statusWith) { return makeError(statusWith.get_status()); } NSError *makeError(realm::Exception const& exception); NSError *makeError(realm::FileAccessError const& exception); NSError *makeError(std::exception const& exception); NSError *makeError(std::system_error const& exception); RLM_HIDDEN_END ================================================ FILE: Realm/RLMGeospatial.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 RLM_HEADER_AUDIT_BEGIN(nullability) /// Conforming protocol for a Geo-shape. @protocol RLMGeospatial @end /** A class that represents the coordinates of a point formed by a latitude and a longitude value. * Latitude ranges between -90 and 90 degrees, inclusive. * Longitude ranges between -180 and 180 degrees, inclusive. * Altitude cannot have negative values. Values outside this ranges will return nil when trying to create a `RLMGeospatialPoint`. @note There is no dedicated type to store Geospatial points, instead points should be stored as [GeoJson-shaped](https://www.mongodb.com/docs/manual/reference/geojson/) embedded object, as explained below. Geospatial queries (`geoWithin`) can only be executed in such a type of objects and will throw otherwise. Persisting geo points in Realm is currently done using duck-typing, which means that any model class with a specific **shape** can be queried as though it contained a geographical location. The recommended approach is using an embedded object. @warning This structure cannot be persisted and can only be used to build other geospatial shapes such as (`RLMGeospatialBox`, `RLMGeospatialPolygon` and `RLMGeospatialCircle`). @warning Altitude is not used in any of the query calculations. */ NS_SWIFT_SENDABLE @interface RLMGeospatialPoint : NSObject /// Latitude in degrees. @property (readonly) double latitude; /// Longitude in degrees. @property (readonly) double longitude; /// Altitude distance. @property (readonly) double altitude; /** Initialize a `RLMGeospatialPoint`, with the specific values for latitude and longitude. Returns `nil` if the values of latitude and longitude are not within the ranges specified. @param latitude Latitude in degrees. Ranges between -90 and 90 degrees, inclusive. @param longitude Longitude in degrees. Ranges between -180 and 180 degrees, inclusive. */ - (nullable instancetype)initWithLatitude:(double)latitude longitude:(double)longitude; /** Initialize a `RLMGeospatialPoint`, with the specific values for latitude and longitude. Returns `nil` if the values of latitude and longitude are not within the ranges specified. @param latitude Latitude in degrees. Ranges between -90 and 90 degrees, inclusive. @param longitude Longitude in degrees. Ranges between -180 and 180 degrees, inclusive. @param altitude Altitude. Distance cannot have negative values @warning Altitude is not used in any of the query calculations. */ - (nullable instancetype)initWithLatitude:(double)latitude longitude:(double)longitude altitude:(double)altitude; @end /** A class that represents a rectangle, that can be used in a geospatial `geoWithin`query. - warning: This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ NS_SWIFT_SENDABLE @interface RLMGeospatialBox : NSObject /// The bottom left corner of the rectangle. @property (readonly, strong) RLMGeospatialPoint *bottomLeft; /// The top right corner of the rectangle. @property (readonly, strong) RLMGeospatialPoint *topRight; /** Initialize a `RLMGeospatialBox`, with values for bottom left corner and top right corner. @param bottomLeft The bottom left corner of the rectangle. @param topRight The top right corner of the rectangle. */ - (instancetype)initWithBottomLeft:(RLMGeospatialPoint *)bottomLeft topRight:(RLMGeospatialPoint *)topRight; @end /** A class that represents a polygon, that can be used in a geospatial `geoWithin`query. A `RLMGeospatialPolygon` describes a shape conformed of and outer `Polygon`, called `outerRing`, and 0 or more inner `Polygon`s, called `holes`, which represents an unlimited number of internal holes inside the outer `Polygon`. A `Polygon` describes a shape conformed by at least three segments, where the last and the first `RLMGeospatialPoint` must be the same to indicate a closed polygon (meaning you need at least 4 points to define a polygon). Inner holes in a `RLMGeospatialPolygon` must be entirely inside the outer ring A `hole` has the following restrictions: - Holes may not cross, i.e. the boundary of a hole may not intersect both the interior and the exterior of any other hole. - Holes may not share edges, i.e. if a hole contains and edge AB, the no other hole may contain it. - Holes may share vertices, however no vertex may appear twice in a single hole. - No hole may be empty. - Only one nesting is allowed. @warning This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ NS_SWIFT_SENDABLE @interface RLMGeospatialPolygon : NSObject /// The polygon's external (outer) ring. @property (readonly, strong) NSArray *outerRing; /// The holes (if any) in the polygon. @property (readonly, strong, nullable) NSArray *> *holes; /** Initialize a `RLMGeospatialPolygon`, with its outer rings and holes (if any). Returns `nil` if the `RLMGeospatialPoints` representing a polygon (outer ring or holes), don't have at least 4 points. Returns `nil` if the first and the last `RLMGeospatialPoint` in a polygon are not the same. @param outerRing The polygon's external (outer) ring. */ - (nullable instancetype)initWithOuterRing:(NSArray *)outerRing; /** Initialize a `RLMGeospatialPolygon`, with its outer rings and holes (if any). Returns `nil` if the `RLMGeospatialPoints` representing a polygon (outer ring or holes), don't have at least 4 points. Returns `nil` if the first and the last `RLMGeospatialPoint` in a polygon are not the same. @param outerRing The polygon's external (outer) ring. @param holes The holes (if any) in the polygon. */ - (nullable instancetype)initWithOuterRing:(NSArray *)outerRing holes:(nullable NSArray *> *)holes; @end /** This structure is a helper to represent/convert a distance. It can be used in geospatial queries like those represented by a `RLMGeospatialCircle` - warning: This structure cannot be persisted and can only be used to build other geospatial shapes */ NS_SWIFT_SENDABLE @interface RLMDistance : NSObject /// The distance in radians. @property (readonly) double radians; /** Constructs a `Distance`. Returns `nil` if the value is lower than 0, because we cannot construct negative distances. @param kilometers Distance in kilometers. @returns A value that represents the provided distance in radians. */ + (nullable instancetype)distanceFromKilometers:(double)kilometers NS_SWIFT_NAME(kilometers(_:)); /** Constructs a `Distance`. Returns `nil` if the value is lower than 0, because we cannot construct negative distances. @param miles Distance in miles. @return A value that represents the provided distance in radians. */ + (nullable instancetype)distanceFromMiles:(double)miles NS_SWIFT_NAME(miles(_:)); /** Constructs a `Distance`. Returns `nil` if the value is lower than 0, because we cannot construct negative distances. @param degrees Distance in degrees. @returns A value that represents the provided distance in radians. */ + (nullable instancetype)distanceFromDegrees:(double)degrees NS_SWIFT_NAME(degrees(_:)); /** Constructs a `Distance`. Returns `nil` if the value is lower than 0, because we cannot construct negative distances. @param radians Distance in radians. @returns A value that represents the provided distance in radians. */ + (nullable instancetype)distanceFromRadians:(double)radians NS_SWIFT_NAME(radians(_:)); /** Returns the current `Distance` value in kilometers. @returns The value un kilometers. */ - (double)asKilometers; /** Returns the current `Distance` value in miles. @returns The value un miles. */ - (double)asMiles; /** Returns the current `Distance` value in degrees. @returns The value un degrees. */ - (double)asDegrees; @end /** A class that represents a circle, that can be used in a geospatial `geoWithin`query. @warning This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ NS_SWIFT_SENDABLE @interface RLMGeospatialCircle : NSObject /// Center of the circle. @property (readonly, strong) RLMGeospatialPoint *center; /// Radius of the circle. @property (readonly) double radians; /** Initialize a `RLMGeospatialCircle`, from its center and radius. @param center Center of the circle. @param radians Radius of the circle. */ - (nullable instancetype)initWithCenter:(RLMGeospatialPoint *)center radiusInRadians:(double)radians; /** Initialize a `GeoCircle`, from its center and radius. @param center Center of the circle. @param radius Radius of the circle. */ - (instancetype)initWithCenter:(RLMGeospatialPoint *)center radius:(RLMDistance *)radius; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMGeospatial.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 "RLMGeospatial_Private.hpp" #import "RLMUtil.hpp" #import @implementation RLMGeospatialPoint - (nullable instancetype)initWithLatitude:(double)latitude longitude:(double)longitude { return [self initWithLatitude:latitude longitude:longitude altitude:0]; } - (nullable instancetype)initWithLatitude:(double)latitude longitude:(double)longitude altitude:(double)altitude { if (self = [super init]) { if ((isnan(latitude) || isnan(longitude) || isnan(altitude)) || (latitude < -90 || latitude > 90) || (longitude < -180 || longitude > 180) || (altitude < 0)) { return nil; } _latitude = latitude; _longitude = longitude; _altitude = altitude; } return self; } - (realm::GeoPoint)value { return realm::GeoPoint{_longitude, _latitude, _altitude}; } - (BOOL)isEqual:(id)other { if (auto point = RLMDynamicCast(other)) { return point.latitude == self.latitude && point.longitude == self.longitude && point.altitude == self.altitude; } return NO; } @end @interface RLMGeospatialBox () @end @implementation RLMGeospatialBox - (instancetype)initWithBottomLeft:(RLMGeospatialPoint *)bottomLeft topRight:(RLMGeospatialPoint *)topRight { if (self = [super init]) { _bottomLeft = bottomLeft; _topRight = topRight; } return self; } - (realm::Geospatial)geoSpatial { realm::GeoBox geo_box{realm::GeoPoint{_bottomLeft.longitude, _bottomLeft.latitude}, realm::GeoPoint{_topRight.longitude, _topRight.latitude}}; return realm::Geospatial{geo_box}; } - (BOOL)isEqual:(id)other { if (auto box = RLMDynamicCast(other)) { if ([box.bottomLeft isEqual:self.bottomLeft] && [box.topRight isEqual: self.topRight]) return YES; } return NO; } @end @interface RLMGeospatialPolygon () @end @implementation RLMGeospatialPolygon - (nullable instancetype)initWithOuterRing:(NSArray *)outerRing holes:(nullable NSArray *> *)holes { if (self = [super init]) { if (([outerRing count] <= 3) || (![[outerRing firstObject] isEqual:[outerRing lastObject]])) { return nil; } if (holes) { for(NSArray *hole in holes) { if (([hole count] <= 3) || (![[hole firstObject] isEqual:[hole lastObject]])) { return nil; } } } _outerRing = outerRing; _holes = holes; } return self; } - (nullable instancetype)initWithOuterRing:(NSArray *)outerRing { return [self initWithOuterRing:outerRing holes:nil]; } - (realm::Geospatial)geoSpatial { std::vector> points; std::vector outer_ring; for (RLMGeospatialPoint *point : _outerRing) { outer_ring.push_back(point.value); } points.push_back(outer_ring); if (_holes) { for (NSArray *array_points : _holes) { std::vector hole; for (RLMGeospatialPoint *point : array_points) { hole.push_back(point.value); } points.push_back(hole); } } realm::GeoPolygon geo_polygon{points}; return realm::Geospatial{geo_polygon}; } - (BOOL)isEqual:(id)other { if (auto polygon = RLMDynamicCast(other)) { if ([polygon.outerRing isEqualToArray:self.outerRing] && [polygon.holes isEqualToArray:self.holes]) return YES; } return NO; } @end /// Earth radius. static double const c_earthRadiusMeters = 6378100.0; @implementation RLMDistance + (nullable instancetype)distanceFromKilometers:(double)kilometers { double radians = (kilometers * 1000) / c_earthRadiusMeters; return [[RLMDistance alloc] initWithRadians:radians]; } + (nullable instancetype)distanceFromMiles:(double)miles { double radians = (miles * 1609.344) / c_earthRadiusMeters; return [[RLMDistance alloc] initWithRadians:radians]; } + (nullable instancetype)distanceFromDegrees:(double)degrees { double radiansPerDegree = M_PI / 180; return [[RLMDistance alloc] initWithRadians:(degrees * radiansPerDegree)]; } + (nullable instancetype)distanceFromRadians:(double)radians { return [[RLMDistance alloc] initWithRadians:radians]; } - (double)asKilometers { return (self.radians * c_earthRadiusMeters) / 1000; } - (double)asMiles { return (self.radians * c_earthRadiusMeters) / 1609.344; } - (double)asDegrees { double radiansPerDegree = M_PI / 180; return (self.radians / radiansPerDegree); } - (nullable instancetype)initWithRadians:(double)radians { if (self = [super init]) { if (isnan(radians) || radians < 0) { return nil; } _radians = radians; } return self; } - (BOOL)isEqual:(id)other { if (auto distance = RLMDynamicCast(other)) { if (distance.radians == self.radians) return YES; } return NO; } @end @interface RLMGeospatialCircle () @end @implementation RLMGeospatialCircle - (nullable instancetype)initWithCenter:(RLMGeospatialPoint *)center radiusInRadians:(double)radians { if (self = [super init]) { if (isnan(radians) || radians < 0) { return nil; } _center = center; _radians = radians; } return self; } - (instancetype)initWithCenter:(RLMGeospatialPoint *)center radius:(RLMDistance *)radius { return [self initWithCenter:center radiusInRadians:radius.radians]; } - (realm::Geospatial)geoSpatial { realm::GeoCircle geo_circle{_radians, _center.value}; return realm::Geospatial{geo_circle}; } - (BOOL)isEqual:(id)other { if (auto circle = RLMDynamicCast(other)) { if (circle.radians == self.radians && [circle.center isEqual:self.center]) return YES; } return NO; } @end ================================================ FILE: Realm/RLMGeospatial_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 namespace realm { class Geospatial; } RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RLMGeospatial_Private - (realm::Geospatial)geoSpatial; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMLogger.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 RLM_HEADER_AUDIT_BEGIN(nullability) /// An enum representing different levels of sync-related logging that can be configured. typedef NS_CLOSED_ENUM(NSUInteger, RLMLogLevel) { /// Nothing will ever be logged. RLMLogLevelOff, /// Only fatal errors will be logged. RLMLogLevelFatal, /// Only errors will be logged. RLMLogLevelError, /// Warnings and errors will be logged. RLMLogLevelWarn, /// Information about sync events will be logged. Fewer events will be logged in order to avoid overhead. RLMLogLevelInfo, /// Information about sync events will be logged. More events will be logged than with `RLMLogLevelInfo`. RLMLogLevelDetail, /// Log information that can aid in debugging. /// /// - warning: Will incur a measurable performance impact. RLMLogLevelDebug, /// Log information that can aid in debugging. More events will be logged than with `RLMLogLevelDebug`. /// /// - warning: Will incur a measurable performance impact. RLMLogLevelTrace, /// Log information that can aid in debugging. More events will be logged than with `RLMLogLevelTrace`. /// /// - warning: Will incur a measurable performance impact. RLMLogLevelAll } NS_SWIFT_NAME(LogLevel); /// A log callback function which can be set on RLMLogger. /// /// The log function may be called from multiple threads simultaneously, and is /// responsible for performing its own synchronization if any is required. NS_SWIFT_SENDABLE // invoked on a background thread typedef void (^RLMLogFunction)(RLMLogLevel level, NSString *message); /** `RLMLogger` is used for creating your own custom logging logic. You can define your own logger creating an instance of `RLMLogger` and define the log function which will be invoked whenever there is a log message. Set this custom logger as you default logger using `setDefaultLogger`. RLMLogger.defaultLogger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug logFunction:^(RLMLogLevel level, NSString * message) { NSLog(@"Realm Log - %lu, %@", (unsigned long)level, message); }]; @note By default default log threshold level is `RLMLogLevelInfo`, and logging strings are output to Apple System Logger. */ @interface RLMLogger : NSObject /** Gets the logging threshold level used by the logger. */ @property (nonatomic) RLMLogLevel level; /// :nodoc: - (instancetype)init NS_UNAVAILABLE; /** Creates a logger with the associated log level and the logic function to define your own logging logic. @param level The log level to be set for the logger. @param logFunction The log function which will be invoked whenever there is a log message. */ - (instancetype)initWithLevel:(RLMLogLevel)level logFunction:(RLMLogFunction)logFunction; #pragma mark RLMLogger Default Logger API /** The current default logger. When setting a logger as default, this logger will be used whenever information must be logged. */ @property (class) RLMLogger *defaultLogger NS_SWIFT_NAME(shared); @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMLogger.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 "RLMLogger_Private.h" #import "RLMUtil.hpp" #import typedef void (^RLMLoggerFunction)(RLMLogLevel level, NSString *message); using namespace realm; using Logger = realm::util::Logger; using Level = Logger::Level; namespace { static Level levelForLogLevel(RLMLogLevel logLevel) { switch (logLevel) { case RLMLogLevelOff: return Level::off; case RLMLogLevelFatal: return Level::fatal; case RLMLogLevelError: return Level::error; case RLMLogLevelWarn: return Level::warn; case RLMLogLevelInfo: return Level::info; case RLMLogLevelDetail: return Level::detail; case RLMLogLevelDebug: return Level::debug; case RLMLogLevelTrace: return Level::trace; case RLMLogLevelAll: return Level::all; } REALM_UNREACHABLE(); // Unrecognized log level. } static RLMLogLevel logLevelForLevel(Level logLevel) { switch (logLevel) { case Level::off: return RLMLogLevelOff; case Level::fatal: return RLMLogLevelFatal; case Level::error: return RLMLogLevelError; case Level::warn: return RLMLogLevelWarn; case Level::info: return RLMLogLevelInfo; case Level::detail: return RLMLogLevelDetail; case Level::debug: return RLMLogLevelDebug; case Level::trace: return RLMLogLevelTrace; case Level::all: return RLMLogLevelAll; } REALM_UNREACHABLE(); // Unrecognized log level. } static NSString* levelPrefix(Level logLevel) { switch (logLevel) { case Level::off: case Level::all: return @""; case Level::trace: return @"Trace"; case Level::debug: return @"Debug"; case Level::detail: return @"Detail"; case Level::info: return @"Info"; case Level::error: return @"Error"; case Level::warn: return @"Warning"; case Level::fatal: return @"Fatal"; } REALM_UNREACHABLE(); // Unrecognized log level. } struct CocoaLogger : public Logger { void do_log(const realm::util::LogCategory&, Level level, const std::string& message) override { NSLog(@"%@: %@", levelPrefix(level), RLMStringDataToNSString(message)); } }; class CustomLogger : public Logger { public: RLMLoggerFunction function; void do_log(const realm::util::LogCategory&, Level level, const std::string& message) override { @autoreleasepool { if (function) { function(logLevelForLevel(level), RLMStringDataToNSString(message)); } } } }; } // anonymous namespace @implementation RLMLogger { std::shared_ptr _logger; } typedef void(^LoggerBlock)(RLMLogLevel level, NSString *message); - (RLMLogLevel)level { return logLevelForLevel(_logger->get_level_threshold()); } - (void)setLevel:(RLMLogLevel)level { _logger->set_level_threshold(levelForLogLevel(level)); } + (void)initialize { auto defaultLogger = std::make_shared(); defaultLogger->set_level_threshold(Level::info); Logger::set_default_logger(defaultLogger); } - (instancetype)initWithLogger:(std::shared_ptr)logger { if (self = [self init]) { self->_logger = logger; } return self; } - (instancetype)initWithLevel:(RLMLogLevel)level logFunction:(RLMLogFunction)logFunction { if (self = [super init]) { auto logger = std::make_shared(); logger->set_level_threshold(levelForLogLevel(level)); logger->function = logFunction; self->_logger = logger; } return self; } - (void)logWithLevel:(RLMLogLevel)logLevel message:(NSString *)message, ... { auto level = levelForLogLevel(logLevel); if (_logger->would_log(level)) { va_list args; va_start(args, message); _logger->log(level, "%1", [[NSString alloc] initWithFormat:message arguments:args].UTF8String); va_end(args); } } - (void)logLevel:(RLMLogLevel)logLevel message:(NSString *)message { auto level = levelForLogLevel(logLevel); if (_logger->would_log(level)) { _logger->log(level, "%1", message.UTF8String); } } #pragma mark Global Logger Setter + (instancetype)defaultLogger { return [[RLMLogger alloc] initWithLogger:Logger::get_default_logger()]; } + (void)setDefaultLogger:(RLMLogger *)logger { Logger::set_default_logger(logger->_logger); } @end ================================================ FILE: Realm/RLMLogger_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 #import RLM_HEADER_AUDIT_BEGIN(nullability) @interface RLMLogger() /** Log a message to the supplied level. @param logLevel The log level for the message. @param message The message to log. */ - (void)logWithLevel:(RLMLogLevel)logLevel message:(NSString *)message, ... NS_SWIFT_UNAVAILABLE(""); - (void)logLevel:(RLMLogLevel)logLevel message:(NSString *)message; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMManagedArray.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMArray_Private.hpp" #import "RLMAccessor.hpp" #import "RLMCollection_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.hpp" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMSchema.h" #import "RLMSectionedResults_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import #import #import @interface RLMManagedArrayHandoverMetadata : NSObject @property (nonatomic) NSString *parentClassName; @property (nonatomic) NSString *key; @end @implementation RLMManagedArrayHandoverMetadata @end @interface RLMManagedArray () @end // // RLMArray implementation // @implementation RLMManagedArray { @public realm::List _backingList; RLMRealm *_realm; RLMClassInfo *_objectInfo; RLMClassInfo *_ownerInfo; std::unique_ptr _observationInfo; } - (RLMManagedArray *)initWithBackingCollection:(realm::List)list parentInfo:(RLMClassInfo *)parentInfo property:(__unsafe_unretained RLMProperty *const)property { if (property.type == RLMPropertyTypeObject) self = [self initWithObjectClassName:property.objectClassName]; else self = [self initWithObjectType:property.type optional:property.optional]; if (self) { _realm = parentInfo->realm; REALM_ASSERT(list.get_realm() == _realm->_realm); _backingList = std::move(list); _ownerInfo = parentInfo; _property = property; if (property.type == RLMPropertyTypeObject) _objectInfo = &parentInfo->linkTargetType(property.index); else _objectInfo = _ownerInfo; } return self; } - (RLMManagedArray *)initWithParent:(realm::Obj)parent property:(__unsafe_unretained RLMProperty *const)property parentInfo:(RLMClassInfo&)info { auto col = info.tableColumn(property); return [self initWithBackingCollection:realm::List(info.realm->_realm, parent, col) parentInfo:&info property:property]; } - (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject property:(__unsafe_unretained RLMProperty *const)property { return [self initWithParent:parentObject->_row property:property parentInfo:*parentObject->_info]; } void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMArray *const array) { if (![keyPath isEqualToString:RLMInvalidatedKey]) { @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@", [array class], array, keyPath); } } void RLMEnsureArrayObservationInfo(std::unique_ptr& info, __unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMArray *const array, __unsafe_unretained id const observed) { RLMValidateArrayObservationKey(keyPath, array); if (!info && array.class == [RLMManagedArray class]) { auto lv = static_cast(array); info = std::make_unique(*lv->_ownerInfo, lv->_backingList.get_parent_object_key(), observed); } } template __attribute__((always_inline)) static auto translateErrors(Function&& f) { return translateCollectionError(static_cast(f), @"List"); } template static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) { translateErrors([&] { ar->_backingList.verify_in_transaction(); }); RLMObservationTracker tracker(ar->_realm); tracker.trackDeletions(); auto obsInfo = RLMGetObservationInfo(ar->_observationInfo.get(), ar->_backingList.get_parent_object_key(), *ar->_ownerInfo); if (obsInfo) { tracker.willChange(obsInfo, ar->_property.name, kind, is()); } translateErrors(f); } static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; }); } static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; }); } static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) { changeArray(ar, kind, f, [=] { return is; }); } // // public method implementations // - (RLMRealm *)realm { return _realm; } - (NSUInteger)count { return translateErrors([&] { return _backingList.size(); }); } - (BOOL)isInvalidated { return translateErrors([&] { return !_backingList.is_valid(); }); } - (RLMClassInfo *)objectInfo { return _objectInfo; } - (bool)isBackedByList:(realm::List const&)list { return _backingList == list; } - (BOOL)isEqual:(id)object { return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList]; } - (NSUInteger)hash { return std::hash()(_backingList); } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { return RLMFastEnumerate(state, len, self); } - (id)objectAtIndex:(NSUInteger)index { return translateErrors([&]() -> id { RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); return _backingList.get(context, index); }); } static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) { RLMArrayValidateMatchingObjectType(ar, object); if (index == NSUIntegerMax) { index = translateErrors([&] { return ar->_backingList.size(); }); } changeArray(ar, NSKeyValueChangeInsertion, index, ^{ RLMAccessorContext context(*ar->_ownerInfo, *ar->_objectInfo, ar->_property); ar->_backingList.insert(context, index, object); }); } - (void)addObject:(id)object { RLMInsertObject(self, object, NSUIntegerMax); } - (void)insertObject:(id)object atIndex:(NSUInteger)index { RLMInsertObject(self, object, index); } - (void)insertObjects:(id)objects atIndexes:(NSIndexSet *)indexes { changeArray(self, NSKeyValueChangeInsertion, indexes, ^{ NSUInteger index = [indexes firstIndex]; RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); for (id obj in objects) { RLMArrayValidateMatchingObjectType(self, obj); _backingList.insert(context, index, obj); index = [indexes indexGreaterThanIndex:index]; } }); } - (void)removeObjectAtIndex:(NSUInteger)index { changeArray(self, NSKeyValueChangeRemoval, index, ^{ _backingList.remove(index); }); } - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes { changeArray(self, NSKeyValueChangeRemoval, indexes, ^{ [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) { _backingList.remove(idx); }]; }); } - (void)addObjectsFromArray:(NSArray *)array { changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); for (id obj in array) { RLMArrayValidateMatchingObjectType(self, obj); _backingList.add(context, obj); } }); } - (void)removeAllObjects { changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{ _backingList.remove_all(); }); } - (void)replaceAllObjectsWithObjects:(NSArray *)objects { if (auto count = self.count) { changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, count), ^{ _backingList.remove_all(); }); } if (![objects respondsToSelector:@selector(count)] || !objects.count) { return; } changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(0, objects.count), ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); _backingList.assign(context, objects); }); } - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object { RLMArrayValidateMatchingObjectType(self, object); changeArray(self, NSKeyValueChangeReplacement, index, ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); if (index >= _backingList.size()) { @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).", (unsigned long long)index, (unsigned long long)_backingList.size()); } _backingList.set(context, index, object); }); } - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex { auto start = std::min(sourceIndex, destinationIndex); auto len = std::max(sourceIndex, destinationIndex) - start + 1; changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{ _backingList.move(sourceIndex, destinationIndex); }); } - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 { changeArray(self, NSKeyValueChangeReplacement, ^{ _backingList.swap(index1, index2); }, [=] { NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1]; [set addIndex:index2]; return set; }); } - (NSUInteger)indexOfObject:(id)object { RLMArrayValidateMatchingObjectType(self, object); return translateErrors([&] { RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); return RLMConvertNotFound(_backingList.find(context, object)); }); } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath hasPrefix:@"@"]) { // Delegate KVC collection operators to RLMResults return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()]; return [results valueForKeyPath:keyPath]; }); } return [super valueForKeyPath:keyPath]; } - (id)valueForKey:(NSString *)key { // Ideally we'd use "@invalidated" for this so that "invalidated" would use // normal array KVC semantics, but observing @things works very oddly (when // it's part of a key path, it's triggered automatically when array index // changes occur, and can't be sent explicitly, but works normally when it's // the entire key path), and an RLMManagedArray *can't* have objects where // invalidated is true, so we're not losing much. return translateErrors([&]() -> id { if ([key isEqualToString:RLMInvalidatedKey]) { return @(!_backingList.is_valid()); } _backingList.verify_attached(); return RLMCollectionValueForKey(_backingList, key, *_objectInfo); }); } - (void)setValue:(id)value forKey:(NSString *)key { if ([key isEqualToString:@"self"]) { RLMArrayValidateMatchingObjectType(self, value); RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); translateErrors([&] { for (size_t i = 0, count = _backingList.size(); i < count; ++i) { _backingList.set(context, i, value); } }); return; } else if (_property->_type == RLMPropertyTypeObject) { RLMArrayValidateMatchingObjectType(self, value); translateErrors([&] { _backingList.verify_in_transaction(); }); RLMCollectionSetValueForKey(self, key, value); } else { [self setValue:value forUndefinedKey:key]; } } - (id)minOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingList, _objectInfo, _property->_type, RLMCollectionTypeArray); auto value = translateErrors([&] { return _backingList.min(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)maxOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingList, _objectInfo, _property->_type, RLMCollectionTypeArray); auto value = translateErrors([&] { return _backingList.max(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)sumOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingList, _objectInfo, _property->_type, RLMCollectionTypeArray); return RLMMixedToObjc(translateErrors([&] { return _backingList.sum(column); })); } - (id)averageOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingList, _objectInfo, _property->_type, RLMCollectionTypeArray); auto value = translateErrors([&] { return _backingList.average(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (void)deleteObjectsFromRealm { auto type = _property->_type; if (type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(type)); } // delete all target rows from the realm RLMObservationTracker tracker(_realm, true); translateErrors([&] { _backingList.delete_all(); }); } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { return translateErrors([&] { return [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))]; }); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()]; return [results distinctResultsUsingKeyPaths:keyPaths]; }); } - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { if (_property->_type != RLMPropertyTypeObject) { @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); } auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group); auto results = translateErrors([&] { return _backingList.filter(std::move(query)); }); return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)]; } - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { if (_property->_type != RLMPropertyTypeObject) { @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); } realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group); return translateErrors([&] { return RLMConvertNotFound(_backingList.find(std::move(query))); }); } - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { size_t c = self.count; NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:indexes.count]; NSUInteger i = [indexes firstIndex]; RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); while (i != NSNotFound) { // Given KVO relies on `objectsAtIndexes` we need to make sure // that no out of bounds exceptions are generated. This disallows us to mirror // the exception logic in Foundation, but it is better than nothing. if (i >= 0 && i < c) { [result addObject:_backingList.get(context, i)]; } else { // silently abort. return nil; } i = [indexes indexGreaterThanIndex:i]; } return result; } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingKeyPath:keyPath ascending:ascending] keyBlock:keyBlock]; } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingDescriptors:sortDescriptors] keyBlock:keyBlock]; } - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } - (realm::TableView)tableView { return translateErrors([&] { return _backingList.get_query(); }).find_all(); } - (RLMFastEnumerator *)fastEnumerator { return translateErrors([&] { return [[RLMFastEnumerator alloc] initWithBackingCollection:_backingList collection:self classInfo:_objectInfo parentInfo:_ownerInfo property:_property]; }); } - (BOOL)isFrozen { return _realm.isFrozen; } - (instancetype)resolveInRealm:(RLMRealm *)realm { auto& parentInfo = _ownerInfo->resolve(realm); return translateErrors([&] { return [[self.class alloc] initWithBackingCollection:_backingList.freeze(realm->_realm) parentInfo:&parentInfo property:parentInfo.rlmObjectSchema[_property.name]]; }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_realm.thaw]; } - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _backingList.add_notification_callback(RLMWrapCollectionChangeCallback(block, self, false), std::move(keyPaths)); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _backingList; } - (RLMManagedArrayHandoverMetadata *)objectiveCMetadata { RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init]; metadata.parentClassName = _ownerInfo->rlmObjectSchema.className; metadata.key = _property.name; return metadata; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(RLMManagedArrayHandoverMetadata *)metadata realm:(RLMRealm *)realm { auto list = reference.resolve(realm->_realm); if (!list.is_valid()) { return nil; } RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName]; return [[RLMManagedArray alloc] initWithBackingCollection:std::move(list) parentInfo:parentInfo property:parentInfo->rlmObjectSchema[metadata.key]]; } @end ================================================ FILE: Realm/RLMManagedDictionary.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMDictionary_Private.hpp" #import "RLMAccessor.hpp" #import "RLMCollection_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMSchema.h" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import @interface RLMManagedDictionary () { @public realm::object_store::Dictionary _backingCollection; } @end @implementation RLMDictionaryChange { realm::DictionaryChangeSet _changes; } - (instancetype)initWithChanges:(realm::DictionaryChangeSet const&)changes { self = [super init]; if (self) { _changes = changes; } return self; } static NSArray *toArray(std::vector const& v) { NSMutableArray *ret = [[NSMutableArray alloc] initWithCapacity:v.size()]; for (auto& mixed : v) { [ret addObject:RLMMixedToObjc(mixed)]; } return ret; } - (NSArray *)insertions { return toArray(_changes.insertions); } - (NSArray *)deletions { return toArray(_changes.deletions); } - (NSArray *)modifications { return toArray(_changes.modifications); } - (NSString *)description { return [NSString stringWithFormat:@" insertions: %@, deletions: %@, modifications: %@", (__bridge void *)self, self.insertions, self.deletions, self.modifications]; } @end @interface RLMManagedCollectionHandoverMetadata : NSObject @property (nonatomic) NSString *parentClassName; @property (nonatomic) NSString *key; @end @implementation RLMManagedCollectionHandoverMetadata @end @implementation RLMManagedDictionary { @public RLMRealm *_realm; RLMClassInfo *_objectInfo; RLMClassInfo *_ownerInfo; RLMProperty *_property; std::unique_ptr _observationInfo; } - (RLMManagedDictionary *)initWithBackingCollection:(realm::object_store::Dictionary)dictionary parentInfo:(RLMClassInfo *)parentInfo property:(__unsafe_unretained RLMProperty *const)property { if (property.type == RLMPropertyTypeObject) self = [self initWithObjectClassName:property.objectClassName keyType:property.dictionaryKeyType]; else if (property.type == RLMPropertyTypeAny) // Because the property is type mixed and we don't know if it will contain a dictionary when the schema // is created, we set RLMPropertyTypeString by default. // If another type is used for the dictionary key in a mixed dictionary context, this will thrown by core. self = [self initWithObjectType:property.type optional:property.optional keyType:RLMPropertyTypeString]; else self = [self initWithObjectType:property.type optional:property.optional keyType:property.dictionaryKeyType]; if (self) { _realm = parentInfo->realm; REALM_ASSERT(dictionary.get_realm() == _realm->_realm); _backingCollection = std::move(dictionary); _ownerInfo = parentInfo; if (property.type == RLMPropertyTypeObject) _objectInfo = &parentInfo->linkTargetType(property.index); else _objectInfo = _ownerInfo; _property = property; } return self; } - (RLMManagedDictionary *)initWithParent:(realm::Obj)parent property:(__unsafe_unretained RLMProperty *const)property parentInfo:(RLMClassInfo&)info { auto col = info.tableColumn(property); return [self initWithBackingCollection:realm::object_store::Dictionary(info.realm->_realm, parent, col) parentInfo:&info property:property]; } - (RLMManagedDictionary *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject property:(__unsafe_unretained RLMProperty *const)property { return [self initWithParent:parentObject->_row property:property parentInfo:*parentObject->_info]; } void RLMDictionaryValidateObservationKey(__unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMDictionary *const dictionary) { if (![keyPath isEqualToString:RLMInvalidatedKey]) { @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@", [dictionary class], dictionary, keyPath); } } void RLMEnsureDictionaryObservationInfo(std::unique_ptr& info, __unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMDictionary *const dictionary, __unsafe_unretained id const observed) { RLMDictionaryValidateObservationKey(keyPath, dictionary); if (!info && dictionary.class == [RLMManagedDictionary class]) { auto lv = static_cast(dictionary); info = std::make_unique(*lv->_ownerInfo, lv->_backingCollection.get_parent_object_key(), observed); } } // // validation helpers // template __attribute__((always_inline)) static auto translateErrors(Function&& f) { return translateCollectionError(static_cast(f), @"Dictionary"); } static void changeDictionary(__unsafe_unretained RLMManagedDictionary *const dict, dispatch_block_t f) { translateErrors([&] { dict->_backingCollection.verify_in_transaction(); }); RLMObservationTracker tracker(dict->_realm); tracker.trackDeletions(); auto obsInfo = RLMGetObservationInfo(dict->_observationInfo.get(), dict->_backingCollection.get_parent_object_key(), *dict->_ownerInfo); if (obsInfo) { tracker.willChange(obsInfo, dict->_property.name); } translateErrors(f); } // // public method implementations // - (RLMRealm *)realm { return _realm; } - (NSUInteger)count { return translateErrors([&] { return _backingCollection.size(); }); } static NSMutableArray *resultsToArray(RLMClassInfo& info, realm::Results r) { RLMAccessorContext c(info); NSMutableArray *array = [NSMutableArray arrayWithCapacity:r.size()]; for (size_t i = 0, size = r.size(); i < size; ++i) { [array addObject:r.get(c, i)]; } return array; } - (NSArray *)allKeys { return translateErrors([&] { return resultsToArray(*_objectInfo, _backingCollection.get_keys()); }); } - (NSArray *)allValues { return translateErrors([&] { return resultsToArray(*_objectInfo, _backingCollection.get_values()); }); } - (BOOL)isInvalidated { return translateErrors([&] { return !_backingCollection.is_valid(); }); } - (RLMClassInfo *)objectInfo { return _objectInfo; } - (bool)isBackedByDictionary:(realm::object_store::Dictionary const&)dictionary { return _backingCollection == dictionary; } - (BOOL)isEqual:(id)object { return [object respondsToSelector:@selector(isBackedByDictionary:)] && [object isBackedByDictionary:_backingCollection]; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { return RLMFastEnumerate(state, len, self); } #pragma mark - Object Retrieval - (nullable id)objectForKey:(id)key { return translateErrors([&]() -> id { [self.realm verifyThread]; RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); if (auto value = _backingCollection.try_get_any(context.unbox(key))) { if (value->is_type(realm::type_Dictionary)) { return context.box(_backingCollection.get_dictionary(realm::PathElement{context.unbox(key)})); } else if (value->is_type(realm::type_List)) { return context.box(_backingCollection.get_list(realm::PathElement{context.unbox(key)})); } else { return context.box(*value); } } return nil; }); } - (void)setObject:(id)obj forKey:(id)key { changeDictionary(self, ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); _backingCollection.insert(context, context.unbox(RLMDictionaryKey(self, key)), RLMDictionaryValue(self, obj)); }); } - (void)removeAllObjects { changeDictionary(self, ^{ _backingCollection.remove_all(); }); } - (void)removeObjectsForKeys:(NSArray *)keyArray { RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); changeDictionary(self, [&] { for (id key in keyArray) { _backingCollection.try_erase(context.unbox(key)); } }); } - (void)removeObjectForKey:(id)key { changeDictionary(self, ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); _backingCollection.try_erase(context.unbox(key)); }); } - (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block { RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); BOOL stop = false; @autoreleasepool { for (auto&& [key, value] : _backingCollection) { block(context.box(key), _backingCollection.get(context, key.get_string()), &stop); if (stop) { break; } } } } - (void)mergeDictionary:(id)dictionary clear:(bool)clear { if (!clear && !dictionary) { return; } if (dictionary && ![dictionary respondsToSelector:@selector(enumerateKeysAndObjectsUsingBlock:)]) { @throw RLMException(@"Cannot %@ object of class '%@'", clear ? @"set dictionary to" : @"add entries from", [dictionary className]); } changeDictionary(self, ^{ RLMAccessorContext context(*_ownerInfo, *_objectInfo, _property); if (clear) { _backingCollection.remove_all(); } [dictionary enumerateKeysAndObjectsUsingBlock:[&](id key, id value, BOOL *) { _backingCollection.insert(context, context.unbox(RLMDictionaryKey(self, key)), RLMDictionaryValue(self, value)); }]; }); } - (void)setDictionary:(id)dictionary { [self mergeDictionary:RLMCoerceToNil(dictionary) clear:true]; } - (void)addEntriesFromDictionary:(id)otherDictionary { [self mergeDictionary:otherDictionary clear:false]; } #pragma mark - KVC - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath hasPrefix:@"@"]) { // Delegate KVC collection operators to RLMResults return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingCollection.as_results()]; return [results valueForKeyPath:keyPath]; }); } return [super valueForKeyPath:keyPath]; } - (id)valueForKey:(NSString *)key { if ([key isEqualToString:RLMInvalidatedKey]) { return @(!_backingCollection.is_valid()); } return [self objectForKey:key]; } - (void)setValue:(id)value forKey:(nonnull NSString *)key { [self setObject:value forKeyedSubscript:key]; } - (id)minOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingCollection, _objectInfo, _property->_type, RLMCollectionTypeDictionary); auto value = translateErrors([&] { return _backingCollection.as_results().min(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)maxOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingCollection, _objectInfo, _property->_type, RLMCollectionTypeDictionary); auto value = translateErrors([&] { return _backingCollection.as_results().max(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)sumOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingCollection, _objectInfo, _property->_type, RLMCollectionTypeDictionary); auto value = translateErrors([&] { return _backingCollection.as_results().sum(column); }); return value ? RLMMixedToObjc(*value) : @0; } - (id)averageOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingCollection, _objectInfo, _property->_type, RLMCollectionTypeDictionary); auto value = translateErrors([&] { return _backingCollection.as_results().average(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (void)deleteObjectsFromRealm { auto type = _property->_type; if (type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted.", RLMTypeToString(type), _optional? @"?": @""); } // delete all target rows from the realm RLMObservationTracker tracker(_realm, true); translateErrors([&] { for (auto&& [key, value] : _backingCollection) { _realm.group.get_object(value.get_link()).remove(); } _backingCollection.remove_all(); }); } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { return translateErrors([&] { return [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingCollection.as_results().sort(RLMSortDescriptorsToKeypathArray(properties))]; }); } - (RLMResults *)sortedResultsUsingKeyPath:(nonnull NSString *)keyPath ascending:(BOOL)ascending { return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingCollection.as_results()]; return [results distinctResultsUsingKeyPaths:keyPaths]; }); } - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { if (_property->_type != RLMPropertyTypeObject) { @throw RLMException(@"Querying is currently only implemented for dictionaries of Realm Objects"); } auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group); auto results = translateErrors([&] { return _backingCollection.as_results().filter(std::move(query)); }); return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)]; } - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMEnsureDictionaryObservationInfo(_observationInfo, keyPath, self, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } - (realm::TableView)tableView { return translateErrors([&] { return _backingCollection.as_results().get_query(); }).find_all(); } - (RLMFastEnumerator *)fastEnumerator { return translateErrors([&] { return [[RLMFastEnumerator alloc] initWithBackingDictionary:_backingCollection dictionary:self classInfo:_objectInfo parentInfo:_ownerInfo property:_property ]; }); } - (BOOL)isFrozen { return _realm.isFrozen; } - (instancetype)resolveInRealm:(RLMRealm *)realm { auto& parentInfo = _ownerInfo->resolve(realm); return translateErrors([&] { return [[self.class alloc] initWithBackingCollection:_backingCollection.freeze(realm->_realm) parentInfo:&parentInfo property:parentInfo.rlmObjectSchema[_property.name]]; }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_realm.thaw]; } namespace { struct DictionaryCallbackWrapper { void (^block)(id, RLMDictionaryChange *, NSError *); RLMManagedDictionary *collection; realm::TransactionRef previousTransaction; DictionaryCallbackWrapper(void (^block)(id, RLMDictionaryChange *, NSError *), RLMManagedDictionary *dictionary) : block(block) , collection(dictionary) , previousTransaction(static_cast(collection.realm.group).duplicate()) { } void operator()(realm::DictionaryChangeSet const& changes) { if (changes.deletions.empty() && changes.insertions.empty() && changes.modifications.empty()) { block(collection, nil, nil); } else { block(collection, [[RLMDictionaryChange alloc] initWithChanges:changes], nil); } if (collection.isInvalidated) { previousTransaction->end_read(); } else { previousTransaction->advance_read(static_cast(collection.realm.group).get_version_of_current_transaction()); } } }; } // anonymous namespace - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _backingCollection.add_key_based_notification_callback(DictionaryCallbackWrapper{block, self}, std::move(keyPaths)); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _backingCollection; } - (RLMManagedCollectionHandoverMetadata *)objectiveCMetadata { RLMManagedCollectionHandoverMetadata *metadata = [[RLMManagedCollectionHandoverMetadata alloc] init]; metadata.parentClassName = _ownerInfo->rlmObjectSchema.className; metadata.key = _property.name; return metadata; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(RLMManagedCollectionHandoverMetadata *)metadata realm:(RLMRealm *)realm { auto dictionary = reference.resolve(realm->_realm); if (!dictionary.is_valid()) { return nil; } RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName]; return [[RLMManagedDictionary alloc] initWithBackingCollection:std::move(dictionary) parentInfo:parentInfo property:parentInfo->rlmObjectSchema[metadata.key]]; } @end ================================================ FILE: Realm/RLMManagedSet.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMSet_Private.hpp" #import "RLMAccessor.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMSchema.h" #import "RLMSectionedResults_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import #import @interface RLMManagedSetHandoverMetadata : NSObject @property (nonatomic) NSString *parentClassName; @property (nonatomic) NSString *key; @end @implementation RLMManagedSetHandoverMetadata @end @interface RLMManagedSet () @end // // RLMSet implementation // @implementation RLMManagedSet { @public realm::object_store::Set _backingSet; RLMRealm *_realm; RLMClassInfo *_objectInfo; RLMClassInfo *_ownerInfo; std::unique_ptr _observationInfo; } - (RLMManagedSet *)initWithBackingCollection:(realm::object_store::Set)set parentInfo:(RLMClassInfo *)parentInfo property:(__unsafe_unretained RLMProperty *const)property { if (property.type == RLMPropertyTypeObject) self = [self initWithObjectClassName:property.objectClassName]; else self = [self initWithObjectType:property.type optional:property.optional]; if (self) { _realm = parentInfo->realm; REALM_ASSERT(set.get_realm() == _realm->_realm); _backingSet = std::move(set); _ownerInfo = parentInfo; _property = property; if (property.type == RLMPropertyTypeObject) _objectInfo = &parentInfo->linkTargetType(property.index); else _objectInfo = _ownerInfo; } return self; } - (RLMManagedSet *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject property:(__unsafe_unretained RLMProperty *const)property { __unsafe_unretained RLMRealm *const realm = parentObject->_realm; auto col = parentObject->_info->tableColumn(property); return [self initWithBackingCollection:realm::object_store::Set(realm->_realm, parentObject->_row, col) parentInfo:parentObject->_info property:property]; } - (RLMManagedSet *)initWithParent:(realm::Obj)parent property:(__unsafe_unretained RLMProperty *const)property parentInfo:(RLMClassInfo&)info { auto col = info.tableColumn(property); return [self initWithBackingCollection:realm::object_store::Set(info.realm->_realm, parent, col) parentInfo:&info property:property]; } void RLMValidateSetObservationKey(__unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMSet *const set) { if (![keyPath isEqualToString:RLMInvalidatedKey]) { @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@", [set class], set, keyPath); } } void RLMEnsureSetObservationInfo(std::unique_ptr& info, __unsafe_unretained NSString *const keyPath, __unsafe_unretained RLMSet *const set, __unsafe_unretained id const observed) { RLMValidateSetObservationKey(keyPath, set); if (!info && set.class == [RLMManagedSet class]) { auto lv = static_cast(set); info = std::make_unique(*lv->_ownerInfo, lv->_backingSet.get_parent_object_key(), observed); } } template __attribute__((always_inline)) static auto translateErrors(Function&& f) { return translateCollectionError(static_cast(f), @"Set"); } static void changeSet(__unsafe_unretained RLMManagedSet *const set, dispatch_block_t f) { translateErrors([&] { set->_backingSet.verify_in_transaction(); }); RLMObservationTracker tracker(set->_realm, false); tracker.trackDeletions(); auto obsInfo = RLMGetObservationInfo(set->_observationInfo.get(), set->_backingSet.get_parent_object_key(), *set->_ownerInfo); if (obsInfo) { tracker.willChange(obsInfo, set->_property.name); } translateErrors(f); } // // public method implementations // - (RLMRealm *)realm { return _realm; } - (NSUInteger)count { return translateErrors([&] { return _backingSet.size(); }); } - (NSArray *)allObjects { NSMutableArray *arr = [NSMutableArray new]; for (id prop : self) { [arr addObject:prop]; } return arr; } - (BOOL)isInvalidated { return translateErrors([&] { return !_backingSet.is_valid(); }); } - (RLMClassInfo *)objectInfo { return _objectInfo; } - (bool)isBackedBySet:(realm::object_store::Set const&)set { return _backingSet == set; } - (BOOL)isEqual:(id)object { return [object respondsToSelector:@selector(isBackedBySet:)] && [object isBackedBySet:_backingSet]; } - (NSUInteger)hash { return std::hash()(_backingSet); } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { return RLMFastEnumerate(state, len, self); } static void RLMInsertObject(RLMManagedSet *set, id object) { RLMSetValidateMatchingObjectType(set, object); changeSet(set, ^{ RLMAccessorContext context(*set->_objectInfo); set->_backingSet.insert(context, object); }); } static void RLMRemoveObject(RLMManagedSet *set, id object) { RLMSetValidateMatchingObjectType(set, object); changeSet(set, ^{ RLMAccessorContext context(*set->_objectInfo); set->_backingSet.remove(context, object); }); } static void ensureInWriteTransaction(NSString *message, RLMManagedSet *set, RLMManagedSet *otherSet) { if (!set.realm.inWriteTransaction && !otherSet.realm.inWriteTransaction) { @throw RLMException(@"Can only perform %@ in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.", message); } } - (void)addObject:(id)object { RLMInsertObject(self, object); } - (void)addObjects:(id)objects { changeSet(self, ^{ RLMAccessorContext context(*_objectInfo); for (id obj in objects) { RLMSetValidateMatchingObjectType(self, obj); _backingSet.insert(context, obj); } }); } - (void)removeObject:(id)object { RLMRemoveObject(self, object); } - (void)removeAllObjects { changeSet(self, ^{ _backingSet.remove_all(); }); } - (void)replaceAllObjectsWithObjects:(NSArray *)objects { changeSet(self, ^{ RLMAccessorContext context(*_objectInfo); _backingSet.assign(context, objects); }); } - (RLMManagedSet *)managedObjectFrom:(RLMSet *)set { auto managedSet = RLMDynamicCast(set); if (!managedSet) { @throw RLMException(@"Right hand side value must be a managed Set."); } if (_type != managedSet->_type) { @throw RLMException(@"Cannot intersect sets of type '%@' and '%@'.", RLMTypeToString(_type), RLMTypeToString(managedSet->_type)); } if (_realm != managedSet->_realm) { @throw RLMException(@"Cannot insersect sets managed by different Realms."); } if (_objectInfo != managedSet->_objectInfo) { @throw RLMException(@"Cannot intersect sets of type '%@' and '%@'.", _objectInfo->rlmObjectSchema.className, managedSet->_objectInfo->rlmObjectSchema.className); } return managedSet; } - (BOOL)isSubsetOfSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; return _backingSet.is_subset_of(rhs->_backingSet); } - (BOOL)intersectsSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; return _backingSet.intersects(rhs->_backingSet); } - (BOOL)containsObject:(id)obj { RLMSetValidateMatchingObjectType(self, obj); RLMAccessorContext context(*_objectInfo); auto r = _backingSet.find(context, obj); return r != realm::npos; } - (BOOL)isEqualToSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; return [self isEqual:rhs]; } - (void)setSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; ensureInWriteTransaction(@"[RLMSet setSet:]", self, rhs); changeSet(self, ^{ RLMAccessorContext context(*_objectInfo); _backingSet.assign(context, rhs); }); } - (void)intersectSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; ensureInWriteTransaction(@"[RLMSet intersectSet:]", self, rhs); changeSet(self, ^{ _backingSet.assign_intersection(rhs->_backingSet); }); } - (void)unionSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; ensureInWriteTransaction(@"[RLMSet unionSet:]", self, rhs); changeSet(self, ^{ _backingSet.assign_union(rhs->_backingSet); }); } - (void)minusSet:(RLMSet *)set { RLMManagedSet *rhs = [self managedObjectFrom:set]; ensureInWriteTransaction(@"[RLMSet minusSet:]", self, rhs); changeSet(self, ^{ _backingSet.assign_difference(rhs->_backingSet); }); } - (id)objectAtIndex:(NSUInteger)index { return translateErrors([&] { RLMAccessorContext context(*_objectInfo); return _backingSet.get(context, index); }); } - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { size_t count = self.count; NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:indexes.count]; RLMAccessorContext context(*_objectInfo); for (NSUInteger i = indexes.firstIndex; i != NSNotFound; i = [indexes indexGreaterThanIndex:i]) { if (i >= count) { return nil; } [result addObject:_backingSet.get(context, i)]; } return result; } - (id)firstObject { return translateErrors([&] { RLMAccessorContext context(*_objectInfo); return _backingSet.size() ? _backingSet.get(context, 0) : nil; }); } - (id)lastObject { return translateErrors([&] { RLMAccessorContext context(*_objectInfo); size_t size = _backingSet.size(); return size ? _backingSet.get(context, size - 1) : nil; }); } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath hasPrefix:@"@"]) { // Delegate KVC collection operators to RLMResults return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingSet.as_results()]; return [results valueForKeyPath:keyPath]; }); } return [super valueForKeyPath:keyPath]; } - (id)valueForKey:(NSString *)key { // Ideally we'd use "@invalidated" for this so that "invalidated" would use // normal array KVC semantics, but observing @things works very oddly (when // it's part of a key path, it's triggered automatically when array index // changes occur, and can't be sent explicitly, but works normally when it's // the entire key path), and an RLMManagedSet *can't* have objects where // invalidated is true, so we're not losing much. return translateErrors([&]() -> id { if ([key isEqualToString:RLMInvalidatedKey]) { return @(!_backingSet.is_valid()); } _backingSet.verify_attached(); return [NSSet setWithArray:RLMCollectionValueForKey(_backingSet, key, *_objectInfo)]; }); return nil; } - (void)setValue:(id)value forKey:(NSString *)key { if ([key isEqualToString:@"self"]) { RLMSetValidateMatchingObjectType(self, value); RLMAccessorContext context(*_objectInfo); translateErrors([&] { _backingSet.remove_all(); _backingSet.insert(context, value); return; }); } else if (_type == RLMPropertyTypeObject) { RLMSetValidateMatchingObjectType(self, value); translateErrors([&] { _backingSet.verify_in_transaction(); }); RLMCollectionSetValueForKey(self, key, value); } else { [self setValue:value forUndefinedKey:key]; } } - (id)minOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet); auto value = translateErrors([&] { return _backingSet.min(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)maxOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet); auto value = translateErrors([&] { return _backingSet.max(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)sumOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet); return RLMMixedToObjc(translateErrors([&] { return _backingSet.sum(column); })); } - (id)averageOfProperty:(NSString *)property { auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet); auto value = translateErrors([&] { return _backingSet.average(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (void)deleteObjectsFromRealm { if (_type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMSet<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type)); } // delete all target rows from the realm RLMObservationTracker tracker(_realm, true); translateErrors([&] { _backingSet.delete_all(); }); } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { return translateErrors([&] { return [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingSet.sort(RLMSortDescriptorsToKeypathArray(properties))]; }); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { return translateErrors([&] { auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingSet.as_results()]; return [results distinctResultsUsingKeyPaths:keyPaths]; }); } - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { if (_type != RLMPropertyTypeObject) { @throw RLMException(@"Querying is currently only implemented for sets of Realm Objects"); } auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group); auto results = translateErrors([&] { return _backingSet.filter(std::move(query)); }); return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)]; } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingKeyPath:keyPath ascending:ascending] keyBlock:keyBlock]; } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingDescriptors:sortDescriptors] keyBlock:keyBlock]; } - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMEnsureSetObservationInfo(_observationInfo, keyPath, self, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } - (RLMFastEnumerator *)fastEnumerator { return translateErrors([&] { return [[RLMFastEnumerator alloc] initWithBackingCollection:_backingSet collection:self classInfo:_objectInfo parentInfo:_ownerInfo property:_property]; }); } - (realm::TableView)tableView { return translateErrors([&] { return _backingSet.get_query(); }).find_all(); } - (BOOL)isFrozen { return _realm.isFrozen; } - (instancetype)resolveInRealm:(RLMRealm *)realm { auto& parentInfo = _ownerInfo->resolve(realm); return translateErrors([&] { return [[self.class alloc] initWithBackingCollection:_backingSet.freeze(realm->_realm) parentInfo:&parentInfo property:parentInfo.rlmObjectSchema[_property.name]]; }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_realm.thaw]; } - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _backingSet.add_notification_callback(RLMWrapCollectionChangeCallback(block, self, false), std::move(keyPaths)); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _backingSet; } - (RLMManagedSetHandoverMetadata *)objectiveCMetadata { RLMManagedSetHandoverMetadata *metadata = [[RLMManagedSetHandoverMetadata alloc] init]; metadata.parentClassName = _ownerInfo->rlmObjectSchema.className; metadata.key = _property.name; return metadata; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(RLMManagedSetHandoverMetadata *)metadata realm:(RLMRealm *)realm { auto set = reference.resolve(realm->_realm); if (!set.is_valid()) { return nil; } RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName]; return [[RLMManagedSet alloc] initWithBackingCollection:std::move(set) parentInfo:parentInfo property:parentInfo->rlmObjectSchema[metadata.key]]; } @end ================================================ FILE: Realm/RLMMigration.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMSchema; @class RLMArray; @class RLMObject; /** A block type which provides both the old and new versions of an object in the Realm. Object properties can only be accessed using keyed subscripting. @see `-[RLMMigration enumerateObjects:block:]` @param oldObject The object from the original Realm (read-only). @param newObject The object from the migrated Realm (read-write). */ typedef void (^RLMObjectMigrationBlock)(RLMObject * __nullable oldObject, RLMObject * __nullable newObject); /** `RLMMigration` instances encapsulate information intended to facilitate a schema migration. A `RLMMigration` instance is passed into a user-defined `RLMMigrationBlock` block when updating the version of a Realm. This instance provides access to the old and new database schemas, the objects in the Realm, and provides functionality for modifying the Realm during the migration. */ @interface RLMMigration : NSObject #pragma mark - Properties /** Returns the old `RLMSchema`. This is the schema which describes the Realm before the migration is applied. */ @property (nonatomic, readonly) RLMSchema *oldSchema NS_REFINED_FOR_SWIFT; /** Returns the new `RLMSchema`. This is the schema which describes the Realm after the migration is applied. */ @property (nonatomic, readonly) RLMSchema *newSchema NS_REFINED_FOR_SWIFT; #pragma mark - Altering Objects during a Migration /** Enumerates all the objects of a given type in the Realm, providing both the old and new versions of each object. Within the block, object properties can only be accessed using keyed subscripting. @param className The name of the `RLMObject` class to enumerate. @warning All objects returned are of a type specific to the current migration and should not be cast to `className`. Instead, treat them as `RLMObject`s and use keyed subscripting to access properties. */ - (void)enumerateObjects:(NSString *)className block:(__attribute__((noescape, swift_attr("@nonSendable"))) RLMObjectMigrationBlock)block NS_REFINED_FOR_SWIFT; /** Creates and returns an `RLMObject` instance of type `className` in the Realm being migrated. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an `NSArray` as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param className The name of the `RLMObject` class to create. @param value The value used to populate the object. */ - (RLMObject *)createObject:(NSString *)className withValue:(id)value NS_REFINED_FOR_SWIFT; /** Deletes an object from a Realm during a migration. It is permitted to call this method from within the block passed to `-[enumerateObjects:block:]`. @param object Object to be deleted from the Realm being migrated. */ - (void)deleteObject:(RLMObject *)object NS_REFINED_FOR_SWIFT; /** Deletes the data for the class with the given name. All objects of the given class will be deleted. If the `RLMObject` subclass no longer exists in your program, any remaining metadata for the class will be removed from the Realm file. @param name The name of the `RLMObject` class to delete. @return A Boolean value indicating whether there was any data to delete. */ - (BOOL)deleteDataForClassName:(NSString *)name NS_REFINED_FOR_SWIFT; /** Renames a property of the given class from `oldName` to `newName`. @param className The name of the class whose property should be renamed. This class must be present in both the old and new Realm schemas. @param oldName The old persisted property name for the property to be renamed. There must not be a property with this name in the class as defined by the new Realm schema. @param newName The new persisted property name for the property to be renamed. There must not be a property with this name in the class as defined by the old Realm schema. */ - (void)renamePropertyForClass:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName NS_REFINED_FOR_SWIFT; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMMigration.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMMigration_Private.h" #import "RLMAccessor.h" #import "RLMObject_Private.h" #import "RLMObject_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMProperty_Private.h" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMResults_Private.hpp" #import "RLMSchema_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import using namespace realm; @implementation RLMMigration { RLMRealm *_oldRealm; RLMRealm *_realm; realm::Schema *_schema; } - (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema { self = [super init]; if (self) { _realm = realm; _oldRealm = oldRealm; _schema = &schema; } return self; } - (RLMSchema *)oldSchema { return _oldRealm.schema; } - (RLMSchema *)newSchema { return _realm.schema; } - (void)enumerateObjects:(NSString *)className block:(__attribute__((noescape)) RLMObjectMigrationBlock)block { RLMResults *objects = [_realm.schema schemaForClassName:className] ? [_realm allObjects:className] : nil; RLMResults *oldObjects = [_oldRealm.schema schemaForClassName:className] ? [_oldRealm allObjects:className] : nil; // For whatever reason if this is a newly added table we enumerate the // objects in it, while in all other cases we enumerate only the existing // objects. It's unclear how this could be useful, but changing it would // also be a pointless breaking change and it's unlikely to be hurting anyone. if (objects && !oldObjects) { for (RLMObject *object in objects) { @autoreleasepool { block(nil, object); } } return; } // If a table will be deleted it can still be enumerated during the migration // so that data can be saved or transfered to other tables if necessary. if (!objects && oldObjects) { for (RLMObject *oldObject in oldObjects) { @autoreleasepool { block(oldObject, nil); } } return; } if (oldObjects.count == 0 || objects.count == 0) { return; } auto& info = _realm->_info[className]; for (RLMObject *oldObject in oldObjects) { @autoreleasepool { Obj newObj; try { newObj = info.table()->get_object(oldObject->_row.get_key()); } catch (KeyNotFound const&) { continue; } block(oldObject, (id)RLMCreateObjectAccessor(info, std::move(newObj))); } } } - (void)execute:(RLMMigrationBlock)block objectClass:(::Class)dynamicObjectClass { if (!dynamicObjectClass) { dynamicObjectClass = RLMDynamicObject.class; } @autoreleasepool { // disable all primary keys for migration and use DynamicObject for all types for (RLMObjectSchema *objectSchema in _realm.schema.objectSchema) { objectSchema.accessorClass = dynamicObjectClass; objectSchema.primaryKeyProperty.isPrimary = NO; } for (RLMObjectSchema *objectSchema in _oldRealm.schema.objectSchema) { objectSchema.accessorClass = dynamicObjectClass; } block(self, _oldRealm->_realm->schema_version()); _oldRealm = nil; _realm = nil; } } - (RLMObject *)createObject:(NSString *)className withValue:(id)value { return [_realm createObject:className withValue:value]; } - (RLMObject *)createObject:(NSString *)className withObject:(id)object { return [self createObject:className withValue:object]; } - (void)deleteObject:(RLMObject *)object { [_realm deleteObject:object]; } - (BOOL)deleteDataForClassName:(NSString *)name { if (!name) { return false; } TableRef table = ObjectStore::table_for_object_type(_realm.group, name.UTF8String); if (!table) { return false; } if ([_realm.schema schemaForClassName:name]) { table->clear(); } else { _realm.group.remove_table(table->get_key()); } return true; } - (void)renamePropertyForClass:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName { realm::ObjectStore::rename_property(_realm.group, *_schema, className.UTF8String, oldName.UTF8String, newName.UTF8String); } @end ================================================ FILE: Realm/RLMMigration_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import namespace realm { class Schema; } RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMMigration () - (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema; - (void)execute:(RLMMigrationBlock)block objectClass:(_Nullable Class)cls; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObject.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMNotificationToken; @class RLMObjectSchema; @class RLMPropertyChange; @class RLMPropertyDescriptor; @class RLMRealm; @class RLMResults; /** `RLMObject` is a base class for model objects representing data stored in Realms. Define your model classes by subclassing `RLMObject` and adding properties to be managed. Then instantiate and use your custom subclasses instead of using the `RLMObject` class directly. // Dog.h @interface Dog : RLMObject @property NSString *name; @property BOOL adopted; @end // Dog.m @implementation Dog @end //none needed ### Supported property types - `NSString` - `NSInteger`, `int`, `long`, `float`, and `double` - `BOOL` or `bool` - `NSDate` - `NSData` - `NSNumber`, where `X` is one of `RLMInt`, `RLMFloat`, `RLMDouble` or `RLMBool`, for optional number properties - `RLMObject` subclasses, to model many-to-one relationships. - `RLMArray`, where `X` is an `RLMObject` subclass, to model many-to-many relationships. ### Querying You can initiate queries directly via the class methods: `allObjects`, `objectsWhere:`, and `objectsWithPredicate:`. These methods allow you to easily query a custom subclass for instances of that class in the default Realm. To search in a Realm other than the default Realm, use the `allObjectsInRealm:`, `objectsInRealm:where:`, and `objectsInRealm:withPredicate:` class methods. @see `RLMRealm` ### Relationships See our [Realm Swift Documentation](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/) for more details. ### Key-Value Observing All `RLMObject` properties (including properties you create in subclasses) are [Key-Value Observing compliant](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html), except for `realm` and `objectSchema`. Keep the following tips in mind when observing Realm objects: 1. Unlike `NSMutableArray` properties, `RLMArray` properties do not require using the proxy object returned from `-mutableArrayValueForKey:`, or defining KVC mutation methods on the containing class. You can simply call methods on the `RLMArray` directly; any changes will be automatically observed by the containing object. 2. Unmanaged `RLMObject` instances cannot be added to a Realm while they have any observed properties. 3. Modifying managed `RLMObject`s within `-observeValueForKeyPath:ofObject:change:context:` is not recommended. Properties may change even when the Realm is not in a write transaction (for example, when `-[RLMRealm refresh]` is called after changes are made on a different thread), and notifications sent prior to the change being applied (when `NSKeyValueObservingOptionPrior` is used) may be sent at times when you *cannot* begin a write transaction. */ @interface RLMObject : RLMObjectBase #pragma mark - Creating & Initializing Objects /** Creates an unmanaged instance of a Realm object. Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. @see `[RLMRealm addObject:]` */ - (instancetype)init NS_DESIGNATED_INITIALIZER; /** Creates an unmanaged instance of a Realm object. Pass in an `NSArray` or `NSDictionary` instance to set the values of the object's properties. Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. @see `[RLMRealm addObject:]` */ - (instancetype)initWithValue:(id)value; /** Returns the class name for a Realm object subclass. @warning Do not override. Realm relies on this method returning the exact class name. @return The class name for the model class. */ + (NSString *)className; /** Creates an instance of a Realm object with a given value, and adds it to the default Realm. If nested objects are included in the argument, `createInDefaultRealmWithValue:` will be recursively called on them. The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param value The value used to populate the object. @see `defaultPropertyValues` */ + (instancetype)createInDefaultRealmWithValue:(id)value; /** Creates an instance of a Realm object with a given value, and adds it to the specified Realm. If nested objects are included in the argument, `createInRealm:withValue:` will be recursively called on them. The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param realm The Realm which should manage the newly-created object. @param value The value used to populate the object. @see `defaultPropertyValues` */ + (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value; /** Creates or updates a Realm object within the default Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the default Realm, its values are updated and the object is returned. Otherwise, this method creates and populates a new instance of the object in the default Realm. If nested objects are included in the argument, `createOrUpdateInDefaultRealmWithValue:` will be recursively called on them if they have primary keys, `createInDefaultRealmWithValue:` if they do not. The `value` argument is used to populate the object. It can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. If the object is being created, an exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is a Realm object already managed by the default Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. If the object is being updated, each property defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value. Each property is set even if the existing value is the same as the new value being set, and notifications will report them all being changed. See `createOrUpdateModifiedInDefaultRealmWithValue:` for a version of this function which only sets the values which have changed. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value; /** Creates or updates a Realm object within the default Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the default Realm, its values are updated and the object is returned. Otherwise, this method creates and populates a new instance of the object in the default Realm. If nested objects are included in the argument, `createOrUpdateModifiedInDefaultRealmWithValue:` will be recursively called on them if they have primary keys, `createInDefaultRealmWithValue:` if they do not. The `value` argument is used to populate the object. It can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. If the object is being created, an exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is a Realm object already managed by the default Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. If the object is being updated, each property defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value. Unlike `createOrUpdateInDefaultRealmWithValue:`, only properties which have changed in value are set, and any change notifications produced by this call will report only which properies have actually changed. Checking which properties have changed imposes a small amount of overhead, and so this method may be slower when all or nearly all of the properties being set have changed. If most or all of the properties being set have not changed, this method will be much faster than unconditionally setting all of them, and will also reduce how much data has to be written to the Realm, saving both i/o time and disk space. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateModifiedInDefaultRealmWithValue:(id)value; /** Creates or updates an Realm object within a specified Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the given Realm, its values are updated and the object is returned. Otherwise this method creates and populates a new instance of this object in the given Realm. If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be recursively called on them if they have primary keys, `createInRealm:withValue:` if they do not. The `value` argument is used to populate the object. It can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. If the object is being created, an exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is a Realm object already managed by the given Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. If the object is being updated, each property defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value. Each property is set even if the existing value is the same as the new value being set, and notifications will report them all being changed. See `createOrUpdateModifiedInRealm:withValue:` for a version of this function which only sets the values which have changed. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param realm The Realm which should own the object. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value; /** Creates or updates an Realm object within a specified Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the given Realm, its values are updated and the object is returned. Otherwise this method creates and populates a new instance of this object in the given Realm. If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be recursively called on them if they have primary keys, `createInRealm:withValue:` if they do not. The `value` argument is used to populate the object. It can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. If the object is being created, an exception will be thrown if any required properties are not present and those properties were not defined with default values. If the `value` argument is a Realm object already managed by the given Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. If the object is being updated, each property defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `valueForKey:` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value. Unlike `createOrUpdateInRealm:withValue:`, only properties which have changed in value are set, and any change notifications produced by this call will report only which properies have actually changed. Checking which properties have changed imposes a small amount of overhead, and so this method may be slower when all or nearly all of the properties being set have changed. If most or all of the properties being set have not changed, this method will be much faster than unconditionally setting all of them, and will also reduce how much data has to be written to the Realm, saving both i/o time and disk space. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. @param realm The Realm which should own the object. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateModifiedInRealm:(RLMRealm *)realm withValue:(id)value; #pragma mark - Properties /** The Realm which manages the object, or `nil` if the object is unmanaged. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** The object schema which lists the managed properties for the object. */ @property (nonatomic, readonly) RLMObjectSchema *objectSchema; /** Indicates if the object can no longer be accessed because it is now invalid. An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if `invalidate` is called on that Realm. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; /** Indicates if this object is frozen. @see `-[RLMObject freeze]` */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Customizing your Objects /** Returns an array of property names for properties which should be indexed. Only string, integer, boolean, and `NSDate` properties are supported. @return An array of property names. */ + (NSArray *)indexedProperties; /** Override this method to specify the default values to be used for each property. @return A dictionary mapping property names to their default values. */ + (nullable NSDictionary *)defaultPropertyValues; /** Override this method to specify the name of a property to be used as the primary key. Only properties of types `RLMPropertyTypeString` and `RLMPropertyTypeInt` can be designated as the primary key. Primary key properties enforce uniqueness for each value whenever the property is set, which incurs minor overhead. Indexes are created automatically for primary key properties. @return The name of the property designated as the primary key. */ + (nullable NSString *)primaryKey; /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. @return An array of property names to ignore. */ + (nullable NSArray *)ignoredProperties; /** Override this method to specify the names of properties that are non-optional (i.e. cannot be assigned a `nil` value). By default, all properties of a type whose values can be set to `nil` are considered optional properties. To require that an object in a Realm always store a non-`nil` value for a property, add the name of the property to the array returned from this method. Properties of `RLMObject` type cannot be non-optional. Array and `NSNumber` properties can be non-optional, but there is no reason to do so: arrays do not support storing nil, and if you want a non-optional number you should instead use the primitive type. @return An array of property names that are required. */ + (NSArray *)requiredProperties; /** Override this method to provide information related to properties containing linking objects. Each property of type `RLMLinkingObjects` must have a key in the dictionary returned by this method consisting of the property name. The corresponding value must be an instance of `RLMPropertyDescriptor` that describes the class and property that the property is linked to. return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Owner.class propertyName:@"dogs"] }; @return A dictionary mapping property names to `RLMPropertyDescriptor` instances. */ + (NSDictionary *)linkingObjectsProperties; #pragma mark - Getting & Querying Objects from the Default Realm /** Returns all objects of this object type from the default Realm. @return An `RLMResults` containing all objects of this type in the default Realm. */ + (RLMResults *)allObjects; /** Returns all objects of this object type matching the given predicate from the default Realm. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. */ + (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: + (RLMResults<__kindof RLMObject *> *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects of this object type matching the given predicate from the default Realm. @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. */ + (RLMResults *)objectsWithPredicate:(nullable NSPredicate *)predicate; /** Retrieves the single instance of this object type with the given primary key from the default Realm. Returns the object from the default Realm which has the given primary key, or `nil` if the object does not exist. This is slightly faster than the otherwise equivalent `[[SubclassName objectsWhere:@"primaryKeyPropertyName = %@", key] firstObject]`. This method requires that `primaryKey` be overridden on the receiving subclass. @return An object of this object type, or `nil` if an object with the given primary key does not exist. @see `-primaryKey` */ + (nullable instancetype)objectForPrimaryKey:(nullable id)primaryKey NS_SWIFT_NAME(object(forPrimaryKey:)); #pragma mark - Querying Specific Realms /** Returns all objects of this object type from the specified Realm. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm. */ + (RLMResults *)allObjectsInRealm:(RLMRealm *)realm; /** Returns all objects of this object type matching the given predicate from the specified Realm. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. */ + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ...; /// :nodoc: + (RLMResults<__kindof RLMObject *> *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects of this object type matching the given predicate from the specified Realm. @param predicate A predicate to use to filter the elements. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. */ + (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(nullable NSPredicate *)predicate; /** Retrieves the single instance of this object type with the given primary key from the specified Realm. Returns the object from the specified Realm which has the given primary key, or `nil` if the object does not exist. This is slightly faster than the otherwise equivalent `[[SubclassName objectsInRealm:realm where:@"primaryKeyPropertyName = %@", key] firstObject]`. This method requires that `primaryKey` be overridden on the receiving subclass. @return An object of this object type, or `nil` if an object with the given primary key does not exist. @see `-primaryKey` */ + (nullable instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(nullable id)primaryKey NS_SWIFT_NAME(object(in:forPrimaryKey:)); #pragma mark - Notifications /** A callback block for `RLMObject` notifications. If the object is deleted from the managing Realm, the block is called with `deleted` set to `YES` and the other two arguments are `nil`. The block will never be called again after this. If the object is modified, the block will be called with `deleted` set to `NO`, a `nil` error, and an array of `RLMPropertyChange` objects which indicate which properties of the objects were modified. `error` is always `nil` and will be removed in a future version. */ typedef void (^RLMObjectChangeBlock)(BOOL deleted, NSArray *_Nullable changes, NSError *_Nullable error); /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block queue:(dispatch_queue_t)queue; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue; /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `-invalidate` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths; #pragma mark - Other Instance Methods /** Returns YES if another Realm object instance points to the same object as the receiver in the Realm managing the receiver. For frozen objects and object types with a primary key, `isEqual:` is overridden to use the same logic as this method (along with a corresponding implementation for `hash`). Non-frozen objects without primary keys use pointer identity for `isEqual:` and `hash`. @param object The object to compare the receiver to. @return Whether the object represents the same object as the receiver. */ - (BOOL)isEqualToObject:(RLMObject *)object; /** Returns a frozen (immutable) snapshot of this object. The frozen copy is an immutable object which contains the same data as this object currently contains, but will not update when writes are made to the containing Realm. Unlike live objects, frozen objects can be accessed from any thread. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. - warning: This method can only be called on a managed object. */ - (instancetype)freeze NS_RETURNS_RETAINED; /** Returns a live (mutable) reference of this object. This method creates a managed accessor to a live copy of the same frozen object. Will return self if called on an already live object. */ - (instancetype)thaw; #pragma mark - Dynamic Accessors /// :nodoc: - (nullable id)objectForKeyedSubscript:(NSString *)key; /// :nodoc: - (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key; @end /** Information about a specific property which changed in an `RLMObject` change notification. */ @interface RLMPropertyChange : NSObject /** The name of the property which changed. */ @property (nonatomic, readonly, strong) NSString *name; /** The value of the property before the change occurred. This will always be `nil` if the change happened on the same thread as the notification and for `RLMArray` properties. For object properties this will give the object which was previously linked to, but that object will have its new values and not the values it had before the changes. This means that `previousValue` may be a deleted object, and you will need to check `invalidated` before accessing any of its properties. */ @property (nonatomic, readonly, strong, nullable) id previousValue; /** The value of the property after the change occurred. This will always be `nil` for `RLMArray` properties. */ @property (nonatomic, readonly, strong, nullable) id value; @end #pragma mark - RLMArray Property Declaration /** Properties on `RLMObject`s of type `RLMArray` must have an associated type. A type is associated with an `RLMArray` property by defining a protocol for the object type that the array should contain. To define the protocol for an object, you can use the macro RLM_ARRAY_TYPE: RLM_ARRAY_TYPE(ObjectType) ... @property RLMArray *arrayOfObjectTypes; */ #define RLM_ARRAY_TYPE(RLM_OBJECT_SUBCLASS)\ __attribute__((deprecated("RLM_ARRAY_TYPE has been deprecated. Use RLM_COLLECTION_TYPE instead."))) \ @protocol RLM_OBJECT_SUBCLASS \ @end /** Properties on `RLMObject`s of type `RLMSet` / `RLMArray` must have an associated type. A type is associated with an `RLMSet` / `RLMArray` property by defining a protocol for the object type that the array should contain. To define the protocol for an object, you can use the macro RLM_COLLECTION_TYPE: RLM_COLLECTION_TYPE(ObjectType) ... @property RLMSet *setOfObjectTypes; @property RLMArray *arrayOfObjectTypes; */ #define RLM_COLLECTION_TYPE(RLM_OBJECT_SUBCLASS)\ @protocol RLM_OBJECT_SUBCLASS \ @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObject.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObject_Private.hpp" #import "RLMAccessor.h" #import "RLMArray.h" #import "RLMCollection_Private.hpp" #import "RLMObjectBase_Private.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import // We declare things in RLMObject which are actually implemented in RLMObjectBase // for documentation's sake, which leads to -Wunimplemented-method warnings. // Other alternatives to this would be to disable -Wunimplemented-method for this // file (but then we could miss legitimately missing things), or declaring the // inherited things in a category (but they currently aren't nicely grouped for // that). @implementation RLMObject // synthesized in RLMObjectBase @dynamic invalidated, realm, objectSchema; #pragma mark - Designated Initializers - (instancetype)init { return [super init]; } #pragma mark - Convenience Initializers - (instancetype)initWithValue:(id)value { if (!(self = [self init])) { return nil; } RLMInitializeWithValue(self, value, RLMSchema.partialPrivateSharedSchema); return self; } #pragma mark - Class-based Object Creation + (instancetype)createInDefaultRealmWithValue:(id)value { return (RLMObject *)RLMCreateObjectInRealmWithValue([RLMRealm defaultRealm], [self className], value, RLMUpdatePolicyError); } + (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value { return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyError); } + (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value { return [self createOrUpdateInRealm:[RLMRealm defaultRealm] withValue:value]; } + (instancetype)createOrUpdateModifiedInDefaultRealmWithValue:(id)value { return [self createOrUpdateModifiedInRealm:[RLMRealm defaultRealm] withValue:value]; } + (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value { RLMVerifyHasPrimaryKey(self); return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyUpdateAll); } + (instancetype)createOrUpdateModifiedInRealm:(RLMRealm *)realm withValue:(id)value { RLMVerifyHasPrimaryKey(self); return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, RLMUpdatePolicyUpdateChanged); } #pragma mark - Subscripting - (id)objectForKeyedSubscript:(NSString *)key { return RLMObjectBaseObjectForKeyedSubscript(self, key); } - (void)setObject:(id)obj forKeyedSubscript:(NSString *)key { RLMObjectBaseSetObjectForKeyedSubscript(self, key, obj); } #pragma mark - Getting & Querying + (RLMResults *)allObjects { return RLMGetObjects(RLMRealm.defaultRealm, self.className, nil); } + (RLMResults *)allObjectsInRealm:(__unsafe_unretained RLMRealm *const)realm { return RLMGetObjects(realm, self.className, nil); } + (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsWhere:predicateFormat args:args]; va_end(args); return results; } + (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsInRealm:realm where:predicateFormat args:args]; va_end(args); return results; } + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args { return [self objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } + (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { return RLMGetObjects(RLMRealm.defaultRealm, self.className, predicate); } + (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(NSPredicate *)predicate { return RLMGetObjects(realm, self.className, predicate); } + (instancetype)objectForPrimaryKey:(id)primaryKey { return RLMGetObject(RLMRealm.defaultRealm, self.className, primaryKey); } + (instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(id)primaryKey { return RLMGetObject(realm, self.className, primaryKey); } #pragma mark - Other Instance Methods - (BOOL)isEqualToObject:(RLMObject *)object { return [object isKindOfClass:RLMObject.class] && RLMObjectBaseAreEqual(self, object); } - (instancetype)freeze { return RLMObjectFreeze(self); } - (instancetype)thaw { return RLMObjectThaw(self); } - (BOOL)isFrozen { return _realm.isFrozen; } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block { return RLMObjectAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block queue:(nonnull dispatch_queue_t)queue { return RLMObjectAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths { return RLMObjectAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMObjectAddNotificationBlock(self, block, keyPaths, queue); } + (NSString *)className { return [super className]; } #pragma mark - Default values for schema definition + (NSArray *)indexedProperties { return @[]; } + (NSDictionary *)linkingObjectsProperties { return @{}; } + (NSDictionary *)defaultPropertyValues { return nil; } + (NSString *)primaryKey { return nil; } + (NSArray *)ignoredProperties { return nil; } + (NSArray *)requiredProperties { return @[]; } + (bool)_realmIgnoreClass { return false; } @end @implementation RLMDynamicObject + (bool)_realmIgnoreClass { return true; } + (BOOL)shouldIncludeInDefaultSchema { return NO; } - (id)valueForUndefinedKey:(NSString *)key { return RLMDynamicGetByName(self, key); } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { RLMDynamicValidatedSet(self, key, value); } + (RLMObjectSchema *)sharedSchema { return nil; } @end BOOL RLMIsObjectOrSubclass(Class klass) { return RLMIsKindOfClass(klass, RLMObjectBase.class); } BOOL RLMIsObjectSubclass(Class klass) { return RLMIsKindOfClass(class_getSuperclass(class_getSuperclass(klass)), RLMObjectBase.class); } ================================================ FILE: Realm/RLMObjectBase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /// :nodoc: @interface RLMObjectBase : NSObject @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; - (instancetype)init NS_DESIGNATED_INITIALIZER; + (NSString *)className; // Returns whether the class is included in the default set of classes managed by a Realm. + (BOOL)shouldIncludeInDefaultSchema; + (nullable NSString *)_realmObjectName; + (nullable NSDictionary *)_realmColumnNames; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObjectBase.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObject_Private.hpp" #import "RLMObjectBase_Private.h" #import "RLMAccessor.h" #import "RLMArray_Private.hpp" #import "RLMDecimal128.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftCollectionBase.h" #import "RLMSwiftSupport.h" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import const NSUInteger RLMDescriptionMaxDepth = 5; static bool isManagedAccessorClass(Class cls) { const char *className = class_getName(cls); const char accessorClassPrefix[] = "RLM:Managed"; return strncmp(className, accessorClassPrefix, sizeof(accessorClassPrefix) - 1) == 0; } static void maybeInitObjectSchemaForUnmanaged(RLMObjectBase *obj) { Class cls = obj.class; if (isManagedAccessorClass(cls)) { return; } obj->_objectSchema = [cls sharedSchema]; if (!obj->_objectSchema) { return; } // set default values if (!obj->_objectSchema.isSwiftClass) { NSDictionary *dict = RLMDefaultValuesForObjectSchema(obj->_objectSchema); for (NSString *key in dict) { [obj setValue:dict[key] forKey:key]; } } // set unmanaged accessor class object_setClass(obj, obj->_objectSchema.unmanagedClass); } @interface RLMObjectBase () @end @implementation RLMObjectBase - (instancetype)init { if ((self = [super init])) { maybeInitObjectSchemaForUnmanaged(self); } return self; } - (void)dealloc { // This can't be a unique_ptr because associated objects are removed // *after* c++ members are destroyed and dealloc is called, and we need it // to be in a validish state when that happens delete _observationInfo; _observationInfo = nullptr; } static id coerceToObjectType(id obj, Class cls, RLMSchema *schema) { if ([obj isKindOfClass:cls]) { return obj; } obj = RLMBridgeSwiftValue(obj) ?: obj; id value = [[cls alloc] init]; RLMInitializeWithValue(value, obj, schema); return value; } static id validatedObjectForProperty(__unsafe_unretained id const obj, __unsafe_unretained RLMObjectSchema *const objectSchema, __unsafe_unretained RLMProperty *const prop, __unsafe_unretained RLMSchema *const schema) { RLMValidateValueForProperty(obj, objectSchema, prop); if (!obj || obj == NSNull.null) { return nil; } if (prop.type == RLMPropertyTypeObject) { Class objectClass = schema[prop.objectClassName].objectClass; id enumerable = RLMAsFastEnumeration(obj); if (prop.dictionary) { NSMutableDictionary *ret = [[NSMutableDictionary alloc] init]; for (id key in enumerable) { id val = RLMCoerceToNil(obj[key]); if (val) { val = coerceToObjectType(obj[key], objectClass, schema); } [ret setObject:val ?: NSNull.null forKey:key]; } return ret; } else if (prop.collection) { NSMutableArray *ret = [[NSMutableArray alloc] init]; for (id el in enumerable) { [ret addObject:coerceToObjectType(el, objectClass, schema)]; } return ret; } return coerceToObjectType(obj, objectClass, schema); } else if (prop.type == RLMPropertyTypeDecimal128 && !prop.collection) { return [[RLMDecimal128 alloc] initWithValue:obj]; } return obj; } void RLMInitializeWithValue(RLMObjectBase *self, id value, RLMSchema *schema) { if (!value || value == NSNull.null) { @throw RLMException(@"Must provide a non-nil value."); } RLMObjectSchema *objectSchema = self->_objectSchema; if (!objectSchema) { // Will be nil if we're called during schema init, when we don't want // to actually populate the object anyway return; } NSArray *properties = objectSchema.properties; if (NSArray *array = RLMDynamicCast(value)) { if (array.count > properties.count) { @throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).", (unsigned long long)array.count, (unsigned long long)properties.count); } NSUInteger i = 0; for (id val in array) { RLMProperty *prop = properties[i++]; [self setValue:validatedObjectForProperty(RLMCoerceToNil(val), objectSchema, prop, schema) forKey:prop.name]; } } else { // assume our object is an NSDictionary or an object with kvc properties for (RLMProperty *prop in properties) { id obj = RLMValidatedValueForProperty(value, prop.name, objectSchema.className); // don't set unspecified properties if (!obj) { continue; } [self setValue:validatedObjectForProperty(RLMCoerceToNil(obj), objectSchema, prop, schema) forKey:prop.name]; } } } id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) { RLMObjectBase *obj = [[cls alloc] init]; obj->_info = info; obj->_realm = info->realm; obj->_objectSchema = info->rlmObjectSchema; return obj; } - (id)valueForKey:(NSString *)key { if (_observationInfo) { return _observationInfo->valueForKey(key); } return [super valueForKey:key]; } // Generic Swift properties can't be dynamic, so KVO doesn't work for them by default - (id)valueForUndefinedKey:(NSString *)key { RLMProperty *prop = _objectSchema[key]; if (Class swiftAccessor = prop.swiftAccessor) { return RLMCoerceToNil([swiftAccessor get:prop on:self]); } return [super valueForUndefinedKey:key]; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { value = RLMCoerceToNil(value); RLMProperty *property = _objectSchema[key]; if (property.collection) { if (id enumerable = RLMAsFastEnumeration(value)) { value = validatedObjectForProperty(value, _objectSchema, property, RLMSchema.partialPrivateSharedSchema); } } if (auto swiftAccessor = property.swiftAccessor) { [swiftAccessor set:property on:self to:value]; } else { [super setValue:value forUndefinedKey:key]; } } // overridden at runtime per-class for performance + (NSString *)className { NSString *className = NSStringFromClass(self); if ([RLMSwiftSupport isSwiftClassName:className]) { className = [RLMSwiftSupport demangleClassName:className]; } return className; } // overridden at runtime per-class for performance + (RLMObjectSchema *)sharedSchema { return [RLMSchema sharedSchemaForClass:self.class]; } + (void)initializeLinkedObjectSchemas { for (RLMProperty *prop in self.sharedSchema.properties) { if (prop.type == RLMPropertyTypeObject && !RLMSchema.partialPrivateSharedSchema[prop.objectClassName]) { [[RLMSchema classForString:prop.objectClassName] initializeLinkedObjectSchemas]; } } } + (nullable NSArray *)_getProperties { return nil; } - (NSString *)description { if (self.isInvalidated) { return @"[invalid object]"; } return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; } - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { if (depth == 0) { return @""; } NSString *baseClassName = _objectSchema.className; NSMutableString *mString = [NSMutableString stringWithFormat:@"%@ {\n", baseClassName]; for (RLMProperty *property in _objectSchema.properties) { id object = [(id)self objectForKeyedSubscript:property.name]; NSString *sub; if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) { sub = [object descriptionWithMaxDepth:depth - 1]; } else if (property.type == RLMPropertyTypeData) { static NSUInteger maxPrintedDataLength = 24; NSData *data = object; NSUInteger length = data.length; if (length > maxPrintedDataLength) { data = [NSData dataWithBytes:data.bytes length:maxPrintedDataLength]; } NSString *dataDescription = [data description]; sub = [NSString stringWithFormat:@"<%@ — %lu total bytes>", [dataDescription substringWithRange:NSMakeRange(1, dataDescription.length - 2)], (unsigned long)length]; } else { sub = [object description]; } [mString appendFormat:@"\t%@ = %@;\n", property.name, [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; } [mString appendString:@"}"]; return [NSString stringWithString:mString]; } - (RLMRealm *)realm { return _realm; } - (RLMObjectSchema *)objectSchema { return _objectSchema; } - (BOOL)isInvalidated { // if not unmanaged and our accessor has been detached, we have been deleted return _info && !_row.is_valid(); } - (BOOL)isEqual:(id)object { if (RLMObjectBase *other = RLMDynamicCast(object)) { if (_objectSchema.primaryKeyProperty || _realm.isFrozen) { return RLMObjectBaseAreEqual(self, other); } } return [super isEqual:object]; } - (NSUInteger)hash { if (_objectSchema.primaryKeyProperty) { // If we have a primary key property, that's an immutable value which we // can use as the identity of the object. id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name]; // modify the hash of our primary key value to avoid potential (although unlikely) collisions return [primaryProperty hash] ^ 1; } else if (_realm.isFrozen) { // The object key can never change for frozen objects, so that's usable // for objects without primary keys return static_cast(_row.get_key().value); } else { // Non-frozen objects without primary keys don't have any immutable // concept of identity that we can hash so we have to fall back to // pointer equality return [super hash]; } } + (BOOL)shouldIncludeInDefaultSchema { return RLMIsObjectSubclass(self); } + (NSString *)primaryKey { return nil; } + (NSString *)_realmObjectName { return nil; } + (NSDictionary *)_realmColumnNames { return nil; } + (bool)_realmIgnoreClass { return false; } + (bool)isEmbedded { return false; } + (bool)isAsymmetric { return false; } // This enables to override the propertiesMapping in Swift, it is not to be used in Objective-C API. + (NSDictionary *)propertiesMapping { return @{}; } - (id)mutableArrayValueForKey:(NSString *)key { id obj = [self valueForKey:key]; if ([obj isKindOfClass:[RLMArray class]]) { return obj; } return [super mutableArrayValueForKey:key]; } - (id)mutableSetValueForKey:(NSString *)key { id obj = [self valueForKey:key]; if ([obj isKindOfClass:[RLMSet class]]) { return obj; } return [super mutableSetValueForKey:key]; } - (void)addObserver:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { if (!_observationInfo) { _observationInfo = new RLMObservationInfo(self); } _observationInfo->recordObserver(_row, _info, _objectSchema, keyPath); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath { [super removeObserver:observer forKeyPath:keyPath]; if (_observationInfo) _observationInfo->removeObserver(); } + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { RLMProperty *prop = [self.class sharedSchema][key]; if (isManagedAccessorClass(self)) { // Managed accessors explicitly call willChange/didChange for managed // properties, so we don't want KVO to override the setters to do that return !prop; } if (prop.swiftAccessor) { // Properties with swift accessors don't have obj-c getters/setters and // will explode if KVO tries to override them return NO; } return [super automaticallyNotifiesObserversForKey:key]; } + (void)observe:(RLMObjectBase *)object keyPaths:(nullable NSArray *)keyPaths block:(RLMObjectNotificationCallback)block completion:(void (^)(RLMNotificationToken *))completion { } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return realm::Object(_realm->_realm, *_info->objectSchema, _row); } - (id)objectiveCMetadata { return nil; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(__unused id)metadata realm:(RLMRealm *)realm { auto object = reference.resolve(realm->_realm); if (!object.is_valid()) { return nil; } NSString *objectClassName = @(object.get_object_schema().name.c_str()); return RLMCreateObjectAccessor(realm->_info[objectClassName], object.get_obj()); } @end RLMRealm *RLMObjectBaseRealm(__unsafe_unretained RLMObjectBase *object) { return object ? object->_realm : nil; } RLMObjectSchema *RLMObjectBaseObjectSchema(__unsafe_unretained RLMObjectBase *object) { return object ? object->_objectSchema : nil; } id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key) { if (!object) { return nil; } if (object->_realm) { return RLMDynamicGetByName(object, key); } else { return [object valueForKey:key]; } } void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj) { if (!object) { return; } if (object->_realm || object.class == object->_objectSchema.accessorClass) { RLMDynamicValidatedSet(object, key, obj); } else { [object setValue:obj forKey:key]; } } BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) { // if not the correct types throw if ((o1 && ![o1 isKindOfClass:RLMObjectBase.class]) || (o2 && ![o2 isKindOfClass:RLMObjectBase.class])) { @throw RLMException(@"Can only compare objects of class RLMObjectBase"); } // if identical object (or both are nil) if (o1 == o2) { return YES; } // if one is nil if (o1 == nil || o2 == nil) { return NO; } // if not in realm or differing realms if (o1->_realm == nil || o1->_realm != o2->_realm) { return NO; } // if either are detached if (!o1->_row.is_valid() || !o2->_row.is_valid()) { return NO; } // if table and index are the same return o1->_row.get_table() == o2->_row.get_table() && o1->_row.get_key() == o2->_row.get_key(); } static id resolveObject(RLMObjectBase *obj, RLMRealm *realm) { RLMObjectBase *resolved = RLMCreateManagedAccessor(obj.class, &realm->_info[obj->_info->rlmObjectSchema.className]); resolved->_row = realm->_realm->import_copy_of(obj->_row); if (!resolved->_row.is_valid()) { return nil; } RLMInitializeSwiftAccessor(resolved, false); return resolved; } id RLMObjectFreeze(RLMObjectBase *obj) { if (!obj->_realm && !obj.isInvalidated) { @throw RLMException(@"Unmanaged objects cannot be frozen."); } RLMVerifyAttached(obj); if (obj->_realm.frozen) { return obj; } obj = resolveObject(obj, obj->_realm.freeze); if (!obj) { @throw RLMException(@"Cannot freeze an object in the same write transaction as it was created in."); } return obj; } id RLMObjectThaw(RLMObjectBase *obj) { if (!obj->_realm && !obj.isInvalidated) { @throw RLMException(@"Unmanaged objects cannot be frozen."); } RLMVerifyAttached(obj); if (!obj->_realm.frozen) { return obj; } return resolveObject(obj, obj->_realm.thaw); } id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) { @try { return [object valueForKey:key]; } @catch (NSException *e) { if ([e.name isEqualToString:NSUndefinedKeyException]) { @throw RLMException(@"Invalid value '%@' to initialize object of type '%@': missing key '%@'", object, className, key); } @throw; } } #pragma mark - Notifications namespace { struct ObjectChangeCallbackWrapper { RLMObjectNotificationCallback block; RLMObjectBase *object; void (^registrationCompletion)(); NSArray *propertyNames = nil; NSArray *oldValues = nil; bool deleted = false; void populateProperties(realm::CollectionChangeSet const& c) { if (propertyNames) { return; } if (!c.deletions.empty()) { deleted = true; return; } if (c.columns.empty()) { return; } // FIXME: It's possible for the column key of a persisted property // to equal the column key of a computed property. auto properties = [NSMutableArray new]; for (RLMProperty *property in object->_info->rlmObjectSchema.properties) { auto columnKey = object->_info->tableColumn(property).value; if (c.columns.count(columnKey)) { [properties addObject:property.name]; } } for (RLMProperty *property in object->_info->rlmObjectSchema.computedProperties) { auto columnKey = object->_info->computedTableColumn(property).value; if (c.columns.count(columnKey)) { [properties addObject:property.name]; } } if (properties.count) { propertyNames = properties; } } NSArray *readValues(realm::CollectionChangeSet const& c) { if (c.empty()) { return nil; } populateProperties(c); if (!propertyNames) { return nil; } auto values = [NSMutableArray arrayWithCapacity:propertyNames.count]; for (NSString *name in propertyNames) { id value = [object valueForKey:name]; if (!value || [value isKindOfClass:[RLMArray class]]) { [values addObject:NSNull.null]; } else { [values addObject:value]; } } return values; } void before(realm::CollectionChangeSet const& c) { @autoreleasepool { oldValues = readValues(c); } } void after(realm::CollectionChangeSet const& c) { @autoreleasepool { if (registrationCompletion) { registrationCompletion(); registrationCompletion = nil; } auto newValues = readValues(c); if (deleted) { block(nil, nil, nil, nil, nil); } else if (newValues) { block(object, propertyNames, oldValues, newValues, nil); } propertyNames = nil; oldValues = nil; } } }; } // anonymous namespace @interface RLMPropertyChange () @property (nonatomic, readwrite, strong) NSString *name; @property (nonatomic, readwrite, strong, nullable) id previousValue; @property (nonatomic, readwrite, strong, nullable) id value; @end @implementation RLMPropertyChange - (NSString *)description { return [NSString stringWithFormat:@" %@ %@ -> %@", (__bridge void *)self, _name, _previousValue, _value]; } @end enum class TokenState { Initializing, Cancelled, Ready }; RLM_DIRECT_MEMBERS @implementation RLMObjectNotificationToken { RLMUnfairMutex _mutex; __unsafe_unretained RLMRealm *_realm; realm::Object _object; realm::NotificationToken _token; void (^_completion)(void); TokenState _state; } - (RLMRealm *)realm { std::lock_guard lock(_mutex); return _realm; } - (void)suppressNextNotification { std::lock_guard lock(_mutex); if (_object.is_valid()) { _token.suppress_next(); } } - (bool)invalidate { dispatch_block_t completion; { std::lock_guard lock(_mutex); if (_state == TokenState::Cancelled) { REALM_ASSERT(!_completion); return false; } _realm = nil; _token = {}; _object = {}; _state = TokenState::Cancelled; std::swap(completion, _completion); } if (completion) { completion(); } return true; } - (void)addNotificationBlock:(RLMObjectNotificationCallback)block threadSafeReference:(RLMThreadSafeReference *)tsr config:(RLMRealmConfiguration *)config keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { std::lock_guard lock(_mutex); if (_state != TokenState::Initializing) { // Token was invalidated before we got this far return; } NSError *error; auto realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error]; _realm = realm; if (!realm) { block(nil, nil, nil, nil, error); return; } RLMObjectBase *obj = [realm resolveThreadSafeReference:tsr]; _object = realm::Object(realm->_realm, *obj->_info->objectSchema, obj->_row); _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj}, obj->_info->keyPathArrayFromStringArray(keyPaths)); } - (void)observe:(RLMObjectBase *)obj keyPaths:(NSArray *)keyPaths block:(RLMObjectNotificationCallback)block { std::lock_guard lock(_mutex); if (_state != TokenState::Initializing) { return; } _object = realm::Object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row); _realm = obj->_realm; auto completion = [self] { dispatch_block_t completion; { std::lock_guard lock(_mutex); if (_state == TokenState::Initializing) { _state = TokenState::Ready; } std::swap(completion, _completion); } if (completion) { completion(); } }; try { _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj, completion}, obj->_info->keyPathArrayFromStringArray(keyPaths)); } catch (const realm::Exception& e) { @throw RLMException(e); } } - (void)registrationComplete:(void (^)())completion { { std::lock_guard lock(_mutex); if (_state == TokenState::Initializing) { _completion = completion; return; } } completion(); } RLMNotificationToken *RLMObjectBaseAddNotificationBlock(RLMObjectBase *obj, NSArray *keyPaths, dispatch_queue_t queue, RLMObjectNotificationCallback block) { if (!obj->_realm) { @throw RLMException(@"Only objects which are managed by a Realm support change notifications"); } if (!queue) { [obj->_realm verifyNotificationsAreSupported:true]; auto token = [[RLMObjectNotificationToken alloc] init]; [token observe:obj keyPaths:keyPaths block:block]; return token; } RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:(id)obj]; auto token = [[RLMObjectNotificationToken alloc] init]; token->_realm = obj->_realm; RLMRealmConfiguration *config = obj->_realm.configurationSharingSchema; dispatch_async(queue, ^{ @autoreleasepool { [token addNotificationBlock:block threadSafeReference:tsr config:config keyPaths:keyPaths queue:queue]; } }); return token; } @end RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectChangeBlock block, NSArray *keyPaths, dispatch_queue_t queue) { return RLMObjectBaseAddNotificationBlock(obj, keyPaths, queue, ^(RLMObjectBase *, NSArray *propertyNames, NSArray *oldValues, NSArray *newValues, NSError *error) { if (error) { block(false, nil, error); } else if (!propertyNames) { block(true, nil, nil); } else { auto properties = [NSMutableArray arrayWithCapacity:propertyNames.count]; for (NSUInteger i = 0, count = propertyNames.count; i < count; ++i) { auto prop = [RLMPropertyChange new]; prop.name = propertyNames[i]; prop.previousValue = RLMCoerceToNil(oldValues[i]); prop.value = RLMCoerceToNil(newValues[i]); [properties addObject:prop]; } block(false, properties, nil); } }); } uint64_t RLMObjectBaseGetCombineId(__unsafe_unretained RLMObjectBase *const obj) { if (obj.invalidated) { RLMVerifyAttached(obj); } if (obj->_realm) { return obj->_row.get_key().value; } return reinterpret_cast((__bridge void *)obj); } @implementation RealmSwiftObject + (BOOL)accessInstanceVariablesDirectly { // By default KVO will try to directly read ivars if a thing with a matching // name is observed and there's no objc property with that name. This // crashes when it tries to read a property wrapper ivar, and is never // useful for Swift classes. return NO; } @end @implementation RealmSwiftEmbeddedObject + (BOOL)accessInstanceVariablesDirectly { return NO; } @end @implementation RealmSwiftAsymmetricObject + (BOOL)accessInstanceVariablesDirectly { return NO; } + (bool)isAsymmetric { return YES; } @end ================================================ FILE: Realm/RLMObjectBase_Dynamic.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @class RLMObjectSchema, RLMRealm; RLM_HEADER_AUDIT_BEGIN(nullability) /** Returns the Realm that manages the object, if one exists. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve the Realm that manages the object via `RLMObject`. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @return The Realm which manages this object. Returns `nil `for unmanaged objects. */ FOUNDATION_EXTERN RLMRealm * _Nullable RLMObjectBaseRealm(RLMObjectBase * _Nullable object); /** Returns an `RLMObjectSchema` which describes the managed properties of the object. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve `objectSchema` via `RLMObject`. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @return The object schema which lists the managed properties for the object. */ FOUNDATION_EXTERN RLMObjectSchema * _Nullable RLMObjectBaseObjectSchema(RLMObjectBase * _Nullable object); /** Returns the object corresponding to a key value. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve key values via `RLMObject`. @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @param key The name of the property. @return The object for the property requested. */ FOUNDATION_EXTERN id _Nullable RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key); /** Sets a value for a key on the object. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to set key values via `RLMObject`. @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @param key The name of the property. @param obj The object to set as the value of the key. */ FOUNDATION_EXTERN void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key, id _Nullable obj); RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMObjectBase_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 @class RLMArray; RLM_HEADER_AUDIT_BEGIN(nullability) // RLMObjectBase private @interface RLMObjectBase () @property (nonatomic, nullable) NSMutableArray *lastAccessedNames; + (void)initializeLinkedObjectSchemas; + (bool)isEmbedded; + (bool)isAsymmetric; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMObjectId.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** A 12-byte (probably) unique object identifier. ObjectIds are similar to a GUID or a UUID, and can be used to uniquely identify objects without a centralized ID generator. An ObjectID consists of: 1. A 4 byte timestamp measuring the creation time of the ObjectId in seconds since the Unix epoch. 2. A 5 byte random value 3. A 3 byte counter, initialized to a random value. ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order. */ NS_SWIFT_SENDABLE // immutable @interface RLMObjectId : NSObject /// Creates a new randomly-initialized ObjectId. + (nonnull instancetype)objectId NS_SWIFT_NAME(generate()); /// Creates a new zero-initialized ObjectId. - (instancetype)init; /// Creates a new ObjectId from the given 24-byte hexadecimal string. /// /// Returns `nil` and sets `error` if the string is not 24 characters long or /// contains any characters other than 0-9a-fA-F. /// /// @param string The string to parse. - (nullable instancetype)initWithString:(NSString *)string error:(NSError **)error; /// Creates a new ObjectId using the given date, machine identifier, process identifier. /// /// @param timestamp A timestamp as NSDate. /// @param machineIdentifier The machine identifier. /// @param processIdentifier The process identifier. - (instancetype)initWithTimestamp:(NSDate *)timestamp machineIdentifier:(int)machineIdentifier processIdentifier:(int)processIdentifier; /// Comparision operator to check if the right hand side is greater than the current value. - (BOOL)isGreaterThan:(nullable RLMObjectId *)objectId; /// Comparision operator to check if the right hand side is greater than or equal to the current value. - (BOOL)isGreaterThanOrEqualTo:(nullable RLMObjectId *)objectId; /// Comparision operator to check if the right hand side is less than the current value. - (BOOL)isLessThan:(nullable RLMObjectId *)objectId; /// Comparision operator to check if the right hand side is less than or equal to the current value. - (BOOL)isLessThanOrEqualTo:(nullable RLMObjectId *)objectId; /// Get the ObjectId as a 24-character hexadecimal string. @property (nonatomic, readonly) NSString *stringValue; /// Get the timestamp for the RLMObjectId @property (nonatomic, readonly) NSDate *timestamp; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObjectId.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMObjectId_Private.hpp" #import "RLMError_Private.hpp" #import "RLMUtil.hpp" #import // Swift's obj-c bridging does not support making an obj-c defined class conform // to Decodable, so we need a Swift-defined subclass for that. This means that // when Realm Swift is being used, we need to produce objects of that type rather // than our obj-c defined type. objc_runtime_visible marks the type as being // visbile only to the obj-c runtime and not the linker, which means that it'll // be `nil` at runtime rather than being a linker error if it's not defined, and // valid if it happens to be defined by some other library (i.e. Realm Swift). // // At the point where the objects are being allocated we generally don't have // any good way of knowing whether or not it's going to end up being used by // Swift, so we just switch to the subclass unconditionally if the subclass // exists. This shouldn't have any impact on obj-c code other than a small // performance hit. [[clang::objc_runtime_visible]] @interface RealmSwiftObjectId : RLMObjectId @end @implementation RLMObjectId - (instancetype)init { if ((self = [super init])) { if (auto cls = [RealmSwiftObjectId class]; cls && cls != self.class) { object_setClass(self, cls); } } return self; } - (instancetype)initWithString:(NSString *)string error:(NSError **)error { if ((self = [self init])) { const char *str = string.UTF8String; if (!realm::ObjectId::is_valid_str(str)) { if (error) { NSString *msg = [NSString stringWithFormat:@"Invalid Object ID string '%@': must be 24 hex digits", string]; *error = [NSError errorWithDomain:RLMErrorDomain code:RLMErrorInvalidInput userInfo:@{NSLocalizedDescriptionKey: msg}]; } return nil; } _value = realm::ObjectId(str); } return self; } - (instancetype)initWithTimestamp:(NSDate *)timestamp machineIdentifier:(int)machineIdentifier processIdentifier:(int)processIdentifier { if ((self = [self init])) { _value = realm::ObjectId(RLMTimestampForNSDate(timestamp), machineIdentifier, processIdentifier); } return self; } - (instancetype)initWithValue:(realm::ObjectId)value { if ((self = [self init])) { _value = value; } return self; } - (id)copyWithZone:(NSZone *)zone { // RLMObjectID is immutable so we don't have to actually copy return self; } + (instancetype)objectId { return [[self alloc] initWithValue:realm::ObjectId::gen()]; } - (BOOL)isEqual:(id)object { if (RLMObjectId *objectId = RLMDynamicCast(object)) { return objectId->_value == _value; } return NO; } - (BOOL)isGreaterThan:(nullable RLMObjectId *)objectId { return _value > objectId.value; } - (BOOL)isGreaterThanOrEqualTo:(nullable RLMObjectId *)objectId { return _value >= objectId.value; } - (BOOL)isLessThan:(nullable RLMObjectId *)objectId { return _value < objectId.value; } - (BOOL)isLessThanOrEqualTo:(nullable RLMObjectId *)objectId { return _value <= objectId.value; } - (NSUInteger)hash { return _value.hash(); } - (NSString *)description { return self.stringValue; } - (NSString *)stringValue { return @(_value.to_string().c_str()); } - (NSDate *)timestamp { return RLMTimestampToNSDate(_value.get_timestamp()); } @end ================================================ FILE: Realm/RLMObjectId_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 namespace realm { class ObjectId; } RLM_DIRECT_MEMBERS @interface RLMObjectId () @property (nonatomic, readonly) realm::ObjectId value; - (instancetype)initWithValue:(realm::ObjectId)value; @end ================================================ FILE: Realm/RLMObjectSchema.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMProperty; /** This class represents Realm model object schemas. When using Realm, `RLMObjectSchema` instances allow performing migrations and introspecting the database's schema. Object schemas map to tables in the core database. */ NS_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is @interface RLMObjectSchema : NSObject #pragma mark - Properties /** An array of `RLMProperty` instances representing the managed properties of a class described by the schema. @see `RLMProperty` */ @property (nonatomic, readonly, copy) NSArray *properties; /** The name of the class the schema describes. */ @property (nonatomic, readonly) NSString *className; /** The property which serves as the primary key for the class the schema describes, if any. */ @property (nonatomic, readonly, nullable) RLMProperty *primaryKeyProperty; /** Whether this object type is embedded. */ @property (nonatomic, readonly) BOOL isEmbedded; /** Whether this object is asymmetric. */ @property (nonatomic, readonly) BOOL isAsymmetric; #pragma mark - Methods /** Retrieves an `RLMProperty` object by the property name. @param propertyName The property's name. @return An `RLMProperty` object, or `nil` if there is no property with the given name. */ - (nullable RLMProperty *)objectForKeyedSubscript:(NSString *)propertyName; /** Returns whether two `RLMObjectSchema` instances are equal. */ - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObjectSchema.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObjectSchema_Private.hpp" #import "RLMEmbeddedObject.h" #import "RLMObject_Private.h" #import "RLMProperty_Private.hpp" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMSwiftCollectionBase.h" #import "RLMSwiftSupport.h" #import "RLMUtil.hpp" #import #import using namespace realm; @protocol RLMCustomEventRepresentable @end // private properties @interface RLMObjectSchema () @property (nonatomic, readwrite) NSDictionary *allPropertiesByName; @property (nonatomic, readwrite) NSString *className; @end @implementation RLMObjectSchema { std::string _objectStoreName; } - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties { self = [super init]; self.className = objectClassName; self.properties = properties; self.objectClass = objectClass; self.accessorClass = objectClass; self.unmanagedClass = objectClass; return self; } // return properties by name - (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key { return _allPropertiesByName[key]; } // create property map when setting property array - (void)setProperties:(NSArray *)properties { _properties = properties; [self _propertiesDidChange]; } - (void)setComputedProperties:(NSArray *)computedProperties { _computedProperties = computedProperties; [self _propertiesDidChange]; } - (void)_propertiesDidChange { _primaryKeyProperty = nil; NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count]; NSUInteger index = 0; for (RLMProperty *prop in _properties) { prop.index = index++; map[prop.name] = prop; if (prop.isPrimary) { if (_primaryKeyProperty) { @throw RLMException(@"Properties '%@' and '%@' are both marked as the primary key of '%@'", prop.name, _primaryKeyProperty.name, _className); } _primaryKeyProperty = prop; } } index = 0; for (RLMProperty *prop in _computedProperties) { prop.index = index++; map[prop.name] = prop; } _allPropertiesByName = map; if (RLMIsSwiftObjectClass(_accessorClass)) { NSMutableArray *genericProperties = [NSMutableArray new]; for (RLMProperty *prop in _properties) { if (prop.swiftAccessor) { [genericProperties addObject:prop]; } } // Currently all computed properties are Swift generics [genericProperties addObjectsFromArray:_computedProperties]; if (genericProperties.count) { _swiftGenericProperties = genericProperties; } else { _swiftGenericProperties = nil; } } } - (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty { _primaryKeyProperty.isPrimary = NO; primaryKeyProperty.isPrimary = YES; _primaryKeyProperty = primaryKeyProperty; _primaryKeyProperty.indexed = YES; } + (instancetype)schemaForObjectClass:(Class)objectClass { RLMObjectSchema *schema = [RLMObjectSchema new]; // determine classname from objectclass as className method has not yet been updated NSString *className = NSStringFromClass(objectClass); bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className]; if (hasSwiftName) { className = [RLMSwiftSupport demangleClassName:className]; } bool isSwift = hasSwiftName || RLMIsSwiftObjectClass(objectClass); schema.className = className; schema.objectClass = objectClass; schema.accessorClass = objectClass; schema.unmanagedClass = objectClass; schema.isSwiftClass = isSwift; schema.hasCustomEventSerialization = [objectClass conformsToProtocol:@protocol(RLMCustomEventRepresentable)]; bool isEmbedded = [(id)objectClass isEmbedded]; bool isAsymmetric = [(id)objectClass isAsymmetric]; REALM_ASSERT(!(isEmbedded && isAsymmetric)); schema.isEmbedded = isEmbedded; schema.isAsymmetric = isAsymmetric; // create array of RLMProperties, inserting properties of superclasses first Class cls = objectClass; Class superClass = class_getSuperclass(cls); NSArray *allProperties = @[]; while (superClass && superClass != RLMObjectBase.class) { allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift] arrayByAddingObjectsFromArray:allProperties]; cls = superClass; superClass = class_getSuperclass(superClass); } NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) { return !RLMPropertyTypeIsComputed(property.type); }]]; schema.properties = persistedProperties; NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) { return RLMPropertyTypeIsComputed(property.type); }]]; schema.computedProperties = computedProperties; // verify that we didn't add any properties twice due to inheritance if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) { NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]]; NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) { return [countedPropertyNames countForObject:object] > 1; }]].allObjects; if (duplicatePropertyNames.count == 1) { @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className); } else { @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]); } } if (NSString *primaryKey = [objectClass primaryKey]) { for (RLMProperty *prop in schema.properties) { if ([primaryKey isEqualToString:prop.name]) { prop.indexed = YES; schema.primaryKeyProperty = prop; break; } } if (!schema.primaryKeyProperty) { @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className); } if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString && schema.primaryKeyProperty.type != RLMPropertyTypeObjectId && schema.primaryKeyProperty.type != RLMPropertyTypeUUID) { @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string', 'int', 'objectId', or 'uuid' property.", primaryKey, className); } } for (RLMProperty *prop in schema.properties) { if (prop.optional && prop.collection && !prop.dictionary && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) { // FIXME: message is awkward @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.", className, prop.name, RLMTypeToString(prop.type)); } } if ([objectClass shouldIncludeInDefaultSchema] && schema.isSwiftClass && 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; } + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass { if (NSArray *props = [objectClass _getProperties]) { return props; } // For Swift subclasses of RLMObject we need an instance of the object when parsing properties id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil; NSArray *ignoredProperties = [objectClass ignoredProperties]; NSDictionary *linkingObjectsProperties = [objectClass linkingObjectsProperties]; NSDictionary *columnNameMap = [objectClass _realmColumnNames]; unsigned int count; std::unique_ptr props(class_copyPropertyList(objectClass, &count), &free); NSMutableArray *propArray = [NSMutableArray arrayWithCapacity:count]; NSSet *indexed = [[NSSet alloc] initWithArray:[objectClass indexedProperties]]; for (unsigned int i = 0; i < count; i++) { NSString *propertyName = @(property_getName(props[i])); if ([ignoredProperties containsObject:propertyName]) { continue; } RLMProperty *prop = nil; if (isSwiftClass) { prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName indexed:[indexed containsObject:propertyName] linkPropertyDescriptor:linkingObjectsProperties[propertyName] property:props[i] instance:swiftObjectInstance]; } else { prop = [[RLMProperty alloc] initWithName:propertyName indexed:[indexed containsObject:propertyName] linkPropertyDescriptor:linkingObjectsProperties[propertyName] property:props[i]]; } if (prop) { if (columnNameMap) { prop.columnName = columnNameMap[prop.name]; } [propArray addObject:prop]; } } if (auto requiredProperties = [objectClass requiredProperties]) { for (RLMProperty *property in propArray) { bool required = [requiredProperties containsObject:property.name]; if (required && property.type == RLMPropertyTypeObject && (!property.collection || property.dictionary)) { @throw RLMException(@"Object properties cannot be made required, " "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name); } property.optional &= !required; } } for (RLMProperty *property in propArray) { if (!property.optional && property.type == RLMPropertyTypeObject && !property.collection) { @throw RLMException(@"The `%@.%@` property must be marked as being optional.", [objectClass className], property.name); } if (property.type == RLMPropertyTypeAny) { property.optional = NO; } } return propArray; } - (id)copyWithZone:(NSZone *)zone { RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init]; schema->_objectClass = _objectClass; schema->_className = _className; schema->_objectClass = _objectClass; schema->_accessorClass = _objectClass; schema->_unmanagedClass = _unmanagedClass; schema->_isSwiftClass = _isSwiftClass; schema->_isEmbedded = _isEmbedded; schema->_isAsymmetric = _isAsymmetric; schema->_properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES]; schema->_computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES]; [schema _propertiesDidChange]; return schema; } - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema { if (objectSchema.properties.count != _properties.count) { return NO; } if (![_properties isEqualToArray:objectSchema.properties]) { return NO; } if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) { return NO; } return YES; } - (NSString *)description { NSMutableString *propertiesString = [NSMutableString string]; for (RLMProperty *property in self.properties) { [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; } for (RLMProperty *property in self.computedProperties) { [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; } return [NSString stringWithFormat:@"%@ %@{\n%@}", self.className, _isEmbedded ? @"(embedded) " : @"", propertiesString]; } - (NSString *)objectName { return [self.objectClass _realmObjectName] ?: _className; } - (std::string const&)objectStoreName { if (_objectStoreName.empty()) { _objectStoreName = self.objectName.UTF8String; } return _objectStoreName; } - (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema { using Type = ObjectSchema::ObjectType; ObjectSchema objectSchema; objectSchema.name = self.objectStoreName; objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.columnName.UTF8String : ""; objectSchema.table_type = _isAsymmetric ? Type::TopLevelAsymmetric : _isEmbedded ? Type::Embedded : Type::TopLevel; for (RLMProperty *prop in _properties) { Property p = [prop objectStoreCopy:schema]; p.is_primary = (prop == _primaryKeyProperty); objectSchema.persisted_properties.push_back(std::move(p)); } for (RLMProperty *prop in _computedProperties) { objectSchema.computed_properties.push_back([prop objectStoreCopy:schema]); } return objectSchema; } + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema { RLMObjectSchema *schema = [RLMObjectSchema new]; schema.className = @(objectSchema.name.c_str()); schema.isEmbedded = objectSchema.table_type == ObjectSchema::ObjectType::Embedded; schema.isAsymmetric = objectSchema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric; // create array of RLMProperties NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()]; for (const Property &prop : objectSchema.persisted_properties) { RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop]; property.isPrimary = (prop.name == objectSchema.primary_key); [properties addObject:property]; } schema.properties = properties; NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()]; for (const Property &prop : objectSchema.computed_properties) { [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]]; } schema.computedProperties = computedProperties; // get primary key from realm metadata if (objectSchema.primary_key.length()) { NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()]; schema.primaryKeyProperty = schema[primaryKeyString]; if (!schema.primaryKeyProperty) { @throw RLMException(@"No property matching primary key '%@'", primaryKeyString); } } // for dynamic schema use vanilla RLMDynamicObject accessor classes schema.objectClass = RLMObject.class; schema.accessorClass = RLMDynamicObject.class; schema.unmanagedClass = RLMObject.class; return schema; } @end ================================================ FILE: Realm/RLMObjectSchema_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import RLM_HEADER_AUDIT_BEGIN(nullability) // RLMObjectSchema private @interface RLMObjectSchema () { @public bool _isSwiftClass; } /// The object type name reported to the object store and core. @property (nonatomic, readonly) NSString *objectName; // writable redeclaration @property (nonatomic, readwrite, copy) NSArray *properties; @property (nonatomic, readwrite, assign) bool isSwiftClass; @property (nonatomic, readwrite, assign) BOOL isEmbedded; @property (nonatomic, readwrite, assign) BOOL isAsymmetric; // class used for this object schema @property (nonatomic, readwrite, assign) Class objectClass; @property (nonatomic, readwrite, assign) Class accessorClass; @property (nonatomic, readwrite, assign) Class unmanagedClass; @property (nonatomic, readwrite, assign) bool hasCustomEventSerialization; @property (nonatomic, readwrite, nullable) RLMProperty *primaryKeyProperty; @property (nonatomic, copy) NSArray *computedProperties; @property (nonatomic, readonly, nullable) NSArray *swiftGenericProperties; // returns a cached or new schema for a given object class + (instancetype)schemaForObjectClass:(Class)objectClass; @end @interface RLMObjectSchema (Dynamic) /** This method is useful only in specialized circumstances, for example, when accessing objects in a Realm produced externally. If you are simply building an app on Realm, it is not recommended to use this method as an [RLMObjectSchema](RLMObjectSchema) is generated automatically for every [RLMObject](RLMObject) subclass. Initialize an RLMObjectSchema with classname, objectClass, and an array of properties @warning This method is useful only in specialized circumstances. @param objectClassName The name of the class used to refer to objects of this type. @param objectClass The Objective-C class used when creating instances of this type. @param properties An array of RLMProperty instances describing the managed properties for this type. @return An initialized instance of RLMObjectSchema. */ - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMObjectSchema_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObjectSchema_Private.h" #import namespace realm { class ObjectSchema; } @class RLMSchema; @interface RLMObjectSchema () - (std::string const&)objectStoreName; // create realm::ObjectSchema copy - (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema; // initialize with realm::ObjectSchema + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema; @end ================================================ FILE: Realm/RLMObjectStore.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #ifdef __cplusplus extern "C" { #endif @class RLMRealm, RLMSchema, RLMObjectBase, RLMResults, RLMProperty; typedef NS_ENUM(NSUInteger, RLMUpdatePolicy) { RLMUpdatePolicyError = 1, RLMUpdatePolicyUpdateChanged = 3, RLMUpdatePolicyUpdateAll = 2, }; RLM_HEADER_AUDIT_BEGIN(nullability) void RLMVerifyHasPrimaryKey(Class cls); void RLMVerifyInWriteTransaction(RLMRealm *const realm); // // Adding, Removing, Getting Objects // // add an object to the given realm void RLMAddObjectToRealm(RLMObjectBase *object, RLMRealm *realm, RLMUpdatePolicy); // delete an object from its realm void RLMDeleteObjectFromRealm(RLMObjectBase *object, RLMRealm *realm); // deletes all objects from a realm void RLMDeleteAllObjectsFromRealm(RLMRealm *realm); // get objects of a given class RLMResults *RLMGetObjects(RLMRealm *realm, NSString *objectClassName, NSPredicate * _Nullable predicate) NS_RETURNS_RETAINED; // get an object with the given primary key id _Nullable RLMGetObject(RLMRealm *realm, NSString *objectClassName, id _Nullable key) NS_RETURNS_RETAINED; // create object from array or dictionary RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, id _Nullable value, RLMUpdatePolicy updatePolicy) NS_RETURNS_RETAINED; // creates an asymmetric object and doesn't return void RLMCreateAsymmetricObjectInRealm(RLMRealm *realm, NSString *className, id value); // // Accessor Creation // // Perform the per-property accessor initialization for a managed RealmSwiftObject // promotingExisting should be true if the object was previously used as an // unmanaged object, and false if it is a newly created object. void RLMInitializeSwiftAccessor(RLMObjectBase *object, bool promotingExisting); #ifdef __cplusplus } namespace realm { class Obj; class Table; struct ColKey; struct ObjLink; } class RLMClassInfo; // get an object with a given table & object key RLMObjectBase *RLMObjectFromObjLink(RLMRealm *realm, realm::ObjLink&& objLink, bool parentIsSwiftObject) NS_RETURNS_RETAINED; // Create accessors RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, int64_t key) NS_RETURNS_RETAINED; RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, const realm::Obj& obj) NS_RETURNS_RETAINED; #endif RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMObjectStore.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObjectStore.h" #import "RLMAccessor.hpp" #import "RLMArray_Private.hpp" #import "RLMObservation.hpp" #import "RLMObject_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftCollectionBase.h" #import "RLMSwiftSupport.h" #import "RLMUtil.hpp" #import "RLMSwiftValueStorage.h" #import #import #import #import #import static inline void RLMVerifyRealmRead(__unsafe_unretained RLMRealm *const realm) { if (!realm) { @throw RLMException(@"Realm must not be nil"); } [realm verifyThread]; if (realm->_realm->is_closed()) { // This message may seem overly specific, but frozen Realms are currently // the only ones which we outright close. @throw RLMException(@"Cannot read from a frozen Realm which has been invalidated."); } } void RLMVerifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm) { RLMVerifyRealmRead(realm); // if realm is not writable throw if (!realm.inWriteTransaction) { @throw RLMException(@"Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first."); } } void RLMInitializeSwiftAccessor(__unsafe_unretained RLMObjectBase *const object, bool promoteExisting) { if (!object || !object->_row || !object->_objectSchema->_isSwiftClass) { return; } if (![object isKindOfClass:object->_objectSchema.objectClass]) { // It can be a different class if it's a dynamic object, and those don't // require any init here (and would crash since they don't have the ivars) return; } if (promoteExisting) { for (RLMProperty *prop in object->_objectSchema.swiftGenericProperties) { [prop.swiftAccessor promote:prop on:object]; } } else { for (RLMProperty *prop in object->_objectSchema.swiftGenericProperties) { [prop.swiftAccessor initialize:prop on:object]; } } } void RLMVerifyHasPrimaryKey(Class cls) { RLMObjectSchema *schema = [cls sharedSchema]; if (!schema.primaryKeyProperty) { NSString *reason = [NSString stringWithFormat:@"'%@' does not have a primary key and can not be updated", schema.className]; @throw [NSException exceptionWithName:@"RLMException" reason:reason userInfo:nil]; } } using realm::CreatePolicy; static CreatePolicy updatePolicyToCreatePolicy(RLMUpdatePolicy policy) { CreatePolicy createPolicy = {.create = true, .copy = false, .diff = false, .update = false}; switch (policy) { case RLMUpdatePolicyError: break; case RLMUpdatePolicyUpdateChanged: createPolicy.diff = true; [[clang::fallthrough]]; case RLMUpdatePolicyUpdateAll: createPolicy.update = true; break; } return createPolicy; } void RLMAddObjectToRealm(__unsafe_unretained RLMObjectBase *const object, __unsafe_unretained RLMRealm *const realm, RLMUpdatePolicy updatePolicy) { RLMVerifyInWriteTransaction(realm); CreatePolicy createPolicy = updatePolicyToCreatePolicy(updatePolicy); createPolicy.copy = false; auto& info = realm->_info[object->_objectSchema.className]; RLMAccessorContext c{info}; c.createObject(object, createPolicy); } RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, id value, RLMUpdatePolicy updatePolicy) { RLMVerifyInWriteTransaction(realm); CreatePolicy createPolicy = updatePolicyToCreatePolicy(updatePolicy); createPolicy.copy = true; auto& info = realm->_info[className]; RLMAccessorContext c{info}; RLMObjectBase *object = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); auto [obj, reuseExisting] = c.createObject(value, createPolicy, true); if (reuseExisting) { return value; } object->_row = std::move(obj); RLMInitializeSwiftAccessor(object, false); return object; } void RLMCreateAsymmetricObjectInRealm(RLMRealm *realm, NSString *className, id value) { RLMVerifyInWriteTransaction(realm); CreatePolicy createPolicy = {.create = true, .copy = true, .diff = false, .update = false}; auto& info = realm->_info[className]; RLMAccessorContext c{info}; c.createObject(value, createPolicy); } RLMObjectBase *RLMObjectFromObjLink(RLMRealm *realm, realm::ObjLink&& objLink, bool parentIsSwiftObject) { if (auto* tableInfo = realm->_info[objLink.get_table_key()]) { return RLMCreateObjectAccessor(*tableInfo, objLink.get_obj_key().value); } else { // Construct the object dynamically. // This code path should only be hit on first access of the object. Class cls = parentIsSwiftObject ? [RealmSwiftDynamicObject class] : [RLMDynamicObject class]; auto& group = realm->_realm->read_group(); auto schema = std::make_unique(group, group.get_table_name(objLink.get_table_key()), objLink.get_table_key()); RLMObjectSchema *rlmObjectSchema = [RLMObjectSchema objectSchemaForObjectStoreSchema:*schema]; rlmObjectSchema.accessorClass = cls; rlmObjectSchema.isSwiftClass = parentIsSwiftObject; realm->_info.appendDynamicObjectSchema(std::move(schema), rlmObjectSchema, realm); return RLMCreateObjectAccessor(realm->_info[rlmObjectSchema.className], objLink.get_obj_key().value); } } void RLMDeleteObjectFromRealm(__unsafe_unretained RLMObjectBase *const object, __unsafe_unretained RLMRealm *const realm) { if (realm != object->_realm) { @throw RLMException(@"Can only delete an object from the Realm it belongs to."); } RLMVerifyInWriteTransaction(object->_realm); if (object->_row.is_valid()) { RLMObservationTracker tracker(realm, true); object->_row.remove(); } object->_realm = nil; } void RLMDeleteAllObjectsFromRealm(RLMRealm *realm) { RLMVerifyInWriteTransaction(realm); // clear table for each object schema for (auto& info : realm->_info) { RLMClearTable(info.second); } } RLMResults *RLMGetObjects(__unsafe_unretained RLMRealm *const realm, NSString *objectClassName, NSPredicate *predicate) { RLMVerifyRealmRead(realm); // create view from table and predicate RLMClassInfo& info = realm->_info[objectClassName]; if (!info.table()) { // read-only realms may be missing tables since we can't add any // missing ones on init return [RLMResults resultsWithObjectInfo:info results:{}]; } if (predicate) { realm::Query query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, realm.schema, realm.group); return [RLMResults resultsWithObjectInfo:info results:realm::Results(realm->_realm, std::move(query))]; } return [RLMResults resultsWithObjectInfo:info results:realm::Results(realm->_realm, info.table())]; } id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) { RLMVerifyRealmRead(realm); auto& info = realm->_info[objectClassName]; if (RLMProperty *prop = info.propertyForPrimaryKey()) { RLMValidateValueForProperty(key, info.rlmObjectSchema, prop); } try { RLMAccessorContext c{info}; auto obj = realm::Object::get_for_primary_key(c, realm->_realm, *info.objectSchema, key ?: NSNull.null); if (!obj.is_valid()) return nil; return RLMCreateObjectAccessor(info, obj.get_obj()); } catch (std::exception const& e) { @throw RLMException(e); } } RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, int64_t key) { return RLMCreateObjectAccessor(info, info.table()->get_object(realm::ObjKey(key))); } // Create accessor and register with realm RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, const realm::Obj& obj) { RLMObjectBase *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info); accessor->_row = obj; RLMInitializeSwiftAccessor(accessor, false); return accessor; } ================================================ FILE: Realm/RLMObject_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMProperty, RLMArray, RLMSchema; typedef NS_ENUM(int32_t, RLMPropertyType); FOUNDATION_EXTERN void RLMInitializeWithValue(RLMObjectBase *, id, RLMSchema *); typedef void (^RLMObjectNotificationCallback)(RLMObjectBase *_Nullable object, NSArray *_Nullable propertyNames, NSArray *_Nullable oldValues, NSArray *_Nullable newValues, NSError *_Nullable error); // RLMObject accessor and read/write realm @interface RLMObjectBase () { @public RLMRealm *_realm; __unsafe_unretained RLMObjectSchema *_objectSchema; } // shared schema for this class + (nullable RLMObjectSchema *)sharedSchema; + (nullable NSArray *)_getProperties; + (bool)_realmIgnoreClass; // This enables to override the propertiesMapping in Swift, it is not to be used in Objective-C API. + (NSDictionary *)propertiesMapping; @end @interface RLMDynamicObject : RLMObject @end // Calls valueForKey: and re-raises NSUndefinedKeyExceptions FOUNDATION_EXTERN id _Nullable RLMValidatedValueForProperty(id object, NSString *key, NSString *className); // Compare two RLObjectBases FOUNDATION_EXTERN BOOL RLMObjectBaseAreEqual(RLMObjectBase * _Nullable o1, RLMObjectBase * _Nullable o2); FOUNDATION_EXTERN RLMNotificationToken *RLMObjectBaseAddNotificationBlock(RLMObjectBase *obj, NSArray *_Nullable keyPaths, dispatch_queue_t _Nullable queue, RLMObjectNotificationCallback block); RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectChangeBlock block, NSArray *_Nullable keyPaths, dispatch_queue_t _Nullable queue); // Returns whether the class is a descendent of RLMObjectBase FOUNDATION_EXTERN BOOL RLMIsObjectOrSubclass(Class klass); // Returns whether the class is an indirect descendant of RLMObjectBase FOUNDATION_EXTERN BOOL RLMIsObjectSubclass(Class klass); FOUNDATION_EXTERN const NSUInteger RLMDescriptionMaxDepth; FOUNDATION_EXTERN id RLMObjectFreeze(RLMObjectBase *obj) NS_RETURNS_RETAINED; FOUNDATION_EXTERN id RLMObjectThaw(RLMObjectBase *obj); // Gets an object identifier suitable for use with Combine. This value may // change when an unmanaged object is added to the Realm. FOUNDATION_EXTERN uint64_t RLMObjectBaseGetCombineId(RLMObjectBase *); // An accessor object which is used to interact with Swift properties from obj-c @interface RLMManagedPropertyAccessor : NSObject // Perform any initialization required for KVO on a *unmanaged* object + (void)observe:(RLMProperty *)property on:(RLMObjectBase *)parent; // Initialize the given property on a *managed* object which previous was unmanaged + (void)promote:(RLMProperty *)property on:(RLMObjectBase *)parent; // Initialize the given property on a newly created *managed* object + (void)initialize:(RLMProperty *)property on:(RLMObjectBase *)parent; // Read the value of the property, on either kind of object + (id)get:(RLMProperty *)property on:(RLMObjectBase *)parent; // Set the property to the given value, on either kind of object + (void)set:(RLMProperty *)property on:(RLMObjectBase *)parent to:(id)value; @end @interface RLMObjectNotificationToken : RLMNotificationToken - (void)observe:(RLMObjectBase *)obj keyPaths:(nullable NSArray *)keyPaths block:(RLMObjectNotificationCallback)block; - (void)registrationComplete:(void (^)(void))completion; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMObject_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMObject_Private.h" #import "RLMSwiftObject.h" #import "RLMRealm_Private.hpp" #import "RLMUtil.hpp" #import class RLMObservationInfo; // RLMObject accessor and read/write realm @interface RLMObjectBase () { @public realm::Obj _row; RLMObservationInfo *_observationInfo; RLMClassInfo *_info; } @end id RLMCreateManagedAccessor(Class cls, RLMClassInfo *info) NS_RETURNS_RETAINED; // throw an exception if the object is invalidated or on the wrong thread static inline void RLMVerifyAttached(__unsafe_unretained RLMObjectBase *const obj) { if (!obj->_row.is_valid()) { @throw RLMException(@"Object has been deleted or invalidated."); } [obj->_realm verifyThread]; } // throw an exception if the object can't be modified for any reason static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMObjectBase *const obj) { // first verify is attached RLMVerifyAttached(obj); if (!obj->_realm.inWriteTransaction) { if (obj->_realm.isFrozen) { @throw RLMException(@"Attempting to modify a frozen object - call thaw on the Object instance first."); } @throw RLMException(@"Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first."); } } [[clang::objc_runtime_visible]] @interface RealmSwiftDynamicObject : RealmSwiftObject @end ================================================ FILE: Realm/RLMObservation.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 #import #import #import #import @class RLMObjectBase, RLMRealm, RLMSchema, RLMProperty, RLMObjectSchema; class RLMClassInfo; class RLMSchemaInfo; namespace realm { class History; class SharedGroup; struct TableKey; struct ColKey; } // RLMObservationInfo stores all of the KVO-related data for RLMObjectBase and // RLMSet/Array. There is a one-to-one relationship between observed objects and // RLMObservationInfo instances, so it could be folded into RLMObjectBase, and // is a separate class mostly to avoid making all accessor objects far larger. // // RLMClassInfo stores a vector of pointers to the first observation info // created for each row. If there are multiple observation infos for a single // row (such as if there are multiple observed objects backed by a single row, // or if both an object and an array property of that object are observed), // they're stored in an intrusive doubly-linked-list in the `next` and `prev` // members. This is done primarily to make it simpler and faster to loop over // all of the observed objects for a single row, as that needs to be done for // every change. class RLMObservationInfo { public: RLMObservationInfo(id object); RLMObservationInfo(RLMClassInfo &objectSchema, realm::ObjKey row, id object); ~RLMObservationInfo(); realm::Obj const& getRow() const { return row; } NSString *columnName(realm::ColKey col) const noexcept; // Send willChange/didChange notifications to all observers for this object/row // Sends the array versions if indexes is non-nil, normal versions otherwise void willChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const; void didChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const; bool isForRow(realm::ObjKey key) const { return row.get_key() == key; } void recordObserver(realm::Obj& row, RLMClassInfo *objectInfo, RLMObjectSchema *objectSchema, NSString *keyPath); void removeObserver(); bool hasObservers() const { return observerCount > 0; } // valueForKey: on observed object and array properties needs to return the // same object each time for KVO to work at all. Doing this all the time // requires some odd semantics to avoid reference cycles, so instead we do // it only to the extent specifically required by KVO. In addition, we // need to continue to return the same object even if this row is deleted, // or deleting an object with active observers will explode horribly. // Once prepareForInvalidation() is called, valueForKey() will always return // the cached value for object and array properties without checking the // backing row to verify it's up-to-date. // // prepareForInvalidation() must be called on the head of the linked list // (i.e. on the object pointed to directly by the object schema) id valueForKey(NSString *key); void prepareForInvalidation(); private: // Doubly-linked-list of observed objects for the same row as this RLMObservationInfo *next = nullptr; RLMObservationInfo *prev = nullptr; // Row being observed realm::Obj row; RLMClassInfo *objectSchema = nullptr; // Object doing the observing __unsafe_unretained id object = nil; // valueForKey: hack bool invalidated = false; size_t observerCount = 0; NSString *lastKey = nil; __unsafe_unretained RLMProperty *lastProp = nil; // objects returned from valueForKey() to keep them alive in case observers // are added and so that they can still be accessed after row is detached NSMutableDictionary *cachedObjects; void setRow(realm::Table const& table, realm::ObjKey newRow); template void forEach(F&& f) const { // The user's observation handler may release their last reference to // the object being observed, which will result in the RLMObservationInfo // being destroyed. As a result, we need to retain the object which owns // both `this` and the current info we're looking at. __attribute__((objc_precise_lifetime)) id self = object, current; for (auto info = prev; info; info = info->prev) { current = info->object; f(info->object); } for (auto info = this; info; info = info->next) { current = info->object; f(info->object); } } // Default move/copy constructors don't work due to the intrusive linked // list and we don't need them RLMObservationInfo(RLMObservationInfo const&) = delete; RLMObservationInfo(RLMObservationInfo&&) = delete; RLMObservationInfo& operator=(RLMObservationInfo const&) = delete; RLMObservationInfo& operator=(RLMObservationInfo&&) = delete; }; // Get the the observation info chain for the given row // Will simply return info if it's non-null, and will search ojectSchema's array // for a matching one otherwise, and return null if there are none RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, realm::ObjKey row, RLMClassInfo& objectSchema); // delete all objects from a single table with change notifications void RLMClearTable(RLMClassInfo &realm); class RLMObservationTracker { public: RLMObservationTracker(RLMRealm *realm, bool trackDeletions=false); ~RLMObservationTracker(); void trackDeletions(); void willChange(RLMObservationInfo *info, NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil); void didChange(); private: std::vector *> _observedTables; __unsafe_unretained RLMRealm const*_realm; realm::Group& _group; RLMObservationInfo *_info = nullptr; NSString *_key; NSKeyValueChange _kind = NSKeyValueChangeSetting; NSIndexSet *_indexes; struct Change { RLMObservationInfo *info; __unsafe_unretained NSString *property; NSMutableIndexSet *indexes; }; std::vector _changes; std::vector _invalidated; template void cascadeNotification(CascadeNotification const&); }; std::vector RLMGetObservedRows(RLMSchemaInfo const& schema); void RLMWillChange(std::vector const& observed, std::vector const& invalidated); void RLMDidChange(std::vector const& observed, std::vector const& invalidated); // Used for checking if an `Object` declared with `@StateRealmObject` needs to have // it's accessors temporarily removed and added back so that the `Object` can be // managed be the Realm. [[clang::objc_runtime_visible]] @interface RLMSwiftUIKVO : NSObject + (BOOL)removeObserversFromObject:(NSObject *)object; + (void)addObserversToObject:(NSObject *)object; @end ================================================ FILE: Realm/RLMObservation.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMObservation.hpp" #import "RLMAccessor.h" #import "RLMArray_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.hpp" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftCollectionBase.h" #import "RLMSwiftValueStorage.h" #import using namespace realm; namespace { template struct IteratorPair { Iterator first; Iterator second; }; template Iterator begin(IteratorPair const& p) { return p.first; } template Iterator end(IteratorPair const& p) { return p.second; } template auto reverse(Container const& c) { return IteratorPair{c.rbegin(), c.rend()}; } } RLMObservationInfo::RLMObservationInfo(RLMClassInfo &objectSchema, realm::ObjKey row, id object) : object(object) , objectSchema(&objectSchema) { setRow(*objectSchema.table(), row); } RLMObservationInfo::RLMObservationInfo(id object) : object(object) { } RLMObservationInfo::~RLMObservationInfo() { if (prev) { // Not the head of the linked list, so just detach from the list REALM_ASSERT_DEBUG(prev->next == this); prev->next = next; if (next) { REALM_ASSERT_DEBUG(next->prev == this); next->prev = prev; } } else if (objectSchema) { // The head of the list, so remove self from the object schema's array // of observation info, either replacing self with the next info or // removing entirely if there is no next auto end = objectSchema->observedObjects.end(); auto it = find(objectSchema->observedObjects.begin(), end, this); if (it != end) { if (next) { *it = next; next->prev = nullptr; } else { iter_swap(it, std::prev(end)); objectSchema->observedObjects.pop_back(); } } } // Otherwise the observed object was unmanaged, so nothing to do #ifdef DEBUG // ensure that incorrect cleanup fails noisily object = (__bridge id)(void *)-1; prev = (RLMObservationInfo *)-1; next = (RLMObservationInfo *)-1; #endif } NSString *RLMObservationInfo::columnName(realm::ColKey col) const noexcept { return objectSchema->propertyForTableColumn(col).name; } void RLMObservationInfo::willChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { if (indexes) { forEach([=](__unsafe_unretained auto o) { [o willChange:kind valuesAtIndexes:indexes forKey:key]; }); } else { forEach([=](__unsafe_unretained auto o) { [o willChangeValueForKey:key]; }); } } void RLMObservationInfo::didChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { if (indexes) { forEach([=](__unsafe_unretained auto o) { [o didChange:kind valuesAtIndexes:indexes forKey:key]; }); } else { forEach([=](__unsafe_unretained auto o) { [o didChangeValueForKey:key]; }); } } void RLMObservationInfo::prepareForInvalidation() { REALM_ASSERT_DEBUG(objectSchema); REALM_ASSERT_DEBUG(!prev); for (auto info = this; info; info = info->next) info->invalidated = true; } void RLMObservationInfo::setRow(realm::Table const& table, realm::ObjKey key) { REALM_ASSERT_DEBUG(!row); REALM_ASSERT_DEBUG(objectSchema); row = table.get_object(key); for (auto info : objectSchema->observedObjects) { if (info->row && info->row.get_key() == key) { prev = info; next = info->next; if (next) next->prev = this; info->next = this; return; } } objectSchema->observedObjects.push_back(this); } void RLMObservationInfo::recordObserver(realm::Obj& objectRow, RLMClassInfo *objectInfo, __unsafe_unretained RLMObjectSchema *const objectSchema, __unsafe_unretained NSString *const keyPath) { ++observerCount; if (row) { return; } // add ourselves to the list of observed objects if this is the first time // an observer is being added to a managed object if (objectRow) { this->objectSchema = objectInfo; setRow(*objectRow.get_table(), objectRow.get_key()); return; } // Arrays need a reference to their containing object to avoid having to // go through the awful proxy object from mutableArrayValueForKey. // For managed objects we do this when the object is added or created // (and have to to support notifications from modifying an object which // was never observed), but for Swift classes (both RealmSwift and // RLMObject) we can't do it then because we don't know what the parent // object is. NSUInteger sep = [keyPath rangeOfString:@"."].location; NSString *key = sep == NSNotFound ? keyPath : [keyPath substringToIndex:sep]; RLMProperty *prop = objectSchema[key]; if (auto swiftAccessor = prop.swiftAccessor) { [swiftAccessor observe:prop on:object]; } else if (prop.collection) { [valueForKey(key) setParent:object property:prop]; } } void RLMObservationInfo::removeObserver() { --observerCount; } id RLMObservationInfo::valueForKey(NSString *key) { if (invalidated) { if ([key isEqualToString:RLMInvalidatedKey]) { return @YES; } return cachedObjects[key]; } if (key != lastKey) { lastKey = key; lastProp = objectSchema ? objectSchema->rlmObjectSchema[key] : nil; } static auto superValueForKey = reinterpret_cast([NSObject methodForSelector:@selector(valueForKey:)]); if (!lastProp) { // Not a managed property, so use NSObject's implementation of valueForKey: return RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); } auto getSuper = [&] { return row ? RLMDynamicGet(object, lastProp) : RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); }; // We need to return the same object each time for observing over keypaths // to work, so we store a cache of them here. We can't just cache them on // the object as that leads to retain cycles. if (lastProp.collection) { id value = cachedObjects[key]; if (!value) { value = getSuper(); if (!cachedObjects) { cachedObjects = [NSMutableDictionary new]; } cachedObjects[key] = value; } return value; } if (lastProp.type == RLMPropertyTypeObject) { auto col = row.get_table()->get_column_key(lastProp.name.UTF8String); if (row.is_null(col)) { [cachedObjects removeObjectForKey:key]; return nil; } RLMObjectBase *value = cachedObjects[key]; if (value && value->_row.get_key() == row.get(col)) { return value; } value = getSuper(); if (!cachedObjects) { cachedObjects = [NSMutableDictionary new]; } cachedObjects[key] = value; return value; } return getSuper(); } RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, realm::ObjKey row, RLMClassInfo& objectSchema) { if (info) { return info; } for (RLMObservationInfo *info : objectSchema.observedObjects) { if (info->isForRow(row)) { return info; } } return nullptr; } void RLMClearTable(RLMClassInfo &objectSchema) { if (!objectSchema.table()) { // Orphaned embedded object types are included in the schema but do not // create a table at all, so we may not have a table here and just // don't need to do anything return; } for (auto info : objectSchema.observedObjects) { info->willChange(RLMInvalidatedKey); } { RLMObservationTracker tracker(objectSchema.realm, true); Results(objectSchema.realm->_realm, objectSchema.table()).clear(); for (auto info : objectSchema.observedObjects) { info->prepareForInvalidation(); } } for (auto info : reverse(objectSchema.observedObjects)) { info->didChange(RLMInvalidatedKey); } objectSchema.observedObjects.clear(); } RLMObservationTracker::RLMObservationTracker(__unsafe_unretained RLMRealm *const realm, bool trackDeletions) : _realm(realm) , _group(realm.group) { if (trackDeletions) { this->trackDeletions(); } } RLMObservationTracker::~RLMObservationTracker() { didChange(); } void RLMObservationTracker::willChange(RLMObservationInfo *info, NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) { _key = key; _kind = kind; _indexes = indexes; _info = info; if (_info) { _info->willChange(key, kind, indexes); } } void RLMObservationTracker::trackDeletions() { if (_group.has_cascade_notification_handler()) { // We're nested inside another call which will handle any cascaded changes for us return; } for (auto& info : _realm->_info) { if (!info.second.observedObjects.empty()) { _observedTables.push_back(&info.second.observedObjects); } } // No need for change tracking if no objects are observed if (_observedTables.empty()) { return; } _group.set_cascade_notification_handler([this](realm::Group::CascadeNotification const& cs) { cascadeNotification(cs); }); } template void RLMObservationTracker::cascadeNotification(CascadeNotification const& cs) { if (cs.rows.empty() && cs.links.empty()) { return; } size_t invalidatedCount = _invalidated.size(); size_t changeCount = _changes.size(); auto tableKey = [](RLMObservationInfo *info) { return info->getRow().get_table()->get_key(); }; std::sort(begin(_observedTables), end(_observedTables), [=](auto a, auto b) { return tableKey(a->front()) < tableKey(b->front()); }); for (auto const& link : cs.links) { auto table = std::find_if(_observedTables.begin(), _observedTables.end(), [&](auto table) { return tableKey(table->front()) == link.origin_table; }); if (table == _observedTables.end()) { continue; } for (auto observer : **table) { if (!observer->isForRow(link.origin_key)) { continue; } NSString *name = observer->columnName(link.origin_col_key); if (!link.origin_col_key.is_list()) { _changes.push_back({observer, name}); continue; } auto c = find_if(begin(_changes), end(_changes), [&](auto const& c) { return c.info == observer && c.property == name; }); if (c == end(_changes)) { _changes.push_back({observer, name, [NSMutableIndexSet new]}); c = prev(end(_changes)); } // We know what row index is being removed from the LinkView, // but what we actually want is the indexes in the LinkView that // are going away auto linkview = observer->getRow().get_linklist(link.origin_col_key); linkview.find_all(link.old_target_key, [&](size_t index) { [c->indexes addIndex:index]; }); } } if (!cs.rows.empty()) { using Row = realm::Group::CascadeNotification::row; auto begin = cs.rows.begin(); for (auto table : _observedTables) { auto currentTableKey = tableKey(table->front()); if (begin->table_key < currentTableKey) { // Find the first deleted object in or after this table begin = std::lower_bound(begin, cs.rows.end(), Row{currentTableKey, realm::ObjKey(0)}); } if (begin == cs.rows.end()) { // No more deleted objects break; } if (currentTableKey < begin->table_key) { // Next deleted object is in a table after this one continue; } // Find the end of the deletions in this table auto end = std::lower_bound(begin, cs.rows.end(), Row{realm::TableKey(currentTableKey.value + 1), realm::ObjKey(0)}); // Check each observed object to see if it's in the deleted rows for (auto info : *table) { if (std::binary_search(begin, end, Row{currentTableKey, info->getRow().get_key()})) { _invalidated.push_back(info); } } // Advance the begin iterator to the start of the next table begin = end; if (begin == cs.rows.end()) { break; } } } // The relative order of these loops is very important for (size_t i = invalidatedCount; i < _invalidated.size(); ++i) { _invalidated[i]->willChange(RLMInvalidatedKey); } for (size_t i = changeCount; i < _changes.size(); ++i) { auto const& change = _changes[i]; change.info->willChange(change.property, NSKeyValueChangeRemoval, change.indexes); } for (size_t i = invalidatedCount; i < _invalidated.size(); ++i) { _invalidated[i]->prepareForInvalidation(); } } void RLMObservationTracker::didChange() { if (_info) { _info->didChange(_key, _kind, _indexes); _info = nullptr; } if (_observedTables.empty()) { return; } _group.set_cascade_notification_handler(nullptr); for (auto const& change : reverse(_changes)) { change.info->didChange(change.property, NSKeyValueChangeRemoval, change.indexes); } for (auto info : reverse(_invalidated)) { info->didChange(RLMInvalidatedKey); } _observedTables.clear(); _changes.clear(); _invalidated.clear(); } namespace { template void forEach(realm::BindingContext::ObserverState const& state, Func&& func) { for (auto& change : state.changes) { func(realm::ColKey(change.first), change.second, static_cast(state.info)); } } } std::vector RLMGetObservedRows(RLMSchemaInfo const& schema) { std::vector observers; for (auto& table : schema) { for (auto info : table.second.observedObjects) { auto const& row = info->getRow(); if (!row.is_valid()) continue; observers.push_back({ row.get_table()->get_key(), row.get_key(), info}); } } sort(begin(observers), end(observers)); return observers; } static NSKeyValueChange convert(realm::BindingContext::ColumnInfo::Kind kind) { switch (kind) { case realm::BindingContext::ColumnInfo::Kind::None: case realm::BindingContext::ColumnInfo::Kind::SetAll: return NSKeyValueChangeSetting; case realm::BindingContext::ColumnInfo::Kind::Set: return NSKeyValueChangeReplacement; case realm::BindingContext::ColumnInfo::Kind::Insert: return NSKeyValueChangeInsertion; case realm::BindingContext::ColumnInfo::Kind::Remove: return NSKeyValueChangeRemoval; } } static NSIndexSet *convert(realm::IndexSet const& in, NSMutableIndexSet *out) { if (in.empty()) { return nil; } [out removeAllIndexes]; for (auto range : in) { [out addIndexesInRange:{range.first, range.second - range.first}]; } return out; } void RLMWillChange(std::vector const& observed, std::vector const& invalidated) { for (auto info : invalidated) { static_cast(info)->willChange(RLMInvalidatedKey); } if (!observed.empty()) { NSMutableIndexSet *indexes = [NSMutableIndexSet new]; for (auto const& o : observed) { forEach(o, [&](realm::ColKey colKey, auto const& change, RLMObservationInfo *info) { info->willChange(info->columnName(colKey), convert(change.kind), convert(change.indices, indexes)); }); } } for (auto info : invalidated) { static_cast(info)->prepareForInvalidation(); } } void RLMDidChange(std::vector const& observed, std::vector const& invalidated) { if (!observed.empty()) { // Loop in reverse order to avoid O(N^2) behavior in Foundation NSMutableIndexSet *indexes = [NSMutableIndexSet new]; for (auto const& o : reverse(observed)) { forEach(o, [&](realm::ColKey col, auto const& change, RLMObservationInfo *info) { info->didChange(info->columnName(col), convert(change.kind), convert(change.indices, indexes)); }); } } for (auto const& info : reverse(invalidated)) { static_cast(info)->didChange(RLMInvalidatedKey); } } ================================================ FILE: Realm/RLMPredicateUtil.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 #import using ExpressionVisitor = std::function; NSPredicate *transformPredicate(NSPredicate *, ExpressionVisitor); ================================================ FILE: Realm/RLMPredicateUtil.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMPredicateUtil.hpp" #include namespace { struct PredicateExpressionTransformer { PredicateExpressionTransformer(ExpressionVisitor visitor) : m_visitor(visitor) { } NSExpression *visit(NSExpression *expression) const; NSPredicate *visit(NSPredicate *predicate) const; ExpressionVisitor m_visitor; }; NSExpression *PredicateExpressionTransformer::visit(NSExpression *expression) const { expression = m_visitor(expression); switch (expression.expressionType) { case NSFunctionExpressionType: { NSMutableArray *arguments = [NSMutableArray array]; for (NSExpression *argument in expression.arguments) { [arguments addObject:visit(argument)]; } if (expression.operand) { return [NSExpression expressionForFunction:visit(expression.operand) selectorName:expression.function arguments:arguments]; } else { return [NSExpression expressionForFunction:expression.function arguments:arguments]; } } case NSUnionSetExpressionType: return [NSExpression expressionForUnionSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; case NSIntersectSetExpressionType: return [NSExpression expressionForIntersectSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; case NSMinusSetExpressionType: return [NSExpression expressionForMinusSet:visit(expression.leftExpression) with:visit(expression.rightExpression)]; case NSSubqueryExpressionType: { NSExpression *collection = expression.collection; // NSExpression.collection is declared as id, but appears to always hold an NSExpression for subqueries. REALM_ASSERT([collection isKindOfClass:[NSExpression class]]); return [NSExpression expressionForSubquery:visit(collection) usingIteratorVariable:expression.variable predicate:visit(expression.predicate)]; } case NSAggregateExpressionType: { NSMutableArray *subexpressions = [NSMutableArray array]; for (NSExpression *subexpression in expression.collection) { [subexpressions addObject:visit(subexpression)]; } return [NSExpression expressionForAggregate:subexpressions]; } case NSConditionalExpressionType: #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" return [NSExpression expressionForConditional:visit(expression.predicate) trueExpression:visit(expression.trueExpression) falseExpression:visit(expression.falseExpression)]; #pragma clang diagnostic pop default: // The remaining expression types do not contain nested expressions or predicates. return expression; } } NSPredicate *PredicateExpressionTransformer::visit(NSPredicate *predicate) const { if ([predicate isKindOfClass:[NSCompoundPredicate class]]) { NSCompoundPredicate *compoundPredicate = (NSCompoundPredicate *)predicate; NSMutableArray *subpredicates = [NSMutableArray array]; for (NSPredicate *subpredicate in compoundPredicate.subpredicates) { [subpredicates addObject:visit(subpredicate)]; } return [[NSCompoundPredicate alloc] initWithType:compoundPredicate.compoundPredicateType subpredicates:subpredicates]; } if ([predicate isKindOfClass:[NSComparisonPredicate class]]) { NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)predicate; NSExpression *leftExpression = visit(comparisonPredicate.leftExpression); NSExpression *rightExpression = visit(comparisonPredicate.rightExpression); return [NSComparisonPredicate predicateWithLeftExpression:leftExpression rightExpression:rightExpression modifier:comparisonPredicate.comparisonPredicateModifier type:comparisonPredicate.predicateOperatorType options:comparisonPredicate.options]; } return predicate; } } // anonymous namespace NSPredicate *transformPredicate(NSPredicate *predicate, ExpressionVisitor visitor) { PredicateExpressionTransformer transformer(visitor); return transformer.visit(predicate); } ================================================ FILE: Realm/RLMPrefix.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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. // //////////////////////////////////////////////////////////////////////////// #ifdef __OBJC__ #import #endif #ifdef __cplusplus #import #import #import #import #import #import #import #import #import #import #endif ================================================ FILE: Realm/RLMProperty.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /// :nodoc: @protocol RLMInt @end /// :nodoc: @protocol RLMBool @end /// :nodoc: @protocol RLMDouble @end /// :nodoc: @protocol RLMFloat @end /// :nodoc: @protocol RLMString @end /// :nodoc: @protocol RLMDate @end /// :nodoc: @protocol RLMData @end /// :nodoc: @protocol RLMDecimal128 @end /// :nodoc: @protocol RLMObjectId @end /// :nodoc: @protocol RLMUUID @end /// :nodoc: @interface NSNumber () @end /** `RLMProperty` instances represent properties managed by a Realm in the context of an object schema. Such properties may be persisted to a Realm file or computed from other data from the Realm. When using Realm, `RLMProperty` instances allow performing migrations and introspecting the database's schema. These property instances map to columns in the core database. */ NS_SWIFT_SENDABLE RLM_FINAL // not actually immutable, but the public API kinda is @interface RLMProperty : NSObject #pragma mark - Properties /** The name of the property. */ @property (nonatomic, readonly) NSString *name; /** The type of the property. @see `RLMPropertyType` */ @property (nonatomic, readonly) RLMPropertyType type; /** Indicates whether this property is indexed. @see `RLMObject` */ @property (nonatomic, readonly) BOOL indexed; /** For `RLMObject` and `RLMCollection` properties, the name of the class of object stored in the property. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** For linking objects properties, the property name of the property the linking objects property is linked to. */ @property (nonatomic, readonly, copy, nullable) NSString *linkOriginPropertyName; /** Indicates whether this property is optional. */ @property (nonatomic, readonly) BOOL optional; /** Indicates whether this property is an array. */ @property (nonatomic, readonly) BOOL array; /** Indicates whether this property is a set. */ @property (nonatomic, readonly) BOOL set; /** Indicates whether this property is a dictionary. */ @property (nonatomic, readonly) BOOL dictionary; /** Indicates whether this property is an array or set. */ @property (nonatomic, readonly) BOOL collection; #pragma mark - Methods /** Returns whether a given property object is equal to the receiver. */ - (BOOL)isEqualToProperty:(RLMProperty *)property; @end /** An `RLMPropertyDescriptor` instance represents a specific property on a given class. */ NS_SWIFT_SENDABLE RLM_FINAL @interface RLMPropertyDescriptor : NSObject /** Creates and returns a property descriptor. @param objectClass The class of this property descriptor. @param propertyName The name of this property descriptor. */ + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName; /// The class of the property. @property (nonatomic, readonly) Class objectClass; /// The name of the property. @property (nonatomic, readonly) NSString *propertyName; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMProperty.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMProperty_Private.hpp" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMObject.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.h" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftSupport.h" #import "RLMUtil.hpp" #import static_assert((int)RLMPropertyTypeInt == (int)realm::PropertyType::Int); static_assert((int)RLMPropertyTypeBool == (int)realm::PropertyType::Bool); static_assert((int)RLMPropertyTypeFloat == (int)realm::PropertyType::Float); static_assert((int)RLMPropertyTypeDouble == (int)realm::PropertyType::Double); static_assert((int)RLMPropertyTypeString == (int)realm::PropertyType::String); static_assert((int)RLMPropertyTypeData == (int)realm::PropertyType::Data); static_assert((int)RLMPropertyTypeDate == (int)realm::PropertyType::Date); static_assert((int)RLMPropertyTypeObject == (int)realm::PropertyType::Object); static_assert((int)RLMPropertyTypeObjectId == (int)realm::PropertyType::ObjectId); static_assert((int)RLMPropertyTypeDecimal128 == (int)realm::PropertyType::Decimal); static_assert((int)RLMPropertyTypeUUID == (int)realm::PropertyType::UUID); static_assert((int)RLMPropertyTypeAny == (int)realm::PropertyType::Mixed); BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType) { return propertyType == RLMPropertyTypeLinkingObjects; } // Swift obeys the ARC naming conventions for method families (except for init) // but the end result doesn't really work (using KVC on a method returning a // retained value results in a leak, but not returning a retained value results // in crashes). Objective-C makes properties with naming fitting the method // families a compile error, so we just disallow them in Swift as well. // http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families void RLMValidateSwiftPropertyName(NSString *name) { // To belong to a method family, the property name must begin with the family // name followed by a non-lowercase letter (or nothing), with an optional // leading underscore const char *str = name.UTF8String; if (str[0] == '_') ++str; auto nameSize = strlen(str); // Note that "init" is deliberately not in this list because Swift does not // infer family membership for it. for (auto family : {"alloc", "new", "copy", "mutableCopy"}) { auto familySize = strlen(family); if (nameSize < familySize || !std::equal(str, str + familySize, family)) { continue; } if (familySize == nameSize || !islower(str[familySize])) { @throw RLMException(@"Property names beginning with '%s' are not " "supported. Swift follows ARC's ownership " "rules for methods based on their name, which " "results in memory leaks when accessing " "properties which return retained values via KVC.", family); } return; } } static bool rawTypeShouldBeTreatedAsComputedProperty(NSString *rawType) { return [rawType isEqualToString:@"@\"RLMLinkingObjects\""] || [rawType hasPrefix:@"@\"RLMLinkingObjects<"]; } @implementation RLMProperty + (instancetype)propertyForObjectStoreProperty:(const realm::Property &)prop { auto ret = [[RLMProperty alloc] initWithName:@(prop.name.c_str()) type:static_cast(prop.type & ~realm::PropertyType::Flags) objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil linkOriginPropertyName:prop.link_origin_property_name.length() ? @(prop.link_origin_property_name.c_str()) : nil indexed:prop.is_indexed optional:isNullable(prop.type)]; if (is_array(prop.type)) { ret->_array = true; } if (is_set(prop.type)) { ret->_set = true; } if (is_dictionary(prop.type)) { // TODO: We need a way to store the dictionary // key type in realm::Property once we support more // key types. ret->_dictionaryKeyType = RLMPropertyTypeString; ret->_dictionary = true; } if (!prop.public_name.empty()) { ret->_columnName = ret->_name; ret->_name = @(prop.public_name.c_str()); } return ret; } - (instancetype)initWithName:(NSString *)name type:(RLMPropertyType)type objectClassName:(NSString *)objectClassName linkOriginPropertyName:(NSString *)linkOriginPropertyName indexed:(BOOL)indexed optional:(BOOL)optional { self = [super init]; if (self) { _name = name; _type = type; _objectClassName = objectClassName; _linkOriginPropertyName = linkOriginPropertyName; _indexed = indexed; _optional = optional; [self updateAccessors]; } return self; } - (void)updateAccessors { // populate getter/setter names if generic if (!_getterName) { _getterName = _name; } if (!_setterName) { // Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z' int asciiCode = [_name characterAtIndex:0]; BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z'; NSString *firstChar = [_name substringToIndex:1]; firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar; _setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]]; } _getterSel = NSSelectorFromString(_getterName); _setterSel = NSSelectorFromString(_setterName); } static std::optional typeFromProtocolString(const char *type) { if (strcmp(type, "RLMValue>\"") == 0) { return RLMPropertyTypeAny; } if (strncmp(type, "RLM", 3)) { return realm::none; } type += 3; if (strcmp(type, "Int>\"") == 0) { return RLMPropertyTypeInt; } if (strcmp(type, "Float>\"") == 0) { return RLMPropertyTypeFloat; } if (strcmp(type, "Double>\"") == 0) { return RLMPropertyTypeDouble; } if (strcmp(type, "Bool>\"") == 0) { return RLMPropertyTypeBool; } if (strcmp(type, "String>\"") == 0) { return RLMPropertyTypeString; } if (strcmp(type, "Data>\"") == 0) { return RLMPropertyTypeData; } if (strcmp(type, "Date>\"") == 0) { return RLMPropertyTypeDate; } if (strcmp(type, "Decimal128>\"") == 0) { return RLMPropertyTypeDecimal128; } if (strcmp(type, "ObjectId>\"") == 0) { return RLMPropertyTypeObjectId; } if (strcmp(type, "UUID>\"") == 0) { return RLMPropertyTypeUUID; } return realm::none; } // determine RLMPropertyType from objc code - returns true if valid type was found/set - (BOOL)setTypeFromRawType:(NSString *)rawType { const char *code = rawType.UTF8String; switch (*code) { case 's': // short case 'i': // int case 'l': // long case 'q': // long long _type = RLMPropertyTypeInt; return YES; case 'f': _type = RLMPropertyTypeFloat; return YES; case 'd': _type = RLMPropertyTypeDouble; return YES; case 'c': // BOOL is stored as char - since rlm has no char type this is ok case 'B': _type = RLMPropertyTypeBool; return YES; case '@': break; default: return NO; } _optional = true; static const char arrayPrefix[] = "@\"RLMArray<"; static const int arrayPrefixLen = sizeof(arrayPrefix) - 1; static const char setPrefix[] = "@\"RLMSet<"; static const int setPrefixLen = sizeof(setPrefix) - 1; static const char dictionaryPrefix[] = "@\"RLMDictionary<"; static const int dictionaryPrefixLen = sizeof(dictionaryPrefix) - 1; static const char numberPrefix[] = "@\"NSNumber<"; static const int numberPrefixLen = sizeof(numberPrefix) - 1; static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects"; static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1; _array = strncmp(code, arrayPrefix, arrayPrefixLen) == 0; _set = strncmp(code, setPrefix, setPrefixLen) == 0; _dictionary = strncmp(code, dictionaryPrefix, dictionaryPrefixLen) == 0; if (strcmp(code, "@\"NSString\"") == 0) { _type = RLMPropertyTypeString; } else if (strcmp(code, "@\"NSDate\"") == 0) { _type = RLMPropertyTypeDate; } else if (strcmp(code, "@\"NSData\"") == 0) { _type = RLMPropertyTypeData; } else if (strcmp(code, "@\"RLMDecimal128\"") == 0) { _type = RLMPropertyTypeDecimal128; } else if (strcmp(code, "@\"RLMObjectId\"") == 0) { _type = RLMPropertyTypeObjectId; } else if (strcmp(code, "@\"NSUUID\"") == 0) { _type = RLMPropertyTypeUUID; } else if (strcmp(code, "@\"\"") == 0) { _type = RLMPropertyTypeAny; // Mixed can represent a null type but can't explicitly be an optional type. _optional = false; } else if (_array || _set || _dictionary) { size_t prefixLen = 0; NSString *collectionName; if (_array) { prefixLen = arrayPrefixLen; collectionName = @"RLMArray"; } else if (_set) { prefixLen = setPrefixLen; collectionName = @"RLMSet"; } else if (_dictionary) { // get the type, by working backward from RLMDictionary size_t typeLen = 0; size_t codeSize = strlen(code); for (size_t i = codeSize; i > 0; i--) { if (code[i] == '>' && i != (codeSize-2)) { // -2 means we skip the first time we see '>' typeLen = i; break; } } prefixLen = typeLen+size_t(2); // +2 start at the type name collectionName = @"RLMDictionary"; // Get the key type if (strstr(code + dictionaryPrefixLen, "RLMString><") != NULL) { _dictionaryKeyType = RLMPropertyTypeString; } } if (auto type = typeFromProtocolString(code + prefixLen)) { _type = *type; return YES; } // get object class from type string - @"RLMSomeCollection" _objectClassName = [[NSString alloc] initWithBytes:code + prefixLen length:strlen(code + prefixLen) - 2 // drop trailing >" encoding:NSUTF8StringEncoding]; if ([RLMSchema classForString:_objectClassName]) { // Dictionaries require object types to be nullable. This is due to // the fact that if you delete a realm object that exists in a dictionary // the key should stay present but the value should be null. _optional = _dictionary; _type = RLMPropertyTypeObject; return YES; } @throw RLMException(@"Property '%@' is of type '%@<%@>' which is not a supported %@ object type. " @"%@ can only contain instances of RLMObject subclasses. " @"See https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/#define-a-to-many-relationship-property " @"for more information.", _name, collectionName, _objectClassName, collectionName, collectionName); } else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) { auto type = typeFromProtocolString(code + numberPrefixLen); if (type && (*type == RLMPropertyTypeInt || *type == RLMPropertyTypeFloat || *type == RLMPropertyTypeDouble || *type == RLMPropertyTypeBool)) { _type = *type; return YES; } @throw RLMException(@"Property '%@' is of type %s which is not a supported NSNumber object type. " @"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. " @"See https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/supported-types/#std-label-ios-supported-property-types" @"for more information.", _name, code + 1); } else if (strncmp(code, linkingObjectsPrefix, linkingObjectsPrefixLen) == 0 && (code[linkingObjectsPrefixLen] == '"' || code[linkingObjectsPrefixLen] == '<')) { _type = RLMPropertyTypeLinkingObjects; _optional = false; _array = true; if (!_objectClassName || !_linkOriginPropertyName) { @throw RLMException(@"Property '%@' is of type RLMLinkingObjects but +linkingObjectsProperties did not specify the class " "or property that is the origin of the link.", _name); } // If the property was declared with a protocol indicating the contained type, validate that it matches // the class from the dictionary returned by +linkingObjectsProperties. if (code[linkingObjectsPrefixLen] == '<') { NSString *classNameFromProtocol = [[NSString alloc] initWithBytes:code + linkingObjectsPrefixLen + 1 length:strlen(code + linkingObjectsPrefixLen) - 3 // drop trailing >" encoding:NSUTF8StringEncoding]; if (![_objectClassName isEqualToString:classNameFromProtocol]) { @throw RLMException(@"Property '%@' was declared with type RLMLinkingObjects<%@>, but a conflicting " "class name of '%@' was returned by +linkingObjectsProperties.", _name, classNameFromProtocol, _objectClassName); } } } else if (strcmp(code, "@\"NSNumber\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber.", _name); } else if (strcmp(code, "@\"RLMArray\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray.", _name); } else if (strcmp(code, "@\"RLMSet\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMSet.", _name); } else if (strcmp(code, "@\"RLMDictionary\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMDictionary.", _name); } else { NSString *className; Class cls = nil; if (code[1] == '\0') { className = @"id"; } else { // for objects strip the quotes and @ className = [rawType substringWithRange:NSMakeRange(2, rawType.length-3)]; cls = [RLMSchema classForString:className]; } if (!cls) { @throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. " @"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMSet, " @"RLMDictionary, RLMLinkingObjects, RLMDecimal128, RLMObjectId, or subclasses of RLMObject. " @"See https://www.mongodb.com/docs/realm-legacy/docs/objc/latest/api/Classes/RLMObject.html " @"for more information.", _name, className); } _type = RLMPropertyTypeObject; _optional = true; _objectClassName = [cls className] ?: className; } return YES; } - (void)parseObjcProperty:(objc_property_t)property readOnly:(bool *)readOnly computed:(bool *)computed rawType:(NSString **)rawType { unsigned int count; objc_property_attribute_t *attrs = property_copyAttributeList(property, &count); *computed = true; for (size_t i = 0; i < count; ++i) { switch (*attrs[i].name) { case 'T': *rawType = @(attrs[i].value); break; case 'R': *readOnly = true; break; case 'G': _getterName = @(attrs[i].value); break; case 'S': _setterName = @(attrs[i].value); break; case 'V': // backing ivar name *computed = false; break; case '&': // retain/assign break; case 'C': // copy break; case 'D': // dynamic break; case 'N': // nonatomic break; case 'P': // GC'able break; case 'W': // weak break; default: break; } } free(attrs); } - (instancetype)initSwiftPropertyWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property instance:(RLMObject *)obj { self = [super init]; if (!self) { return nil; } RLMValidateSwiftPropertyName(name); _name = name; _indexed = indexed; if (linkPropertyDescriptor) { _objectClassName = [linkPropertyDescriptor.objectClass className]; _linkOriginPropertyName = linkPropertyDescriptor.propertyName; } NSString *rawType; bool readOnly = false; bool isComputed = false; [self parseObjcProperty:property readOnly:&readOnly computed:&isComputed rawType:&rawType]; // Swift sometimes doesn't explicitly set the ivar name in the metadata, so check if // there's an ivar with the same name as the property. if (!readOnly && isComputed && class_getInstanceVariable([obj class], name.UTF8String)) { isComputed = false; } // Check if there's a storage ivar for a lazy property in this name. We don't honor // @lazy in managed objects, but allow it for unmanaged objects which are // subclasses of RLMObject (but not RealmSwift.Object). It's unclear if there's a // good reason for this difference. if (!readOnly && isComputed) { // Xcode 10 and earlier NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name]; isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); } if (!readOnly && isComputed) { // Xcode 11 NSString *backingPropertyName = [NSString stringWithFormat:@"$__lazy_storage_$_%@", name]; isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); } if (readOnly || isComputed) { return nil; } id propertyValue = [obj valueForKey:_name]; // FIXME: temporarily workaround added since Objective-C generics used in Swift show up as `@` // * broken starting in Swift 3.0 Xcode 8 b1 // * tested to still be broken in Swift 3.0 Xcode 8 b6 // * if the Realm Objective-C Swift tests pass with this removed, it's been fixed // * once it has been fixed, remove this entire conditional block (contents included) entirely // * Bug Report: SR-2031 https://bugs.swift.org/browse/SR-2031 if ([rawType isEqualToString:@"@"]) { if (propertyValue) { rawType = [NSString stringWithFormat:@"@\"%@\"", [propertyValue class]]; } else if (linkPropertyDescriptor) { // we're going to naively assume that the user used the correct type since we can't check it rawType = @"@\"RLMLinkingObjects\""; } } // convert array / set / dictionary types to objc variant if ([rawType isEqualToString:@"@\"RLMArray\""]) { RLMArray *value = propertyValue; _type = value.type; _optional = value.optional; _array = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. " @"RLMArrays can only contain instances of RLMObject subclasses. " @"See https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/#define-a-to-many-relationship-property " @"for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"RLMSet\""]) { RLMSet *value = propertyValue; _type = value.type; _optional = value.optional; _set = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMSet<%@>' which is not a supported RLMSet object type. " @"RLMSets can only contain instances of RLMObject subclasses. " @"See https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/#define-a-to-many-relationship-property " @"for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"RLMDictionary\""]) { RLMDictionary *value = propertyValue; _type = value.type; _dictionaryKeyType = value.keyType; _optional = value.optional; _dictionary = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMDictionary' which is not a supported RLMDictionary object type. " @"RLMDictionarys can only contain instances of RLMObject subclasses. " @"See https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/#define-a-to-many-relationship-property " @"for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"NSNumber\""]) { const char *numberType = [propertyValue objCType]; if (!numberType) { @throw RLMException(@"Can't persist NSNumber without default value: use a Swift-native number type or provide a default value."); } _optional = true; switch (*numberType) { case 'i': case 'l': case 'q': _type = RLMPropertyTypeInt; break; case 'f': _type = RLMPropertyTypeFloat; break; case 'd': _type = RLMPropertyTypeDouble; break; case 'B': case 'c': _type = RLMPropertyTypeBool; break; default: @throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType); } } else if (![self setTypeFromRawType:rawType]) { @throw RLMException(@"Can't persist property '%@' with incompatible type. " "Add to Object.ignoredProperties() class method to ignore.", self.name); } if ([rawType isEqualToString:@"c"]) { // Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if // it actually sets it to 1. [obj setValue:@2 forKey:name]; NSNumber *value = [obj valueForKey:name]; _type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool; } // update getter/setter names [self updateAccessors]; return self; } - (instancetype)initWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property { self = [super init]; if (!self) { return nil; } _name = name; _indexed = indexed; if (linkPropertyDescriptor) { _objectClassName = [linkPropertyDescriptor.objectClass className]; _linkOriginPropertyName = linkPropertyDescriptor.propertyName; } NSString *rawType; bool isReadOnly = false; bool isComputed = false; [self parseObjcProperty:property readOnly:&isReadOnly computed:&isComputed rawType:&rawType]; bool shouldBeTreatedAsComputedProperty = rawTypeShouldBeTreatedAsComputedProperty(rawType); if ((isReadOnly || isComputed) && !shouldBeTreatedAsComputedProperty) { return nil; } if (![self setTypeFromRawType:rawType]) { @throw RLMException(@"Can't persist property '%@' with incompatible type. " "Add to ignoredPropertyNames: method to ignore.", self.name); } if (!isReadOnly && shouldBeTreatedAsComputedProperty) { @throw RLMException(@"Property '%@' must be declared as readonly as %@ properties cannot be written to.", self.name, RLMTypeToString(_type)); } // update getter/setter names [self updateAccessors]; return self; } - (id)copyWithZone:(NSZone *)zone { RLMProperty *prop = [[RLMProperty allocWithZone:zone] init]; prop->_name = _name; prop->_columnName = _columnName; prop->_type = _type; prop->_objectClassName = _objectClassName; prop->_array = _array; prop->_set = _set; prop->_dictionary = _dictionary; prop->_dictionaryKeyType = _dictionaryKeyType; prop->_indexed = _indexed; prop->_getterName = _getterName; prop->_setterName = _setterName; prop->_getterSel = _getterSel; prop->_setterSel = _setterSel; prop->_isPrimary = _isPrimary; prop->_swiftAccessor = _swiftAccessor; prop->_swiftIvar = _swiftIvar; prop->_optional = _optional; prop->_linkOriginPropertyName = _linkOriginPropertyName; return prop; } - (RLMProperty *)copyWithNewName:(NSString *)name { RLMProperty *prop = [self copy]; prop.name = name; return prop; } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[RLMProperty class]]) { return NO; } return [self isEqualToProperty:object]; } - (BOOL)isEqualToProperty:(RLMProperty *)property { return _type == property->_type && _indexed == property->_indexed && _isPrimary == property->_isPrimary && _optional == property->_optional && [_name isEqualToString:property->_name] && (_objectClassName == property->_objectClassName || [_objectClassName isEqualToString:property->_objectClassName]) && (_linkOriginPropertyName == property->_linkOriginPropertyName || [_linkOriginPropertyName isEqualToString:property->_linkOriginPropertyName]); } - (BOOL)collection { return self.set || self.array || self.dictionary; } - (NSString *)description { NSString *objectClassName = @""; if (self.type == RLMPropertyTypeObject || self.type == RLMPropertyTypeLinkingObjects) { objectClassName = [NSString stringWithFormat: @"\tobjectClassName = %@;\n" @"\tlinkOriginPropertyName = %@;\n", self.objectClassName, self.linkOriginPropertyName]; } return [NSString stringWithFormat: @"%@ {\n" "\ttype = %@;\n" "%@" "\tcolumnName = %@;\n" "\tindexed = %@;\n" "\tisPrimary = %@;\n" "\tarray = %@;\n" "\tset = %@;\n" "\tdictionary = %@;\n" "\toptional = %@;\n" "}", self.name, RLMTypeToString(self.type), objectClassName, self.columnName, self.indexed ? @"YES" : @"NO", self.isPrimary ? @"YES" : @"NO", self.array ? @"YES" : @"NO", self.set ? @"YES" : @"NO", self.dictionary ? @"YES" : @"NO", self.optional ? @"YES" : @"NO"]; } - (NSString *)columnName { return _columnName ?: _name; } - (realm::Property)objectStoreCopy:(RLMSchema *)schema { realm::Property p; p.name = self.columnName.UTF8String; if (_columnName) { p.public_name = _name.UTF8String; } if (_objectClassName) { RLMObjectSchema *targetSchema = schema[_objectClassName]; p.object_type = (targetSchema.objectName ?: _objectClassName).UTF8String; if (_linkOriginPropertyName) { p.link_origin_property_name = (targetSchema[_linkOriginPropertyName].columnName ?: _linkOriginPropertyName).UTF8String; } } p.is_indexed = static_cast(_indexed); p.type = static_cast(_type); if (_array) { p.type |= realm::PropertyType::Array; } if (_set) { p.type |= realm::PropertyType::Set; } if (_dictionary) { p.type |= realm::PropertyType::Dictionary; } if (_optional || p.type == realm::PropertyType::Mixed) { p.type |= realm::PropertyType::Nullable; } return p; } - (NSString *)typeName { if (!self.collection) { return RLMTypeToString(_type); } NSString *collectionName; if (_swiftAccessor) { collectionName = _array ? @"List" : _set ? @"MutableSet" : @"Map"; } else { collectionName = _array ? @"RLMArray" : _set ? @"RLMSet" : @"RLMDictionary"; } return [NSString stringWithFormat:@"%@<%@>", collectionName, RLMTypeToString(_type)]; } @end @implementation RLMPropertyDescriptor + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName { RLMPropertyDescriptor *descriptor = [[RLMPropertyDescriptor alloc] init]; descriptor->_objectClass = objectClass; descriptor->_propertyName = propertyName; return descriptor; } @end ================================================ FILE: Realm/RLMProperty_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import @class RLMObjectBase; RLM_HEADER_AUDIT_BEGIN(nullability) BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType); FOUNDATION_EXTERN void RLMValidateSwiftPropertyName(NSString *name); // Translate an rlmtype to a string representation static inline NSString *RLMTypeToString(RLMPropertyType type) { switch (type) { case RLMPropertyTypeString: return @"string"; case RLMPropertyTypeInt: return @"int"; case RLMPropertyTypeBool: return @"bool"; case RLMPropertyTypeDate: return @"date"; case RLMPropertyTypeData: return @"data"; case RLMPropertyTypeDouble: return @"double"; case RLMPropertyTypeFloat: return @"float"; case RLMPropertyTypeAny: return @"mixed"; case RLMPropertyTypeObject: return @"object"; case RLMPropertyTypeLinkingObjects: return @"linking objects"; case RLMPropertyTypeDecimal128: return @"decimal128"; case RLMPropertyTypeObjectId: return @"object id"; case RLMPropertyTypeUUID: return @"uuid"; } return @"Unknown"; } // private property interface @interface RLMProperty () { @public RLMPropertyType _type; } - (instancetype)initWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property; - (instancetype)initSwiftPropertyWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property instance:(RLMObjectBase *)objectInstance; - (void)updateAccessors; // private setters @property (nonatomic, readwrite) NSString *name; @property (nonatomic, readwrite, assign) RLMPropertyType type; @property (nonatomic, readwrite) BOOL indexed; @property (nonatomic, readwrite) BOOL optional; @property (nonatomic, readwrite) BOOL array; @property (nonatomic, readwrite) BOOL set; @property (nonatomic, readwrite) BOOL dictionary; @property (nonatomic, copy, nullable) NSString *objectClassName; @property (nonatomic, copy, nullable) NSString *linkOriginPropertyName; // private properties @property (nonatomic, readwrite, nullable) NSString *columnName; @property (nonatomic, assign) NSUInteger index; @property (nonatomic, assign) BOOL isPrimary; @property (nonatomic, assign) BOOL isLegacy; @property (nonatomic, assign) ptrdiff_t swiftIvar; @property (nonatomic, assign, nullable) Class swiftAccessor; @property (nonatomic, readwrite, assign) RLMPropertyType dictionaryKeyType; @property (nonatomic, readwrite) BOOL customMappingIsOptional; // getter and setter names @property (nonatomic, copy) NSString *getterName; @property (nonatomic, copy) NSString *setterName; @property (nonatomic, nullable) SEL getterSel; @property (nonatomic, nullable) SEL setterSel; - (RLMProperty *)copyWithNewName:(NSString *)name; - (NSString *)typeName; @end @interface RLMProperty (Dynamic) /** This method is useful only in specialized circumstances, for example, in conjunction with +[RLMObjectSchema initWithClassName:objectClass:properties:]. If you are simply building an app on Realm, it is not recommended to use this method. Initialize an RLMProperty @warning This method is useful only in specialized circumstances. @param name The property name. @param type The property type. @param objectClassName The object type used for Object and Array types. @param linkOriginPropertyName The property name of the origin of a link. Used for linking objects properties. @return An initialized instance of RLMProperty. */ - (instancetype)initWithName:(NSString *)name type:(RLMPropertyType)type objectClassName:(nullable NSString *)objectClassName linkOriginPropertyName:(nullable NSString *)linkOriginPropertyName indexed:(BOOL)indexed optional:(BOOL)optional; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMProperty_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 #import @class RLMSchema; RLM_DIRECT_MEMBERS @interface RLMProperty () + (instancetype)propertyForObjectStoreProperty:(const realm::Property&)property; - (realm::Property)objectStoreCopy:(RLMSchema *)schema; @end static inline bool isNullable(const realm::PropertyType& t) { return t != realm::PropertyType::Mixed && is_nullable(t); } ================================================ FILE: Realm/RLMQueryUtil.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import namespace realm { class Group; class Query; class SortDescriptor; } @class RLMObjectSchema, RLMProperty, RLMSchema, RLMSortDescriptor; class RLMClassInfo; realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema, RLMSchema *schema, realm::Group &group); // return property - throw for invalid column name RLMProperty *RLMValidatedProperty(RLMObjectSchema *objectSchema, NSString *columnName); ================================================ FILE: Realm/RLMQueryUtil.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMQueryUtil.hpp" #import "RLMAccessor.hpp" #import "RLMGeospatial_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.hpp" #import "RLMPredicateUtil.hpp" #import "RLMProperty_Private.h" #import "RLMSchema.h" #import "RLMUtil.hpp" #import #import #import #import #import #import #import #import using namespace realm; namespace { NSString * const RLMPropertiesComparisonTypeMismatchException = @"RLMPropertiesComparisonTypeMismatchException"; NSString * const RLMPropertiesComparisonTypeMismatchReason = @"Property type mismatch between %@ and %@"; // small helper to create the many exceptions thrown when parsing predicates [[gnu::cold]] [[noreturn]] void throwException(NSString *name, NSString *format, ...) { va_list args; va_start(args, format); NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); @throw [NSException exceptionWithName:name reason:reason userInfo:nil]; } // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions void RLMPrecondition(bool condition, NSString *name, NSString *format, ...) { if (__builtin_expect(condition, 1)) { return; } va_list args; va_start(args, format); NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); @throw [NSException exceptionWithName:name reason:reason userInfo:nil]; } BOOL propertyTypeIsNumeric(RLMPropertyType propertyType) { switch (propertyType) { case RLMPropertyTypeInt: case RLMPropertyTypeFloat: case RLMPropertyTypeDouble: case RLMPropertyTypeDecimal128: case RLMPropertyTypeDate: case RLMPropertyTypeAny: return YES; default: return NO; } } bool propertyTypeIsLink(RLMPropertyType type) { return type == RLMPropertyTypeObject || type == RLMPropertyTypeLinkingObjects; } bool isObjectValidForProperty(id value, RLMProperty *prop) { if (prop.collection) { if (propertyTypeIsLink(prop.type)) { RLMObjectBase *obj = RLMDynamicCast(value); if (!obj) { obj = RLMDynamicCast(RLMBridgeSwiftValue(value)); } if (!obj) { return false; } return [RLMObjectBaseObjectSchema(obj).className isEqualToString:prop.objectClassName]; } return RLMValidateValue(value, prop.type, prop.optional, false, nil); } return RLMIsObjectValidForProperty(value, prop); } // Equal and ContainsSubstring are used by QueryBuilder::add_string_constraint as the comparator // for performing diacritic-insensitive comparisons. StringData get_string(Mixed const& m) { if (m.is_null()) return StringData(); if (m.get_type() == type_String) return m.get_string(); auto b = m.get_binary(); return StringData(b.data(), b.size()); } bool equal(CFStringCompareFlags options, StringData v1, StringData v2) { if (v1.is_null() || v2.is_null()) { return v1.is_null() == v2.is_null(); } auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), kCFStringEncodingUTF8, false, kCFAllocatorNull)); auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), kCFStringEncodingUTF8, false, kCFAllocatorNull)); return CFStringCompare(s1.get(), s2.get(), options) == kCFCompareEqualTo; } template struct Equal { using CaseSensitive = Equal; using CaseInsensitive = Equal; bool operator()(Mixed v1, Mixed v2) const { return equal(options, get_string(v1), get_string(v2)); } static const char* description() { return options & kCFCompareCaseInsensitive ? "==[cd]" : "==[d]"; } }; template struct NotEqual { using CaseSensitive = NotEqual; using CaseInsensitive = NotEqual; bool operator()(Mixed v1, Mixed v2) const { return !equal(options, get_string(v1), get_string(v2)); } static const char* description() { return options & kCFCompareCaseInsensitive ? "!=[cd]" : "!=[d]"; } }; bool contains_substring(CFStringCompareFlags options, StringData v1, StringData v2) { if (v2.is_null()) { // Everything contains NULL return true; } if (v1.is_null()) { // NULL contains nothing (except NULL, handled above) return false; } if (v2.size() == 0) { // Everything (except NULL, handled above) contains the empty string return true; } auto s1 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v1.data(), v1.size(), kCFStringEncodingUTF8, false, kCFAllocatorNull)); auto s2 = util::adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8*)v2.data(), v2.size(), kCFStringEncodingUTF8, false, kCFAllocatorNull)); return CFStringFind(s1.get(), s2.get(), options).location != kCFNotFound; } template struct ContainsSubstring { using CaseSensitive = ContainsSubstring; using CaseInsensitive = ContainsSubstring; bool operator()(Mixed v1, Mixed v2) const { return contains_substring(options, get_string(v1), get_string(v2)); } static const char* description() { return options & kCFCompareCaseInsensitive ? "CONTAINS[cd]" : "CONTAINS[d]"; } }; NSString *operatorName(NSPredicateOperatorType operatorType) { switch (operatorType) { case NSLessThanPredicateOperatorType: return @"<"; case NSLessThanOrEqualToPredicateOperatorType: return @"<="; case NSGreaterThanPredicateOperatorType: return @">"; case NSGreaterThanOrEqualToPredicateOperatorType: return @">="; case NSEqualToPredicateOperatorType: return @"=="; case NSNotEqualToPredicateOperatorType: return @"!="; case NSMatchesPredicateOperatorType: return @"MATCHES"; case NSLikePredicateOperatorType: return @"LIKE"; case NSBeginsWithPredicateOperatorType: return @"BEGINSWITH"; case NSEndsWithPredicateOperatorType: return @"ENDSWITH"; case NSInPredicateOperatorType: return @"IN"; case NSContainsPredicateOperatorType: return @"CONTAINS"; case NSBetweenPredicateOperatorType: return @"BETWEEN"; case NSCustomSelectorPredicateOperatorType: return @"custom selector"; } return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType]; } [[gnu::cold]] [[noreturn]] void unsupportedOperator(RLMPropertyType datatype, NSPredicateOperatorType operatorType) { throwException(@"Invalid operator type", @"Operator '%@' not supported for type '%@'", operatorName(operatorType), RLMTypeToString(datatype)); } bool isNSNull(id value) { return !value || value == NSNull.null; } template bool isNSNull(T) { return false; } Table& get_table(Group& group, RLMObjectSchema *objectSchema) { return *ObjectStore::table_for_object_type(group, objectSchema.objectStoreName); } // A reference to a column within a query. Can be resolved to a Columns for use in query expressions. class ColumnReference { public: ColumnReference(Query& query, Group& group, RLMSchema *schema, RLMProperty* property, std::vector&& links = {}) : m_links(std::move(links)), m_property(property), m_schema(schema), m_group(&group), m_query(&query), m_link_chain(query.get_table()) { for (const auto& link : m_links) { if (link.type != RLMPropertyTypeLinkingObjects) { m_link_chain.link(link.columnName.UTF8String); } else { auto [link_origin_table, link_origin_column] = link_origin(link); m_link_chain.backlink(link_origin_table, link_origin_column); } } m_col = m_link_chain.get_current_table()->get_column_key(m_property.columnName.UTF8String); } ColumnReference(Query& query, Group& group, RLMSchema *schema) : m_schema(schema), m_group(&group), m_query(&query) { } template auto resolve(SubQuery&&... subquery) const { static_assert(sizeof...(SubQuery) < 2, "resolve() takes at most one subquery"); // LinkChain::column() mutates it, so we need to make a copy auto lc = m_link_chain; if (type() != RLMPropertyTypeLinkingObjects) { return lc.column(column(), std::forward(subquery)...); } if constexpr (std::is_same_v) { auto [table, col] = link_origin(m_property); return lc.column(table, col, std::forward(subquery)...); } REALM_TERMINATE("LinkingObjects property did not have column type Link"); } RLMProperty *property() const { return m_property; } ColKey column() const { return m_col; } RLMPropertyType type() const { return property().type; } RLMObjectSchema *link_target_object_schema() const { REALM_ASSERT(is_link()); return m_schema[property().objectClassName]; } bool is_link() const noexcept { return propertyTypeIsLink(type()); } bool has_any_to_many_links() const { return std::any_of(begin(m_links), end(m_links), [](RLMProperty *property) { return property.collection; }); } ColumnReference last_link_column() const { REALM_ASSERT(!m_links.empty()); return {*m_query, *m_group, m_schema, m_links.back(), {m_links.begin(), m_links.end() - 1}}; } ColumnReference column_ignoring_links(Query& query) const { return {query, *m_group, m_schema, m_property}; } ColumnReference append(RLMProperty *prop) const { auto links = m_links; if (m_property) { links.push_back(m_property); } return ColumnReference(*m_query, *m_group, m_schema, prop, std::move(links)); } void validate_comparison(id value) const { RLMPrecondition(isObjectValidForProperty(value, m_property), @"Invalid value", @"Cannot compare value '%@' of type '%@' to property '%@' of type '%@'", value, [value class], m_property.name, m_property.objectClassName ?: RLMTypeToString(m_property.type)); if (RLMObjectBase *obj = RLMDynamicCast(value)) { RLMPrecondition(!obj->_row.is_valid() || m_group == &obj->_realm.group, @"Invalid value origin", @"Object must be from the Realm being queried"); } } private: std::pair link_origin(RLMProperty *prop) const { RLMObjectSchema *link_origin_schema = m_schema[prop.objectClassName]; Table& link_origin_table = get_table(*m_group, link_origin_schema); NSString *column_name = link_origin_schema[prop.linkOriginPropertyName].columnName; auto link_origin_column = link_origin_table.get_column_key(column_name.UTF8String); return {link_origin_table, link_origin_column}; } std::vector m_links; RLMProperty *m_property; RLMSchema *m_schema; Group *m_group; Query *m_query; LinkChain m_link_chain; ColKey m_col; }; class CollectionOperation { public: enum Type { None, Count, Minimum, Maximum, Sum, Average, // Dictionary specific. AllKeys }; CollectionOperation(Type type, ColumnReference&& link_column, ColumnReference&& column) : m_type(type) , m_link_column(std::move(link_column)) , m_column(std::move(column)) { REALM_ASSERT(m_type != None); } Type type() const { return m_type; } const ColumnReference& link_column() const { return m_link_column; } const ColumnReference& column() const { return m_column; } void validate_comparison(id value) const { bool valid = true; switch (m_type) { case Count: RLMPrecondition([value isKindOfClass:[NSNumber class]], @"Invalid operand", @"%@ can only be compared with a numeric value.", name_for_type(m_type)); return; case Average: case Minimum: case Maximum: // Null on min/max/average matches arrays with no non-null values, including on non-nullable types valid = isNSNull(value) || isObjectValidForProperty(value, m_column.property()); break; case Sum: // Sums are never null valid = !isNSNull(value) && isObjectValidForProperty(value, m_column.property()); break; case AllKeys: RLMPrecondition(isNSNull(value) || [value isKindOfClass:[NSString class]], @"Invalid operand", @"@allKeys can only be compared with a string value."); return; case None: break; } RLMPrecondition(valid, @"Invalid operand", @"%@ on a property of type %@ cannot be compared with '%@'", name_for_type(m_type), RLMTypeToString(m_column.type()), value); } void validate_comparison(const ColumnReference& column) const { switch (m_type) { case Count: RLMPrecondition(propertyTypeIsNumeric(column.type()), @"Invalid operand", @"%@ can only be compared with a numeric value.", name_for_type(m_type)); break; case Average: case Minimum: case Maximum: case Sum: RLMPrecondition(propertyTypeIsNumeric(column.type()), @"Invalid operand", @"%@ on a property of type %@ cannot be compared with property of type '%@'", name_for_type(m_type), RLMTypeToString(m_column.type()), RLMTypeToString(column.type())); break; case AllKeys: RLMPrecondition(column.type() == RLMPropertyTypeString, @"Invalid operand", @"@allKeys can only be compared with a string value."); break; case None: break; } } static Type type_for_name(NSString *name) { if ([name isEqualToString:@"@count"]) { return Count; } if ([name isEqualToString:@"@min"]) { return Minimum; } if ([name isEqualToString:@"@max"]) { return Maximum; } if ([name isEqualToString:@"@sum"]) { return Sum; } if ([name isEqualToString:@"@avg"]) { return Average; } if ([name isEqualToString:@"@allKeys"]) { return AllKeys; } return None; } private: static NSString *name_for_type(Type type) { switch (type) { case Count: return @"@count"; case Minimum: return @"@min"; case Maximum: return @"@max"; case Sum: return @"@sum"; case Average: return @"@avg"; case AllKeys: return @"@allKeys"; case None: REALM_UNREACHABLE(); } } Type m_type; ColumnReference m_link_column; ColumnReference m_column; }; struct KeyPath; class QueryBuilder { public: QueryBuilder(Query& query, Group& group, RLMSchema *schema) : m_query(query), m_group(group), m_schema(schema) { } void apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema); void apply_collection_operator_expression(KeyPath&& kp, id value, NSComparisonPredicate *pred); void apply_value_expression(KeyPath&& kp, id value, NSComparisonPredicate *pred); void apply_column_expression(KeyPath&& left, KeyPath&& right, NSComparisonPredicate *predicate); void apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, NSPredicateOperatorType operatorType, NSExpression *right); void apply_map_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, NSComparisonPredicateOptions options, NSPredicateOperatorType operatorType, NSExpression *right); template void add_numeric_constraint(RLMPropertyType datatype, NSPredicateOperatorType operatorType, A&& lhs, B&& rhs); template void add_bool_constraint(RLMPropertyType, NSPredicateOperatorType operatorType, A&& lhs, B&& rhs); template void add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, Columns&& column, T value); template void add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, Columns&& column, const ColumnReference& c); template void do_add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, Columns&& column, Mixed&& value); template void add_substring_constraint(const T& value, Query condition); template void add_substring_constraint(const Columns& value, Query condition); template void add_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value); template void add_diacritic_sensitive_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value); template void do_add_diacritic_sensitive_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value); template void add_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, ColumnReference const& column, R const& rhs); template typename W, typename T> void do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, ColumnReference const& column, T&& value); void add_between_constraint(const ColumnReference& column, id value); void add_memberwise_equality_constraint(const ColumnReference& column, RLMObjectBase *obj); void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, RLMObjectBase *obj); void add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, realm::null); void add_link_constraint(NSPredicateOperatorType, const ColumnReference&, const ColumnReference&); void add_within_constraint(const ColumnReference& column, id geospatial); template void add_collection_operation_constraint(NSPredicateOperatorType operatorType, const CollectionOperation& collectionOperation, R&& rhs, NSComparisonPredicateOptions comparisionOptions); template void add_collection_operation_constraint(NSPredicateOperatorType operatorType, const CollectionOperation& collectionOperation, R&& rhs, NSComparisonPredicateOptions comparisionOptions); template void add_collection_operation_constraint(NSPredicateOperatorType operatorType, const CollectionOperation& collectionOperation, R&& rhs, NSComparisonPredicateOptions comparisionOptions); CollectionOperation collection_operation_from_key_path(KeyPath&& kp); ColumnReference column_reference_from_key_path(KeyPath&& kp, bool isAggregate); NSString* get_path_elements(std::vector &paths, NSExpression *expression); private: Query& m_query; Group& m_group; RLMSchema *m_schema; }; #pragma mark Numeric Constraints // add a clause for numeric constraints based on operator type template void QueryBuilder::add_numeric_constraint(RLMPropertyType datatype, NSPredicateOperatorType operatorType, A&& lhs, B&& rhs) { switch (operatorType) { case NSLessThanPredicateOperatorType: m_query.and_query(lhs < rhs); break; case NSLessThanOrEqualToPredicateOperatorType: m_query.and_query(lhs <= rhs); break; case NSGreaterThanPredicateOperatorType: m_query.and_query(lhs > rhs); break; case NSGreaterThanOrEqualToPredicateOperatorType: m_query.and_query(lhs >= rhs); break; case NSEqualToPredicateOperatorType: m_query.and_query(lhs == rhs); break; case NSNotEqualToPredicateOperatorType: m_query.and_query(lhs != rhs); break; default: unsupportedOperator(datatype, operatorType); } } template void QueryBuilder::add_bool_constraint(RLMPropertyType datatype, NSPredicateOperatorType operatorType, A&& lhs, B&& rhs) { switch (operatorType) { case NSEqualToPredicateOperatorType: m_query.and_query(lhs == rhs); break; case NSNotEqualToPredicateOperatorType: m_query.and_query(lhs != rhs); break; default: unsupportedOperator(datatype, operatorType); } } #pragma mark String Constraints template void QueryBuilder::add_substring_constraint(const T& value, Query condition) { // Foundation always returns false for substring operations with a RHS of null or "". m_query.and_query(value.size() ? std::move(condition) : std::unique_ptr(new FalseExpression)); } template<> void QueryBuilder::add_substring_constraint(const Mixed& value, Query condition) { // Foundation always returns false for substring operations with a RHS of null or "". bool empty = value.is_type(type_String) ? value.get_string().size() : value.get_binary().size(); m_query.and_query(empty ? std::move(condition) : std::unique_ptr(new FalseExpression)); } template void QueryBuilder::add_substring_constraint(const Columns& value, Query condition) { // Foundation always returns false for substring operations with a RHS of null or "". // We don't need to concern ourselves with the possibility of value traversing a link list // and producing multiple values per row as such expressions will have been rejected. m_query.and_query(const_cast&>(value).size() != 0 && std::move(condition)); } template Query make_diacritic_insensitive_constraint(bool caseSensitive, std::unique_ptr left, std::unique_ptr right) { using CompareCS = Compare; using CompareCI = Compare; if (caseSensitive) { return make_expression(std::move(left), std::move(right)); } else { return make_expression(std::move(left), std::move(right)); } }; Query make_diacritic_insensitive_constraint(NSPredicateOperatorType operatorType, bool caseSensitive, std::unique_ptr left, std::unique_ptr right) { switch (operatorType) { case NSBeginsWithPredicateOperatorType: { constexpr auto flags = kCFCompareDiacriticInsensitive | kCFCompareAnchored; return make_diacritic_insensitive_constraint>(caseSensitive, std::move(left), std::move(right)); } case NSEndsWithPredicateOperatorType: { constexpr auto flags = kCFCompareDiacriticInsensitive | kCFCompareAnchored | kCFCompareBackwards; return make_diacritic_insensitive_constraint>(caseSensitive, std::move(left), std::move(right)); } case NSContainsPredicateOperatorType: { constexpr auto flags = kCFCompareDiacriticInsensitive; return make_diacritic_insensitive_constraint>(caseSensitive, std::move(left), std::move(right)); } default: REALM_COMPILER_HINT_UNREACHABLE(); } } // static_assert is always evaluated even if it's inside a if constexpr // unless the value is derived from the template argument, in which case it's // only evaluated if that branch is active template struct AlwaysFalse : std::false_type {}; template Query make_lexicographical_constraint(NSPredicateOperatorType operatorType, bool caseSensitive, C& column, T const& value) { if (!caseSensitive) { throwException(@"Invalid predicate", @"Lexicographical comparisons must be case-sensitive"); } switch (operatorType) { case NSLessThanPredicateOperatorType: return column < value; case NSLessThanOrEqualToPredicateOperatorType: return column <= value; case NSGreaterThanPredicateOperatorType: return column > value; case NSGreaterThanOrEqualToPredicateOperatorType: return column >= value; default: REALM_COMPILER_HINT_UNREACHABLE(); } } template Query make_diacritic_sensitive_constraint(NSPredicateOperatorType operatorType, bool caseSensitive, C& column, T const& value) { switch (operatorType) { case NSBeginsWithPredicateOperatorType: return column.begins_with(value, caseSensitive); case NSEndsWithPredicateOperatorType: return column.ends_with(value, caseSensitive); case NSContainsPredicateOperatorType: return column.contains(value, caseSensitive); case NSEqualToPredicateOperatorType: return column.equal(value, caseSensitive); case NSNotEqualToPredicateOperatorType: return column.not_equal(value, caseSensitive); case NSLikePredicateOperatorType: return column.like(value, caseSensitive); case NSLessThanPredicateOperatorType: case NSLessThanOrEqualToPredicateOperatorType: case NSGreaterThanPredicateOperatorType: case NSGreaterThanOrEqualToPredicateOperatorType: return make_lexicographical_constraint(operatorType, caseSensitive, column, value); default: { if constexpr (is_any_v, Columns>, Columns>, ColumnDictionaryKeys>) { unsupportedOperator(RLMPropertyTypeString, operatorType); } else if constexpr (is_any_v, Columns>, Columns>>) { unsupportedOperator(RLMPropertyTypeData, operatorType); } else if constexpr (is_any_v, Columns>, Columns>>) { unsupportedOperator(RLMPropertyTypeAny, operatorType); } else if constexpr (is_any_v>) { // The underlying storage type Dictionary is always Mixed. This creates an issue // where we cannot be descriptive about the exception as we do not know // the actual value type. throwException(@"Invalid operand type", @"Operator '%@' not supported for string queries on Dictionary.", operatorName(operatorType)); } else { static_assert(AlwaysFalse::value, "unsupported column type"); } } } } template void QueryBuilder::do_add_diacritic_sensitive_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value) { bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); Query condition = make_diacritic_sensitive_constraint(operatorType, caseSensitive, column, value); switch (operatorType) { case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: case NSContainsPredicateOperatorType: add_substring_constraint(value, std::move(condition)); break; default: m_query.and_query(std::move(condition)); break; } } template void QueryBuilder::add_diacritic_sensitive_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value) { if constexpr (is_any_v> && is_any_v, Columns>) { // Core only implements these for Columns due to Dictionary being Mixed internall throwException(@"Unsupported predicate", @"String comparisons on a Dictionary and another property are only implemented for AnyRealmValue properties."); } else { do_add_diacritic_sensitive_string_constraint(operatorType, predicateOptions, std::forward(column), std::forward(value)); } } template void QueryBuilder::add_string_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, C&& column, T&& value) { if (!(predicateOptions & NSDiacriticInsensitivePredicateOption)) { add_diacritic_sensitive_string_constraint(operatorType, predicateOptions, std::forward(column), std::forward(value)); return; } auto as_subexpr = util::overload{ [](StringData value) { return make_subexpr(value); }, [](BinaryData value) { return make_subexpr(StringData(value.data(), value.size())); }, [](Mixed value) { // When Mixed is null calling `get_type` will throw an exception. if (value.is_null()) return make_subexpr(value.get_string()); switch (value.get_type()) { case DataType::Type::String: return make_subexpr(value.get_string()); case DataType::Type::Binary: return make_subexpr(StringData(value.get_binary().data(), value.get_binary().size())); default: REALM_UNREACHABLE(); } }, [](auto& c) { return c.clone(); } }; auto left = as_subexpr(column); auto right = as_subexpr(value); bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); constexpr auto flags = kCFCompareDiacriticInsensitive | kCFCompareAnchored; switch (operatorType) { case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: case NSContainsPredicateOperatorType: add_substring_constraint(value, make_diacritic_insensitive_constraint(operatorType, caseSensitive, std::move(left), std::move(right))); break; case NSNotEqualToPredicateOperatorType: m_query.and_query(make_diacritic_insensitive_constraint>(caseSensitive, std::move(left), std::move(right))); break; case NSEqualToPredicateOperatorType: m_query.and_query(make_diacritic_insensitive_constraint>(caseSensitive, std::move(left), std::move(right))); break; case NSLikePredicateOperatorType: throwException(@"Invalid operator type", @"Operator 'LIKE' not supported with diacritic-insensitive modifier."); default: unsupportedOperator(RLMPropertyTypeString, operatorType); } } id value_from_constant_expression_or_value(id value) { if (NSExpression *exp = RLMDynamicCast(value)) { RLMPrecondition(exp.expressionType == NSConstantValueExpressionType, @"Invalid value", @"Expressions within predicate aggregates must be constant values"); return exp.constantValue; } return value; } void validate_and_extract_between_range(id value, RLMProperty *prop, id *from, id *to) { NSArray *array = RLMDynamicCast(value); RLMPrecondition(array, @"Invalid value", @"object must be of type NSArray for BETWEEN operations"); RLMPrecondition(array.count == 2, @"Invalid value", @"NSArray object must contain exactly two objects for BETWEEN operations"); *from = value_from_constant_expression_or_value(array.firstObject); *to = value_from_constant_expression_or_value(array.lastObject); RLMPrecondition(isObjectValidForProperty(*from, prop) && isObjectValidForProperty(*to, prop), @"Invalid value", @"NSArray objects must be of type %@ for BETWEEN operations", RLMTypeToString(prop.type)); } #pragma mark Between Constraint void QueryBuilder::add_between_constraint(const ColumnReference& column, id value) { if (column.has_any_to_many_links()) { auto link_column = column.last_link_column(); Query subquery = get_table(m_group, link_column.link_target_object_schema()).where(); QueryBuilder(subquery, m_group, m_schema).add_between_constraint(column.column_ignoring_links(subquery), value); m_query.and_query(link_column.resolve(std::move(subquery)).count() > 0); return; } id from, to; validate_and_extract_between_range(value, column.property(), &from, &to); if (!propertyTypeIsNumeric(column.type())) { return unsupportedOperator(column.type(), NSBetweenPredicateOperatorType); } m_query.group(); add_constraint(NSGreaterThanOrEqualToPredicateOperatorType, 0, column, from); add_constraint(NSLessThanOrEqualToPredicateOperatorType, 0, column, to); m_query.end_group(); } #pragma mark Link Constraints void QueryBuilder::add_memberwise_equality_constraint(const ColumnReference& column, RLMObjectBase *obj) { for (RLMProperty *property in obj->_objectSchema.properties) { // Both of these probably are implementable, but are significantly more complicated. RLMPrecondition(!property.collection, @"Invalid predicate", @"Unsupported property '%@.%@' for memberwise equality query: equality on collections is not implemented.", obj->_objectSchema.className, property.name); RLMPrecondition(!propertyTypeIsLink(property.type), @"Invalid predicate", @"Unsupported property '%@.%@' for memberwise equality query: object links are not implemented.", obj->_objectSchema.className, property.name); add_constraint(NSEqualToPredicateOperatorType, 0, column.append(property), RLMDynamicGet(obj, property)); } } void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, RLMObjectBase *obj) { // If the value isn't actually a RLMObject then it's something which bridges // to RLMObject, i.e. a custom type mapping to an embedded object. For those // we want to perform memberwise equality rather than equality on the link itself. if (![obj isKindOfClass:[RLMObjectBase class]]) { obj = RLMBridgeSwiftValue(obj); REALM_ASSERT([obj isKindOfClass:[RLMObjectBase class]]); // Collections need to use subqueries, but unary links can just use a // group. Unary links could also use a subquery but that has worse performance. if (column.property().collection) { Query subquery = get_table(m_group, column.link_target_object_schema()).where(); QueryBuilder(subquery, m_group, m_schema) .add_memberwise_equality_constraint(ColumnReference(subquery, m_group, m_schema), obj); if (operatorType == NSEqualToPredicateOperatorType) { m_query.and_query(column.resolve(std::move(subquery)).count() > 0); } else { // This strange condition is because "ANY list != x" isn't // "NONE list == x"; there must be an object in the list for // this to match m_query.and_query(column.resolve().count() > 0 && column.resolve(std::move(subquery)).count() == 0); } } else { if (operatorType == NSNotEqualToPredicateOperatorType) { m_query.Not(); } m_query.group(); add_memberwise_equality_constraint(column, obj); m_query.end_group(); } return; } if (!obj->_row.is_valid()) { // Unmanaged or deleted objects are not equal to any managed objects. // For arrays this effectively checks if there are any objects in the // array, while for links it's just always constant true or false // (for != and = respectively). if (column.has_any_to_many_links() || column.property().collection) { add_link_constraint(operatorType, column, null()); } else if (operatorType == NSEqualToPredicateOperatorType) { m_query.and_query(std::unique_ptr(new FalseExpression)); } else { m_query.and_query(std::unique_ptr(new TrueExpression)); } } else { if (column.property().dictionary) { add_bool_constraint(RLMPropertyTypeObject, operatorType, column.resolve(), obj->_row); } else { add_bool_constraint(RLMPropertyTypeObject, operatorType, column.resolve(), obj->_row); } } } void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& column, realm::null) { if (column.property().dictionary) { add_bool_constraint(RLMPropertyTypeObject, operatorType, column.resolve(), null()); } else { add_bool_constraint(RLMPropertyTypeObject, operatorType, column.resolve(), null()); } } void QueryBuilder::add_link_constraint(NSPredicateOperatorType operatorType, const ColumnReference& a, const ColumnReference& b) { if (a.property().dictionary) { add_bool_constraint(RLMPropertyTypeObject, operatorType, a.resolve(), b.resolve()); } else { add_bool_constraint(RLMPropertyTypeObject, operatorType, a.resolve(), b.resolve()); } } #pragma mark Geospatial void QueryBuilder::add_within_constraint(const ColumnReference& column, id geospatial) { auto geoQuery = column.resolve().geo_within(geospatial.geoSpatial); m_query.and_query(std::move(geoQuery)); } // iterate over an array of subpredicates, using @func to build a query from each // one and ORing them together template void process_or_group(Query &query, id array, Func&& func) { array = RLMAsFastEnumeration(array); RLMPrecondition(array, @"Invalid value", @"IN clause requires an array of items"); query.group(); bool first = true; for (id item in array) { if (!first) { query.Or(); } first = false; func(item); } if (first) { // Queries can't be empty, so if there's zero things in the OR group // validation will fail. Work around this by adding an expression which // will never find any rows in a table. query.and_query(std::unique_ptr(new FalseExpression)); } query.end_group(); } #pragma mark Conversion Helpers template realm::null value_of_type(realm::null) { return realm::null(); } template auto value_of_type(__unsafe_unretained const id value) { return RLMStatelessAccessorContext::unbox(value); } template <> auto value_of_type(id value) { return RLMObjcToMixed(value, nil, CreatePolicy::Skip); } template auto value_of_type(const ColumnReference& column) { return column.resolve(); } template void convert_null(T&& value, Fn&& fn) { if (isNSNull(value)) { fn(null()); } else { fn(std::forward(value)); } } template typename W, typename T> void QueryBuilder::do_add_constraint(RLMPropertyType type, NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, ColumnReference const& column, T&& value) { switch (type) { case RLMPropertyTypeBool: convert_null(value, [&](auto&& value) { add_bool_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeObjectId: convert_null(value, [&](auto&& value) { add_bool_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeDate: convert_null(value, [&](auto&& value) { add_numeric_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeDouble: convert_null(value, [&](auto&& value) { add_numeric_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeFloat: convert_null(value, [&](auto&& value) { add_numeric_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeInt: convert_null(value, [&](auto&& value) { add_numeric_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeDecimal128: convert_null(value, [&](auto&& value) { add_numeric_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeString: add_string_constraint(operatorType, predicateOptions, column.resolve>(), value_of_type(value)); break; case RLMPropertyTypeData: add_string_constraint(operatorType, predicateOptions, column.resolve>(), value_of_type(value)); break; case RLMPropertyTypeObject: case RLMPropertyTypeLinkingObjects: convert_null(value, [&](auto&& value) { add_link_constraint(operatorType, column, value); }); break; case RLMPropertyTypeUUID: convert_null(value, [&](auto&& value) { add_bool_constraint(type, operatorType, column.resolve>(), value_of_type(value)); }); break; case RLMPropertyTypeAny: convert_null(value, [&](auto&& value) { add_mixed_constraint(operatorType, predicateOptions, column.resolve>(), value); }); } } #pragma mark Mixed Constraints template void QueryBuilder::add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, Columns&& column, T value) { // Handle cases where a string might be '1' or '0'. Without this the string // will be boxed as a bool and thus string query operations will crash in core. if constexpr(std::is_same_v) { if (auto str = RLMDynamicCast(value)) { add_string_constraint(operatorType, predicateOptions, std::move(column), realm::Mixed([str UTF8String])); return; } } do_add_mixed_constraint(operatorType, predicateOptions, std::move(column), value_of_type(value)); } template void QueryBuilder::do_add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, Columns&& column, Mixed&& value) { switch (operatorType) { case NSLessThanPredicateOperatorType: m_query.and_query(column < value); break; case NSLessThanOrEqualToPredicateOperatorType: m_query.and_query(column <= value); break; case NSGreaterThanPredicateOperatorType: m_query.and_query(column > value); break; case NSGreaterThanOrEqualToPredicateOperatorType: m_query.and_query(column >= value); break; case NSEqualToPredicateOperatorType: m_query.and_query(column == value); break; case NSNotEqualToPredicateOperatorType: m_query.and_query(column != value); break; // String comparison operators: There isn't a way for a string value // to get down here, but a rhs of NULL can case NSLikePredicateOperatorType: case NSMatchesPredicateOperatorType: case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: case NSContainsPredicateOperatorType: add_string_constraint(operatorType, predicateOptions, std::move(column), value); break; default: break; } } template void QueryBuilder::add_mixed_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions, Columns&& column, const ColumnReference& value) { add_bool_constraint(RLMPropertyTypeObject, operatorType, column, value.resolve()); } template using Identity = T; template using AlwaysDictionary = Dictionary; template void QueryBuilder::add_constraint(NSPredicateOperatorType operatorType, NSComparisonPredicateOptions predicateOptions, ColumnReference const& column, R const& rhs) { auto type = column.type(); if (column.property().array) { do_add_constraint(type, operatorType, predicateOptions, column, rhs); } else if (column.property().set) { do_add_constraint(type, operatorType, predicateOptions, column, rhs); } else if (column.property().dictionary) { do_add_constraint(type, operatorType, predicateOptions, column, rhs); } else { do_add_constraint(type, operatorType, predicateOptions, column, rhs); } } struct KeyPath { std::vector links; RLMProperty *property; CollectionOperation::Type collectionOperation; bool containsToManyRelationship; }; KeyPath key_path_from_string(RLMSchema *schema, RLMObjectSchema *objectSchema, NSString *keyPath) { RLMProperty *property; std::vector links; CollectionOperation::Type collectionOperation = CollectionOperation::None; NSString *collectionOperationName; bool keyPathContainsToManyRelationship = false; NSUInteger start = 0, length = keyPath.length, end = length; for (; end != NSNotFound; start = end + 1) { end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location; RLMPrecondition(end == NSNotFound || end + 1 < length, @"Invalid predicate", @"Invalid keypath '%@': no key name after last '.'", keyPath); RLMPrecondition(end > start, @"Invalid predicate", @"Invalid keypath '%@': no key name before '.'", keyPath); NSString *propertyName = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}]; if ([propertyName characterAtIndex:0] == '@') { if ([propertyName isEqualToString:@"@allValues"]) { RLMPrecondition(property.dictionary, @"Invalid predicate", @"Invalid keypath '%@': @allValues must follow a dictionary property.", keyPath); continue; } RLMPrecondition(collectionOperation == CollectionOperation::None, @"Invalid predicate", @"Invalid keypath '%@': at most one collection operation per keypath is supported.", keyPath); collectionOperation = CollectionOperation::type_for_name(propertyName); RLMPrecondition(collectionOperation != CollectionOperation::None, @"Invalid predicate", @"Invalid keypath '%@': Unsupported collection operation '%@'", keyPath, propertyName); RLMPrecondition(property.collection, @"Invalid predicate", @"Invalid keypath '%@': collection operation '%@' must be applied to a collection", keyPath, propertyName); switch (collectionOperation) { case CollectionOperation::None: REALM_UNREACHABLE(); case CollectionOperation::Count: RLMPrecondition(end == NSNotFound, @"Invalid predicate", @"Invalid keypath '%@': @count must appear at the end of a keypath.", keyPath); break; case CollectionOperation::AllKeys: RLMPrecondition(end == NSNotFound, @"Invalid predicate", @"Invalid keypath '%@': @allKeys must appear at the end of a keypath.", keyPath); RLMPrecondition(property.dictionary, @"Invalid predicate", @"Invalid keypath '%@': @allKeys must follow a dictionary property.", keyPath); break; default: if (propertyTypeIsLink(property.type)) { RLMPrecondition(end != NSNotFound, @"Invalid predicate", @"Invalid keypath '%@': %@ on a collection of objects cannot appear at the end of a keypath.", keyPath, propertyName); } else { RLMPrecondition(end == NSNotFound, @"Invalid predicate", @"Invalid keypath '%@': %@ on a collection of values must appear at the end of a keypath.", keyPath, propertyName); RLMPrecondition(propertyTypeIsNumeric(property.type), @"Invalid predicate", @"Invalid keypath '%@': %@ can only be applied to a collection of numeric values.", keyPath, propertyName); } } collectionOperationName = propertyName; continue; } RLMPrecondition(objectSchema, @"Invalid predicate", @"Invalid keypath '%@': %@ property %@ can only be followed by a collection operation.", keyPath, property.typeName, property.name); property = objectSchema[propertyName]; RLMPrecondition(property, @"Invalid predicate", @"Invalid keypath '%@': Property '%@' not found in object of type '%@'", keyPath, propertyName, objectSchema.className); RLMPrecondition(collectionOperation == CollectionOperation::None || (propertyTypeIsNumeric(property.type) && !property.collection), @"Invalid predicate", @"Invalid keypath '%@': %@ must be followed by a numeric property.", keyPath, collectionOperationName); if (property.collection) keyPathContainsToManyRelationship = true; links.push_back(property); if (end != NSNotFound) { RLMPrecondition(property.type == RLMPropertyTypeObject || property.type == RLMPropertyTypeLinkingObjects || property.collection, @"Invalid predicate", @"Invalid keypath '%@': Property '%@.%@' is not a link or collection and can only appear at the end of a keypath.", keyPath, objectSchema.className, propertyName); objectSchema = property.objectClassName ? schema[property.objectClassName] : nil; } }; links.pop_back(); return {std::move(links), property, collectionOperation, keyPathContainsToManyRelationship}; } ColumnReference QueryBuilder::column_reference_from_key_path(KeyPath&& kp, bool isAggregate) { if (isAggregate && !kp.containsToManyRelationship && kp.property.type != RLMPropertyTypeAny) { throwException(@"Invalid predicate", @"Aggregate operations can only be used on key paths that include an collection property"); } else if (!isAggregate && kp.containsToManyRelationship) { throwException(@"Invalid predicate", @"Key paths that include a collection property must use aggregate operations"); } return ColumnReference(m_query, m_group, m_schema, kp.property, std::move(kp.links)); } #pragma mark Collection Operations template auto collection_operation_expr_2(Column&& column) { if constexpr (OperationType == CollectionOperation::Minimum) { return column.min(); } else if constexpr (OperationType == CollectionOperation::Maximum) { return column.max(); } else if constexpr (OperationType == CollectionOperation::Sum) { return column.sum(); } else if constexpr (OperationType == CollectionOperation::Average) { return column.average(); } else { static_assert(AlwaysFalse>::value, "invalid operation type"); } } template auto collection_operation_expr(CollectionOperation operation) { REALM_ASSERT(operation.type() == OperationType); if constexpr (IsLinkCollection) { auto&& resolved = operation.link_column().resolve(); auto col = operation.column().column(); return collection_operation_expr_2(resolved.template column(col)); } else if constexpr (IsDictionary) { return collection_operation_expr_2(operation.link_column().resolve()); } else { return collection_operation_expr_2(operation.link_column().resolve>()); } } template void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType, CollectionOperation const& collectionOperation, R&& rhs, NSComparisonPredicateOptions) { auto type = IsLinkCollection ? collectionOperation.column().type() : collectionOperation.link_column().type(); switch (type) { case RLMPropertyTypeInt: add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); break; case RLMPropertyTypeFloat: add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); break; case RLMPropertyTypeDouble: add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); break; case RLMPropertyTypeDecimal128: add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); break; case RLMPropertyTypeDate: if constexpr (Operation == CollectionOperation::Sum || Operation == CollectionOperation::Average) { throwException(@"Unsupported predicate value type", @"Cannot sum or average date properties"); } else { add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); } break; case RLMPropertyTypeAny: add_numeric_constraint(type, operatorType, collection_operation_expr(collectionOperation), value_of_type(rhs)); break; default: REALM_ASSERT(false && "Only numeric property types should hit this path."); } } template void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType, CollectionOperation const& collectionOperation, R&& rhs, NSComparisonPredicateOptions options) { convert_null(std::forward(rhs), [&](T&& rhs) { if (collectionOperation.link_column().is_link()) { add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), options); } else if (collectionOperation.column().property().dictionary) { add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), options); } else { add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), options); } }); } template void get_collection_type(__unsafe_unretained RLMProperty *prop, Fn&& fn) { if (prop.array) { fn((Lst*)0); } else if (prop.set) { fn((Set*)0); } else { fn((Dictionary*)0); } } template void QueryBuilder::add_collection_operation_constraint(NSPredicateOperatorType operatorType, CollectionOperation const& collectionOperation, R&& rhs, NSComparisonPredicateOptions comparisonOptions) { switch (collectionOperation.type()) { case CollectionOperation::None: break; case CollectionOperation::Count: { auto& column = collectionOperation.link_column(); RLMPropertyType type = column.type(); auto rhsValue = value_of_type(rhs); auto continuation = [&](T *) { add_numeric_constraint(type, operatorType, column.resolve().size(), rhsValue); }; switch (type) { case RLMPropertyTypeBool: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeObjectId: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeDate: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeDouble: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeFloat: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeInt: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeDecimal128: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeString: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeData: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeUUID: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeAny: return get_collection_type(column.property(), std::move(continuation)); case RLMPropertyTypeObject: case RLMPropertyTypeLinkingObjects: return add_numeric_constraint(type, operatorType, column.resolve().count(), rhsValue); } } case CollectionOperation::Minimum: add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), comparisonOptions); break; case CollectionOperation::Maximum: add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), comparisonOptions); break; case CollectionOperation::Sum: add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), comparisonOptions); break; case CollectionOperation::Average: add_collection_operation_constraint(operatorType, collectionOperation, std::forward(rhs), comparisonOptions); break; case CollectionOperation::AllKeys: { // BETWEEN and IN are not supported by @allKeys as the parsing for collection // operators happens before and disection of a rhs array of values. add_string_constraint(operatorType, comparisonOptions, Columns(collectionOperation.link_column().column(), m_query.get_table()).keys(), value_of_type(rhs)); break; } } } bool key_path_contains_collection_operator(const KeyPath& kp) { return kp.collectionOperation != CollectionOperation::None; } CollectionOperation QueryBuilder::collection_operation_from_key_path(KeyPath&& kp) { // Collection operations can either come at the end, or immediately before // the last property. Count and AllKeys are always the end, while // min/max/sum/avg are at the end for collections of primitives and one // before the end for collections of objects (with the aggregate done on a // property of those objects). For one-before-the-end we need to construct // a KeyPath to both the final link and the final property. KeyPath linkPrefix = kp; if (kp.collectionOperation != CollectionOperation::Count && kp.collectionOperation != CollectionOperation::AllKeys && !kp.property.collection) { REALM_ASSERT(!kp.links.empty()); linkPrefix.property = linkPrefix.links.back(); linkPrefix.links.pop_back(); } return CollectionOperation(kp.collectionOperation, column_reference_from_key_path(std::move(linkPrefix), true), column_reference_from_key_path(std::move(kp), true)); } NSPredicateOperatorType invert_comparison_operator(NSPredicateOperatorType type) { switch (type) { case NSLessThanPredicateOperatorType: return NSGreaterThanPredicateOperatorType; case NSLessThanOrEqualToPredicateOperatorType: return NSGreaterThanOrEqualToPredicateOperatorType; case NSGreaterThanPredicateOperatorType: return NSLessThanPredicateOperatorType; case NSGreaterThanOrEqualToPredicateOperatorType: return NSLessThanOrEqualToPredicateOperatorType; case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: case NSContainsPredicateOperatorType: case NSLikePredicateOperatorType: throwException(@"Unsupported predicate", @"Operator '%@' requires a keypath on the left side.", operatorName(type)); default: return type; } } void QueryBuilder::apply_collection_operator_expression(KeyPath&& kp, id value, NSComparisonPredicate *pred) { CollectionOperation operation = collection_operation_from_key_path(std::move(kp)); operation.validate_comparison(value); auto type = pred.predicateOperatorType; if (pred.leftExpression.expressionType != NSKeyPathExpressionType) { // Turn "a > b" into "b < a" so that we can always put the column on the lhs type = invert_comparison_operator(type); } add_collection_operation_constraint(type, operation, value, pred.options); } void QueryBuilder::apply_value_expression(KeyPath&& kp, id value, NSComparisonPredicate *pred) { if (key_path_contains_collection_operator(kp)) { apply_collection_operator_expression(std::move(kp), value, pred); return; } bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier; ColumnReference column = column_reference_from_key_path(std::move(kp), isAny); // check to see if this is a between query if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) { add_between_constraint(std::move(column), value); return; } if (pred.predicateOperatorType == NSInPredicateOperatorType) { if ([value conformsToProtocol:@protocol(RLMGeospatial)]) { // In case of `IN` check if the value is a Geo-shape, create a `geoWithin` query add_within_constraint(std::move(column), value); } else { // turn "key.path IN collection" into ored together ==. "collection IN key.path" is handled elsewhere. process_or_group(m_query, value, [&](id item) { id normalized = value_from_constant_expression_or_value(item); column.validate_comparison(normalized); add_constraint(NSEqualToPredicateOperatorType, pred.options, column, normalized); }); } return; } column.validate_comparison(value); if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { add_constraint(pred.predicateOperatorType, pred.options, std::move(column), value); } else { add_constraint(invert_comparison_operator(pred.predicateOperatorType), pred.options, std::move(column), value); } } void QueryBuilder::apply_column_expression(KeyPath&& leftKeyPath, KeyPath&& rightKeyPath, NSComparisonPredicate *predicate) { bool left_key_path_contains_collection_operator = key_path_contains_collection_operator(leftKeyPath); bool right_key_path_contains_collection_operator = key_path_contains_collection_operator(rightKeyPath); if (left_key_path_contains_collection_operator && right_key_path_contains_collection_operator) { throwException(@"Unsupported predicate", @"Key paths including aggregate operations cannot be compared with other aggregate operations."); } if (left_key_path_contains_collection_operator) { CollectionOperation left = collection_operation_from_key_path(std::move(leftKeyPath)); ColumnReference right = column_reference_from_key_path(std::move(rightKeyPath), false); left.validate_comparison(right); add_collection_operation_constraint(predicate.predicateOperatorType, std::move(left), std::move(right), predicate.options); return; } if (right_key_path_contains_collection_operator) { ColumnReference left = column_reference_from_key_path(std::move(leftKeyPath), false); CollectionOperation right = collection_operation_from_key_path(std::move(rightKeyPath)); right.validate_comparison(left); add_collection_operation_constraint(invert_comparison_operator(predicate.predicateOperatorType), std::move(right), std::move(left), predicate.options); return; } bool isAny = false; ColumnReference left = column_reference_from_key_path(std::move(leftKeyPath), isAny); ColumnReference right = column_reference_from_key_path(std::move(rightKeyPath), isAny); // NOTE: It's assumed that column type must match and no automatic type conversion is supported. RLMPrecondition(left.type() == right.type(), RLMPropertiesComparisonTypeMismatchException, RLMPropertiesComparisonTypeMismatchReason, RLMTypeToString(left.type()), RLMTypeToString(right.type())); // TODO: Should we handle special case where left row is the same as right row (tautology) add_constraint(predicate.predicateOperatorType, predicate.options, std::move(left), std::move(right)); } // Identify expressions of the form [SELF valueForKeyPath:] bool is_self_value_for_key_path_function_expression(NSExpression *expression) { if (expression.expressionType != NSFunctionExpressionType) return false; if (expression.operand.expressionType != NSEvaluatedObjectExpressionType) return false; return [expression.function isEqualToString:@"valueForKeyPath:"]; } // -[NSPredicate predicateWithSubtitutionVariables:] results in function expressions of the form [SELF valueForKeyPath:] // that apply_predicate cannot handle. Replace such expressions with equivalent NSKeyPathExpressionType expressions. NSExpression *simplify_self_value_for_key_path_function_expression(NSExpression *expression) { if (is_self_value_for_key_path_function_expression(expression)) { if (NSString *keyPath = [expression.arguments.firstObject keyPath]) { return [NSExpression expressionForKeyPath:keyPath]; } } return expression; } void QueryBuilder::apply_map_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, NSComparisonPredicateOptions options, NSPredicateOperatorType operatorType, NSExpression *right) { std::vector pathElements; NSString *keyPath = get_path_elements(pathElements, functionExpression); ColumnReference collectionColumn = column_reference_from_key_path(key_path_from_string(m_schema, objectSchema, keyPath), true); if (collectionColumn.property().type == RLMPropertyTypeAny && !collectionColumn.property().dictionary) { add_mixed_constraint(operatorType, options, std::move(collectionColumn.resolve().path(pathElements)), right.constantValue); } else { RLMPrecondition(collectionColumn.property().dictionary, @"Invalid predicate", @"Invalid keypath '%@': only dictionaries and realm `Any` support subscript predicates.", functionExpression); RLMPrecondition(pathElements.size() == 1, @"Invalid subscript size", @"Invalid subscript size '%@': nested dictionaries queries are only allowed in mixed properties.", functionExpression); RLMPrecondition(pathElements[0].is_key(), @"Invalid subscript type", @"Invalid subscript type '%@'; only string keys are allowed as subscripts in dictionary queries.", functionExpression); add_mixed_constraint(operatorType, options, std::move(collectionColumn.resolve().key(pathElements[0].get_key())), right.constantValue); } } void QueryBuilder::apply_function_expression(RLMObjectSchema *objectSchema, NSExpression *functionExpression, NSPredicateOperatorType operatorType, NSExpression *right) { RLMPrecondition(functionExpression.operand.expressionType == NSSubqueryExpressionType, @"Invalid predicate", @"The '%@' function is not supported.", functionExpression.function); RLMPrecondition([functionExpression.function isEqualToString:@"valueForKeyPath:"] && functionExpression.arguments.count == 1, @"Invalid predicate", @"The '%@' function is not supported on the result of a SUBQUERY.", functionExpression.function); NSExpression *keyPathExpression = functionExpression.arguments.firstObject; RLMPrecondition([keyPathExpression.keyPath isEqualToString:@"@count"], @"Invalid predicate", @"SUBQUERY is only supported when immediately followed by .@count that is compared with a constant number."); RLMPrecondition(right.expressionType == NSConstantValueExpressionType && [right.constantValue isKindOfClass:[NSNumber class]], @"Invalid predicate expression", @"SUBQUERY(…).@count is only supported when compared with a constant number."); NSExpression *subqueryExpression = functionExpression.operand; int64_t value = [right.constantValue integerValue]; ColumnReference collectionColumn = column_reference_from_key_path(key_path_from_string(m_schema, objectSchema, [subqueryExpression.collection keyPath]), true); RLMObjectSchema *collectionMemberObjectSchema = m_schema[collectionColumn.property().objectClassName]; // Eliminate references to the iteration variable in the subquery. NSPredicate *subqueryPredicate = [subqueryExpression.predicate predicateWithSubstitutionVariables:@{subqueryExpression.variable: [NSExpression expressionForEvaluatedObject]}]; subqueryPredicate = transformPredicate(subqueryPredicate, simplify_self_value_for_key_path_function_expression); Query subquery = RLMPredicateToQuery(subqueryPredicate, collectionMemberObjectSchema, m_schema, m_group); add_numeric_constraint(RLMPropertyTypeInt, operatorType, collectionColumn.resolve(std::move(subquery)).count(), value); } void QueryBuilder::apply_predicate(NSPredicate *predicate, RLMObjectSchema *objectSchema) { // Compound predicates. if ([predicate isMemberOfClass:[NSCompoundPredicate class]]) { NSCompoundPredicate *comp = (NSCompoundPredicate *)predicate; switch ([comp compoundPredicateType]) { case NSAndPredicateType: if (comp.subpredicates.count) { // Add all of the subpredicates. m_query.group(); for (NSPredicate *subp in comp.subpredicates) { apply_predicate(subp, objectSchema); } m_query.end_group(); } else { // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE. m_query.and_query(std::unique_ptr(new TrueExpression)); } break; case NSOrPredicateType: { // Add all of the subpredicates with ors inbetween. process_or_group(m_query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) { apply_predicate(subp, objectSchema); }); break; } case NSNotPredicateType: // Add the negated subpredicate m_query.Not(); apply_predicate(comp.subpredicates.firstObject, objectSchema); break; default: // Not actually possible short of users making their own weird // broken subclass of NSPredicate throwException(@"Invalid compound predicate type", @"Only AND, OR, and NOT compound predicates are supported"); } } else if ([predicate isMemberOfClass:[NSComparisonPredicate class]]) { NSComparisonPredicate *compp = (NSComparisonPredicate *)predicate; RLMPrecondition(compp.comparisonPredicateModifier != NSAllPredicateModifier, @"Invalid predicate", @"ALL modifier not supported"); NSExpressionType exp1Type = compp.leftExpression.expressionType; NSExpressionType exp2Type = compp.rightExpression.expressionType; if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) { // Inserting an array via %@ gives NSConstantValueExpressionType, but including it directly gives NSAggregateExpressionType if (exp1Type == NSKeyPathExpressionType && (exp2Type == NSAggregateExpressionType || exp2Type == NSConstantValueExpressionType)) { // "key.path IN %@", "key.path IN {…}", "key.path BETWEEN %@", or "key.path BETWEEN {…}". exp2Type = NSConstantValueExpressionType; } else if (compp.predicateOperatorType == NSInPredicateOperatorType && exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { // "%@ IN key.path" is equivalent to "ANY key.path IN %@". Rewrite the former into the latter. compp = [NSComparisonPredicate predicateWithLeftExpression:compp.rightExpression rightExpression:compp.leftExpression modifier:NSAnyPredicateModifier type:NSEqualToPredicateOperatorType options:0]; exp1Type = NSKeyPathExpressionType; exp2Type = NSConstantValueExpressionType; } else { if (compp.predicateOperatorType == NSBetweenPredicateOperatorType) { throwException(@"Invalid predicate", @"Predicate with BETWEEN operator must compare a KeyPath with an aggregate with two values"); } else if (compp.predicateOperatorType == NSInPredicateOperatorType) { throwException(@"Invalid predicate", @"Predicate with IN operator must compare a KeyPath with an aggregate"); } } } if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) { // both expression are KeyPaths apply_column_expression(key_path_from_string(m_schema, objectSchema, compp.leftExpression.keyPath), key_path_from_string(m_schema, objectSchema, compp.rightExpression.keyPath), compp); } else if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) { // comparing keypath to value apply_value_expression(key_path_from_string(m_schema, objectSchema, compp.leftExpression.keyPath), compp.rightExpression.constantValue, compp); } else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { // comparing value to keypath apply_value_expression(key_path_from_string(m_schema, objectSchema, compp.rightExpression.keyPath), compp.leftExpression.constantValue, compp); } else if (exp1Type == NSFunctionExpressionType) { if (compp.leftExpression.operand.expressionType == NSSubqueryExpressionType) { apply_function_expression(objectSchema, compp.leftExpression, compp.predicateOperatorType, compp.rightExpression); } else { apply_map_expression(objectSchema, compp.leftExpression, compp.options, compp.predicateOperatorType, compp.rightExpression); } } else if (exp1Type == NSSubqueryExpressionType) { // The subquery expressions that we support are handled by the NSFunctionExpressionType case above. throwException(@"Invalid predicate expression", @"SUBQUERY is only supported when immediately followed by .@count."); } else { throwException(@"Invalid predicate expressions", @"Predicate expressions must compare a keypath and another keypath or a constant value"); } } else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) { m_query.and_query(std::unique_ptr(new TrueExpression)); } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) { m_query.and_query(std::unique_ptr(new FalseExpression)); } else { // invalid predicate type throwException(@"Invalid predicate", @"Only support compound, comparison, and constant predicates"); } } // This function returns the nested subscripts from a NSPredicate with the following format `anyCol[0]['key'][#any]` // and its respective keypath (including any linked keypath) // This will iterate each argument of the NSExpression and its nested NSExpressions, takes the constant subscript // and creates a PathElement to be used in the query. If we use `#any` as a wildcard this will show in the parser // predicate as NSKeyPathExpressionType. NSString* QueryBuilder::get_path_elements(std::vector &paths, NSExpression *expression) { NSString *keyPath = @""; for (NSUInteger i = 0; i < expression.arguments.count; i++) { NSString *nestedKeyPath = @""; if (expression.arguments[i].expressionType == NSFunctionExpressionType) { nestedKeyPath = get_path_elements(paths, expression.arguments[i]); } else if (expression.arguments[i].expressionType == NSConstantValueExpressionType) { id value = [expression.arguments[i] constantValue]; if ([value isKindOfClass:[NSNumber class]]) { paths.push_back(PathElement{[(NSNumber *)value intValue]}); } else if ([value isKindOfClass:[NSString class]]) { NSString *key = (NSString *)value; paths.push_back(PathElement{key.UTF8String}); } else { throwException(@"Invalid subscript type", @"Invalid subscript type '%@': Only `Strings` or index are allowed subscripts", expression); } } else if (expression.arguments[i].expressionType == NSKeyPathExpressionType) { auto keyPath = [(id)expression.arguments[i] predicateFormat]; if ([keyPath isEqual:@"#any"]) { paths.emplace_back(); } else { nestedKeyPath = keyPath; } } else { throwException(@"Invalid expression type", @"Invalid expression type '%@': Subscripts queries don't allow any other expression types", expression); } if ([nestedKeyPath length] > 0) { keyPath = ([keyPath length] > 0) ? [NSString stringWithFormat:@"%@.%@", keyPath, nestedKeyPath] : nestedKeyPath; } } return keyPath; } } // namespace realm::Query RLMPredicateToQuery(NSPredicate *predicate, RLMObjectSchema *objectSchema, RLMSchema *schema, Group &group) { auto query = get_table(group, objectSchema).where(); // passing a nil predicate is a no-op if (!predicate) { return query; } try { @autoreleasepool { QueryBuilder(query, group, schema).apply_predicate(predicate, objectSchema); } } catch (std::exception const& e) { @throw RLMException(e); } return query; } // return the property for a validated column name RLMProperty *RLMValidatedProperty(RLMObjectSchema *desc, NSString *columnName) { RLMProperty *prop = desc[columnName]; RLMPrecondition(prop, @"Invalid property name", @"Property '%@' not found in object of type '%@'", columnName, desc.className); return prop; } ================================================ FILE: Realm/RLMRealm.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @class RLMRealmConfiguration, RLMRealm, RLMObject, RLMSchema, RLMMigration, RLMNotificationToken, RLMThreadSafeReference, RLMAsyncOpenTask; /** A callback block for opening Realms asynchronously. Returns the Realm if the open was successful, or an error otherwise. */ typedef void(^RLMAsyncOpenRealmCallback)(RLMRealm * _Nullable realm, NSError * _Nullable error); /// The Id of the asynchronous transaction. typedef unsigned RLMAsyncTransactionId; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** An `RLMRealm` instance (also referred to as "a Realm") represents a Realm database. Realms can either be stored on disk (see `+[RLMRealm realmWithURL:]`) or in memory (see `RLMRealmConfiguration`). `RLMRealm` instances are cached internally, and constructing equivalent `RLMRealm` objects (for example, by using the same path or identifier) multiple times on a single thread within a single iteration of the run loop will normally return the same `RLMRealm` object. If you specifically want to ensure an `RLMRealm` instance is destroyed (for example, if you wish to open a Realm, check some property, and then possibly delete the Realm file and re-open it), place the code which uses the Realm within an `@autoreleasepool {}` and ensure you have no other strong references to it. @warning Non-frozen `RLMRealm` instances are thread-confined and cannot be shared across threads or dispatch queues. Trying to do so will cause an exception to be thrown. You must call this method on each thread you want to interact with the Realm on. For dispatch queues, this means that you must call it in each block which is dispatched, as a queue is not guaranteed to run all of its blocks on the same thread. */ @interface RLMRealm : NSObject #pragma mark - Creating & Initializing a Realm /** Obtains an instance of the default Realm. The default Realm is used by the `RLMObject` class methods which do not take an `RLMRealm` parameter, but is otherwise not special. The default Realm is persisted as *default.realm* under the *Documents* directory of your Application on iOS, in your application's *Application Support* directory on macOS, and in the *Cache* directory on tvOS. The default Realm is created using the default `RLMRealmConfiguration`, which can be changed via `+[RLMRealmConfiguration setDefaultConfiguration:]`. @return The default `RLMRealm` instance for the current thread. */ + (instancetype)defaultRealm; /** Obtains an instance of the default Realm bound to the given queue. Rather than being confined to the thread they are opened on, queue-bound RLMRealms are confined to the given queue. They can be accessed from any thread as long as it is from within a block dispatch to the queue, and notifications will be delivered to the queue instead of a thread's run loop. Realms can only be confined to a serial queue. Queue-confined RLMRealm instances can be obtained when not on that queue, but attempting to do anything with that instance without first dispatching to the queue will throw an incorrect thread exception. The default Realm is created using the default `RLMRealmConfiguration`, which can be changed via `+[RLMRealmConfiguration setDefaultConfiguration:]`. @param queue A serial dispatch queue to confine the Realm to. @return The default `RLMRealm` instance for the given queue. */ + (instancetype)defaultRealmForQueue:(dispatch_queue_t)queue; /** Obtains an `RLMRealm` instance with the given configuration. @param configuration A configuration object to use when creating the Realm. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return An `RLMRealm` instance. */ + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error NS_RETURNS_RETAINED; /** Obtains an `RLMRealm` instance with the given configuration bound to the given queue. Rather than being confined to the thread they are opened on, queue-bound RLMRealms are confined to the given queue. They can be accessed from any thread as long as it is from within a block dispatch to the queue, and notifications will be delivered to the queue instead of a thread's run loop. Realms can only be confined to a serial queue. Queue-confined RLMRealm instances can be obtained when not on that queue, but attempting to do anything with that instance without first dispatching to the queue will throw an incorrect thread exception. @param configuration A configuration object to use when creating the Realm. @param queue A serial dispatch queue to confine the Realm to. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return An `RLMRealm` instance. */ + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration queue:(nullable dispatch_queue_t)queue error:(NSError **)error NS_RETURNS_RETAINED; /** Obtains an `RLMRealm` instance persisted at a specified file URL. @param fileURL The local URL of the file the Realm should be saved at. @return An `RLMRealm` instance. */ + (instancetype)realmWithURL:(NSURL *)fileURL NS_RETURNS_RETAINED; /** Asynchronously open a Realm and deliver it to a block on the given queue. Opening a Realm asynchronously will perform all work needed to get the Realm to a usable state (such as running potentially time-consuming migrations) on a background thread before dispatching to the given queue. In addition, synchronized Realms wait for all remote content available at the time the operation began to be downloaded and available locally. The Realm passed to the callback function is confined to the callback queue as if `-[RLMRealm realmWithConfiguration:queue:error]` was used. @param configuration A configuration object to use when opening the Realm. @param callbackQueue The serial dispatch queue on which the callback should be run. @param callback A callback block. If the Realm was successfully opened, it will be passed in as an argument. Otherwise, an `NSError` describing what went wrong will be passed to the block instead. */ + (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration callbackQueue:(dispatch_queue_t)callbackQueue callback:(RLMAsyncOpenRealmCallback)callback; /** The `RLMSchema` used by the Realm. */ @property (nonatomic, readonly) RLMSchema *schema; /** Indicates if the Realm is currently engaged in a write transaction. @warning Do not simply check this property and then start a write transaction whenever an object needs to be created, updated, or removed. Doing so might cause a large number of write transactions to be created, degrading performance. Instead, always prefer performing multiple updates during a single transaction. */ @property (nonatomic, readonly) BOOL inWriteTransaction; /** The `RLMRealmConfiguration` object that was used to create this `RLMRealm` instance. */ @property (nonatomic, readonly) RLMRealmConfiguration *configuration; /** Indicates if this Realm contains any objects. */ @property (nonatomic, readonly) BOOL isEmpty; /** Indicates if this Realm is frozen. @see `-[RLMRealm freeze]` */ @property (nonatomic, readonly, getter=isFrozen) BOOL frozen; /** Returns a frozen (immutable) snapshot of this Realm. A frozen Realm is an immutable snapshot view of a particular version of a Realm's data. Unlike normal RLMRealm instances, it does not live-update to reflect writes made to the Realm, and can be accessed from any thread. Writing to a frozen Realm is not allowed, and attempting to begin a write transaction will throw an exception. All objects and collections read from a frozen Realm will also be frozen. */ - (RLMRealm *)freeze NS_RETURNS_RETAINED; /** Returns a live reference of this Realm. All objects and collections read from the returned Realm will no longer be frozen. This method will return `self` if it is not already frozen. */ - (RLMRealm *)thaw; #pragma mark - File Management /** Writes a compacted and optionally encrypted copy of the Realm to the given local URL. The destination file cannot already exist. Note that if this method is called from within a write transaction, the *current* data is written, not the data from the point when the previous write transaction was committed. @param fileURL Local URL to save the Realm to. @param key Optional 64-byte encryption key to encrypt the new file with. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return `YES` if the Realm was successfully written to disk, `NO` if an error occurred. */ - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error; /** Writes a copy of the Realm to a given location specified by a given configuration. The destination file cannot already exist. @param configuration A Realm Configuration. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return `YES` if the Realm was successfully written to disk, `NO` if an error occurred. */ - (BOOL)writeCopyForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; /** Checks if the Realm file for the given configuration exists locally on disk. For non-synchronized, non-in-memory Realms, this is equivalent to `-[NSFileManager.defaultManager fileExistsAtPath:config.path]`. For synchronized Realms, it takes care of computing the actual path on disk based on the server, virtual path, and user as is done when opening the Realm. @param config A Realm configuration to check the existence of. @return YES if the Realm file for the given configuration exists on disk, NO otherwise. */ + (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config; /** Deletes the local Realm file and associated temporary files for the given configuration. This deletes the ".realm", ".note" and ".management" files which would be created by opening the Realm with the given configuration. It does not delete the ".lock" file (which contains no persisted data and is recreated from scratch every time the Realm file is opened). The Realm must not be currently open on any thread or in another process. If it is, this will return NO and report the error RLMErrorAlreadyOpen. Attempting to open the Realm on another thread while the deletion is happening will block (and then create a new Realm and open that afterwards). If the Realm already does not exist this will return `NO` and report the error NSFileNoSuchFileError; @param config A Realm configuration identifying the Realm to be deleted. @return YES if any files were deleted, NO otherwise. */ + (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error __attribute__((swift_error(nonnull_error))); #pragma mark - Notifications /** The type of a block to run whenever the data within the Realm is modified. @see `-[RLMRealm addNotificationBlock:]` */ typedef void (^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); #pragma mark - Receiving Notification when a Realm Changes /** Adds a notification handler for changes in this Realm, and returns a notification token. Notification handlers are called after each write transaction is committed, either on the current thread or other threads. Handler blocks are called on the same thread that they were added on, and may only be added on threads which are currently within a run loop. Unless you are specifically creating and running a run loop on a background thread, this will normally only be the main thread. The block has the following definition: typedef void(^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); It receives the following parameters: - `NSString` \***notification**: The name of the incoming notification. See `RLMRealmNotification` for information on what notifications are sent. - `RLMRealm` \***realm**: The Realm for which this notification occurred. @param block A block which is called to process Realm notifications. @return A token object which must be retained as long as you wish to continue receiving change notifications. */ - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block __attribute__((warn_unused_result)); #pragma mark - Writing to a Realm /** Begins a write transaction on the Realm. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `beginWriteTransaction` from `RLMRealm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `beginWriteTransaction` updates the `RLMRealm` instance to the latest Realm version, as if `refresh` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. It is rarely a good idea to have write transactions span multiple cycles of the run loop, but if you do wish to do so you will need to ensure that the Realm participating in the write transaction is kept alive until the write transaction is committed. */ - (void)beginWriteTransaction; /** Commits all write operations in the current write transaction, and ends the transaction. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are invoked asynchronously. If you do not want to receive a specific notification for this write tranaction, see `commitWriteTransactionWithoutNotifying:error:`. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. This version of the method throws an exception when errors occur. Use the version with a `NSError` out parameter instead if you wish to handle errors. @warning This method may only be called during a write transaction. */ - (void)commitWriteTransaction NS_SWIFT_UNAVAILABLE(""); /** Commits all write operations in the current write transaction, and ends the transaction. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are invoked asynchronously. If you do not want to receive a specific notification for this write tranaction, see `commitWriteTransactionWithoutNotifying:error:`. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. @warning This method may only be called during a write transaction. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)commitWriteTransaction:(NSError **)error; /** Commits all write operations in the current write transaction, without notifying specific notification blocks of the changes. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are scheduled to be invoked asynchronously. You can skip notifiying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this method must be for notifications for this specific `RLMRealm` instance. Notifications for different threads cannot be skipped using this method. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. @warning This method may only be called during a write transaction. @param tokens An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray *)tokens error:(NSError **)error; /** Reverts all writes made during the current write transaction and ends the transaction. This rolls back all objects in the Realm to the state they were in at the beginning of the write transaction, and then ends the transaction. This restores the data for deleted objects, but does not revive invalidated object instances. Any `RLMObject`s which were added to the Realm will be invalidated rather than becoming unmanaged. Given the following code: ObjectType *oldObject = [[ObjectType objectsWhere:@"..."] firstObject]; ObjectType *newObject = [[ObjectType alloc] init]; [realm beginWriteTransaction]; [realm addObject:newObject]; [realm deleteObject:oldObject]; [realm cancelWriteTransaction]; Both `oldObject` and `newObject` will return `YES` for `isInvalidated`, but re-running the query which provided `oldObject` will once again return the valid object. KVO observers on any objects which were modified during the transaction will be notified about the change back to their initial values, but no other notifications are produced by a cancelled write transaction. @warning This method may only be called during a write transaction. */ - (void)cancelWriteTransaction; /** Performs actions contained within the given block inside a write transaction. @see `[RLMRealm transactionWithoutNotifying:block:error:]` */ - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block NS_SWIFT_UNAVAILABLE(""); /** Performs actions contained within the given block inside a write transaction. @see `[RLMRealm transactionWithoutNotifying:block:error:]` */ - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error; /** Performs actions contained within the given block inside a write transaction. @see `[RLMRealm transactionWithoutNotifying:block:error:]` */ - (void)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block; /** Performs actions contained within the given block inside a write transaction. Write transactions cannot be nested, and trying to execute a write transaction on a Realm which is already participating in a write transaction will throw an exception. Calls to `transactionWithBlock:` from `RLMRealm` instances in other threads will block until the current write transaction completes. Before beginning the write transaction, `transactionWithBlock:` updates the `RLMRealm` instance to the latest Realm version, as if `refresh` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. You can skip notifiying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this method must be for notifications for this specific `RLMRealm` instance. Notifications for different threads cannot be skipped using this method. @param tokens An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. @param block The block containing actions to perform. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error; /** Indicates if the Realm is currently performing async write operations. This becomes YES following a call to `beginAsyncWriteTransaction`, `commitAsyncWriteTransaction`, or `asyncTransactionWithBlock:`, and remains so until all scheduled async write work has completed. @warning If this is `YES`, closing or invalidating the Realm will block until scheduled work has completed. */ @property (nonatomic, readonly) BOOL isPerformingAsynchronousWriteOperations; /** Begins an asynchronous write transaction. This function asynchronously begins a write transaction on a background thread, and then invokes the block on the original thread or queue once the transaction has begun. Unlike `beginWriteTransaction`, this does not block the calling thread if another thread is current inside a write transaction, and will always return immediately. Multiple calls to this function (or the other functions which perform asynchronous write transactions) will queue the blocks to be called in the same order as they were queued. This includes calls from inside a write transaction block, which unlike with synchronous transactions are allowed. @param block The block containing actions to perform inside the write transaction. `block` should end by calling `commitAsyncWriteTransaction`, `commitWriteTransaction` or `cancelWriteTransaction`. Returning without one of these calls is equivalent to calling `cancelWriteTransaction`. @return An id identifying the asynchronous transaction which can be passed to `cancelAsyncTransaction:` prior to the block being called to cancel the pending invocation of the block. */ - (RLMAsyncTransactionId)beginAsyncWriteTransaction:(void(^)(void))block; /** Asynchronously commits a write transaction. The call returns immediately allowing the caller to proceed while the I/O is performed on a dedicated background thread. This can be used regardless of if the write transaction was begun with `beginWriteTransaction` or `beginAsyncWriteTransaction`. @param completionBlock A block which will be called on the source thread or queue once the commit has either completed or failed with an error. @param allowGrouping If `YES`, multiple sequential calls to `commitAsyncWriteTransaction:` may be batched together and persisted to stable storage in one group. This improves write performance, particularly when the individual transactions being batched are small. In the event of a crash or power failure, either all of the grouped transactions will be lost or none will, rather than the usual guarantee that data has been persisted as soon as a call to commit has returned. @return An id identifying the asynchronous transaction commit can be passed to `cancelAsyncTransaction:` prior to the completion block being called to cancel the pending invocation of the block. Note that this does *not* cancel the commit itself. */ - (RLMAsyncTransactionId)commitAsyncWriteTransaction:(nullable void(^)(NSError *_Nullable))completionBlock allowGrouping:(BOOL)allowGrouping __attribute__((swift_async(not_swift_private, 1))) __attribute__((swift_attr("@_unsafeInheritExecutor"))); /** Asynchronously commits a write transaction. The call returns immediately allowing the caller to proceed while the I/O is performed on a dedicated background thread. This can be used regardless of if the write transaction was begun with `beginWriteTransaction` or `beginAsyncWriteTransaction`. @param completionBlock A block which will be called on the source thread or queue once the commit has either completed or failed with an error. @return An id identifying the asynchronous transaction commit can be passed to `cancelAsyncTransaction:` prior to the completion block being called to cancel the pending invocation of the block. Note that this does *not* cancel the commit itself. */ - (RLMAsyncTransactionId)commitAsyncWriteTransaction:(void(^)(NSError *_Nullable))completionBlock; /** Asynchronously commits a write transaction. The call returns immediately allowing the caller to proceed while the I/O is performed on a dedicated background thread. This can be used regardless of if the write transaction was begun with `beginWriteTransaction` or `beginAsyncWriteTransaction`. @return An id identifying the asynchronous transaction commit can be passed to `cancelAsyncTransaction:` prior to the completion block being called to cancel the pending invocation of the block. Note that this does *not* cancel the commit itself. */ - (RLMAsyncTransactionId)commitAsyncWriteTransaction; /** Cancels a queued block for an asynchronous transaction. This can cancel a block passed to either an asynchronous begin or an asynchronous commit. Canceling a begin cancels that transaction entirely, while canceling a commit merely cancels the invocation of the completion callback, and the commit will still happen. Transactions can only be canceled before the block is invoked, and calling `cancelAsyncTransaction:` from within the block is a no-op. @param asyncTransactionId A transaction id from either `beginAsyncWriteTransaction:` or `commitAsyncWriteTransaction:`. */ - (void)cancelAsyncTransaction:(RLMAsyncTransactionId)asyncTransactionId; /** Asynchronously performs actions contained within the given block inside a write transaction. The write transaction is begun asynchronously as if calling `beginAsyncWriteTransaction:`, and by default the transaction is commited asynchronously after the block completes. You can also explicitly call `commitWriteTransaction` or `cancelWriteTransaction` from within the block to synchronously commit or cancel the write transaction. @param block The block containing actions to perform. @param completionBlock A block which will be called on the source thread or queue once the commit has either completed or failed with an error. @return An id identifying the asynchronous transaction which can be passed to `cancelAsyncTransaction:` prior to the block being called to cancel the pending invocation of the block. */ - (RLMAsyncTransactionId)asyncTransactionWithBlock:(void(^)(void))block onComplete:(nullable void(^)(NSError *))completionBlock; /** Asynchronously performs actions contained within the given block inside a write transaction. The write transaction is begun asynchronously as if calling `beginAsyncWriteTransaction:`, and by default the transaction is commited asynchronously after the block completes. You can also explicitly call `commitWriteTransaction` or `cancelWriteTransaction` from within the block to synchronously commit or cancel the write transaction. @param block The block containing actions to perform. @return An id identifying the asynchronous transaction which can be passed to `cancelAsyncTransaction:` prior to the block being called to cancel the pending invocation of the block. */ - (RLMAsyncTransactionId)asyncTransactionWithBlock:(void(^)(void))block; /** Updates the Realm and outstanding objects managed by the Realm to point to the most recent data. If the version of the Realm is actually changed, Realm and collection notifications will be sent to reflect the changes. This may take some time, as collection notifications are prepared on a background thread. As a result, calling this method on the main thread is not advisable. @return Whether there were any updates for the Realm. Note that `YES` may be returned even if no data actually changed. */ - (BOOL)refresh; /** Set this property to `YES` to automatically update this Realm when changes happen in other threads. If set to `YES` (the default), changes made on other threads will be reflected in this Realm on the next cycle of the run loop after the changes are committed. If set to `NO`, you must manually call `-refresh` on the Realm to update it to get the latest data. Note that by default, background threads do not have an active run loop and you will need to manually call `-refresh` in order to update to the latest version, even if `autorefresh` is set to `YES`. Even with this property enabled, you can still call `-refresh` at any time to update the Realm before the automatic refresh would occur. Write transactions will still always advance a Realm to the latest version and produce local notifications on commit even if autorefresh is disabled. Disabling `autorefresh` on a Realm without any strong references to it will not have any effect, and `autorefresh` will revert back to `YES` the next time the Realm is created. This is normally irrelevant as it means that there is nothing to refresh (as managed `RLMObject`s, `RLMArray`s, and `RLMResults` have strong references to the Realm that manages them), but it means that setting `RLMRealm.defaultRealm.autorefresh = NO` in `application:didFinishLaunchingWithOptions:` and only later storing Realm objects will not work. Defaults to `YES`. */ @property (nonatomic) BOOL autorefresh; /** Invalidates all `RLMObject`s, `RLMResults`, `RLMLinkingObjects`, and `RLMArray`s managed by the Realm. A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need. All `RLMObject`, `RLMResults` and `RLMArray` instances obtained from this `RLMRealm` instance on the current thread are invalidated. `RLMObject`s and `RLMArray`s cannot be used. `RLMResults` will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm. Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. */ - (void)invalidate; #pragma mark - Accessing Objects /** Returns the same object as the one referenced when the `RLMThreadSafeReference` was first created, but resolved for the current Realm for this thread. Returns `nil` if this object was deleted after the reference was created. @param reference The thread-safe reference to the thread-confined object to resolve in this Realm. @warning A `RLMThreadSafeReference` object must be resolved at most once. Failing to resolve a `RLMThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. An exception will be thrown if a reference is resolved more than once. @warning Cannot call within a write transaction. @note Will refresh this Realm if the source Realm was at a later version than this one. @see `+[RLMThreadSafeReference referenceWithThreadConfined:]` */ - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference NS_REFINED_FOR_SWIFT; #pragma mark - Adding and Removing Objects from a Realm /** Adds an object to the Realm. Once added, this object is considered to be managed by the Realm. It can be retrieved using the `objectsWhere:` selectors on `RLMRealm` and on subclasses of `RLMObject`. When added, all child relationships referenced by this object will also be added to the Realm if they are not already in it. If the object or any related objects are already being managed by a different Realm an exception will be thrown. Use `-[RLMObject createInRealm:withObject:]` to insert a copy of a managed object into a different Realm. The object to be added must be valid and cannot have been previously deleted from a Realm (i.e. `isInvalidated` must be `NO`). @warning This method may only be called during a write transaction. @param object The object to be added to this Realm. */ - (void)addObject:(RLMObject *)object; /** Adds all the objects in a collection to the Realm. This is the equivalent of calling `addObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, containing Realm objects to be added to the Realm. @see `addObject:` */ - (void)addObjects:(id)objects; /** Adds or updates an existing object into the Realm. The object provided must have a designated primary key. If no objects exist in the Realm with the same primary key value, the object is inserted. Otherwise, the existing object is updated with any changed values. As with `addObject:`, the object cannot already be managed by a different Realm. Use `-[RLMObject createOrUpdateInRealm:withValue:]` to copy values to a different Realm. If there is a property or KVC value on `object` whose value is nil, and it corresponds to a nullable property on an existing object being updated, that nullable property will be set to nil. @warning This method may only be called during a write transaction. @param object The object to be added or updated. */ - (void)addOrUpdateObject:(RLMObject *)object; /** Adds or updates all the objects in a collection into the Realm. This is the equivalent of calling `addOrUpdateObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, containing Realm objects to be added to or updated within the Realm. @see `addOrUpdateObject:` */ - (void)addOrUpdateObjects:(id)objects; /** Deletes an object from the Realm. Once the object is deleted it is considered invalidated. @warning This method may only be called during a write transaction. @param object The object to be deleted. */ - (void)deleteObject:(RLMObject *)object; /** Deletes one or more objects from the Realm. This is the equivalent of calling `deleteObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param objects An enumerable collection such as `NSArray`, `RLMArray`, or `RLMResults`, containing objects to be deleted from the Realm. @see `deleteObject:` */ - (void)deleteObjects:(id)objects; /** Deletes all objects from the Realm. @warning This method may only be called during a write transaction. @see `deleteObject:` */ - (void)deleteAllObjects; #pragma mark - Migrations /** The type of a migration block used to migrate a Realm. @param migration A `RLMMigration` object used to perform the migration. The migration object allows you to enumerate and alter any existing objects which require migration. @param oldSchemaVersion The schema version of the Realm being migrated. */ NS_SWIFT_SENDABLE typedef void (^RLMMigrationBlock)(RLMMigration *migration, uint64_t oldSchemaVersion); /** Returns the schema version for a Realm at a given local URL. @param fileURL Local URL to a Realm file. @param key 64-byte key used to encrypt the file, or `nil` if it is unencrypted. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return The version of the Realm at `fileURL`, or `RLMNotVersioned` if the version cannot be read. */ + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error NS_REFINED_FOR_SWIFT; /** Performs the given Realm configuration's migration block on a Realm at the given path. This method is called automatically when opening a Realm for the first time and does not need to be called explicitly. You can choose to call this method to control exactly when and how migrations are performed. @param configuration The Realm configuration used to open and migrate the Realm. @return The error that occurred while applying the migration, if any. @see RLMMigration */ + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; #pragma mark - Unavailable Methods /** RLMRealm instances are cached internally by Realm and cannot be created directly. Use `+[RLMRealm defaultRealm]`, `+[RLMRealm realmWithConfiguration:error:]` or `+[RLMRealm realmWithURL]` to obtain a reference to an RLMRealm. */ - (instancetype)init __attribute__((unavailable("Use +defaultRealm, +realmWithConfiguration: or +realmWithURL:."))); /** RLMRealm instances are cached internally by Realm and cannot be created directly. Use `+[RLMRealm defaultRealm]`, `+[RLMRealm realmWithConfiguration:error:]` or `+[RLMRealm realmWithURL]` to obtain a reference to an RLMRealm. */ + (instancetype)new __attribute__((unavailable("Use +defaultRealm, +realmWithConfiguration: or +realmWithURL:."))); /// :nodoc: - (void)addOrUpdateObjectsFromArray:(id)array __attribute__((unavailable("Renamed to -addOrUpdateObjects:."))); @end // MARK: - RLMNotificationToken /** A token which is returned from methods which subscribe to changes to a Realm. Change subscriptions in Realm return an `RLMNotificationToken` instance, which can be used to unsubscribe from the changes. You must store a strong reference to the token for as long as you want to continue to receive notifications. When you wish to stop, call the `-invalidate` method. Notifications are also stopped if the token is deallocated. */ NS_SWIFT_SENDABLE // is internally thread-safe @interface RLMNotificationToken : NSObject /// Stops notifications for the change subscription that returned this token. /// /// @return True if the token was previously valid, and false if it was already invalidated. - (bool)invalidate; /// Stops notifications for the change subscription that returned this token. - (void)stop __attribute__((unavailable("Renamed to -invalidate."))) NS_REFINED_FOR_SWIFT; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMRealm.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMRealm_Private.hpp" #import "RLMAsyncTask_Private.h" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMError_Private.hpp" #import "RLMLogger.h" #import "RLMMigration_Private.h" #import "RLMObject_Private.h" #import "RLMObject_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObservation.hpp" #import "RLMProperty.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealmUtil.hpp" #import "RLMScheduler.h" #import "RLMSchema_Private.hpp" #import "RLMSet_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import #import #import #import using namespace realm; using util::File; @interface RLMRealmNotificationToken : RLMNotificationToken @property (nonatomic, strong) RLMRealm *realm; @property (nonatomic, copy) RLMNotificationBlock block; @end @interface RLMRealm () @property (nonatomic, strong) NSHashTable *notificationHandlers; - (void)sendNotifications:(RLMNotification)notification; @end void RLMDisableSyncToDisk() { realm::disable_sync_to_disk(); } static std::atomic s_set_skip_backup_attribute{true}; void RLMSetSkipBackupAttribute(bool value) { s_set_skip_backup_attribute = value; } static void RLMAddSkipBackupAttributeToItemAtPath(std::string_view path) { [[NSURL fileURLWithPath:@(path.data())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; } void RLMWaitForRealmToClose(NSString *path) { NSString *lockfilePath = [path stringByAppendingString:@".lock"]; if (![NSFileManager.defaultManager fileExistsAtPath:lockfilePath]) { return; } File lockfile(lockfilePath.UTF8String, File::mode_Update); lockfile.set_fifo_path([path stringByAppendingString:@".management"].UTF8String, "lock.fifo"); while (!lockfile.try_rw_lock_exclusive()) { sched_yield(); } } BOOL RLMIsRealmCachedAtPath(NSString *path) { return RLMGetAnyCachedRealmForPath([path cStringUsingEncoding:NSUTF8StringEncoding]) != nil; } RLM_HIDDEN @implementation RLMRealmNotificationToken - (bool)invalidate { if (_realm) { [_realm verifyThread]; [_realm.notificationHandlers removeObject:self]; _realm = nil; _block = nil; return true; } return false; } - (void)suppressNextNotification { // Temporarily replace the block with one which restores the old block // rather than producing a notification. // This briefly creates a retain cycle but it's fine because the block will // be synchronously called shortly after this method is called. Unlike with // collection notifications, this does not have to go through the object // store or do fancy things to handle transaction coalescing because it's // called synchronously by the obj-c code and not by the object store. auto notificationBlock = _block; _block = ^(RLMNotification, RLMRealm *) { _block = notificationBlock; }; } - (void)dealloc { if (_realm || _block) { NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold " @"on to the RLMNotificationToken returned from addNotificationBlock and call " @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications."); } } @end static bool shouldForciblyDisableEncryption() { static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION"); return disableEncryption; } NSData *RLMRealmValidatedEncryptionKey(NSData *key) { if (shouldForciblyDisableEncryption()) { return nil; } if (key && key.length != 64) { @throw RLMException(@"Encryption key must be exactly 64 bytes long"); } return key; } REALM_NOINLINE void RLMRealmTranslateException(NSError **error) { try { throw; } catch (FileAccessError const& ex) { RLMSetErrorOrThrow(makeError(ex), error); } catch (Exception const& ex) { RLMSetErrorOrThrow(makeError(ex), error); } catch (std::system_error const& ex) { RLMSetErrorOrThrow(makeError(ex), error); } catch (std::exception const& ex) { RLMSetErrorOrThrow(makeError(ex), error); } } namespace { RLMRealm *getCachedRealm(RLMRealmConfiguration *configuration, RLMScheduler *options) NS_RETURNS_RETAINED { auto& config = configuration.configRef; if (!configuration.cache && !configuration.dynamic) { return nil; } RLMRealm *realm = RLMGetCachedRealm(configuration, options); if (!realm) { return nil; } auto const& oldConfig = realm->_realm->config(); if ((oldConfig.read_only() || oldConfig.immutable()) != configuration.readOnly) { @throw RLMException(@"Realm at path '%@' already opened with different read permissions", configuration.fileURL.path); } if (oldConfig.in_memory != config.in_memory) { @throw RLMException(@"Realm at path '%@' already opened with different inMemory settings", configuration.fileURL.path); } if (realm.dynamic != configuration.dynamic) { @throw RLMException(@"Realm at path '%@' already opened with different dynamic settings", configuration.fileURL.path); } if (oldConfig.encryption_key != config.encryption_key) { @throw RLMException(@"Realm at path '%@' already opened with different encryption key", configuration.fileURL.path); } return realm; } bool copySeedFile(RLMRealmConfiguration *configuration, NSError **error) { if (!configuration.seedFilePath) { return false; } NSError *copyError; bool didCopySeed = false; @autoreleasepool { DB::call_with_lock(configuration.path, [&](auto const&) { didCopySeed = [[NSFileManager defaultManager] copyItemAtURL:configuration.seedFilePath toURL:configuration.fileURL error:©Error]; }); } if (!didCopySeed && copyError && copyError.code != NSFileWriteFileExistsError) { RLMSetErrorOrThrow(copyError, error); return true; } return false; } } // anonymous namespace @implementation RLMRealm { std::mutex _collectionEnumeratorMutex; NSHashTable *_collectionEnumerators; bool _sendingNotifications; } + (void)initialize { // In cases where we are not using a synced Realm, we initialise the default logger // before opening any realm. [RLMLogger class]; } - (instancetype)initPrivate { self = [super init]; return self; } - (BOOL)isEmpty { return realm::ObjectStore::is_empty(self.group); } - (void)verifyThread { try { _realm->verify_thread(); } catch (std::exception const& e) { @throw RLMException(e); } } - (BOOL)inWriteTransaction { return _realm->is_in_transaction(); } - (realm::Group &)group { return _realm->read_group(); } - (BOOL)autorefresh { return _realm->auto_refresh(); } - (void)setAutorefresh:(BOOL)autorefresh { try { _realm->set_auto_refresh(autorefresh); } catch (std::exception const& e) { @throw RLMException(e); } } + (instancetype)defaultRealm { return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil]; } + (instancetype)defaultRealmForQueue:(dispatch_queue_t)queue { return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] queue:queue error:nil]; } + (instancetype)realmWithURL:(NSURL *)fileURL { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = fileURL; return [RLMRealm realmWithConfiguration:configuration error:nil]; } + (RLMAsyncOpenTask *)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration callbackQueue:(dispatch_queue_t)callbackQueue callback:(RLMAsyncOpenRealmCallback)callback { return [[RLMAsyncOpenTask alloc] initWithConfiguration:configuration confinedTo:[RLMScheduler dispatchQueue:callbackQueue] completion:callback]; } + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema dynamic:(bool)dynamic { RLMRealm *realm = [[RLMRealm alloc] initPrivate]; realm->_realm = sharedRealm; realm->_dynamic = dynamic; realm->_schema = schema; if (!dynamic) { realm->_realm->set_schema_subset(schema.objectStoreCopy); } realm->_info = RLMSchemaInfo(realm); return realm; } + (instancetype)realmWithSharedRealm:(std::shared_ptr)osRealm schema:(RLMSchema *)schema dynamic:(bool)dynamic freeze:(bool)freeze { RLMRealm *realm = [[RLMRealm alloc] initPrivate]; realm->_realm = osRealm; realm->_dynamic = dynamic; if (dynamic) { realm->_schema = schema ?: [RLMSchema dynamicSchemaFromObjectStoreSchema:osRealm->schema()]; } else @autoreleasepool { if (auto cachedRealm = RLMGetAnyCachedRealmForPath(osRealm->config().path)) { realm->_realm->set_schema_subset(cachedRealm->_realm->schema()); realm->_schema = cachedRealm.schema; realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm); } else if (osRealm->is_frozen()) { realm->_schema = schema ?: RLMSchema.sharedSchema; realm->_realm->set_schema_subset(realm->_schema.objectStoreCopy); } else { realm->_schema = schema ?: RLMSchema.sharedSchema; try { // No migration function: currently this is only used as part of // client resets on sync Realms, so none is needed. If that // changes, this'll need to as well. realm->_realm->update_schema(realm->_schema.objectStoreCopy, osRealm->config().schema_version); } catch (...) { RLMRealmTranslateException(nil); REALM_COMPILER_HINT_UNREACHABLE(); } } } if (realm->_info.begin() == realm->_info.end()) { realm->_info = RLMSchemaInfo(realm); } if (freeze && !realm->_realm->is_frozen()) { realm->_realm = realm->_realm->freeze(); } return realm; } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { return [self realmWithConfiguration:configuration confinedTo:RLMScheduler.currentRunLoop error:error]; } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration queue:(dispatch_queue_t)queue error:(NSError **)error { return [self realmWithConfiguration:configuration confinedTo:[RLMScheduler dispatchQueue:queue] error:error]; } + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)scheduler error:(NSError **)error { // First check if we already have a cached Realm for this config if (auto realm = getCachedRealm(configuration, scheduler)) { return realm; } if (copySeedFile(configuration, error)) { return nil; } bool dynamic = configuration.dynamic; bool cache = configuration.cache; Realm::Config config = configuration.config; RLMRealm *realm = [[self alloc] initPrivate]; realm->_dynamic = dynamic; realm->_actor = scheduler.actor; // protects the realm cache and accessors cache static auto& initLock = *new RLMUnfairMutex; std::lock_guard lock(initLock); try { config.scheduler = scheduler.osScheduler; if (config.scheduler && !config.scheduler->is_on_thread()) { throw RLMException(@"Realm opened from incorrect dispatch queue."); } realm->_realm = Realm::get_shared_realm(config); } catch (...) { RLMRealmTranslateException(error); return nil; } // if we have a cached realm on another thread we can skip a few steps and // just grab its schema @autoreleasepool { // ensure that cachedRealm doesn't end up in this thread's autorelease pool if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) { realm->_realm->set_schema_subset(cachedRealm->_realm->schema()); realm->_schema = cachedRealm.schema; realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm); } } if (realm->_schema) { } else if (dynamic) { realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()]; realm->_info = RLMSchemaInfo(realm); } else { // set/align schema or perform migration if needed RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema; MigrationFunction migrationFunction; auto migrationBlock = configuration.migrationBlock; if (migrationBlock && configuration.schemaVersion > 0) { migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) { RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()]; RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema dynamic:true]; // The destination RLMRealm can't just use the schema from the // SharedRealm because it doesn't have information about whether or // not a class was defined in Swift, which effects how new objects // are created RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy dynamic:true]; [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock objectClass:configuration.migrationObjectClass]; oldRealm->_realm = nullptr; newRealm->_realm = nullptr; }; } try { realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version, std::move(migrationFunction)); } catch (...) { RLMRealmTranslateException(error); return nil; } realm->_schema = schema; realm->_info = RLMSchemaInfo(realm); RLMSchemaEnsureAccessorsCreated(realm.schema); if (!configuration.readOnly) { REALM_ASSERT(!realm->_realm->is_in_read_transaction()); if (s_set_skip_backup_attribute) { RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management"); RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock"); RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note"); } } } if (cache) { RLMCacheRealm(configuration, scheduler, realm); } if (!configuration.readOnly) { realm->_realm->m_binding_context = RLMCreateBindingContext(realm); realm->_realm->m_binding_context->realm = realm->_realm; } return realm; } + (void)resetRealmState { RLMClearRealmCache(); realm::_impl::RealmCoordinator::clear_cache(); [RLMRealmConfiguration resetRealmConfigurationState]; } - (void)verifyNotificationsAreSupported:(bool)isCollection { [self verifyThread]; if (_realm->config().immutable()) { @throw RLMException(@"Read-only Realms do not change and do not have change notifications."); } if (_realm->is_frozen()) { @throw RLMException(@"Frozen Realms do not change and do not have change notifications."); } if (_realm->config().automatic_change_notifications && !_realm->can_deliver_notifications()) { @throw RLMException(@"Can only add notification blocks from within runloops."); } } - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block { if (!block) { @throw RLMException(@"The notification block should not be nil"); } [self verifyNotificationsAreSupported:false]; _realm->read_group(); if (!_notificationHandlers) { _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; } RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init]; token.realm = self; token.block = block; [_notificationHandlers addObject:token]; return token; } - (void)sendNotifications:(RLMNotification)notification { NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications"); if (_sendingNotifications) { return; } NSUInteger count = _notificationHandlers.count; if (count == 0) { return; } _sendingNotifications = true; auto cleanup = realm::util::make_scope_exit([&]() noexcept { _sendingNotifications = false; }); // call this realm's notification blocks if (count == 1) { if (auto block = [_notificationHandlers.anyObject block]) { block(notification, self); } } else { for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) { if (auto block = token.block) { block(notification, self); } } } } - (RLMRealmConfiguration *)configuration { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.configRef = _realm->config(); configuration.dynamic = _dynamic; configuration.customSchema = _schema; return configuration; } - (RLMRealmConfiguration *)configurationSharingSchema { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.configRef = _realm->config(); configuration.dynamic = _dynamic; [configuration setCustomSchemaWithoutCopying:_schema]; return configuration; } - (void)beginWriteTransaction { [self beginWriteTransactionWithError:nil]; } - (BOOL)beginWriteTransactionWithError:(NSError **)error { try { _realm->begin_transaction(); return YES; } catch (...) { RLMRealmTranslateException(error); return NO; } } - (void)commitWriteTransaction { [self commitWriteTransaction:nil]; } - (BOOL)commitWriteTransaction:(NSError **)error { return [self commitWriteTransactionWithoutNotifying:@[] error:error]; } - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray *)tokens error:(NSError **)error { for (RLMNotificationToken *token in tokens) { if (token.realm != self) { @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped."); } [token suppressNextNotification]; } try { _realm->commit_transaction(); return YES; } catch (...) { RLMRealmTranslateException(error); return NO; } } - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block { [self transactionWithBlock:block error:nil]; } - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError { return [self transactionWithoutNotifying:@[] block:block error:outError]; } - (void)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block { [self transactionWithoutNotifying:tokens block:block error:nil]; } - (BOOL)transactionWithoutNotifying:(NSArray *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error { [self beginWriteTransactionWithError:error]; block(); if (_realm->is_in_transaction()) { return [self commitWriteTransactionWithoutNotifying:tokens error:error]; } return YES; } - (void)cancelWriteTransaction { try { _realm->cancel_transaction(); } catch (std::exception &ex) { @throw RLMException(ex); } } - (BOOL)isPerformingAsynchronousWriteOperations { return _realm->is_in_async_transaction(); } - (RLMAsyncTransactionId)beginAsyncWriteTransaction:(void(^)())block { try { return _realm->async_begin_transaction(block); } catch (std::exception &ex) { @throw RLMException(ex); } } - (RLMAsyncTransactionId)commitAsyncWriteTransaction { try { return _realm->async_commit_transaction(); } catch (...) { RLMRealmTranslateException(nil); return 0; } } - (RLMAsyncWriteTask *)beginAsyncWrite { try { auto write = [[RLMAsyncWriteTask alloc] initWithRealm:self]; write.transactionId = _realm->async_begin_transaction(^{ [write complete:false]; }, true); return write; } catch (std::exception &ex) { @throw RLMException(ex); } } - (void)commitAsyncWriteWithGrouping:(bool)allowGrouping completion:(void(^)(NSError *_Nullable))completion { [self commitAsyncWriteTransaction:completion allowGrouping:allowGrouping]; } - (RLMAsyncTransactionId)commitAsyncWriteTransaction:(void(^)(NSError *))completionBlock { return [self commitAsyncWriteTransaction:completionBlock allowGrouping:false]; } - (RLMAsyncTransactionId)commitAsyncWriteTransaction:(nullable void(^)(NSError *))completionBlock allowGrouping:(BOOL)allowGrouping { try { auto completion = [=](std::exception_ptr err) { @autoreleasepool { if (!completionBlock) { std::rethrow_exception(err); return; } if (err) { try { std::rethrow_exception(err); } catch (...) { NSError *error; RLMRealmTranslateException(&error); completionBlock(error); } } else { completionBlock(nil); } } }; if (completionBlock) { return _realm->async_commit_transaction(completion, allowGrouping); } return _realm->async_commit_transaction(nullptr, allowGrouping); } catch (...) { RLMRealmTranslateException(nil); return 0; } } - (void)cancelAsyncTransaction:(RLMAsyncTransactionId)asyncTransactionId { try { _realm->async_cancel_transaction(asyncTransactionId); } catch (std::exception &ex) { @throw RLMException(ex); } } - (RLMAsyncTransactionId)asyncTransactionWithBlock:(void(^)())block onComplete:(nullable void(^)(NSError *))completionBlock { return [self beginAsyncWriteTransaction:^{ block(); if (_realm->is_in_transaction()) { [self commitAsyncWriteTransaction:completionBlock]; } }]; } - (RLMAsyncTransactionId)asyncTransactionWithBlock:(void(^)())block { return [self beginAsyncWriteTransaction:^{ block(); if (_realm->is_in_transaction()) { [self commitAsyncWriteTransaction]; } }]; } - (void)invalidate { if (_realm->is_in_transaction()) { NSLog(@"WARNING: An RLMRealm instance was invalidated during a write " "transaction and all pending changes have been rolled back."); } [self detachAllEnumerators]; for (auto& objectInfo : _info) { for (RLMObservationInfo *info : objectInfo.second.observedObjects) { info->willChange(RLMInvalidatedKey); } } _realm->invalidate(); for (auto& objectInfo : _info) { for (RLMObservationInfo *info : objectInfo.second.observedObjects) { info->didChange(RLMInvalidatedKey); } } if (_realm->is_frozen()) { _realm->close(); } } - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference { return [reference resolveReferenceInRealm:self]; } /** Replaces all string columns in this Realm with a string enumeration column and compacts the database file. Cannot be called from a write transaction. Compaction will not occur if other `RLMRealm` instances exist. While compaction is in progress, attempts by other threads or processes to open the database will wait. Be warned that resource requirements for compaction is proportional to the amount of live data in the database. Compaction works by writing the database contents to a temporary database file and then replacing the database with the temporary one. The name of the temporary file is formed by appending `.tmp_compaction_space` to the name of the database. @return YES if the compaction succeeded. */ - (BOOL)compact { // compact() automatically ends the read transaction, but we need to clean // up cached state and send invalidated notifications when that happens, so // explicitly end it first unless we're in a write transaction (in which // case compact() will throw an exception) if (!_realm->is_in_transaction()) { [self invalidate]; } try { return _realm->compact(); } catch (std::exception const& ex) { @throw RLMException(ex); } } - (void)dealloc { if (_realm) { if (_realm->is_in_transaction()) { [self cancelWriteTransaction]; NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all " "pending changes have been rolled back. Make sure to retain a reference to the " "RLMRealm for the duration of the write transaction."); } } } - (BOOL)refresh { if (_realm->config().immutable()) { @throw RLMException(@"Read-only Realms do not change and cannot be refreshed."); } try { return _realm->refresh(); } catch (std::exception const& e) { @throw RLMException(e); } } - (void)addObject:(__unsafe_unretained RLMObject *const)object { RLMAddObjectToRealm(object, self, RLMUpdatePolicyError); } - (void)addObjects:(id)objects { for (RLMObject *obj in objects) { if (![obj isKindOfClass:RLMObjectBase.class]) { @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.", NSStringFromClass(obj.class)); } [self addObject:obj]; } } - (void)addOrUpdateObject:(RLMObject *)object { // verify primary key if (!object.objectSchema.primaryKeyProperty) { @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className); } RLMAddObjectToRealm(object, self, RLMUpdatePolicyUpdateAll); } - (void)addOrUpdateObjects:(id)objects { for (RLMObject *obj in objects) { if (![obj isKindOfClass:RLMObjectBase.class]) { @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are" " supported.", NSStringFromClass(obj.class)); } [self addOrUpdateObject:obj]; } } - (void)deleteObject:(RLMObject *)object { RLMDeleteObjectFromRealm(object, self); } - (void)deleteObjects:(id)objects { id idObjects = objects; if ([idObjects respondsToSelector:@selector(realm)] && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) { if (self != (RLMRealm *)[idObjects realm]) { @throw RLMException(@"Can only delete objects from the Realm they belong to."); } [idObjects deleteObjectsFromRealm]; return; } if (auto array = RLMDynamicCast(objects)) { if (array.type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(array.type)); } } else if (auto set = RLMDynamicCast(objects)) { if (set.type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMSet<%@>: only RLMObjects can be deleted.", RLMTypeToString(set.type)); } } else if (auto dictionary = RLMDynamicCast(objects)) { if (dictionary.type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMDictionary of type %@: only RLMObjects can be deleted.", RLMTypeToString(dictionary.type)); } for (RLMObject *obj in dictionary.allValues) { RLMDeleteObjectFromRealm(obj, self); } return; } for (RLMObject *obj in objects) { if (![obj isKindOfClass:RLMObjectBase.class]) { @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.", NSStringFromClass(obj.class)); } RLMDeleteObjectFromRealm(obj, self); } } - (void)deleteAllObjects { RLMDeleteAllObjectsFromRealm(self); } - (RLMResults *)allObjects:(NSString *)objectClassName { return RLMGetObjects(self, objectClassName, nil); } - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objects:objectClassName where:predicateFormat args:args]; va_end(args); return results; } - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args { return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate { return RLMGetObjects(self, objectClassName, predicate); } - (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey { return RLMGetObject(self, className, primaryKey); } + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error { RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; config.fileURL = fileURL; config.encryptionKey = RLMRealmValidatedEncryptionKey(key); uint64_t version = RLMNotVersioned; try { version = Realm::get_schema_version(config.configRef); } catch (...) { RLMRealmTranslateException(error); return version; } if (error && version == realm::ObjectStore::NotVersioned) { auto msg = [NSString stringWithFormat:@"Realm at path '%@' has not been initialized.", fileURL.path]; *error = [NSError errorWithDomain:RLMErrorDomain code:RLMErrorInvalidDatabase userInfo:@{NSLocalizedDescriptionKey: msg, NSFilePathErrorKey: fileURL.path}]; } return version; } + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { if (RLMGetAnyCachedRealmForPath(configuration.path)) { @throw RLMException(@"Cannot migrate Realms that are already open."); } NSError *localError; // Prevents autorelease BOOL success; @autoreleasepool { success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil; } if (!success && error) { *error = localError; // Must set outside pool otherwise will free anyway } return success; } - (RLMObject *)createObject:(NSString *)className withValue:(id)value { return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, RLMUpdatePolicyError); } - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error { RLMRealmConfiguration *configuration = [RLMRealmConfiguration new]; configuration.fileURL = fileURL; configuration.encryptionKey = key; return [self writeCopyForConfiguration:configuration error:error]; } - (BOOL)writeCopyForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error { try { _realm->convert(configuration.configRef, false); return YES; } catch (...) { if (error) { RLMRealmTranslateException(error); } } return NO; } + (BOOL)fileExistsForConfiguration:(RLMRealmConfiguration *)config { return [NSFileManager.defaultManager fileExistsAtPath:config.pathOnDisk]; } + (BOOL)deleteFilesForConfiguration:(RLMRealmConfiguration *)config error:(NSError **)error { bool didDeleteAny = false; try { realm::Realm::delete_files(config.path, &didDeleteAny); } catch (realm::FileAccessError const& e) { if (error) { // For backwards compatibility, but this should go away in 11.0 if (e.code() == realm::ErrorCodes::PermissionDenied) { *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteNoPermissionError userInfo:@{NSLocalizedDescriptionKey: @(e.what()), NSFilePathErrorKey: @(e.get_path().data())}]; } else { RLMRealmTranslateException(error); } } } catch (...) { if (error) { RLMRealmTranslateException(error); } } return didDeleteAny; } - (BOOL)isFrozen { return _realm->is_frozen(); } - (RLMRealm *)freeze { [self verifyThread]; return self.isFrozen ? self : RLMGetFrozenRealmForSourceRealm(self); } - (RLMRealm *)thaw { [self verifyThread]; return self.isFrozen ? [RLMRealm realmWithConfiguration:self.configurationSharingSchema error:nil] : self; } - (RLMRealm *)frozenCopy { try { RLMRealm *realm = [[RLMRealm alloc] initPrivate]; realm->_realm = _realm->freeze(); realm->_realm->read_group(); realm->_dynamic = _dynamic; realm->_schema = _schema; realm->_info = RLMSchemaInfo(realm); return realm; } catch (std::exception const& e) { @throw RLMException(e); } } - (void)registerEnumerator:(RLMFastEnumerator *)enumerator { std::lock_guard lock(_collectionEnumeratorMutex); if (!_collectionEnumerators) { _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; } [_collectionEnumerators addObject:enumerator]; } - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator { std::lock_guard lock(_collectionEnumeratorMutex); [_collectionEnumerators removeObject:enumerator]; } - (void)detachAllEnumerators { std::lock_guard lock(_collectionEnumeratorMutex); for (RLMFastEnumerator *enumerator in _collectionEnumerators) { [enumerator detach]; } _collectionEnumerators = nil; } @end ================================================ FILE: Realm/RLMRealmConfiguration.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** A block called when opening a Realm for the first time during the life of a process to determine if it should be compacted before being returned to the user. It is passed the total file size (data + free space) and the total bytes used by data in the file. Return `YES` to indicate that an attempt to compact the file should be made. The compaction will be skipped if another process is accessing it. */ NS_SWIFT_SENDABLE typedef BOOL (^RLMShouldCompactOnLaunchBlock)(NSUInteger totalBytes, NSUInteger bytesUsed); /** An `RLMRealmConfiguration` instance describes the different options used to create an instance of a Realm. `RLMRealmConfiguration` instances are just plain `NSObject`s. Unlike `RLMRealm`s and `RLMObject`s, they can be freely shared between threads as long as you do not mutate them. Creating configuration objects for class subsets (by setting the `objectClasses` property) can be expensive. Because of this, you will normally want to cache and reuse a single configuration object for each distinct configuration rather than creating a new object each time you open a Realm. */ @interface RLMRealmConfiguration : NSObject #pragma mark - Default Configuration /** Returns the default configuration used to create Realms when no other configuration is explicitly specified (i.e. `+[RLMRealm defaultRealm]`). @return The default Realm configuration. */ + (instancetype)defaultConfiguration; /** Sets the default configuration to the given `RLMRealmConfiguration`. @param configuration The new default Realm configuration. */ + (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration; #pragma mark - Properties /// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier`; /// setting one of the two properties will automatically nil out the other. @property (nonatomic, copy, nullable) NSURL *fileURL; /// A string used to identify a particular in-memory Realm. Mutually exclusive /// with `fileURL` and `seedFilePath`. /// Setting an in-memory identifier will automatically nil out the other two. @property (nonatomic, copy, nullable) NSString *inMemoryIdentifier; /// A 64-byte key to use to encrypt the data, or `nil` if encryption is not enabled. @property (nonatomic, copy, nullable) NSData *encryptionKey; /// Whether to open the Realm in read-only mode. /// /// For non-synchronized Realms, this is required to be able to open Realm /// files which are not writeable or are in a directory which is not writeable. /// This should only be used on files which will not be modified by anyone /// while they are open, and not just to get a read-only view of a file which /// may be written to by another thread or process. Opening in read-only mode /// requires disabling Realm's reader/writer coordination, so committing a /// write transaction from another process will result in crashes. /// /// Syncronized Realms must always be writeable (as otherwise no /// synchronization could happen), and this instead merely disallows performing /// write transactions on the Realm. In addition, it will skip some automatic /// writes made to the Realm, such as to initialize the Realm's schema. Setting /// `readOnly = YES` is not strictly required for Realms which the sync user /// does not have write access to, but is highly recommended as it will improve /// error reporting and catch some errors earlier. /// /// Realms using query-based sync cannot be opened in read-only mode. @property (nonatomic) BOOL readOnly; /// The current schema version. @property (nonatomic) uint64_t schemaVersion; /// The block which migrates the Realm to the current version. @property (nonatomic, copy, nullable) RLMMigrationBlock migrationBlock; /** Whether to recreate the Realm file with the provided schema if a migration is required. This is the case when the stored schema differs from the provided schema or the stored schema version differs from the version on this configuration. Setting this property to `YES` deletes the file if a migration would otherwise be required or executed. @note Setting this property to `YES` doesn't disable file format migrations. */ @property (nonatomic) BOOL deleteRealmIfMigrationNeeded; /** A block called when opening a Realm for the first time during the life of a process to determine if it should be compacted before being returned to the user. It is passed the total file size (data + free space) and the total bytes used by data in the file. Return `YES` to indicate that an attempt to compact the file should be made. The compaction will be skipped if another process is accessing it. */ @property (nonatomic, copy, nullable) RLMShouldCompactOnLaunchBlock shouldCompactOnLaunch; /// The classes managed by the Realm. @property (nonatomic, copy, nullable) NSArray *objectClasses; /** The maximum number of live versions in the Realm file before an exception will be thrown when attempting to start a write transaction. Realm provides MVCC snapshot isolation, meaning that writes on one thread do not overwrite data being read on another thread, and instead write a new copy of that data. When a Realm refreshes it updates to the latest version of the data and releases the old versions, allowing them to be overwritten by subsequent write transactions. Under normal circumstances this is not a problem, but if the number of active versions grow too large, it will have a negative effect on the filesize on disk. This can happen when performing writes on many different threads at once, when holding on to frozen objects for an extended time, or when performing long operations on background threads which do not allow the Realm to refresh. Setting this property to a non-zero value makes it so that exceeding the set number of versions will instead throw an exception. This can be used with a low value during development to help identify places that may be problematic, or in production use to cause the app to crash rather than produce a Realm file which is too large to be opened. */ @property (nonatomic) NSUInteger maximumNumberOfActiveVersions; /** When opening the Realm for the first time, instead of creating an empty file, the Realm file will be copied from the provided seed file path and used instead. This can be used to open a Realm file with pre-populated data. If a realm file already exists at the configuration's destination 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 `[RLMRealm writeCopyForConfiguration:]` first. This option is mutually exclusive with `inMemoryIdentifier`. Setting a `seedFilePath` will nil out the `inMemoryIdentifier`. */ @property (nonatomic, copy, nullable) NSURL *seedFilePath; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMRealmConfiguration.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMRealmConfiguration_Private.h" #import "RLMObjectSchema_Private.hpp" #import "RLMRealm_Private.h" #import "RLMSchema_Private.hpp" #import "RLMUtil.hpp" #import #import static NSString *const c_RLMRealmConfigurationProperties[] = { @"fileURL", @"inMemoryIdentifier", @"encryptionKey", @"readOnly", @"schemaVersion", @"migrationBlock", @"deleteRealmIfMigrationNeeded", @"shouldCompactOnLaunch", @"dynamic", @"customSchema", }; static NSString *const c_defaultRealmFileName = @"default.realm"; RLMRealmConfiguration *s_defaultConfiguration; NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *bundleIdentifier) { return [RLMDefaultDirectoryForBundleIdentifier(bundleIdentifier) stringByAppendingPathComponent:fileName]; } NSString *RLMRealmPathForFile(NSString *fileName) { static NSString *directory = RLMDefaultDirectoryForBundleIdentifier(nil); return [directory stringByAppendingPathComponent:fileName]; } @implementation RLMRealmConfiguration { realm::Realm::Config _config; } - (realm::Realm::Config&)configRef { return _config; } - (std::string const&)path { return _config.path; } + (instancetype)defaultConfiguration { return [[self rawDefaultConfiguration] copy]; } + (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration { if (!configuration) { @throw RLMException(@"Cannot set the default configuration to nil."); } @synchronized(c_defaultRealmFileName) { s_defaultConfiguration = [configuration copy]; } } + (RLMRealmConfiguration *)rawDefaultConfiguration { RLMRealmConfiguration *configuration; @synchronized(c_defaultRealmFileName) { if (!s_defaultConfiguration) { s_defaultConfiguration = [[RLMRealmConfiguration alloc] init]; } configuration = s_defaultConfiguration; } return configuration; } + (void)resetRealmConfigurationState { @synchronized(c_defaultRealmFileName) { s_defaultConfiguration = nil; } } - (instancetype)init { self = [super init]; if (self) { static NSURL *defaultRealmURL = [NSURL fileURLWithPath:RLMRealmPathForFile(c_defaultRealmFileName)]; self.fileURL = defaultRealmURL; self.schemaVersion = 0; self.cache = YES; _config.automatically_handle_backlinks_in_migrations = true; } return self; } - (instancetype)copyWithZone:(NSZone *)zone { RLMRealmConfiguration *configuration = [[[self class] allocWithZone:zone] init]; configuration->_config = _config; configuration->_cache = _cache; configuration->_dynamic = _dynamic; configuration->_migrationBlock = _migrationBlock; configuration->_shouldCompactOnLaunch = _shouldCompactOnLaunch; configuration->_customSchema = _customSchema; configuration->_migrationObjectClass = _migrationObjectClass; configuration->_seedFilePath = _seedFilePath; return configuration; } - (NSString *)description { NSMutableString *string = [NSMutableString stringWithFormat:@"%@ {\n", self.class]; for (NSString *key : c_RLMRealmConfigurationProperties) { NSString *description = [[self valueForKey:key] description]; description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; [string appendFormat:@"\t%@ = %@;\n", key, description]; } return [string stringByAppendingString:@"}"]; } - (NSURL *)fileURL { if (_config.in_memory) { return nil; } return [NSURL fileURLWithPath:@(_config.path.c_str())]; } - (void)setFileURL:(NSURL *)fileURL { NSString *path = fileURL.path; if (path.length == 0) { @throw RLMException(@"Realm path must not be empty"); } RLMNSStringToStdString(_config.path, path); _config.in_memory = false; } - (NSString *)inMemoryIdentifier { if (!_config.in_memory) { return nil; } return [@(_config.path.c_str()) lastPathComponent]; } - (void)setInMemoryIdentifier:(NSString *)inMemoryIdentifier { if (inMemoryIdentifier.length == 0) { @throw RLMException(@"In-memory identifier must not be empty"); } _seedFilePath = nil; RLMNSStringToStdString(_config.path, [NSTemporaryDirectory() stringByAppendingPathComponent:inMemoryIdentifier]); _config.in_memory = true; } - (void)setSeedFilePath:(NSURL *)seedFilePath { _seedFilePath = seedFilePath; if (_seedFilePath) { _config.in_memory = false; } } - (NSData *)encryptionKey { return _config.encryption_key.empty() ? nil : [NSData dataWithBytes:_config.encryption_key.data() length:_config.encryption_key.size()]; } - (void)setEncryptionKey:(NSData * __nullable)encryptionKey { if (NSData *key = RLMRealmValidatedEncryptionKey(encryptionKey)) { auto bytes = static_cast(key.bytes); _config.encryption_key.assign(bytes, bytes + key.length); } else { _config.encryption_key.clear(); } } - (BOOL)readOnly { return _config.immutable() || _config.read_only(); } - (void)updateSchemaMode { if (self.readOnly) { _config.schema_mode = realm::SchemaMode::Immutable; } else { _config.schema_mode = realm::SchemaMode::Automatic; } } - (void)setReadOnly:(BOOL)readOnly { if (readOnly) { if (self.deleteRealmIfMigrationNeeded) { @throw RLMException(@"Cannot set `readOnly` when `deleteRealmIfMigrationNeeded` is set."); } else if (self.shouldCompactOnLaunch) { @throw RLMException(@"Cannot set `readOnly` when `shouldCompactOnLaunch` is set."); } _config.schema_mode = realm::SchemaMode::Immutable; } else if (self.readOnly) { _config.schema_mode = realm::SchemaMode::Automatic; [self updateSchemaMode]; } } - (uint64_t)schemaVersion { return _config.schema_version; } - (void)setSchemaVersion:(uint64_t)schemaVersion { if (schemaVersion == RLMNotVersioned) { @throw RLMException(@"Cannot set schema version to %llu (RLMNotVersioned)", RLMNotVersioned); } _config.schema_version = schemaVersion; } - (BOOL)deleteRealmIfMigrationNeeded { return _config.schema_mode == realm::SchemaMode::SoftResetFile; } - (void)setDeleteRealmIfMigrationNeeded:(BOOL)deleteRealmIfMigrationNeeded { if (deleteRealmIfMigrationNeeded) { if (self.readOnly) { @throw RLMException(@"Cannot set `deleteRealmIfMigrationNeeded` when `readOnly` is set."); } _config.schema_mode = realm::SchemaMode::SoftResetFile; } else if (self.deleteRealmIfMigrationNeeded) { _config.schema_mode = realm::SchemaMode::Automatic; } } - (NSArray *)objectClasses { return [_customSchema.objectSchema valueForKeyPath:@"objectClass"]; } - (void)setObjectClasses:(NSArray *)objectClasses { _customSchema = objectClasses ? [RLMSchema schemaWithObjectClasses:objectClasses] : nil; [self updateSchemaMode]; } - (NSUInteger)maximumNumberOfActiveVersions { if (_config.max_number_of_active_versions > std::numeric_limits::max()) { return std::numeric_limits::max(); } return static_cast(_config.max_number_of_active_versions); } - (void)setMaximumNumberOfActiveVersions:(NSUInteger)maximumNumberOfActiveVersions { if (maximumNumberOfActiveVersions == 0) { _config.max_number_of_active_versions = std::numeric_limits::max(); } else { _config.max_number_of_active_versions = maximumNumberOfActiveVersions; } } - (void)setDynamic:(bool)dynamic { _dynamic = dynamic; self.cache = !dynamic; } - (bool)disableFormatUpgrade { return _config.disable_format_upgrade; } - (void)setDisableFormatUpgrade:(bool)disableFormatUpgrade { _config.disable_format_upgrade = disableFormatUpgrade; } - (realm::SchemaMode)schemaMode { return _config.schema_mode; } - (void)setSchemaMode:(realm::SchemaMode)mode { _config.schema_mode = mode; } - (NSString *)pathOnDisk { return @(_config.path.c_str()); } - (void)setShouldCompactOnLaunch:(RLMShouldCompactOnLaunchBlock)shouldCompactOnLaunch { if (shouldCompactOnLaunch) { if (_config.immutable()) { @throw RLMException(@"Cannot set `shouldCompactOnLaunch` when `readOnly` is set."); } _config.should_compact_on_launch_function = shouldCompactOnLaunch; } else { _config.should_compact_on_launch_function = nullptr; } _shouldCompactOnLaunch = shouldCompactOnLaunch; } - (void)setCustomSchemaWithoutCopying:(RLMSchema *)schema { _customSchema = schema; } - (bool)disableAutomaticChangeNotifications { return !_config.automatic_change_notifications; } - (void)setDisableAutomaticChangeNotifications:(bool)disableAutomaticChangeNotifications { _config.automatic_change_notifications = !disableAutomaticChangeNotifications; } - (realm::Realm::Config)config { return _config; } @end ================================================ FILE: Realm/RLMRealmConfiguration_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 @class RLMSchema, RLMEventConfiguration; RLM_HEADER_AUDIT_BEGIN(nullability) @interface RLMRealmConfiguration () @property (nonatomic, readwrite) bool cache; @property (nonatomic, readwrite) bool dynamic; @property (nonatomic, readwrite) bool disableFormatUpgrade; @property (nonatomic, copy, nullable) RLMSchema *customSchema; @property (nonatomic, copy) NSString *pathOnDisk; @property (nonatomic, retain, nullable) RLMEventConfiguration *eventConfiguration; @property (nonatomic, nullable) Class migrationObjectClass; @property (nonatomic) bool disableAutomaticChangeNotifications; // Get the default configuration without copying it + (RLMRealmConfiguration *)rawDefaultConfiguration; + (void)resetRealmConfigurationState; - (void)setCustomSchemaWithoutCopying:(nullable RLMSchema *)schema; @end // Get a path in the platform-appropriate documents directory with the given filename FOUNDATION_EXTERN NSString *RLMRealmPathForFile(NSString *fileName); FOUNDATION_EXTERN NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *mainBundleIdentifier); RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMRealmConfiguration_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMRealmConfiguration_Private.h" #import @interface RLMRealmConfiguration () - (realm::Realm::Config)config; - (realm::Realm::Config&)configRef; - (std::string const&)path; @property (nonatomic) realm::SchemaMode schemaMode; - (void)updateSchemaMode; @end void RLMDeferredAuditConfigInit(realm::AuditConfig& auditConfig, RLMRealmConfiguration *realmConfig); ================================================ FILE: Realm/RLMRealmUtil.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import @class RLMRealm, RLMRealmConfiguration, RLMScheduler; namespace realm { class BindingContext; } // Add a Realm to the weak cache void RLMCacheRealm(RLMRealmConfiguration *configuration, RLMScheduler *options, RLMRealm *realm); RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path) NS_RETURNS_RETAINED; // Clear the weak cache of Realms void RLMClearRealmCache(); RLMRealm *RLMGetFrozenRealmForSourceRealm(RLMRealm *realm) NS_RETURNS_RETAINED; std::unique_ptr RLMCreateBindingContext(RLMRealm *realm); ================================================ FILE: Realm/RLMRealmUtil.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMRealmUtil.hpp" #import "RLMAsyncTask_Private.h" #import "RLMObservation.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealm_Private.hpp" #import "RLMScheduler.h" #import "RLMUtil.hpp" #import #import #import #import #import // Global realm state static auto& s_realmCacheMutex = *new RLMUnfairMutex; static auto& s_realmsPerPath = *new std::map(); static auto& s_frozenRealms = *new std::map(); void RLMCacheRealm(__unsafe_unretained RLMRealmConfiguration *const configuration, RLMScheduler *scheduler, __unsafe_unretained RLMRealm *const realm) { auto& path = configuration.path; auto key = scheduler.cacheKey; std::lock_guard lock(s_realmCacheMutex); NSMapTable *realms = s_realmsPerPath[path]; if (!realms) { s_realmsPerPath[path] = realms = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsOpaquePersonality|NSPointerFunctionsOpaqueMemory valueOptions:NSPointerFunctionsWeakMemory]; } [realms setObject:realm forKey:(__bridge id)key]; } RLMRealm *RLMGetCachedRealm(__unsafe_unretained RLMRealmConfiguration *const configuration, RLMScheduler *scheduler) { auto key = scheduler.cacheKey; auto& path = configuration.path; std::lock_guard lock(s_realmCacheMutex); RLMRealm *realm = [s_realmsPerPath[path] objectForKey:(__bridge id)key]; if (realm && !realm->_realm->scheduler()->is_on_thread()) { // We can get here in two cases: if the user is trying to open a // queue-bound Realm from the wrong queue, or if we have a stale cached // Realm which is bound to a thread that no longer exists. In the first // case we'll throw an error later on; in the second we'll just create // a new RLMRealm and replace the cache entry with one bound to the // thread that now exists. realm = nil; } return realm; } RLMRealm *RLMGetAnyCachedRealm(__unsafe_unretained RLMRealmConfiguration *const configuration) { return RLMGetAnyCachedRealmForPath(configuration.path); } RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path) { std::lock_guard lock(s_realmCacheMutex); return [s_realmsPerPath[path] objectEnumerator].nextObject; } void RLMClearRealmCache() { std::lock_guard lock(s_realmCacheMutex); s_realmsPerPath.clear(); s_frozenRealms.clear(); } RLMRealm *RLMGetFrozenRealmForSourceRealm(__unsafe_unretained RLMRealm *const sourceRealm) { std::lock_guard lock(s_realmCacheMutex); auto& r = *sourceRealm->_realm; auto& path = r.config().path; NSMapTable *realms = s_realmsPerPath[path]; if (!realms) { s_realmsPerPath[path] = realms = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsIntegerPersonality|NSPointerFunctionsOpaqueMemory valueOptions:NSPointerFunctionsWeakMemory]; } r.read_group(); auto version = reinterpret_cast(r.read_transaction_version().version); RLMRealm *realm = [realms objectForKey:(__bridge id)version]; if (!realm) { realm = [sourceRealm frozenCopy]; [realms setObject:realm forKey:(__bridge id)version]; } return realm; } namespace { void advance_to_ready(realm::Realm& realm) { if (!realm.auto_refresh()) { realm.set_auto_refresh(true); realm.notify(); realm.set_auto_refresh(false); } } class RLMNotificationHelper : public realm::BindingContext { public: RLMNotificationHelper(RLMRealm *realm) : _realm(realm) { } void before_notify() override { @autoreleasepool { auto blocks = std::move(_beforeNotify); _beforeNotify.clear(); for (auto block : blocks) { block(); } } } void changes_available() override { @autoreleasepool { auto realm = _realm; if (!realm || realm.autorefresh) { return; } // If an async refresh has been requested, then do that now instead // of notifying of a pending version available. Note that this will // recursively call this function and then exit above due to // autorefresh being true. if (_refreshHandlers.empty()) { [realm sendNotifications:RLMRealmRefreshRequiredNotification]; } else { advance_to_ready(*realm->_realm); } } } std::vector get_observed_rows() override { @autoreleasepool { if (auto realm = _realm) { [realm detachAllEnumerators]; return RLMGetObservedRows(realm->_info); } return {}; } } void will_change(std::vector const& observed, std::vector const& invalidated) override { @autoreleasepool { RLMWillChange(observed, invalidated); } } void did_change(std::vector const& observed, std::vector const& invalidated, bool version_changed) override { @autoreleasepool { __strong auto realm = _realm; try { RLMDidChange(observed, invalidated); if (version_changed) { [realm sendNotifications:RLMRealmDidChangeNotification]; } } catch (...) { // This can only be called during a write transaction if it was // called due to the transaction beginning, so cancel it to ensure // exceptions thrown here behave the same as exceptions thrown when // actually beginning the write if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } throw; } if (!realm || !version_changed) { return; } auto new_version = realm->_realm->current_transaction_version(); if (!new_version) { return; } std::erase_if(_refreshHandlers, [&](auto& handler) { auto& [target_version, completion] = handler; if (new_version->version >= target_version) { completion(true); return true; } return false; }); } } void add_before_notify_block(dispatch_block_t block) { _beforeNotify.push_back(block); } void wait_for_refresh(realm::DB::version_type version, RLMAsyncRefreshCompletion completion) { _refreshHandlers.emplace_back(version, completion); } private: // This is owned by the realm, so it needs to not retain the realm __weak RLMRealm *const _realm; std::vector _beforeNotify; std::vector> _refreshHandlers; }; } // anonymous namespace std::unique_ptr RLMCreateBindingContext(__unsafe_unretained RLMRealm *const realm) { return std::unique_ptr(new RLMNotificationHelper(realm)); } void RLMAddBeforeNotifyBlock(RLMRealm *realm, dispatch_block_t block) { static_cast(realm->_realm->m_binding_context.get())->add_before_notify_block(block); } @implementation RLMPinnedRealm { realm::TransactionRef _pin; } - (instancetype)initWithRealm:(RLMRealm *)realm { if (self = [super init]) { _pin = realm->_realm->duplicate(); _configuration = realm.configurationSharingSchema; } return self; } - (void)unpin { _pin.reset(); } @end RLMAsyncRefreshTask *RLMRealmRefreshAsync(RLMRealm *rlmRealm) { auto& realm = *rlmRealm->_realm; if (realm.is_frozen() || realm.config().immutable()) { return nil; } // Refresh is a no-op if the Realm isn't currently in a read transaction // or is up-to-date auto latest = realm.latest_snapshot_version(); auto current = realm.current_transaction_version(); if (!latest || !current || current->version == *latest) return nil; // If autorefresh is disabled, we may have already been notified of a new // version and simply not advanced to it. advance_to_ready(realm); // This may have advanced to the latest version in which case there's // nothing left to do current = realm.current_transaction_version(); if (current && current->version >= *latest) return [RLMAsyncRefreshTask completedRefresh]; auto refresh = [[RLMAsyncRefreshTask alloc] init]; // Register the continuation to be called once the new version is ready auto& context = static_cast(*realm.m_binding_context); context.wait_for_refresh(*latest, ^(bool didRefresh) { [refresh complete:didRefresh]; }); return refresh; } void RLMRunAsyncNotifiers(NSString *path) { realm::_impl::RealmCoordinator::get_existing_coordinator(path.UTF8String)->on_change(); } ================================================ FILE: Realm/RLMRealm_Dynamic.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import @class RLMResults; RLM_HEADER_AUDIT_BEGIN(nullability) @interface RLMRealm (Dynamic) #pragma mark - Getting Objects from a Realm /** Returns all objects of a given type from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The name of the `RLMObject` subclass to retrieve on (e.g. `MyClass.className`). @return An `RLMResults` containing all objects in the Realm of the given type. @see `+[RLMObject allObjects]` */ - (RLMResults *)allObjects:(NSString *)className; /** Returns all objects matching the given predicate from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The type of objects you are looking for (name of the class). @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing results matching the given predicate. @see `+[RLMObject objectsWhere:]` */ - (RLMResults *)objects:(NSString *)className where:(NSString *)predicateFormat, ...; /** Returns all objects matching the given predicate from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The type of objects you are looking for (name of the class). @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing results matching the given predicate. @see `+[RLMObject objectsWhere:]` */ - (RLMResults *)objects:(NSString *)className withPredicate:(NSPredicate *)predicate; /** Returns the object of the given type with the given primary key from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get an object of a single class is to use the class methods on `RLMObject`. @param className The class name for the object you are looking for. @param primaryKey The primary key value for the object you are looking for. @return An object, or `nil` if an object with the given primary key does not exist. @see `+[RLMObject objectForPrimaryKey:]` */ - (nullable RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey; /** Creates an `RLMObject` instance of type `className` in the Realm, and populates it using a given object. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use `[RLMObject createInDefaultRealmWithValue:]`. @param value The value used to populate the object. @return An `RLMObject` instance of type `className`. */ -(RLMObject *)createObject:(NSString *)className withValue:(id)value; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMRealm_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @class RLMFastEnumerator, RLMScheduler, RLMAsyncRefreshTask, RLMAsyncWriteTask; RLM_HEADER_AUDIT_BEGIN(nullability) // Disable syncing files to disk. Cannot be re-enabled. Use only for tests. FOUNDATION_EXTERN void RLMDisableSyncToDisk(void); // Set whether the skip backup attribute should be set on temporary files. FOUNDATION_EXTERN void RLMSetSkipBackupAttribute(bool value); FOUNDATION_EXTERN NSData * _Nullable RLMRealmValidatedEncryptionKey(NSData *key); // Set the queue used for async open. For testing purposes only. FOUNDATION_EXTERN void RLMSetAsyncOpenQueue(dispatch_queue_t queue); // Translate an in-flight exception resulting from an operation on a SharedGroup to // an NSError or NSException (if error is nil) void RLMRealmTranslateException(NSError **error); // Block until the Realm at the given path is closed. FOUNDATION_EXTERN void RLMWaitForRealmToClose(NSString *path); BOOL RLMIsRealmCachedAtPath(NSString *path); // Register a block to be called from the next before_notify() invocation FOUNDATION_EXTERN void RLMAddBeforeNotifyBlock(RLMRealm *realm, dispatch_block_t block); // Test hook to run the async notifiers for a Realm which has the background thread disabled FOUNDATION_EXTERN void RLMRunAsyncNotifiers(NSString *path); // Get the cached Realm for the given configuration and scheduler, if any FOUNDATION_EXTERN RLMRealm *_Nullable RLMGetCachedRealm(RLMRealmConfiguration *, RLMScheduler *) NS_RETURNS_RETAINED; // Get a cached Realm for the given configuration and any scheduler. The returned // Realm is not confined to the current thread, so very few operations are safe // to perform on it FOUNDATION_EXTERN RLMRealm *_Nullable RLMGetAnyCachedRealm(RLMRealmConfiguration *) NS_RETURNS_RETAINED; // Scheduler an async refresh for the given Realm FOUNDATION_EXTERN RLMAsyncRefreshTask *_Nullable RLMRealmRefreshAsync(RLMRealm *rlmRealm) NS_RETURNS_RETAINED; // RLMRealm private members @interface RLMRealm () @property (nonatomic, readonly) BOOL dynamic; @property (nonatomic, readwrite) RLMSchema *schema; @property (nonatomic, readonly, nullable) id actor; // `-configuration` does a deep copy of the schema as if the user mutates the // RLMSchema in use by a RLMRealm things will break horribly. When we know that // the configuration won't be exposed we can skip the copy. - (RLMRealmConfiguration *)configurationSharingSchema NS_RETURNS_RETAINED; + (void)resetRealmState; - (void)registerEnumerator:(RLMFastEnumerator *)enumerator; - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator; - (void)detachAllEnumerators; - (void)sendNotifications:(RLMNotification)notification; - (void)verifyThread; - (void)verifyNotificationsAreSupported:(bool)isCollection; - (RLMRealm *)frozenCopy NS_RETURNS_RETAINED; + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration confinedTo:(RLMScheduler *)options error:(NSError **)error NS_RETURNS_RETAINED; - (RLMAsyncWriteTask *)beginAsyncWrite NS_RETURNS_RETAINED; - (void)commitAsyncWriteWithGrouping:(bool)allowGrouping completion:(void(^)(NSError *_Nullable))completion; @end @interface RLMPinnedRealm : NSObject @property (nonatomic, readonly) RLMRealmConfiguration *configuration; - (instancetype)initWithRealm:(RLMRealm *)realm; - (void)unpin; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMRealm_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMRealm_Private.h" #import "RLMClassInfo.hpp" #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) namespace realm { class Group; class Realm; } @interface RLMRealm () { @public std::shared_ptr _realm; RLMSchemaInfo _info; } + (instancetype)realmWithSharedRealm:(std::shared_ptr)sharedRealm schema:(nullable RLMSchema *)schema dynamic:(bool)dynamic freeze:(bool)freeze NS_RETURNS_RETAINED; @property (nonatomic, readonly) realm::Group &group; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMResults.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /// A block type used for APIs which asynchronously return a `Results`. typedef void(^RLMResultsCompletionBlock)(RLMResults * _Nullable, NSError * _Nullable); @class RLMObject; /** `RLMResults` is an auto-updating container type in Realm returned from object queries. It represents the results of the query in the form of a collection of objects. `RLMResults` can be queried using the same predicates as `RLMObject` and `RLMArray`, and you can chain queries to further filter results. `RLMResults` always reflect the current state of the Realm on the current thread, including during write transactions on the current thread. The one exception to this is when using `for...in` fast enumeration, which will always enumerate over the objects which matched the query when the enumeration is begun, even if some of them are deleted or modified to be excluded by the filter during the enumeration. `RLMResults` are lazily evaluated the first time they are accessed; they only run queries when the result of the query is requested. This means that chaining several temporary `RLMResults` to sort and filter your data does not perform any extra work processing the intermediate state. Once the results have been evaluated or a notification block has been added, the results are eagerly kept up-to-date, with the work done to keep them up-to-date done on a background thread whenever possible. `RLMResults` cannot be directly instantiated. */ @interface RLMResults : NSObject #pragma mark - Properties /** The number of objects in the results collection. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The type of the objects in the results collection. */ @property (nonatomic, readonly, assign) RLMPropertyType type; /** Indicates whether the objects in the collection can be `nil`. */ @property (nonatomic, readwrite, getter = isOptional) BOOL optional; /** The class name of the objects contained in the results collection. Will be `nil` if `type` is not RLMPropertyTypeObject. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** The Realm which manages this results collection. */ @property (nonatomic, readonly) RLMRealm *realm; /** Indicates if the results collection is no longer valid. The results collection becomes invalid if `invalidate` is called on the containing `realm`. An invalidated results collection can be accessed, but will always be empty. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Accessing Objects from an RLMResults /** Returns the object at the index specified. @param index The index to look up. @return An object of the type contained in the results collection. */ - (RLMObjectType)objectAtIndex:(NSUInteger)index; /** Returns an array containing the objects in the results at the indexes specified by a given index set. `nil` will be returned if the index set contains an index out of the arrays bounds. @param indexes The indexes in the results to retrieve objects from. @return The objects at the specified indexes. */ - (nullable NSArray *)objectsAtIndexes:(NSIndexSet *)indexes; /** Returns the first object in the results collection. Returns `nil` if called on an empty results collection. @return An object of the type contained in the results collection. */ - (nullable RLMObjectType)firstObject; /** Returns the last object in the results collection. Returns `nil` if called on an empty results collection. @return An object of the type contained in the results collection. */ - (nullable RLMObjectType)lastObject; #pragma mark - Querying Results /** Returns the index of an object in the results collection. Returns `NSNotFound` if the object is not found in the results collection. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(RLMObjectType)object; /** Returns the index of the first object in the results collection matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the results collection. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the results collection matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the results collection. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; /** Returns all the objects matching the given predicate in the results collection. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all the objects matching the given predicate in the results collection. @param predicate The predicate with which to filter the objects. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from an existing results collection. @param keyPath The key path to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from an existing results collection. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /** Returns a distinct `RLMResults` from an existing results collection. @param keyPaths The key paths used produce distinct results @return An `RLMResults` made distinct based on the specified key paths */ - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; #pragma mark - Notifications /** Registers a block to be called each time the results collection changes. The block will be asynchronously called with the initial results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the results collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the `RLMResults` object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; NSLog(@"dogs.count: %zu", dogs.count); // => 0 self.token = [results addNotificationBlock:^(RLMResults *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the results collection changes. The block will be asynchronously called with the initial results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the results collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the `RLMResults` object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the results collection changes. The block will be asynchronously called with the initial results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the results collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the `RLMResults` object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the results collection changes. The block will be asynchronously called with the initial results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the results collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. At the time when the block is called, the `RLMResults` object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *_Nullable results, RLMCollectionChange *_Nullable change, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths __attribute__((warn_unused_result)); #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the objects represented by the results collection. NSNumber *min = [results minOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The minimum value of the property, or `nil` if the Results are empty. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects represented by the results collection. NSNumber *max = [results maxOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose maximum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The maximum value of the property, or `nil` if the Results are empty. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of the values of a given property over all the objects represented by the results collection. NSNumber *sum = [results sumOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose values should be summed. Only properties of types `int`, `float`, and `double` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects represented by the results collection. NSNumber *average = [results averageOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose average value should be calculated. Only properties of types `int`, `float`, and `double` are supported. @return The average value of the given property, or `nil` if the Results are empty. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; /// :nodoc: - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; #pragma mark - Sectioned Results /** Sorts and sections this collection from a given property key path, returning the result as an instance of `RLMSectionedResults`. @param keyPath The property key path to sort on. @param ascending The direction to sort in. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; /** Sorts and sections this collection from a given array of sort descriptors, returning the result as an instance of `RLMSectionedResults`. @param sortDescriptors An array of `RLMSortDescriptor`s to sort by. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @note The primary sort descriptor must be responsible for determining the section key. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; #pragma mark - Freeze /** Indicates if the result are frozen. Frozen Results are immutable and can be accessed from any thread.The objects read from a frozen Results will also be frozen. */ @property (nonatomic, readonly, getter=isFrozen) BOOL frozen; /** Returns a frozen (immutable) snapshot of these results. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live Results, frozen Results can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; #pragma mark - Unavailable Methods /** `-[RLMResults init]` is not available because `RLMResults` cannot be created directly. `RLMResults` can be obtained by querying a Realm. */ - (instancetype)init __attribute__((unavailable("RLMResults cannot be created directly"))); /** `+[RLMResults new]` is not available because `RLMResults` cannot be created directly. `RLMResults` can be obtained by querying a Realm. */ + (instancetype)new __attribute__((unavailable("RLMResults cannot be created directly"))); @end /** `RLMLinkingObjects` is an auto-updating container type. It represents a collection of objects that link to its parent object. For more information, please see the "Inverse Relationships" section in the [documentation](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/relationships/). */ @interface RLMLinkingObjects : RLMResults @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMResults.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMResults_Private.hpp" #import "RLMAccessor.hpp" #import "RLMArray_Private.hpp" #import "RLMCollection_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMScheduler.h" #import "RLMSchema_Private.h" #import "RLMSectionedResults_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import using namespace realm; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation RLMNotificationToken - (bool)invalidate { return false; } @end #pragma clang diagnostic pop @interface RLMResults () @end // private properties @interface RLMResults () @property (nonatomic, nullable) RLMObjectId *associatedSubscriptionId; @end // // RLMResults implementation // @implementation RLMResults { RLMRealm *_realm; RLMClassInfo *_info; } - (instancetype)initPrivate { self = [super init]; return self; } - (instancetype)initWithResults:(Results)results { if (self = [super init]) { _results = std::move(results); } return self; } static void assertKeyPathIsNotNested(NSString *keyPath) { if ([keyPath rangeOfString:@"."].location != NSNotFound) { @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); } } void RLMThrowCollectionException(NSString *collectionName) { try { throw; } catch (realm::WrongTransactionState const&) { @throw RLMException(@"Cannot modify %@ outside of a write transaction.", collectionName); } catch (realm::OutOfBounds const& e) { @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).", e.index, e.size); } catch (realm::Exception const& e) { @throw RLMException(e); } catch (std::exception const& e) { @throw RLMException(e); } } template __attribute__((always_inline)) static auto translateErrors(Function&& f) { return translateCollectionError(static_cast(f), @"Results"); } - (instancetype)initWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results { if (self = [super init]) { _results = std::move(results); _realm = info.realm; _info = &info; } return self; } + (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results { return [[self alloc] initWithObjectInfo:info results:std::move(results)]; } + (instancetype)emptyDetachedResults { return [[self alloc] initPrivate]; } - (instancetype)subresultsWithResults:(realm::Results)results { return [self.class resultsWithObjectInfo:*_info results:std::move(results)]; } static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) { ar->_realm->_realm->verify_thread(); ar->_realm->_realm->verify_in_write(); } - (BOOL)isInvalidated { return translateErrors([&] { return !_results.is_valid(); }); } - (NSUInteger)count { return translateErrors([&] { return _results.size(); }); } - (RLMPropertyType)type { return translateErrors([&] { return static_cast(_results.get_type() & ~realm::PropertyType::Nullable); }); } - (BOOL)isOptional { return translateErrors([&] { return is_nullable(_results.get_type()); }); } - (NSString *)objectClassName { return translateErrors([&] { if (_info && _results.get_type() == realm::PropertyType::Object) { return _info->rlmObjectSchema.className; } return (NSString *)nil; }); } - (RLMClassInfo *)objectInfo { return _info; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { if (!_info) { return 0; } if (state->state == 0) { translateErrors([&] { _results.evaluate_query_if_needed(); }); } return RLMFastEnumerate(state, len, self); } - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args]; va_end(args); return index; } - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args { return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate { if (_results.get_mode() == Results::Mode::Empty) { return NSNotFound; } return translateErrors([&] { if (_results.get_type() != realm::PropertyType::Object) { @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); } return RLMConvertNotFound(_results.index_of(RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group))); }); } - (id)objectAtIndex:(NSUInteger)index { RLMAccessorContext ctx(*_info); return translateErrors([&] { return _results.get(ctx, index); }); } - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { if (!_info) { return nil; } size_t c = self.count; NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:indexes.count]; NSUInteger i = [indexes firstIndex]; RLMAccessorContext context(*_info); while (i != NSNotFound) { if (i >= 0 && i < c) { [result addObject:_results.get(context, i)]; } else { return nil; } i = [indexes indexGreaterThanIndex:i]; } return result; } - (id)firstObject { if (!_info) { return nil; } RLMAccessorContext ctx(*_info); return translateErrors([&] { return _results.first(ctx); }); } - (id)lastObject { if (!_info) { return nil; } RLMAccessorContext ctx(*_info); return translateErrors([&] { return _results.last(ctx); }); } - (NSUInteger)indexOfObject:(id)object { if (!_info || !object) { return NSNotFound; } if (RLMObjectBase *obj = RLMDynamicCast(object)) { // Unmanaged objects are considered not equal to all managed objects if (!obj->_realm && !obj.invalidated) { return NSNotFound; } } RLMAccessorContext ctx(*_info); return translateErrors([&] { return RLMConvertNotFound(_results.index_of(ctx, object)); }); } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath characterAtIndex:0] != '@') { return [super valueForKeyPath:keyPath]; } if ([keyPath isEqualToString:@"@count"]) { return @(self.count); } NSRange operatorRange = [keyPath rangeOfString:@"." options:NSLiteralSearch]; NSUInteger keyPathLength = keyPath.length; NSUInteger separatorIndex = operatorRange.location != NSNotFound ? operatorRange.location : keyPathLength; NSString *operatorName = [keyPath substringWithRange:NSMakeRange(1, separatorIndex - 1)]; SEL opSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@ForKeyPath:", operatorName]); if (![self respondsToSelector:opSelector]) { @throw RLMException(@"Unsupported KVC collection operator found in key path '%@'", keyPath); } if (separatorIndex >= keyPathLength - 1) { @throw RLMException(@"Missing key path for KVC collection operator %@ in key path '%@'", operatorName, keyPath); } NSString *operatorKeyPath = [keyPath substringFromIndex:separatorIndex + 1]; return ((id(*)(id, SEL, id))objc_msgSend)(self, opSelector, operatorKeyPath); } - (id)valueForKey:(NSString *)key { if (!_info) { return @[]; } return translateErrors([&] { return RLMCollectionValueForKey(_results, key, *_info); }); } - (void)setValue:(id)value forKey:(NSString *)key { translateErrors([&] { RLMResultsValidateInWriteTransaction(self); }); RLMCollectionSetValueForKey(self, key, value); } - (NSNumber *)_aggregateForKeyPath:(NSString *)keyPath method:(std::optional (Results::*)(ColKey))method methodName:(NSString *)methodName returnNilForEmpty:(BOOL)returnNilForEmpty { assertKeyPathIsNotNested(keyPath); return [self aggregate:keyPath method:method returnNilForEmpty:returnNilForEmpty]; } - (NSNumber *)_minForKeyPath:(NSString *)keyPath { return [self _aggregateForKeyPath:keyPath method:&Results::min methodName:@"@min" returnNilForEmpty:YES]; } - (NSNumber *)_maxForKeyPath:(NSString *)keyPath { return [self _aggregateForKeyPath:keyPath method:&Results::max methodName:@"@max" returnNilForEmpty:YES]; } - (NSNumber *)_sumForKeyPath:(NSString *)keyPath { return [self _aggregateForKeyPath:keyPath method:&Results::sum methodName:@"@sum" returnNilForEmpty:NO]; } - (NSNumber *)_avgForKeyPath:(NSString *)keyPath { assertKeyPathIsNotNested(keyPath); return [self averageOfProperty:keyPath]; } - (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath { assertKeyPathIsNotNested(keyPath); return translateErrors([&] { return RLMCollectionValueForKey(_results, keyPath, *_info); }); } - (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath { return [NSSet setWithArray:[self _unionOfObjectsForKeyPath:keyPath]].allObjects; } - (NSArray *)_unionOfArraysForKeyPath:(NSString *)keyPath { assertKeyPathIsNotNested(keyPath); if ([keyPath isEqualToString:@"self"]) { @throw RLMException(@"self is not a valid key-path for a KVC array collection operator as 'unionOfArrays'."); } return translateErrors([&] { NSMutableArray *flatArray = [NSMutableArray new]; for (id array in RLMCollectionValueForKey(_results, keyPath, *_info)) { for (id value in array) { [flatArray addObject:value]; } } return flatArray; }); } - (NSArray *)_distinctUnionOfArraysForKeyPath:(__unused NSString *)keyPath { return [NSSet setWithArray:[self _unionOfArraysForKeyPath:keyPath]].allObjects; } - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsWhere:predicateFormat args:args]; va_end(args); return results; } - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { return translateErrors([&] { if (_results.get_mode() == Results::Mode::Empty) { return self; } if (_results.get_type() != realm::PropertyType::Object) { @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); } auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group); return [self subresultsWithResults:_results.filter(std::move(query))]; }); } - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { if (properties.count == 0) { return self; } return translateErrors([&] { if (_results.get_mode() == Results::Mode::Empty) { return self; } return [self subresultsWithResults:_results.sort(RLMSortDescriptorsToKeypathArray(properties))]; }); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { for (NSString *keyPath in keyPaths) { if ([keyPath rangeOfString:@"@"].location != NSNotFound) { @throw RLMException(@"Cannot distinct on keypath '%@': KVC collection operators are not supported.", keyPath); } } return translateErrors([&] { if (_results.get_mode() == Results::Mode::Empty) { return self; } std::vector keyPathsVector; for (NSString *keyPath in keyPaths) { keyPathsVector.push_back(keyPath.UTF8String); } return [self subresultsWithResults:_results.distinct(keyPathsVector)]; }); } - (id)objectAtIndexedSubscript:(NSUInteger)index { return [self objectAtIndex:index]; } - (id)aggregate:(NSString *)property method:(std::optional (Results::*)(ColKey))method returnNilForEmpty:(BOOL)returnNilForEmpty { if (_results.get_mode() == Results::Mode::Empty) { return returnNilForEmpty ? nil : @0; } ColKey column; if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) { column = _info->tableColumn(property); } auto value = translateErrors([&] { return (_results.*method)(column); }); return value ? RLMMixedToObjc(*value) : nil; } - (id)minOfProperty:(NSString *)property { return [self aggregate:property method:&Results::min returnNilForEmpty:YES]; } - (id)maxOfProperty:(NSString *)property { return [self aggregate:property method:&Results::max returnNilForEmpty:YES]; } - (id)sumOfProperty:(NSString *)property { return [self aggregate:property method:&Results::sum returnNilForEmpty:NO]; } - (id)averageOfProperty:(NSString *)property { return [self aggregate:property method:&Results::average returnNilForEmpty:YES]; } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingKeyPath:keyPath ascending:ascending] keyBlock:keyBlock]; } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingDescriptors:sortDescriptors] keyBlock:keyBlock]; } - (void)deleteObjectsFromRealm { if (self.type != RLMPropertyTypeObject) { @throw RLMException(@"Cannot delete objects from RLMResults<%@>: only RLMObjects can be deleted.", RLMTypeToString(self.type)); } return translateErrors([&] { if (_results.get_mode() == Results::Mode::Table) { RLMResultsValidateInWriteTransaction(self); RLMClearTable(*_info); } else { RLMObservationTracker tracker(_realm, true); _results.clear(); } }); } - (NSString *)description { return RLMDescriptionWithMaxDepth(@"RLMResults", self, RLMDescriptionMaxDepth); } - (realm::TableView)tableView { return translateErrors([&] { return _results.get_tableview(); }); } - (RLMFastEnumerator *)fastEnumerator { return translateErrors([&] { return [[RLMFastEnumerator alloc] initWithResults:_results collection:self classInfo:*_info]; }); } - (RLMResults *)snapshot { return translateErrors([&] { return [self subresultsWithResults:_results.snapshot()]; }); } - (BOOL)isFrozen { return _realm.frozen; } - (instancetype)resolveInRealm:(RLMRealm *)realm { return translateErrors([&] { return [self.class resultsWithObjectInfo:_info->resolve(realm) results:_results.freeze(realm->_realm)]; }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_realm.thaw]; } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } #pragma clang diagnostic pop - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _results.add_notification_callback(RLMWrapCollectionChangeCallback(block, self, true), std::move(keyPaths)); } - (BOOL)isAttached { return !!_realm; } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _results; } - (id)objectiveCMetadata { return nil; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(__unused id)metadata realm:(RLMRealm *)realm { auto results = reference.resolve(realm->_realm); return [RLMResults resultsWithObjectInfo:realm->_info[RLMStringDataToNSString(results.get_object_type())] results:std::move(results)]; } @end @implementation RLMLinkingObjects - (NSString *)description { return RLMDescriptionWithMaxDepth(@"RLMLinkingObjects", self, RLMDescriptionMaxDepth); } @end ================================================ FILE: Realm/RLMResults_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "RLMRealm_Private.h" @class RLMObjectSchema; RLM_HEADER_AUDIT_BEGIN(nullability) @interface RLMResults () @property (nonatomic, readonly, getter=isAttached) BOOL attached; + (instancetype)emptyDetachedResults; - (RLMResults *)snapshot; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMResults_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMResults_Private.h" #import "RLMCollection_Private.hpp" #import class RLMClassInfo; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMResults () { @public realm::Results _results; } /** Initialize a 'raw' `RLMResults` using only an object store level Results. This is only meant for applications where a results collection is being backed by an object store object class that has no binding-level equivalent. The consumer is responsible for bridging between the underlying objects and whatever binding-level class is being vended out. */ - (instancetype)initWithResults:(realm::Results)results; - (instancetype)initWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results; + (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info results:(realm::Results&&)results; - (instancetype)subresultsWithResults:(realm::Results)results; - (RLMClassInfo *)objectInfo; - (void)deleteObjectsFromRealm; @end // Utility functions [[gnu::noinline]] [[noreturn]] void RLMThrowCollectionException(NSString *collectionName); template static auto translateCollectionError(Function&& f, NSString *collectionName) { try { return f(); } catch (...) { RLMThrowCollectionException(collectionName); } } RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMScheduler.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 #ifdef __cplusplus #include namespace realm::util { class Scheduler; } #endif RLM_HEADER_AUDIT_BEGIN(nullability, sendability) // A serial work queue of some sort which represents a thread-confinement context // of some sort which blocks can be invoked inside. Realms are confined to a // scheduler, which can be a thread (actually a run loop), a dispatch queue, or // an actor. The scheduler ensures that the Realm is only used on one thread at // a time, and allows us to dispatch work to the thread where we can access the // Realm safely. NS_SWIFT_SENDABLE // is immutable @interface RLMScheduler : NSObject + (RLMScheduler *)mainRunLoop __attribute__((objc_direct)); + (RLMScheduler *)currentRunLoop __attribute__((objc_direct)); // A scheduler for the given queue if it's non-nil, and currentRunLoop otherwise + (RLMScheduler *)dispatchQueue:(nullable dispatch_queue_t)queue; + (RLMScheduler *)actor:(id)actor invoke:(void (^)(dispatch_block_t))invoke verify:(void (^)(void))verify; // Invoke the block on this scheduler. Currently not actually implement for run // loop schedulers. - (void)invoke:(dispatch_block_t)block; // Cache key for this scheduler suitable for use with NSMapTable. Only valid // when called from the current scheduler. - (void *)cacheKey; - (nullable id)actor; #ifdef __cplusplus // The object store Scheduler corresponding to this scheduler - (std::shared_ptr)osScheduler; #endif @end FOUNDATION_EXTERN void RLMSetMainActor(id actor); RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMScheduler.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 "RLMScheduler.h" #import "RLMUtil.hpp" #include @interface RLMMainRunLoopScheduler : RLMScheduler @end RLM_HIDDEN @implementation RLMMainRunLoopScheduler - (std::shared_ptr)osScheduler { return realm::util::Scheduler::make_runloop(CFRunLoopGetMain()); } - (void *)cacheKey { // The main thread and main queue share a cache key of `std::numeric_limits::max()` // so that they give the same instance. Other Realms are keyed on either the thread or the queue. // Note that despite being a void* the cache key is not actually a pointer; // this is just an artifact of NSMapTable's strange API. return reinterpret_cast(std::numeric_limits::max()); } // We can't access MainActor.shared directly from obj-c and need to set it from // Swift. The locking here is _almost_ unnecessary as this is set from a static // initializer before the value can ever be read, but mixed use of the obj-c and // Swift APIs could potentially race on the read. static auto& g_mainActorLock = *new RLMUnfairMutex; static id g_mainActor; void RLMSetMainActor(id actor) { std::lock_guard lock(g_mainActorLock); g_mainActor = actor; } - (id)actor { std::lock_guard lock(g_mainActorLock); return g_mainActor; } - (void)invoke:(dispatch_block_t)block { dispatch_async(dispatch_get_main_queue(), block); } @end @interface RLMDispatchQueueScheduler : RLMScheduler @end RLM_HIDDEN @implementation RLMDispatchQueueScheduler { dispatch_queue_t _queue; } - (instancetype)initWithQueue:(dispatch_queue_t)queue { if (self = [super init]) { _queue = queue; } return self; } - (void)invoke:(dispatch_block_t)block { dispatch_async(_queue, block); } - (std::shared_ptr)osScheduler { if (_queue == dispatch_get_main_queue()) { return RLMScheduler.mainRunLoop.osScheduler; } return realm::util::Scheduler::make_dispatch((__bridge void *)_queue); } - (void *)cacheKey { if (_queue == dispatch_get_main_queue()) { return RLMScheduler.mainRunLoop.cacheKey; } return (__bridge void *)_queue; } @end namespace { class ActorScheduler final : public realm::util::Scheduler { public: ActorScheduler(void (^invoke)(dispatch_block_t), dispatch_block_t verify) : _invoke(invoke) , _verify(verify) {} void invoke(realm::util::UniqueFunction&& fn) override { auto ptr = fn.release(); _invoke(^{ realm::util::UniqueFunction fn(ptr); fn(); }); } // This currently isn't actually implementable, but fortunately is only used // to report errors when we aren't on the thread, so triggering the actor // data race detection is good enough. bool is_on_thread() const noexcept override { _verify(); return true; } // This is used for OS Realm caching, which we don't use (as we have our own cache) bool is_same_as(const Scheduler *) const noexcept override { REALM_UNREACHABLE(); } // Actor isolated Realms can always invoke blocks bool can_invoke() const noexcept override { return true; } private: void (^_invoke)(dispatch_block_t); dispatch_block_t _verify; }; } @interface RLMActorScheduler : RLMScheduler @end RLM_HIDDEN @implementation RLMActorScheduler { id _actor; void (^_invoke)(dispatch_block_t); void (^_verify)(); } - (instancetype)initWithActor:(id)actor invoke:(void (^)(dispatch_block_t))invoke verify:(void (^)())verify { if (self = [super init]) { _actor = actor; _invoke = invoke; _verify = verify; } return self; } - (void)invoke:(dispatch_block_t)block { _invoke(block); } - (std::shared_ptr)osScheduler { return std::make_shared(_invoke, _verify); } - (void *)cacheKey { return (__bridge void *)_actor; } - (id)actor { return _actor; } @end @implementation RLMScheduler + (RLMScheduler *)currentRunLoop { if (pthread_main_np()) { return RLMScheduler.mainRunLoop; } static RLMScheduler *currentRunLoopScheduler = [[RLMScheduler alloc] init]; return currentRunLoopScheduler; } + (RLMScheduler *)mainRunLoop { static RLMScheduler *mainRunLoopScheduler = [[RLMMainRunLoopScheduler alloc] init]; return mainRunLoopScheduler; } + (RLMScheduler *)dispatchQueue:(dispatch_queue_t)queue { if (queue) { return [[RLMDispatchQueueScheduler alloc] initWithQueue:queue]; } return RLMScheduler.currentRunLoop; } + (RLMScheduler *)actor:(id)actor invoke:(void (^)(dispatch_block_t))invoke verify:(void (^)())verify { auto mainRunLoopScheduler = RLMScheduler.mainRunLoop; if (actor == mainRunLoopScheduler.actor) { return mainRunLoopScheduler; } return [[RLMActorScheduler alloc] initWithActor:actor invoke:invoke verify:verify]; } - (void)invoke:(dispatch_block_t)block { // Currently not used or needed for run loops REALM_UNREACHABLE(); } - (std::shared_ptr)osScheduler { // For normal thread-confined Realms we let object store create the scheduler return nullptr; } - (void *)cacheKey { return pthread_self(); } - (id)actor { return nil; } @end ================================================ FILE: Realm/RLMSchema.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObjectSchema; /** `RLMSchema` instances represent collections of model object schemas managed by a Realm. When using Realm, `RLMSchema` instances allow performing migrations and introspecting the database's schema. Schemas map to collections of tables in the core database. */ NS_SWIFT_SENDABLE // not actually immutable, but the public API kinda is @interface RLMSchema : NSObject #pragma mark - Properties /** An `NSArray` containing `RLMObjectSchema`s for all object types in the Realm. This property is intended to be used during migrations for dynamic introspection. @see `RLMObjectSchema` */ @property (nonatomic, readonly, copy) NSArray *objectSchema; #pragma mark - Methods /** Returns an `RLMObjectSchema` for the given class name in the schema. @param className The object class name. @return An `RLMObjectSchema` for the given class in the schema. @see `RLMObjectSchema` */ - (nullable RLMObjectSchema *)schemaForClassName:(NSString *)className; /** Looks up and returns an `RLMObjectSchema` for the given class name in the Realm. If there is no object of type `className` in the schema, an exception will be thrown. @param className The object class name. @return An `RLMObjectSchema` for the given class in this Realm. @see `RLMObjectSchema` */ - (RLMObjectSchema *)objectForKeyedSubscript:(NSString *)className; /** Returns whether two `RLMSchema` instances are equivalent. */ - (BOOL)isEqualToSchema:(RLMSchema *)schema; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSchema.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMSchema_Private.hpp" #import "RLMAccessor.h" #import "RLMObjectBase_Private.h" #import "RLMObject_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMProperty_Private.h" #import "RLMRealm_Private.hpp" #import "RLMSwiftSupport.h" #import "RLMUtil.hpp" #import #import #import #import #import #import #import using namespace realm; const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned; // RLMSchema private properties @interface RLMSchema () @property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName; @end // Private RLMSchema subclass that skips class registration on lookup @interface RLMPrivateSchema : RLMSchema @end @implementation RLMPrivateSchema - (RLMObjectSchema *)schemaForClassName:(NSString *)className { return self.objectSchemaByName[className]; } - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className { return [self schemaForClassName:className]; } @end static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init]; static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init]; static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init]; static enum class SharedSchemaState { Uninitialized, Initializing, Initialized } s_sharedSchemaState = SharedSchemaState::Uninitialized; @implementation RLMSchema { NSArray *_objectSchema; realm::Schema _objectStoreSchema; } static void createAccessors(RLMObjectSchema *objectSchema) { constexpr const size_t bufferSize = sizeof("RLM:Managed ") // includes spot for null terminator + std::numeric_limits::digits10 + realm::Group::max_table_name_length; char className[bufferSize] = "RLM:Managed "; char *const start = className + strlen(className); static unsigned long long count = 0; snprintf(start, bufferSize - strlen(className), "%llu %s", count++, objectSchema.className.UTF8String); objectSchema.accessorClass = RLMManagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, className); objectSchema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema); } void RLMSchemaEnsureAccessorsCreated(RLMSchema *schema) { for (RLMObjectSchema *objectSchema in schema.objectSchema) { if (objectSchema.accessorClass == objectSchema.objectClass) { // Locking inside the loop to optimize for the common case at // the expense of worse perf in the rare scenario where this is // actually needed. @synchronized(s_localNameToClass) { createAccessors(objectSchema); } } } } // Caller must @synchronize on s_localNameToClass static RLMObjectSchema *registerClass(Class cls) { if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) { return schema; } auto prevState = s_sharedSchemaState; s_sharedSchemaState = SharedSchemaState::Initializing; RLMObjectSchema *schema; { util::ScopeExit cleanup([&]() noexcept { s_sharedSchemaState = prevState; }); schema = [RLMObjectSchema schemaForObjectClass:cls]; } createAccessors(schema); // override sharedSchema class methods for performance RLMReplaceSharedSchemaMethod(cls, schema); s_privateSharedSchema.objectSchemaByName[schema.className] = schema; if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) { s_sharedSchema.objectSchemaByName[schema.className] = schema; } return schema; } // Caller must @synchronize on s_localNameToClass static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) { for (NSUInteger i = 0; i < count; i++) { Class cls = classes[i]; if (!RLMIsObjectSubclass(cls)) { continue; } if ([cls _realmIgnoreClass]) { continue; } NSString *className = NSStringFromClass(cls); if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) { continue; } if ([RLMSwiftSupport isSwiftClassName:className]) { className = [RLMSwiftSupport demangleClassName:className]; } // NSStringFromClass demangles the names for top-level Swift classes // but not for nested classes. _T indicates it's a Swift symbol, t // indicates it's a type, and C indicates it's a class. else if ([className hasPrefix:@"_TtC"]) { @throw RLMException(@"Object subclass '%@' must explicitly set the class's objective-c name with @objc(ClassName) because it is not a top-level public class.", className); } if (Class existingClass = s_localNameToClass[className]) { if (existingClass != cls) { @throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. " @"Please make sure '%@' is only linked once to your current target.", className); } continue; } s_localNameToClass[className] = cls; RLMReplaceClassNameMethod(cls, className); } } - (instancetype)init { self = [super init]; if (self) { _objectSchemaByName = [[NSMutableDictionary alloc] init]; } return self; } - (NSArray *)objectSchema { if (!_objectSchema) { _objectSchema = [_objectSchemaByName allValues]; } return _objectSchema; } - (void)setObjectSchema:(NSArray *)objectSchema { _objectSchema = objectSchema; _objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count]; for (RLMObjectSchema *object in objectSchema) { [_objectSchemaByName setObject:object forKey:object.className]; } } - (RLMObjectSchema *)schemaForClassName:(NSString *)className { if (RLMObjectSchema *schema = _objectSchemaByName[className]) { return schema; // fast path for already-initialized schemas } else if (Class cls = [RLMSchema classForString:className]) { [cls sharedSchema]; // initialize the schema return _objectSchemaByName[className]; // try again } else { return nil; } } - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className { RLMObjectSchema *schema = [self schemaForClassName:className]; if (!schema) { @throw RLMException(@"Object type '%@' not managed by the Realm", className); } return schema; } + (instancetype)schemaWithObjectClasses:(NSArray *)classes { NSUInteger count = classes.count; auto classArray = std::make_unique<__unsafe_unretained Class[]>(count); [classes getObjects:classArray.get() range:NSMakeRange(0, count)]; RLMSchema *schema = [[self alloc] init]; @synchronized(s_localNameToClass) { RLMRegisterClassLocalNames(classArray.get(), count); schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count]; for (Class cls in classes) { if (!RLMIsObjectSubclass(cls)) { @throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls); } schema->_objectSchemaByName[[cls className]] = registerClass(cls); } } NSMutableArray *errors = [NSMutableArray new]; // Verify that all of the targets of links are included in the class list [schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) { for (RLMProperty *prop in objectSchema.properties) { if (prop.type != RLMPropertyTypeObject) { continue; } if (!schema->_objectSchemaByName[prop.objectClassName]) { [errors addObject:[NSString stringWithFormat:@"- '%@.%@' links to class '%@', which is missing from the list of classes managed by the Realm", objectSchema.className, prop.name, prop.objectClassName]]; } } }]; if (errors.count) { @throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]); } return schema; } + (RLMObjectSchema *)sharedSchemaForClass:(Class)cls { @synchronized(s_localNameToClass) { // We create instances of Swift objects during schema init, and they // obviously need to not also try to initialize the schema if (s_sharedSchemaState == SharedSchemaState::Initializing) { return nil; } // Don't register the base classes in the schema even if someone calls // sharedSchema on them directly if (cls == [RLMObjectBase class] || class_getSuperclass(cls) == [RLMObjectBase class]) { return nil; } RLMRegisterClassLocalNames(&cls, 1); RLMObjectSchema *objectSchema = registerClass(cls); [cls initializeLinkedObjectSchemas]; return objectSchema; } } + (instancetype)partialSharedSchema { return s_sharedSchema; } + (instancetype)partialPrivateSharedSchema { return s_privateSharedSchema; } // schema based on runtime objects + (instancetype)sharedSchema { @synchronized(s_localNameToClass) { // We replace this method with one which just returns s_sharedSchema // once initialization is complete, but we still need to check if it's // already complete because it may have been done by another thread // while we were waiting for the lock if (s_sharedSchemaState == SharedSchemaState::Initialized) { return s_sharedSchema; } if (s_sharedSchemaState == SharedSchemaState::Initializing) { @throw RLMException(@"Illegal recursive call of +[%@ %@]. Note: Properties of Swift `Object` classes must not be prepopulated with queried results from a Realm.", self, NSStringFromSelector(_cmd)); } s_sharedSchemaState = SharedSchemaState::Initializing; try { // Make sure we've discovered all classes { unsigned int numClasses; using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>; malloc_ptr classes(objc_copyClassList(&numClasses), &free); RLMRegisterClassLocalNames(classes.get(), numClasses); } [s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) { registerClass(cls); }]; } catch (...) { s_sharedSchemaState = SharedSchemaState::Uninitialized; throw; } // Replace this method with one that doesn't need to acquire a lock Class metaClass = objc_getMetaClass(class_getName(self)); IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; }); class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:"); s_sharedSchemaState = SharedSchemaState::Initialized; } return s_sharedSchema; } // schema based on tables in a realm + (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema const&)objectStoreSchema { // cache descriptors for all subclasses of RLMObject NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()]; for (auto &objectSchema : objectStoreSchema) { RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema]; [schemaArray addObject:schema]; } // set class array and mapping RLMSchema *schema = [RLMSchema new]; schema.objectSchema = schemaArray; return schema; } + (Class)classForString:(NSString *)className { if (Class cls = s_localNameToClass[className]) { return cls; } if (Class cls = NSClassFromString(className)) { return RLMIsObjectSubclass(cls) ? cls : nil; } // className might be the local name of a Swift class we haven't registered // yet, so scan them all then recheck { unsigned int numClasses; std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free); RLMRegisterClassLocalNames(classes.get(), numClasses); } return s_localNameToClass[className]; } - (id)copyWithZone:(NSZone *)zone { RLMSchema *schema = [[RLMSchema allocWithZone:zone] init]; schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:_objectSchemaByName copyItems:YES]; return schema; } - (BOOL)isEqualToSchema:(RLMSchema *)schema { if (_objectSchemaByName.count != schema->_objectSchemaByName.count) { return NO; } __block BOOL matches = YES; [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) { if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) { *stop = YES; matches = NO; } }]; return matches; } - (NSString *)description { NSMutableString *objectSchemaString = [NSMutableString string]; NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]]; for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) { [objectSchemaString appendFormat:@"\t%@\n", [objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]]; } return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString]; } - (Schema)objectStoreCopy { if (_objectStoreSchema.size() == 0) { std::vector schema; schema.reserve(_objectSchemaByName.count); [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) { schema.push_back([objectSchema objectStoreCopy:self]); }]; // Having both obj-c and Swift classes for the same tables results in // duplicate ObjectSchemas that we need to filter out std::sort(begin(schema), end(schema), [](auto&& a, auto&& b) { return a.name < b.name; }); schema.erase(std::unique(begin(schema), end(schema), [](auto&& a, auto&& b) { if (a.name == b.name) { // If we make _realmObjectName public this needs to be turned into an exception REALM_ASSERT_DEBUG(a.persisted_properties == b.persisted_properties); return true; } return false; }), end(schema)); _objectStoreSchema = std::move(schema); } return _objectStoreSchema; } @end ================================================ FILE: Realm/RLMSchema_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability) @class RLMRealm; // // RLMSchema private interface // @interface RLMSchema () /** Returns an `RLMSchema` containing only the given `RLMObject` subclasses. @param classes The classes to be included in the schema. @return An `RLMSchema` containing only the given classes. */ + (instancetype)schemaWithObjectClasses:(NSArray *)classes; @property (nonatomic, readwrite, copy) NSArray *objectSchema; // schema based on runtime objects + (instancetype)sharedSchema; // schema based upon all currently registered object classes + (instancetype)partialSharedSchema; // private schema based upon all currently registered object classes. // includes classes that are excluded from the default schema. + (instancetype)partialPrivateSharedSchema; // class for string + (nullable Class)classForString:(NSString *)className; + (nullable RLMObjectSchema *)sharedSchemaForClass:(Class)cls; @end RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/RLMSchema_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMSchema_Private.h" #import namespace realm { class Schema; class ObjectSchema; } RLM_DIRECT_MEMBERS @interface RLMSchema () + (instancetype)dynamicSchemaFromObjectStoreSchema:(realm::Schema const&)objectStoreSchema; - (realm::Schema)objectStoreCopy; @end // Ensure that all objectSchema in the given schema have managed accessors created. // This is normally done during schema discovery but may not be when using // dynamically created schemas. void RLMSchemaEnsureAccessorsCreated(RLMSchema *schema); ================================================ FILE: Realm/RLMSectionedResults.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RLMValue; @class RLMResults; /** A `RLMSectionedResultsChange` object encapsulates information about changes to sectioned results that are reported by Realm notifications. `RLMSectionedResultsChange` is passed to the notification blocks registered with `-addNotificationBlock` on `RLMSectionedResults`, and reports what sections and rows in the collection changed since the last time the notification block was called. A complete example of updating a `UITableView` named `tv`: [tv beginUpdates]; [tv deleteRowsAtIndexPaths:changes.deletions withRowAnimation:UITableViewRowAnimationAutomatic]; [tv insertRowsAtIndexPaths:changes.insertions withRowAnimation:UITableViewRowAnimationAutomatic]; [tv reloadRowsAtIndexPaths:changes.modifications withRowAnimation:UITableViewRowAnimationAutomatic]; [tv insertSections:changes.sectionsToInsert withRowAnimation:UITableViewRowAnimationAutomatic]; [tv deleteSections:changes.sectionsToRemove withRowAnimation:UITableViewRowAnimationAutomatic]; [tv endUpdates]; All of the arrays in an `RLMSectionedResultsChange` are always sorted in ascending order. */ @interface RLMSectionedResultsChange : NSObject /// The index paths of objects in the previous version of the collection which have /// been removed from this one. @property (nonatomic, readonly) NSArray *deletions; /// The index paths in the new version of the collection which were newly inserted. @property (nonatomic, readonly) NSArray *insertions; /// The index paths in the old version of the collection which were modified. @property (nonatomic, readonly) NSArray *modifications; /// The indices of the sections to be inserted. @property (nonatomic, readonly) NSIndexSet *sectionsToInsert; /// The indices of the sections to be removed. @property (nonatomic, readonly) NSIndexSet *sectionsToRemove; /// Returns the index paths of the deletion indices in the given section. - (NSArray *)deletionsInSection:(NSUInteger)section; /// Returns the index paths of the insertion indices in the given section. - (NSArray *)insertionsInSection:(NSUInteger)section; /// Returns the index paths of the modification indices in the given section. - (NSArray *)modificationsInSection:(NSUInteger)section; @end /// The `RLMSectionedResult` protocol defines properties and methods common to both `RLMSectionedResults and RLMSection` @protocol RLMSectionedResult #pragma mark - Object Access /// The count of objects in the collection. @property (nonatomic, readonly) NSUInteger count; /// Returns the object for a given index in the collection. - (id)objectAtIndexedSubscript:(NSUInteger)index; /// Returns the object for a given index in the collection. - (id)objectAtIndex:(NSUInteger)index; #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of this collection. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live arrays, frozen collections can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; /** Indicates if the underlying collection is frozen. Frozen collections are immutable and can be accessed from any thread. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Sectioned Results Notifications /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` / `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(id, RLMSectionedResultsChange *))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` / `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(id, RLMSectionedResultsChange *))block queue:(dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` / `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(id, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths __attribute__((warn_unused_result)); /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` / `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @note When filtering with key paths a notification will be fired in the following scenarios: - An object in the collection has been modified at the filtered properties. - An object has been modified on the section key path property, and the result of that modification has changed it's position in the section, or the object may need to move to another section. - An object of the same observed type has been inserted or deleted from the Realm. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(id, RLMSectionedResultsChange *))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); @end /// An RLMSection contains the objects which belong to a specified section key. @interface RLMSection, RLMObjectType> : NSObject /// The value that represents the key in this section. @property (nonatomic, readonly) RLMKeyType key; /// The count of objects in the section. @property (nonatomic, readonly) NSUInteger count; /// Returns the object for a given index in the section. - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; /// Returns the object for a given index in the section. - (RLMObjectType)objectAtIndex:(NSUInteger)index; #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of this section. The frozen copy is an immutable section which contains the same data as this section currently contains, but will not update when writes are made to the containing Realm. Unlike live arrays, frozen collections can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning Holding onto a frozen section for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen section. This method resolves a reference to a live copy of the same frozen section. If called on a live section, will return itself. */ - (instancetype)thaw; /** Indicates if the underlying section is frozen. Frozen sections are immutable and can be accessed from any thread. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Section Notifications /** Registers a block to be called each time the section changes. The block will be asynchronously called with the initial section, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; RLMSection *section = sectionedResults[0] // section with dogs aged '5' already exists. self.token = [section addNotificationBlock:^(RLMSection *section, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"section.count: %zu", section.count); // => 2 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSection *, RLMSectionedResultsChange *))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the section changes. The block will be asynchronously called with the initial section, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; RLMSection *section = sectionedResults[0] // section with dogs aged '5' already exists. self.token = [section addNotificationBlock:^(RLMSection *section, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"section.count: %zu", section.count); // => 2 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSection *, RLMSectionedResultsChange *))block queue:(dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the section changes. The block will be asynchronously called with the initial section, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; RLMSection *section = sectionedResults[0] // section with dogs aged '5' already exists. self.token = [section addNotificationBlock:^(RLMSection *section, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"section.count: %zu", section.count); // => 2 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @note When filtering with key paths a notification will be fired in the following scenarios: - An object in the collection has been modified at the filtered properties. - An object has been modified on the section key path property, and the result of that modification has changed it's position in the section, or the object may need to move to another section. - An object of the same observed type has been inserted or deleted from the Realm. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSection *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths __attribute__((warn_unused_result)); /** Registers a block to be called each time the section changes. The block will be asynchronously called with the initial section, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSection` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; RLMSection *section = sectionedResults[0] // section with dogs aged '5' already exists. self.token = [section addNotificationBlock:^(RLMSection *section, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"section.count: %zu", section.count); // => 2 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @note When filtering with key paths a notification will be fired in the following scenarios: - An object in the collection has been modified at the filtered properties. - An object has been modified on the section key path property, and the result of that modification has changed it's position in the section, or the object may need to move to another section. - An object of the same observed type has been inserted or deleted from the Realm. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSection *, RLMSectionedResultsChange *))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); @end /// A lazily evaluated collection that holds elements in sections determined by a section key. @interface RLMSectionedResults, RLMObjectType: id> : NSObject /// An array of all keys in the sectioned results collection. @property (nonatomic) NSArray *allKeys; /// The total amount of sections in this collection. @property (nonatomic, readonly, assign) NSUInteger count; /// Returns the section at a given index. - (RLMSection *)objectAtIndexedSubscript:(NSUInteger)index; /// Returns the section at a given index. - (RLMSection *)objectAtIndex:(NSUInteger)index; #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of this sectioned results collection. The frozen copy is an immutable sectioned results collection which contains the same data as this sectioned results collection currently contains, but will not update when writes are made to the containing Realm. Unlike live sectioned results collections, frozen sectioned results collection can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning Holding onto a frozen sectioned results collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen sectioned results collection. This method resolves a reference to a live copy of the same frozen sectioned results collection. If called on a live section, will return itself. */ - (instancetype)thaw; /** Indicates if the underlying sectioned results collection is frozen. Frozen sectioned results collections are immutable and can be accessed from any thread. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Sectioned Results Notifications /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSectionedResults *, RLMSectionedResultsChange *))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSectionedResults *, RLMSectionedResultsChange *))block queue:(dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @note When filtering with key paths a notification will be fired in the following scenarios: - An object in the collection has been modified at the filtered properties. - An object has been modified on the section key path property, and the result of that modification has changed it's position in the section, or the object may need to move to another section. - An object of the same observed type has been inserted or deleted from the Realm. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSectionedResults *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths __attribute__((warn_unused_result)); /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the section were added, removed or modified. If a write transaction did not modify any objects in the section, the block is not called at all. See the `RLMSectionedResultsChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. At the time when the block is called, the `RLMSectionedResults` object will be fully evaluated and up-to-date. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; RLMSectionedResults *sectionedResults = [results sectionedResultsUsingKeyPath:@"age" ascending:YES]; self.token = [sectionedResults addNotificationBlock:^(RLMSectionedResults *sectionedResults, RLMSectionedResultsChange *changes) { // Only fired once for the example NSLog(@"sectionedResults.count: %zu", sectionedResults.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 5; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning The queue must be a serial queue. @note When filtering with key paths a notification will be fired in the following scenarios: - An object in the collection has been modified at the filtered properties. - An object has been modified on the section key path property, and the result of that modification has changed it's position in the section, or the object may need to move to another section. - An object of the same observed type has been inserted or deleted from the Realm. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSectionedResults *, RLMSectionedResultsChange *))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSectionedResults.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 "RLMSectionedResults_Private.hpp" #import "RLMAccessor.hpp" #import "RLMCollection_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObservation.hpp" #import "RLMRealm_Private.hpp" #import "RLMResults.h" #import "RLMResults_Private.hpp" #import "RLMThreadSafeReference_Private.hpp" namespace { struct CollectionCallbackWrapper { void (^block)(id, RLMSectionedResultsChange *); id collection; bool ignoreChangesInInitialNotification = true; void operator()(realm::SectionedResultsChangeSet const& changes) { if (ignoreChangesInInitialNotification) { ignoreChangesInInitialNotification = false; return block(collection, nil); } block(collection, [[RLMSectionedResultsChange alloc] initWithChanges:changes]); } }; template __attribute__((always_inline)) auto translateErrors(Function&& f) { return translateCollectionError(static_cast(f), @"SectionedResults"); } } // anonymous namespace @implementation RLMSectionedResultsChange { realm::SectionedResultsChangeSet _indices; } - (instancetype)initWithChanges:(realm::SectionedResultsChangeSet)indices { self = [super init]; if (self) { _indices = std::move(indices); } return self; } - (NSArray *)indexesFromVector:(std::vector const&)indexMap { NSMutableArray *a = [NSMutableArray new]; for (size_t i = 0; i < indexMap.size(); ++i) { NSUInteger path[2] = {i, 0}; for (auto index : indexMap[i].as_indexes()) { path[1] = index; [a addObject:[NSIndexPath indexPathWithIndexes:path length:2]]; } } return a; } - (NSArray *)insertions { return [self indexesFromVector:_indices.insertions]; } - (NSArray *)deletions { return [self indexesFromVector:_indices.deletions]; } - (NSArray *)modifications { return [self indexesFromVector:_indices.modifications]; } - (NSIndexSet *)sectionsToInsert { NSMutableIndexSet *indices = [NSMutableIndexSet new]; for (auto i : _indices.sections_to_insert.as_indexes()) { [indices addIndex:i]; } return indices; } - (NSIndexSet *)sectionsToRemove { NSMutableIndexSet *indices = [NSMutableIndexSet new]; for (auto i : _indices.sections_to_delete.as_indexes()) { [indices addIndex:i]; } return indices; } /// Returns the index paths of the deletion indices in the given section. - (NSArray *)deletionsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.deletions[section], section); } /// Returns the index paths of the insertion indices in the given section. - (NSArray *)insertionsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.insertions[section], section); } /// Returns the index paths of the modification indices in the given section. - (NSArray *)modificationsInSection:(NSUInteger)section { return RLMToIndexPathArray(_indices.modifications[section], section); } static NSString *indexPathToString(NSArray *indexes) { if (indexes.count == 0) { return @"[]"; } return [NSString stringWithFormat:@"[\n\t%@\n\t]", [indexes componentsJoinedByString:@"\n\t\t"]]; }; static NSString *indexSetToString(NSIndexSet *sections) { if (sections.count == 0) { return @"[]"; } return [NSString stringWithFormat:@"[\n\t%@\n\t]", sections]; } - (NSString *)description { return [NSString stringWithFormat:@" {\n\tinsertions: %@,\n\tdeletions: %@,\n\tmodifications: %@,\n\tsectionsToInsert: %@,\n\tsectionsToRemove: %@\n}", (__bridge void *)self, indexPathToString(self.insertions), indexPathToString(self.deletions), indexPathToString(self.modifications), indexSetToString(self.sectionsToInsert), indexSetToString(self.sectionsToRemove)]; } @end struct SectionedResultsKeyProjection { RLMClassInfo *_info; RLMSectionedResultsKeyBlock _block; realm::Mixed operator()(realm::Mixed obj, realm::SharedRealm) { RLMAccessorContext context(*_info); id value = _block(context.box(obj)); return context.unbox(value); } }; @interface RLMSectionedResultsEnumerator() { // The buffer supplied by fast enumeration does not retain the objects given // to it, but because we create objects on-demand and don't want them // autoreleased (a table can have more rows than the device has memory for // accessor objects) we need a thing to retain them. id _strongBuffer[16]; id _sectionedResult; } @end @implementation RLMSectionedResultsEnumerator - (instancetype)initWithSectionedResults:(RLMSectionedResults *)sectionedResults { if (self = [super init]) { _sectionedResult = [sectionedResults snapshot]; return self; } return nil; } - (instancetype)initWithResultsSection:(RLMSection *)resultsSection { if (self = [super init]) { _sectionedResult = resultsSection; return self; } return nil; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state count:(NSUInteger)len { NSUInteger batchCount = 0, count = [_sectionedResult count]; for (NSUInteger index = state->state; index < count && batchCount < len; ++index) { id sectionedResults = [_sectionedResult objectAtIndex:index]; _strongBuffer[batchCount] = sectionedResults; batchCount++; } for (NSUInteger i = batchCount; i < len; ++i) { _strongBuffer[i] = nil; } if (batchCount == 0) { // Release our data if we're done, as we're autoreleased and so may // stick around for a while if (_sectionedResult) { _sectionedResult = nil; } } state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer; state->state += batchCount; state->mutationsPtr = state->extra+1; return batchCount; } @end NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, RLMSectionedResults *collection) { __autoreleasing RLMSectionedResultsEnumerator *enumerator; if (state->state == 0) { enumerator = collection.fastEnumerator; state->extra[0] = (long)enumerator; state->extra[1] = collection.count; } else { enumerator = (__bridge id)(void *)state->extra[0]; } return [enumerator countByEnumeratingWithState:state count:len]; } NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, RLMSection *collection) { __autoreleasing RLMSectionedResultsEnumerator *enumerator; if (state->state == 0) { enumerator = collection.fastEnumerator; state->extra[0] = (long)enumerator; state->extra[1] = collection.count; } else { enumerator = (__bridge id)(void *)state->extra[0]; } return [enumerator countByEnumeratingWithState:state count:len]; } @interface RLMSectionedResults () @end @implementation RLMSectionedResults { @public realm::SectionedResults _sectionedResults; RLMSectionedResultsKeyBlock _keyBlock; // We need to hold an instance to the parent // `Results` so we can obtain a ThreadSafeReference // for notifications. realm::Results _results; @private RLMRealm *_realm; RLMClassInfo *_info; } - (instancetype)initWithResults:(realm::Results&&)results realm:(RLMRealm *)realm objectInfo:(RLMClassInfo&)objectInfo keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { if (self = [super init]) { _info = &objectInfo; _realm = realm; _keyBlock = keyBlock; _results = std::move(results); _sectionedResults = _results.sectioned_results(SectionedResultsKeyProjection{_info, _keyBlock}); } return self; } - (instancetype)initWithSectionedResults:(realm::SectionedResults&&)sectionedResults objectInfo:(RLMClassInfo&)objectInfo keyBlock:(RLMSectionedResultsKeyBlock)keyBlock{ if (self = [super init]) { _info = &objectInfo; _realm = _info->realm; _sectionedResults = std::move(sectionedResults); _keyBlock = keyBlock; } return self; } - (instancetype)initWithResults:(RLMResults *)results keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { if (self = [super init]) { _info = results.objectInfo; _realm = results.realm; _keyBlock = keyBlock; _results = results->_results; _sectionedResults = results->_results.sectioned_results(SectionedResultsKeyProjection{_info, _keyBlock}); } return self; } - (NSArray *)allKeys { return translateErrors([&] { NSUInteger count = [self count]; NSMutableArray *arr = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; i++) { [arr addObject:RLMMixedToObjc(_sectionedResults[i].key())]; } return arr; }); } - (RLMSectionedResultsEnumerator *)fastEnumerator { return [[RLMSectionedResultsEnumerator alloc] initWithSectionedResults:self]; } - (RLMRealm *)realm { return _realm; } - (NSUInteger)count { return translateErrors([&] { return _sectionedResults.size(); }); } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { return RLMFastEnumerate(state, len, self); } - (id)objectAtIndexedSubscript:(NSUInteger)index { return [self objectAtIndex:index]; } - (id)objectAtIndex:(NSUInteger)index { return [[RLMSection alloc] initWithResultsSection:_sectionedResults[index] parent:self]; } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } #pragma clang diagnostic pop - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _sectionedResults.add_notification_callback(CollectionCallbackWrapper{block, self}, std::move(keyPaths)); } - (RLMClassInfo *)objectInfo { return _info; } - (instancetype)resolveInRealm:(RLMRealm *)realm { return translateErrors([&] { if (realm.isFrozen) { return [[RLMSectionedResults alloc] initWithSectionedResults:_sectionedResults.freeze(realm->_realm) objectInfo:_info->resolve(realm) keyBlock:_keyBlock]; } else { auto sr = _sectionedResults.freeze(realm->_realm); sr.reset_section_callback(SectionedResultsKeyProjection {&_info->resolve(realm), _keyBlock}); return [[RLMSectionedResults alloc] initWithSectionedResults:std::move(sr) objectInfo:_info->resolve(realm) keyBlock:_keyBlock]; } }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_realm.thaw]; } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _results; } - (id)objectiveCMetadata { return _keyBlock; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(id)metadata realm:(RLMRealm *)realm { auto results = reference.resolve(realm->_realm); auto objType = RLMStringDataToNSString(results.get_object_type()); return [[RLMSectionedResults alloc] initWithResults:std::move(results) realm:realm objectInfo:realm->_info[objType] keyBlock:(RLMSectionedResultsKeyBlock)metadata]; } - (BOOL)isInvalidated { return translateErrors([&] { return !_sectionedResults.is_valid(); }); } - (NSString *)description { NSString *objType = @""; if (_info) { objType = [NSString stringWithFormat:@"<%@>", _info->rlmObjectSchema.className]; } const NSUInteger maxObjects = 100; auto str = [NSMutableString stringWithFormat:@"RLMSectionedResults%@ <%p> (\n", objType, (void *)self]; size_t index = 0, skipped = 0; for (RLMSection *section in self) { NSString *sub = [section description]; // Indent child objects NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; [str appendFormat:@"\t[%@] %@,\n", section.key, objDescription]; index++; if (index >= maxObjects) { skipped = self.count - maxObjects; break; } } // Remove last comma and newline characters if (self.count > 0) { [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)]; } if (skipped) { [str appendFormat:@"\n\t... %zu objects skipped.", skipped]; } [str appendFormat:@"\n)"]; return str; } - (RLMSectionedResults *)snapshot { RLMSectionedResults *sr = [RLMSectionedResults new]; sr->_sectionedResults = _sectionedResults.snapshot(); sr->_info = _info; sr->_realm = _realm; return sr; } - (BOOL)isFrozen { return translateErrors([&] { return _sectionedResults.is_frozen(); }); } @end /// Stores information about a given section during thread handover. @interface RLMSectionMetadata : NSObject @property (nonatomic, strong) RLMSectionedResultsKeyBlock keyBlock; @property (nonatomic, copy) id sectionKey; - (instancetype)initWithKeyBlock:(RLMSectionedResultsKeyBlock)keyBlock sectionKey:(id)sectionKey; @end @implementation RLMSectionMetadata - (instancetype)initWithKeyBlock:(RLMSectionedResultsKeyBlock)keyBlock sectionKey:(id)sectionKey { if (self = [super init]) { _keyBlock = keyBlock; _sectionKey = sectionKey; } return self; } @end @interface RLMSection () @end @implementation RLMSection { RLMSectionedResults *_parent; realm::ResultsSection _resultsSection; } - (NSString *)description { const NSUInteger maxObjects = 100; auto str = [NSMutableString stringWithFormat:@"RLMSection <%p> (\n", (void *)self]; size_t index = 0, skipped = 0; for (id obj in self) { NSString *sub = [obj description]; // Indent child objects NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]; [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription]; if (index >= maxObjects) { skipped = self.count - maxObjects; break; } } // Remove last comma and newline characters if (self.count > 0) { [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)]; } if (skipped) { [str appendFormat:@"\n\t... %zu objects skipped.", skipped]; } [str appendFormat:@"\n)"]; return str; } - (instancetype)initWithResultsSection:(realm::ResultsSection&&)resultsSection parent:(RLMSectionedResults *)parent { if (self = [super init]) { _resultsSection = std::move(resultsSection); _parent = parent; } return self; } - (id)objectAtIndexedSubscript:(NSUInteger)index { return [self objectAtIndex:index]; } - (id)objectAtIndex:(NSUInteger)index { RLMAccessorContext ctx(*_parent.objectInfo); return translateErrors([&] { return ctx.box(_resultsSection[index]); }); } - (NSUInteger)count { return translateErrors([&] { return _resultsSection.size(); }); } - (id)key { return translateErrors([&] { return RLMMixedToObjc(_resultsSection.key()); }); } - (RLMSectionedResultsEnumerator *)fastEnumerator { return [[RLMSectionedResultsEnumerator alloc] initWithResultsSection:self]; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(NSUInteger)len { return RLMFastEnumerate(state, len, self); } - (RLMRealm *)realm { return _parent.realm; } - (RLMClassInfo *)objectInfo { return _parent.objectInfo; } - (BOOL)isInvalidated { return translateErrors([&] { return !_resultsSection.is_valid(); }); } - (BOOL)isFrozen { return translateErrors([&] { return _parent.frozen; }); } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMSectionedResultsChange *))block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } #pragma clang diagnostic pop - (realm::NotificationToken)addNotificationCallback:(id)block keyPaths:(std::optional>>>&&)keyPaths { return _resultsSection.add_notification_callback(CollectionCallbackWrapper{block, self}, std::move(keyPaths)); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { return _parent->_results; } - (RLMSectionMetadata *)objectiveCMetadata { return [[RLMSectionMetadata alloc] initWithKeyBlock:_parent->_keyBlock sectionKey:self.key]; } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(RLMSectionMetadata *)metadata realm:(RLMRealm *)realm { auto results = reference.resolve(realm->_realm); auto objType = RLMStringDataToNSString(results.get_object_type()); RLMSectionedResults *sr = [[RLMSectionedResults alloc] initWithResults:std::move(results) realm:realm objectInfo:realm->_info[objType] keyBlock:metadata.keyBlock]; return translateErrors([&] { return [[RLMSection alloc] initWithResultsSection:sr->_sectionedResults[RLMObjcToMixed(metadata.sectionKey)] parent:sr]; }); } - (instancetype)resolveInRealm:(RLMRealm *)realm { return translateErrors([&] { RLMSectionedResults *sr = realm.isFrozen ? [_parent freeze] : [_parent thaw]; return [[RLMSection alloc] initWithResultsSection:sr->_sectionedResults[RLMObjcToMixed(self.key)] parent:sr]; }); } - (instancetype)freeze { if (self.frozen) { return self; } return [self resolveInRealm:_parent.realm.freeze]; } - (instancetype)thaw { if (!self.frozen) { return self; } return [self resolveInRealm:_parent.realm.thaw]; } @end ================================================ FILE: Realm/RLMSectionedResults_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 "RLMClassInfo.hpp" #import "RLMSectionedResults.h" #import #import @protocol RLMValue; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) RLM_HIDDEN_BEGIN RLM_DIRECT_MEMBERS @interface RLMSectionedResultsChange () - (instancetype)initWithChanges:(realm::SectionedResultsChangeSet)indices; @end RLM_DIRECT_MEMBERS @interface RLMSectionedResultsEnumerator : NSObject - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state count:(NSUInteger)len; - (instancetype)initWithSectionedResults:(RLMSectionedResults *)sectionedResults; - (instancetype)initWithResultsSection:(RLMSection *)resultsSection; @end @interface RLMSectionedResults () - (instancetype)initWithResults:(RLMResults *)results keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; - (RLMSectionedResultsEnumerator *)fastEnumerator; - (RLMClassInfo *)objectInfo; - (RLMSectionedResults *)snapshot; NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, RLMSectionedResults *collection); @end @interface RLMSection () - (instancetype)initWithResultsSection:(realm::ResultsSection&&)resultsSection parent:(RLMSectionedResults *)parent; - (RLMSectionedResultsEnumerator *)fastEnumerator; - (RLMClassInfo *)objectInfo; NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, RLMSection *collection); @end RLM_HIDDEN_END RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSet.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObject, RLMResults; /** A collection datatype used for storing distinct objects. - Note: `RLMSet` supports storing primitive and `RLMObject` types. `RLMSet` does not support storing Embedded Realm Objects. */ @interface RLMSet : NSObject #pragma mark - Properties /** The number of objects in the set. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The type of the objects in the set. */ @property (nonatomic, readonly, assign) RLMPropertyType type; /** Indicates whether the objects in the collection can be `nil`. */ @property (nonatomic, readonly, getter = isOptional) BOOL optional; /** The objects in the RLMSet as an NSArray value. */ @property (nonatomic, readonly) NSArray *allObjects; /** The class name of the objects contained in the set. Will be `nil` if `type` is not RLMPropertyTypeObject. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** The Realm which manages the set. Returns `nil` for unmanaged set. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** Indicates if the set can no longer be accessed. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; /** Indicates if the set is frozen. Frozen sets are immutable and can be accessed from any thread. Frozen sets are created by calling `-freeze` on a managed live set. Unmanaged sets are never frozen. */ @property (nonatomic, readonly, getter = isFrozen) BOOL frozen; #pragma mark - Adding, Removing, and Replacing Objects in a Set /** Adds an object to the set if it is not already present. @warning This method may only be called during a write transaction. @param object An object of the type contained in the set. */ - (void)addObject:(RLMObjectType)object; /** Adds an array of distinct objects to the set. @warning This method may only be called during a write transaction. @param objects An enumerable object such as `NSArray`, `NSSet` or `RLMResults` which contains objects of the same class as the set. */ - (void)addObjects:(id)objects; /** Removes a given object from the set. @warning This method may only be called during a write transaction. @param object The object in the set that you want to remove. */ - (void)removeObject:(RLMObjectType)object; /** Removes all objects from the set. @warning This method may only be called during a write transaction. */ - (void)removeAllObjects; /** Empties the receiving set, then adds each object contained in another given set. @warning This method may only be called during a write transaction. @param set The RLMSet whose members replace the receiving set's content. */ - (void)setSet:(RLMSet *)set; /** Removes from the receiving set each object that isn’t a member of another given set. @warning This method may only be called during a write transaction. @param set The RLMSet with which to perform the intersection. */ - (void)intersectSet:(RLMSet *)set; /** Removes each object in another given set from the receiving set, if present. @warning This method may only be called during a write transaction. @param set The set of objects to remove from the receiving set. */ - (void)minusSet:(RLMSet *)set; /** Adds each object in another given set to the receiving set, if not present. @warning This method may only be called during a write transaction. @param set The set of objects to add to the receiving set. */ - (void)unionSet:(RLMSet *)set; #pragma mark - Querying a Set /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /// :nodoc: - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /// :nodoc: - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /// :nodoc: - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /// :nodoc: - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths; /** Returns a Boolean value that indicates whether at least one object in the receiving set is also present in another given set. @param set The RLMSet to compare the receiving set to. @return YES if at least one object in the receiving set is also present in otherSet, otherwise NO. */ - (BOOL)intersectsSet:(RLMSet *)set; /** Returns a Boolean value that indicates whether every object in the receiving set is also present in another given set. @param set The RLMSet to compare the receiving set to. @return YES if every object in the receiving set is also present in otherSet, otherwise NO. */ - (BOOL)isSubsetOfSet:(RLMSet *)set; /** Returns a Boolean value that indicates whether a given object is present in the set. @param anObject An object to look for in the set. @return YES if anObject is present in the set, otherwise NO. */ - (BOOL)containsObject:(RLMObjectType)anObject; /** Compares the receiving set to another set. @param otherSet The set with which to compare the receiving set. @return YES if the contents of otherSet are equal to the contents of the receiving set, otherwise NO. */ - (BOOL)isEqualToSet:(RLMSet *)otherSet; #pragma mark - Sectioning a Set /** Sorts and sections this collection from a given property key path, returning the result as an instance of `RLMSectionedResults`. @param keyPath The property key path to sort on. @param ascending The direction to sort in. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; /** Sorts and sections this collection from a given array of sort descriptors, returning the result as an instance of `RLMSectionedResults`. @param sortDescriptors An array of `RLMSortDescriptor`s to sort by. @param keyBlock A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. @note The primary sort descriptor must be responsible for determining the section key. @return An instance of RLMSectionedResults. */ - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock; #pragma mark - Notifications /** Registers a block to be called each time the set changes. The block will be asynchronously called with the initial set, and then called again after each write transaction which changes any of the objects in the set, which objects are in the results, or the order of the objects in the set. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the set were added, removed or modified. If a write transaction did not modify any objects in the set, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. Person *person = [[Person allObjectsInRealm:realm] firstObject]; NSLog(@"person.dogs.count: %zu", person.dogs.count); // => 0 self.token = [person.dogs addNotificationBlock(RLMSet *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count) // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [person.dogs addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a non-frozen managed set. @param block The block to be called each time the set changes. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSet *_Nullable set, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block __attribute__((warn_unused_result)); /** Registers a block to be called each time the set changes. The block will be asynchronously called with the initial set, and then called again after each write transaction which changes any of the objects in the set, which objects are in the results, or the order of the objects in the set. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the set were added, removed or modified. If a write transaction did not modify any objects in the set, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSet *_Nullable set, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the set changes. The block will be asynchronously called with the initial set, and then called again after each write transaction which changes any of the objects in the set, which objects are in the results, or the order of the objects in the set. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the set were added, removed or modified. If a write transaction did not modify any objects in the set, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered on the given queue. If the queue is blocked and notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @param queue The serial queue to deliver notifications to. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSet *_Nullable set, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths queue:(nullable dispatch_queue_t)queue __attribute__((warn_unused_result)); /** Registers a block to be called each time the set changes. The block will be asynchronously called with the initial set, and then called again after each write transaction which changes any of the objects in the set, which objects are in the results, or the order of the objects in the set. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the set were added, removed or modified. If a write transaction did not modify any objects in the set, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. The error parameter is present only for backwards compatibility and will always be `nil`. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-invalidate` on the token. @warning This method cannot be called when the containing Realm is read-only or frozen. @warning The queue must be a serial queue. @param block The block to be called whenever a change occurs. @param keyPaths The block will be called for changes occurring on these keypaths. If no key paths are given, notifications are delivered for every property key path. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMSet *_Nullable set, RLMCollectionChange *_Nullable changes, NSError *_Nullable error))block keyPaths:(nullable NSArray *)keyPaths __attribute__((warn_unused_result)); #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the objects in the set. NSNumber *min = [object.setProperty minOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, `RLMSet`, and `NSData` properties. @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The minimum value of the property, or `nil` if the set is empty. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects in the set. NSNumber *max = [object.setProperty maxOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, `RLMSet`, and `NSData` properties. @param property The property whose maximum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The maximum value of the property, or `nil` if the set is empty. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of distinct values of a given property over all the objects in the set. NSNumber *sum = [object.setProperty sumOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, `RLMSet and `NSData` properties. @param property The property whose values should be summed. Only properties of types `int`, `float`, and `double` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects in the set. NSNumber *average = [object.setProperty averageOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMSet`, `RLMArray`, and `NSData` properties. @param property The property whose average value should be calculated. Only properties of types `int`, `float`, and `double` are supported. @return The average value of the given property, or `nil` if the set is empty. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; #pragma mark - Freeze /** Returns a frozen (immutable) snapshot of this set. The frozen copy is an immutable set which contains the same data as this et currently contains, but will not update when writes are made to the containing Realm. Unlike live sets, frozen sets can be accessed from any thread. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a managed set. @warning Holding onto a frozen set for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ - (instancetype)freeze; /** Returns a live version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ - (instancetype)thaw; #pragma mark - Unavailable Methods /** `-[RLMSet init]` is not available because `RLMSet`s cannot be created directly. `RLMSet` properties on `RLMObject`s are lazily created when accessed. */ - (instancetype)init __attribute__((unavailable("RLMSets cannot be created directly"))); /** `+[RLMSet new]` is not available because `RLMSet`s cannot be created directly. `RLMSet` properties on `RLMObject`s are lazily created when accessed. */ + (instancetype)new __attribute__((unavailable("RLMSet cannot be created directly"))); @end /// :nodoc: @interface RLMSet (Swift) // for use only in Swift class definitions - (instancetype)initWithObjectClassName:(NSString *)objectClassName; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSet.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMSet_Private.hpp" #import "RLMObjectSchema.h" #import "RLMObjectStore.h" #import "RLMObject_Private.h" #import "RLMProperty_Private.h" #import "RLMQueryUtil.hpp" #import "RLMSchema_Private.h" #import "RLMSwiftSupport.h" #import "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" @interface RLMSet () @end @implementation RLMSet { @public // Backing set when this instance is unmanaged NSMutableOrderedSet *_backingCollection; } #pragma mark - Initializers - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName keyType:(__unused RLMPropertyType)keyType { return [self initWithObjectClassName:objectClassName]; } - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional keyType:(__unused RLMPropertyType)keyType { return [self initWithObjectType:type optional:optional]; } - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName { REALM_ASSERT([objectClassName length] > 0); self = [super init]; if (self) { _objectClassName = objectClassName; _type = RLMPropertyTypeObject; } return self; } - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional { self = [super init]; if (self) { _type = type; _optional = optional; } return self; } - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property { _parentObject = parentObject; _property = property; _isLegacyProperty = property.isLegacy; } #pragma mark - Convenience wrappers used for all RLMSet types - (void)addObjects:(id)objects { for (id obj in objects) { [self addObject:obj]; } } - (void)addObject:(id)object { RLMSetValidateMatchingObjectType(self, object); changeSet(self, ^{ [_backingCollection addObject:object]; }); } - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index { REALM_TERMINATE("Replacing objects at an indexed subscript is not supported on RLMSet"); } - (void)setSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } changeSet(self, ^{ [_backingCollection removeAllObjects]; [_backingCollection unionOrderedSet:set->_backingCollection]; }); } - (void)intersectSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } changeSet(self, ^{ [_backingCollection intersectOrderedSet:set->_backingCollection]; }); } - (void)minusSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } changeSet(self, ^{ [_backingCollection minusOrderedSet:set->_backingCollection]; }); } - (void)unionSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } changeSet(self, ^{ [_backingCollection unionOrderedSet:set->_backingCollection]; }); } - (BOOL)isSubsetOfSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } return [_backingCollection isSubsetOfOrderedSet:set->_backingCollection]; } - (BOOL)intersectsSet:(RLMSet *)set { for (id obj in set) { RLMSetValidateMatchingObjectType(self, obj); } return [_backingCollection intersectsOrderedSet:set->_backingCollection]; } - (BOOL)containsObject:(id)obj { RLMSetValidateMatchingObjectType(self, obj); return [_backingCollection containsObject:obj]; } - (BOOL)isEqualToSet:(RLMSet *)set { return [self isEqual:set]; } - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending { return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]]; } - (nonnull id)objectAtIndexedSubscript:(NSUInteger)index { return [self objectAtIndex:index]; } // The compiler complains about the method's argument type not matching due to // it not having the generic type attached, but it doesn't seem to be possible // to actually include the generic type // http://www.openradar.me/radar?id=6135653276319744 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-parameter-types" - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block { return RLMAddNotificationBlock(self, block, nil, nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, nil, queue); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths { return RLMAddNotificationBlock(self, block, keyPaths,nil); } - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block keyPaths:(NSArray *)keyPaths queue:(dispatch_queue_t)queue { return RLMAddNotificationBlock(self, block, keyPaths, queue); } #pragma clang diagnostic pop #pragma mark - Unmanaged RLMSet implementation - (RLMRealm *)realm { return nil; } - (NSUInteger)count { return _backingCollection.count; } - (NSArray *)allObjects { return _backingCollection.array; } // For use with MutableSet subscripting, NSSet does not support // subscripting while its Swift counterpart `Set` does. - (id)objectAtIndex:(NSUInteger)index { validateSetBounds(self, index); return _backingCollection[index]; } - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes { if ([indexes indexGreaterThanOrEqualToIndex:self.count] != NSNotFound) { return nil; } return [_backingCollection objectsAtIndexes:indexes] ?: @[]; } - (id)firstObject { return _backingCollection.firstObject; } - (id)lastObject { return _backingCollection.lastObject; } - (BOOL)isInvalidated { return NO; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(__unused NSUInteger)len { return RLMUnmanagedFastEnumerate(_backingCollection, state); } static void changeSet(__unsafe_unretained RLMSet *const set, dispatch_block_t f) { if (!set->_backingCollection) { set->_backingCollection = [NSMutableOrderedSet new]; } if (RLMObjectBase *parent = set->_parentObject) { [parent willChangeValueForKey:set->_property.name]; f(); [parent didChangeValueForKey:set->_property.name]; } else { f(); } } static void validateSetBounds(__unsafe_unretained RLMSet *const set, NSUInteger index, bool allowOnePastEnd=false) { NSUInteger max = set->_backingCollection.count + allowOnePastEnd; if (index >= max) { @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).", (unsigned long long)index, (unsigned long long)max); } } - (void)removeAllObjects { changeSet(self, ^{ [_backingCollection removeAllObjects]; }); } - (void)removeObject:(id)object { RLMSetValidateMatchingObjectType(self, object); changeSet(self, ^{ [_backingCollection removeObject:object]; }); } - (void)replaceAllObjectsWithObjects:(NSArray *)objects { changeSet(self, ^{ [_backingCollection removeAllObjects]; if (!objects || (id)objects == NSNull.null) { return; } for (id object in objects) { // object should always be non-nil since it's a value stored in a // NSArray, but as of Xcode 16.3 [Decimal128?] sometimes ends up // with nil instead of NSNull when bridged from Swift. [_backingCollection addObject:object ?: NSNull.null]; } }); } - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); RLMResults *results = [self objectsWhere:predicateFormat args:args]; va_end(args); return results; } - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args { return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]]; } - (RLMPropertyType)typeForProperty:(NSString *)propertyName { if ([propertyName isEqualToString:@"self"]) { return _type; } RLMObjectSchema *objectSchema; if (_backingCollection.count) { objectSchema = [_backingCollection[0] objectSchema]; } else { objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName]; } return RLMValidatedProperty(objectSchema, propertyName).type; } - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel { // Although delegating to valueForKeyPath: here would allow to support // nested key paths as well, limiting functionality gives consistency // between unmanaged and managed arrays. if ([key rangeOfString:@"."].location != NSNotFound) { @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators."); } if ([op isEqualToString:@"@distinctUnionOfObjects"]) { @throw RLMException(@"this class does not implement the distinctUnionOfObjects"); } bool allowDate = false; bool sum = false; if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) { allowDate = true; } else if ([op isEqualToString:@"@sum"]) { sum = true; } else if (![op isEqualToString:@"@avg"]) { // Just delegate to NSSet for all other operators return [_backingCollection valueForKeyPath:[op stringByAppendingPathExtension:key]]; } RLMPropertyType type = [self typeForProperty:key]; if (!canAggregate(type, allowDate)) { NSString *method = sel ? NSStringFromSelector(sel) : op; if (_type == RLMPropertyTypeObject) { @throw RLMException(@"%@: is not supported for %@ property '%@.%@'", method, RLMTypeToString(type), _objectClassName, key); } else { @throw RLMException(@"%@ is not supported for %@%s set", method, RLMTypeToString(_type), _optional ? "?" : ""); } } // `valueForKeyPath` on NSSet will only return distinct values, which is an // issue as the realm::object_store::Set aggregate methods will calculate // the result based on each element of a property regardless of uniqueness. // To get around this we will need to use the `array` property of the NSMutableOrderedSet NSArray *values = [key isEqualToString:@"self"] ? _backingCollection.array : [_backingCollection.array valueForKey:key]; if (_optional) { // Filter out NSNull values to match our behavior on managed arrays NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) { return obj != NSNull.null; }]; if (nonnull.count < values.count) { values = [values objectsAtIndexes:nonnull]; } } id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]]; return sum && !result ? @0 : result; } static NSSet *toUnorderedSet(id value) { if (auto orderedSet = RLMDynamicCast(value)) { return orderedSet.set; } return value; } - (id)valueForKeyPath:(NSString *)keyPath { if ([keyPath characterAtIndex:0] != '@') { return toUnorderedSet(_backingCollection ? [_backingCollection valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath]); } if (!_backingCollection) { _backingCollection = [NSMutableOrderedSet new]; } NSUInteger dot = [keyPath rangeOfString:@"."].location; if (dot == NSNotFound) { return [_backingCollection valueForKeyPath:keyPath]; } NSString *op = [keyPath substringToIndex:dot]; NSString *key = [keyPath substringFromIndex:dot + 1]; return [self aggregateProperty:key operation:op method:nil]; } - (id)valueForKey:(NSString *)key { if ([key isEqualToString:RLMInvalidatedKey]) { return @NO; // Unmanaged sets are never invalidated } if (!_backingCollection) { _backingCollection = [NSMutableOrderedSet new]; } return toUnorderedSet([_backingCollection valueForKey:key]); } - (void)setValue:(id)value forKey:(NSString *)key { if ([key isEqualToString:@"self"]) { RLMSetValidateMatchingObjectType(self, value); [_backingCollection removeAllObjects]; [_backingCollection addObject:value]; return; } else if (_type == RLMPropertyTypeObject) { [_backingCollection setValue:value forKey:key]; } else { [self setValue:value forUndefinedKey:key]; } } - (id)minOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@min" method:_cmd]; } - (id)maxOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@max" method:_cmd]; } - (id)sumOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@sum" method:_cmd]; } - (id)averageOfProperty:(NSString *)property { return [self aggregateProperty:property operation:@"@avg" method:_cmd]; } - (BOOL)isEqual:(id)object { if (auto set = RLMDynamicCast(object)) { return !set.realm && ((_backingCollection.count == 0 && set->_backingCollection.count == 0) || [_backingCollection isEqual:set->_backingCollection]); } return NO; } - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { RLMValidateSetObservationKey(keyPath, self); [super addObserver:observer forKeyPath:keyPath options:options context:context]; } void RLMSetValidateMatchingObjectType(__unsafe_unretained RLMSet *const set, __unsafe_unretained id const value) { if (!value && !set->_optional) { @throw RLMException(@"Invalid nil value for set of '%@'.", set->_objectClassName ?: RLMTypeToString(set->_type)); } if (set->_type != RLMPropertyTypeObject) { if (!RLMValidateValue(value, set->_type, set->_optional, false, nil)) { @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.", value, [value class], RLMTypeToString(set->_type), set->_optional ? "?" : ""); } return; } auto object = RLMDynamicCast(value); if (!object) { return; } if (!object->_objectSchema) { @throw RLMException(@"Object cannot be inserted unless the schema is initialized. " "This can happen if you try to insert objects into a RLMSet / Set from a default value or from an overriden unmanaged initializer (`init()`)."); } if (![set->_objectClassName isEqualToString:object->_objectSchema.className] && (set->_type != RLMPropertyTypeAny)) { @throw RLMException(@"Object of type '%@' does not match RLMSet type '%@'.", object->_objectSchema.className, set->_objectClassName); } } #pragma mark - Key Path Strings - (NSString *)propertyKey { return _property.name; } #pragma mark - Methods unsupported on unmanaged RLMSet instances #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray *)keyPaths { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray *)sortDescriptors keyBlock:(RLMSectionedResultsKeyBlock)keyBlock { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (instancetype)freeze { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } - (instancetype)thaw { @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm"); } #pragma mark - Thread Confined Protocol Conformance - (realm::ThreadSafeReference)makeThreadSafeReference { REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`"); } - (id)objectiveCMetadata { REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`"); } + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(id)metadata realm:(RLMRealm *)realm { REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`"); } #pragma clang diagnostic pop // unused parameter warning #pragma mark - Superclass Overrides - (NSString *)description { return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth]; } - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth { return RLMDescriptionWithMaxDepth(@"RLMSet", self, depth); } @end ================================================ FILE: Realm/RLMSet_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 #import @class RLMObjectBase, RLMProperty; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMSet () - (instancetype)initWithObjectClassName:(NSString *)objectClassName; - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional; - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth; - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; // YES if the property is declared with old property syntax. @property (nonatomic, readonly) BOOL isLegacyProperty; // The name of the property which this collection represents @property (nonatomic, readonly) NSString *propertyKey; @end void RLMSetValidateMatchingObjectType(RLMSet *set, id value); @interface RLMManagedSet : RLMSet - (instancetype)initWithParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSet_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMSet_Private.h" #import "RLMCollection_Private.hpp" #import "RLMResults_Private.hpp" namespace realm { class SetBase; class CollectionBase; namespace object_store { class Set; } } @class RLMObjectBase, RLMObjectSchema, RLMProperty; class RLMClassInfo; class RLMObservationInfo; @interface RLMSet () { @protected NSString *_objectClassName; RLMPropertyType _type; BOOL _optional; @public // The name of the property which this RLMSet represents RLMProperty *_property; __weak RLMObjectBase *_parentObject; } @end @interface RLMManagedSet () - (RLMManagedSet *)initWithBackingCollection:(realm::object_store::Set)set parentInfo:(RLMClassInfo *)parentInfo property:(__unsafe_unretained RLMProperty *const)property; - (bool)isBackedBySet:(realm::object_store::Set const&)set; // deletes all objects in the RLMSet from their containing realms - (void)deleteObjectsFromRealm; @end void RLMValidateSetObservationKey(NSString *keyPath, RLMSet *set); // Initialize the observation info for a set if needed void RLMEnsureSetObservationInfo(std::unique_ptr& info, NSString *keyPath, RLMSet *set, id observed); ================================================ FILE: Realm/RLMSwiftBridgingHeader.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import @interface RLMRealm (Swift) + (void)resetRealmState; @end @interface RLMArray (Swift) - (instancetype)initWithObjectClassName:(NSString *)objectClassName; - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; @end @interface RLMResults (Swift) - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; @end @interface RLMObjectBase (Swift) - (instancetype)initWithRealm:(RLMRealm *)realm schema:(RLMObjectSchema *)schema defaultValues:(BOOL)useDefaults; + (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args; @end ================================================ FILE: Realm/RLMSwiftCollectionBase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 @class RLMObjectBase, RLMResults, RLMProperty, RLMLinkingObjects; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMSwiftCollectionBase : NSProxy @property (nonatomic, strong) id _rlmCollection; - (instancetype)init; + (Class)_backingCollectionType; - (instancetype)initWithCollection:(id)collection; - (nullable id)valueForKey:(NSString *)key; - (nullable id)valueForKeyPath:(NSString *)keyPath; - (BOOL)isEqual:(nullable id)object; @end @interface RLMLinkingObjectsHandle : NSObject - (instancetype)initWithObject:(RLMObjectBase *)object property:(RLMProperty *)property; - (instancetype)initWithLinkingObjects:(RLMResults *)linkingObjects; @property (nonatomic, readonly) RLMLinkingObjects *results; @property (nonatomic, readonly) NSString *_propertyKey; @property (nonatomic, readonly) BOOL _isLegacyProperty; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSwiftCollectionBase.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMSwiftCollectionBase.h" #import "RLMArray_Private.hpp" #import "RLMObjectSchema_Private.h" #import "RLMObject_Private.hpp" #import "RLMObservation.hpp" #import "RLMProperty_Private.h" #import "RLMSet_Private.hpp" #import "RLMDictionary_Private.hpp" @interface RLMArray (KVO) - (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes; @end // Some of the things declared in the interface are handled by the proxy forwarding #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation RLMSwiftCollectionBase + (id)_unmanagedCollection { return nil; } + (Class)_backingCollectionType { REALM_UNREACHABLE(); } - (instancetype)init { return self; } - (instancetype)initWithCollection:(id)collection { __rlmCollection = collection; return self; } - (id)_rlmCollection { if (!__rlmCollection) { __rlmCollection = self.class._unmanagedCollection; } return __rlmCollection; } - (BOOL)isKindOfClass:(Class)aClass { return [self._rlmCollection isKindOfClass:aClass] || RLMIsKindOfClass(object_getClass(self), aClass); } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [(id)self._rlmCollection methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self._rlmCollection]; } - (id)forwardingTargetForSelector:(__unused SEL)sel { return self._rlmCollection; } - (BOOL)respondsToSelector:(SEL)aSelector { return [self._rlmCollection respondsToSelector:aSelector]; } - (void)doesNotRecognizeSelector:(SEL)aSelector { [(id)self._rlmCollection doesNotRecognizeSelector:aSelector]; } - (BOOL)isEqual:(id)object { if (auto collection = RLMDynamicCast(object)) { if (!__rlmCollection) { return !collection->__rlmCollection.realm && collection->__rlmCollection.count == 0; } return [__rlmCollection isEqual:collection->__rlmCollection]; } return NO; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { return aProtocol == @protocol(NSFastEnumeration) || [self._rlmCollection conformsToProtocol:aProtocol]; } @end #pragma clang diagnostic pop @implementation RLMLinkingObjectsHandle { realm::TableKey _tableKey; realm::ObjKey _objKey; RLMClassInfo *_info; RLMRealm *_realm; RLMProperty *_property; RLMResults *_results; } - (instancetype)initWithObject:(RLMObjectBase *)object property:(RLMProperty *)prop { if (!(self = [super init])) { return nil; } // KeyPath strings will invoke this initializer with an unmanaged object // so guard against that. if (object->_realm) { auto& obj = object->_row; _tableKey = obj.get_table()->get_key(); _objKey = obj.get_key(); _info = object->_info; _realm = object->_realm; } _property = prop; return self; } - (instancetype)initWithLinkingObjects:(RLMResults *)linkingObjects { if (!(self = [super init])) { return nil; } _realm = linkingObjects.realm; _results = linkingObjects; return self; } - (RLMResults *)results { if (_results) { return _results; } [_realm verifyThread]; auto table = _realm.group.get_table(_tableKey); if (!table->is_valid(_objKey)) { @throw RLMException(@"Object has been deleted or invalidated."); } auto obj = _realm.group.get_table(_tableKey)->get_object(_objKey); auto& objectInfo = _realm->_info[_property.objectClassName]; auto& linkOrigin = _info->objectSchema->computed_properties[_property.index].link_origin_property_name; auto linkingProperty = objectInfo.objectSchema->property_for_name(linkOrigin); realm::Results results(_realm->_realm, obj.get_backlink_view(objectInfo.table(), linkingProperty->column_key)); _results = [RLMLinkingObjects resultsWithObjectInfo:objectInfo results:std::move(results)]; _realm = nil; return _results; } - (NSString *)_propertyKey { return _property.name; } - (BOOL)_isLegacyProperty { return _property.isLegacy; } @end ================================================ FILE: Realm/RLMSwiftObject.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** `Object` is a class used to define Realm model objects. In Realm you define your model classes by subclassing `Object` and adding properties to be managed. You then instantiate and use your custom subclasses instead of using the `Object` class directly. ```swift class Dog: Object { @objc dynamic var name: String = "" @objc dynamic var adopted: Bool = false let siblings = List() } ``` ### Supported property types - `String`, `NSString` - `Int` - `Int8`, `Int16`, `Int32`, `Int64` - `Float` - `Double` - `Bool` - `Date`, `NSDate` - `Data`, `NSData` - `Decimal128` - `ObjectId` - `@objc enum` which has been delcared as conforming to `RealmEnum`. - `RealmOptional` for optional numeric properties - `Object` subclasses, to model many-to-one relationships - `EmbeddedObject` subclasses, to model owning one-to-one relationships - `List`, to model many-to-many relationships `String`, `NSString`, `Date`, `NSDate`, `Data`, `NSData`, `Decimal128`, and `ObjectId` properties can be declared as optional. `Object` and `EmbeddedObject` subclasses *must* be declared as optional. `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `Bool`, enum, and `List` properties cannot. To store an optional number, use `RealmOptional`, `RealmOptional`, `RealmOptional`, or `RealmOptional` instead, which wraps an optional numeric value. Lists cannot be optional at all. All property types except for `List` and `RealmOptional` *must* be declared as `@objc dynamic var`. `List` and `RealmOptional` properties must be declared as non-dynamic `let` properties. Swift `lazy` properties are not allowed. Note that none of the restrictions listed above apply to properties that are configured to be ignored by Realm. ### Querying You can retrieve all objects of a given type from a Realm by calling the `objects(_:)` instance method. ### Relationships See our [Objective-C guide](https://docs.mongodb.com/realm/sdk/swift/fundamentals/relationships/) for more details. */ @interface RealmSwiftObject : RLMObjectBase @end /** `EmbeddedObject` is a base class used to define embedded Realm model objects. Embedded objects work similarly to normal objects, but are owned by a single parent Object (which itself may be embedded). Unlike normal top-level objects, embedded objects cannot be directly created in or added to a Realm. Instead, they can only be created as part of a parent object, or by assigning an unmanaged object to a parent object's property. Embedded objects are automatically deleted when the parent object is deleted or when the parent is modified to no longer point at the embedded object, either by reassigning an Object property or by removing the embedded object from the List containing it. Embedded objects can only ever have a single parent object which links to them, and attempting to link to an existing managed embedded object will throw an exception. The property types supported on `EmbeddedObject` are the same as for `Object`, except for that embedded objects cannot link to top-level objects, so `Object` and `List` properties are not supported (`EmbeddedObject` and `List` *are*). Embedded objects cannot have primary keys or indexed properties. ```swift class Owner: Object { @objc dynamic var name: String = "" let dogs = List() } class Dog: EmbeddedObject { @objc dynamic var name: String = "" @objc dynamic var adopted: Bool = false let owner = LinkingObjects(fromType: Owner.self, property: "dogs") } ``` */ @interface RealmSwiftEmbeddedObject : RLMObjectBase @end /** `AsymmetricObject` is a base class used to define asymmetric Realm objects. Asymmetric objects can only be created using the `create(_ object:)` function, and cannot be added, removed or queried. When created, asymmetric objects will be synced unidirectionally to the MongoDB database and cannot be accessed locally. Incoming links from any asymmetric table are not allowed, meaning embedding an asymmetric object within an `Object` will throw an error. The property types supported on `AsymmetricObject` are the same as for `Object`, except for that asymmetric objects can only link to embedded objects, so `Object` and `List` properties are not supported (`EmbeddedObject` and `List` *are*). ```swift class Person: AsymmetricObject { @Persisted(primaryKey: true) var _id: ObjectId = ObjectId.generate() @Persisted var name: String @Persisted var age: Int } ``` */ @interface RealmSwiftAsymmetricObject : RLMObjectBase @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSwiftProperty.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 #import @class RLMObjectBase, RLMArray, RLMSet; #ifdef __cplusplus extern "C" { #endif RLM_HEADER_AUDIT_BEGIN(nullability) #define REALM_FOR_EACH_SWIFT_PRIMITIVE_TYPE(macro) \ macro(bool, Bool, bool) \ macro(double, Double, double) \ macro(float, Float, float) \ macro(int64_t, Int64, int) #define REALM_FOR_EACH_SWIFT_OBJECT_TYPE(macro) \ macro(NSString, String, string) \ macro(NSDate, Date, date) \ macro(NSData, Data, data) \ macro(NSUUID, UUID, uuid) \ macro(RLMDecimal128, Decimal128, decimal128) \ macro(RLMObjectId, ObjectId, objectId) #define REALM_SWIFT_PROPERTY_ACCESSOR(objc, swift, rlmtype) \ objc RLMGetSwiftProperty##swift(RLMObjectBase *, uint16_t); \ objc RLMGetSwiftProperty##swift##Optional(RLMObjectBase *, uint16_t, bool *); \ void RLMSetSwiftProperty##swift(RLMObjectBase *, uint16_t, objc); REALM_FOR_EACH_SWIFT_PRIMITIVE_TYPE(REALM_SWIFT_PROPERTY_ACCESSOR) #undef REALM_SWIFT_PROPERTY_ACCESSOR #define REALM_SWIFT_PROPERTY_ACCESSOR(objc, swift, rlmtype) \ objc *_Nullable RLMGetSwiftProperty##swift(RLMObjectBase *, uint16_t); \ void RLMSetSwiftProperty##swift(RLMObjectBase *, uint16_t, objc *_Nullable); REALM_FOR_EACH_SWIFT_OBJECT_TYPE(REALM_SWIFT_PROPERTY_ACCESSOR) #undef REALM_SWIFT_PROPERTY_ACCESSOR id _Nullable RLMGetSwiftPropertyAny(RLMObjectBase *, uint16_t); void RLMSetSwiftPropertyAny(RLMObjectBase *, uint16_t, id); RLMObjectBase *_Nullable RLMGetSwiftPropertyObject(RLMObjectBase *, uint16_t); void RLMSetSwiftPropertyNil(RLMObjectBase *, uint16_t); void RLMSetSwiftPropertyObject(RLMObjectBase *, uint16_t, RLMObjectBase *_Nullable); RLMArray *_Nonnull RLMGetSwiftPropertyArray(RLMObjectBase *obj, uint16_t); RLMSet *_Nonnull RLMGetSwiftPropertySet(RLMObjectBase *obj, uint16_t); RLMDictionary *_Nonnull RLMGetSwiftPropertyMap(RLMObjectBase *obj, uint16_t); RLM_HEADER_AUDIT_END(nullability) #ifdef __cplusplus } // extern "C" #endif ================================================ FILE: Realm/RLMSwiftSupport.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface RLMSwiftSupport : NSObject + (BOOL)isSwiftClassName:(NSString *)className; + (NSString *)demangleClassName:(NSString *)className; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSwiftSupport.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMSwiftSupport.h" @implementation RLMSwiftSupport + (BOOL)isSwiftClassName:(NSString *)className { return [className rangeOfString:@"."].location != NSNotFound; } + (NSString *)demangleClassName:(NSString *)className { return [className substringFromIndex:[className rangeOfString:@"."].location + 1]; } @end ================================================ FILE: Realm/RLMSwiftValueStorage.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @class RLMObjectBase, RLMProperty; /// This class implements the backing storage for `RealmProperty<>` and `RealmOptional<>`. /// This class should not be subclassed or used directly. @interface RLMSwiftValueStorage : NSProxy - (instancetype)init; @end /// Retrieves the value that is stored, or nil if it is empty. FOUNDATION_EXTERN id _Nullable RLMGetSwiftValueStorage(RLMSwiftValueStorage *); /// Sets a value on the property this instance represents for an object. FOUNDATION_EXTERN void RLMSetSwiftValueStorage(RLMSwiftValueStorage *, id _Nullable); /// Initialises managed accessors on an instance of `RLMSwiftValueStorage` /// @param parent The enclosing parent object. /// @param prop The property which this class represents. FOUNDATION_EXTERN void RLMInitializeManagedSwiftValueStorage(RLMSwiftValueStorage *, RLMObjectBase *parent, RLMProperty *prop); /// Initialises unmanaged accessors on an instance of `RLMSwiftValueStorage` /// @param parent The enclosing parent object. /// @param prop The property which this class represents. FOUNDATION_EXTERN void RLMInitializeUnmanagedSwiftValueStorage(RLMSwiftValueStorage *, RLMObjectBase *parent, RLMProperty *prop); /// Gets the property name for the RealmProperty instance. This is required for tracing the key path on /// objects that use the legacy property declaration syntax. FOUNDATION_EXTERN NSString *RLMSwiftValueStorageGetPropertyName(RLMSwiftValueStorage *); RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMSwiftValueStorage.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMSwiftValueStorage.h" #import "RLMAccessor.hpp" #import "RLMObject_Private.hpp" #import "RLMProperty_Private.h" #import "RLMUtil.hpp" #import namespace { struct SwiftValueStorageBase { virtual id get() = 0; virtual void set(id) = 0; virtual NSString *propertyName() = 0; virtual ~SwiftValueStorageBase() = default; }; class UnmanagedSwiftValueStorage : public SwiftValueStorageBase { public: id get() override { return _value; } void set(__unsafe_unretained const id newValue) override { @autoreleasepool { RLMObjectBase *object = _parent; [object willChangeValueForKey:_property]; _value = newValue; [object didChangeValueForKey:_property]; } } void attach(__unsafe_unretained RLMObjectBase *const obj, NSString *property) { if (!_property) { _property = property; _parent = obj; } } NSString *propertyName() override { return _property; } private: id _value; NSString *_property; __weak RLMObjectBase *_parent; }; class ManagedSwiftValueStorage : public SwiftValueStorageBase { public: ManagedSwiftValueStorage(RLMObjectBase *obj, RLMProperty *prop) : _realm(obj->_realm) , _object(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row) , _columnName(prop.columnName.UTF8String) , _ctx(obj, &obj->_info->objectSchema->persisted_properties[prop.index]) { } id get() override { return _object.get_property_value(_ctx, _columnName); } void set(__unsafe_unretained id const value) override { _object.set_property_value(_ctx, _columnName, value ?: NSNull.null); } NSString *propertyName() override { // Should never be called on a managed object. REALM_UNREACHABLE(); } private: // We have to hold onto a strong reference to the Realm as // RLMAccessorContext holds a non-retaining one. __unused RLMRealm *_realm; realm::Object _object; std::string _columnName; RLMAccessorContext _ctx; }; } // anonymous namespace @interface RLMSwiftValueStorage () { std::unique_ptr _impl; } @end @implementation RLMSwiftValueStorage - (instancetype)init { return self; } - (BOOL)isKindOfClass:(Class)aClass { return [RLMGetSwiftValueStorage(self) isKindOfClass:aClass] || RLMIsKindOfClass(object_getClass(self), aClass); } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [RLMGetSwiftValueStorage(self) methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:RLMGetSwiftValueStorage(self)]; } - (id)forwardingTargetForSelector:(__unused SEL)sel { return RLMGetSwiftValueStorage(self); } - (BOOL)respondsToSelector:(SEL)aSelector { return [RLMGetSwiftValueStorage(self) respondsToSelector:aSelector]; } - (void)doesNotRecognizeSelector:(SEL)aSelector { [RLMGetSwiftValueStorage(self) doesNotRecognizeSelector:aSelector]; } id RLMGetSwiftValueStorage(__unsafe_unretained RLMSwiftValueStorage *const self) { try { return self->_impl ? RLMCoerceToNil(self->_impl->get()) : nil; } catch (std::exception const& err) { @throw RLMException(err); } } void RLMSetSwiftValueStorage(__unsafe_unretained RLMSwiftValueStorage *const self, __unsafe_unretained const id value) { try { if (!self->_impl && value) { self->_impl.reset(new UnmanagedSwiftValueStorage); } if (self->_impl) { self->_impl->set(value); } } catch (std::exception const& err) { @throw RLMException(err); } } void RLMInitializeManagedSwiftValueStorage(__unsafe_unretained RLMSwiftValueStorage *const self, __unsafe_unretained RLMObjectBase *const parent, __unsafe_unretained RLMProperty *const prop) { REALM_ASSERT(parent->_realm); self->_impl.reset(new ManagedSwiftValueStorage(parent, prop)); } void RLMInitializeUnmanagedSwiftValueStorage(__unsafe_unretained RLMSwiftValueStorage *const self, __unsafe_unretained RLMObjectBase *const parent, __unsafe_unretained RLMProperty *const prop) { if (parent->_realm) { return; } if (!self->_impl) { self->_impl.reset(new UnmanagedSwiftValueStorage); } static_cast(*self->_impl).attach(parent, prop.name); } NSString *RLMSwiftValueStorageGetPropertyName(RLMSwiftValueStorage *const self) { return self->_impl->propertyName(); } @end ================================================ FILE: Realm/RLMThreadSafeReference.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 @class RLMRealm; RLM_HEADER_AUDIT_BEGIN(nullability, sendability) /** Objects of types which conform to `RLMThreadConfined` can be managed by a Realm, which will make them bound to a thread-specific `RLMRealm` instance. Managed objects must be explicitly exported and imported to be passed between threads. Managed instances of objects conforming to this protocol can be converted to a thread-safe reference for transport between threads by passing to the `+[RLMThreadSafeReference referenceWithThreadConfined:]` constructor. Note that only types defined by Realm can meaningfully conform to this protocol, and defining new classes which attempt to conform to it will not make them work with `RLMThreadSafeReference`. */ @protocol RLMThreadConfined // Conformance to the `RLMThreadConfined_Private` protocol will be enforced at runtime. /** The Realm which manages the object, or `nil` if the object is unmanaged. Unmanaged objects are not confined to a thread and cannot be passed to methods expecting a `RLMThreadConfined` object. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /// Indicates if the object can no longer be accessed because it is now invalid. @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; @end /** An object intended to be passed between threads containing a thread-safe reference to its thread-confined object. To resolve a thread-safe reference on a target Realm on a different thread, pass to `-[RLMRealm resolveThreadSafeReference:]`. @warning A `RLMThreadSafeReference` object must be resolved at most once. Failing to resolve a `RLMThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. @note Prefer short-lived `RLMThreadSafeReference`s as the data for the version of the source Realm will be retained until all references have been resolved or deallocated. @see `RLMThreadConfined` @see `-[RLMRealm resolveThreadSafeReference:]` */ NS_SWIFT_SENDABLE RLM_FINAL // is internally thread-safe @interface RLMThreadSafeReference<__covariant Confined : id> : NSObject /** Create a thread-safe reference to the thread-confined object. @param threadConfined The thread-confined object to create a thread-safe reference to. @note You may continue to use and access the thread-confined object after passing it to this constructor. */ + (instancetype)referenceWithThreadConfined:(Confined)threadConfined; /** Indicates if the reference can no longer be resolved because an attempt to resolve it has already occurred. References can only be resolved once. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Unavailable Methods /** `-[RLMThreadSafeReference init]` is not available because `RLMThreadSafeReference` cannot be created directly. `RLMThreadSafeReference` instances must be obtained by calling `-[RLMRealm resolveThreadSafeReference:]`. */ - (instancetype)init __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); /** `-[RLMThreadSafeReference new]` is not available because `RLMThreadSafeReference` cannot be created directly. `RLMThreadSafeReference` instances must be obtained by calling `-[RLMRealm resolveThreadSafeReference:]`. */ + (instancetype)new __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMThreadSafeReference.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMThreadSafeReference_Private.hpp" #import "RLMUtil.hpp" @implementation RLMThreadSafeReference { realm::ThreadSafeReference _reference; id _metadata; Class _type; } - (instancetype)initWithThreadConfined:(id)threadConfined { if (!(self = [super init])) { return nil; } REALM_ASSERT_DEBUG([threadConfined conformsToProtocol:@protocol(RLMThreadConfined)]); if (![threadConfined conformsToProtocol:@protocol(RLMThreadConfined_Private)]) { @throw RLMException(@"Illegal custom conformance to `RLMThreadConfined` by `%@`", threadConfined.class); } else if (threadConfined.invalidated) { @throw RLMException(@"Cannot construct reference to invalidated object"); } else if (!threadConfined.realm) { @throw RLMException(@"Cannot construct reference to unmanaged object, " "which can be passed across threads directly"); } RLMTranslateError([&] { _reference = [(id)threadConfined makeThreadSafeReference]; _metadata = ((id)threadConfined).objectiveCMetadata; }); _type = threadConfined.class; return self; } + (instancetype)referenceWithThreadConfined:(id)threadConfined { return [[self alloc] initWithThreadConfined:threadConfined]; } - (id)resolveReferenceInRealm:(RLMRealm *)realm { if (!_reference) { @throw RLMException(@"Can only resolve a thread safe reference once."); } return RLMTranslateError([&] { return [_type objectWithThreadSafeReference:std::move(_reference) metadata:_metadata realm:realm]; }); } - (BOOL)isInvalidated { return !_reference; } @end ================================================ FILE: Realm/RLMThreadSafeReference_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMThreadSafeReference.h" #import RLM_HEADER_AUDIT_BEGIN(nullability, sendability) RLM_HIDDEN @protocol RLMThreadConfined_Private // Constructs a new `ThreadSafeReference` - (realm::ThreadSafeReference)makeThreadSafeReference; // The extra information needed to construct an instance of this type from the Object Store type @property (nonatomic, readonly, nullable) id objectiveCMetadata; // Constructs an new instance of this type + (nullable instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference metadata:(nullable id)metadata realm:(RLMRealm *)realm; @end RLM_DIRECT_MEMBERS @interface RLMThreadSafeReference () - (nullable id)resolveReferenceInRealm:(RLMRealm *)realm; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMUUID.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMUUID_Private.hpp" #import @implementation NSUUID (RLMUUIDSupport) - (instancetype)initWithRealmUUID:(realm::UUID)rUuid { self = [self initWithUUIDBytes:rUuid.to_bytes().data()]; return self; } - (realm::UUID)rlm_uuidValue { return realm::UUID(self.UUIDString.UTF8String); } @end ================================================ FILE: Realm/RLMUUID_Private.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 utilied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import "RLMProperty.h" namespace realm { class UUID; } RLM_HEADER_AUDIT_BEGIN(nullability, sendability) @interface NSUUID (RLMUUIDSupport) - (instancetype)initWithRealmUUID:(realm::UUID)uuidValue; - (realm::UUID)rlm_uuidValue; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/RLMUtil.hpp ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import #import #import #import #import #import #import #import #import namespace realm { class Decimal128; class Exception; class Mixed; } class RLMClassInfo; @class RLMObjectSchema; @class RLMProperty; __attribute__((format(NSString, 1, 2))) NSException *RLMException(NSString *fmt, ...); NSException *RLMException(std::exception const& exception); NSException *RLMException(realm::Exception const& exception); void RLMSetErrorOrThrow(NSError *error, NSError **outError); RLM_HIDDEN_BEGIN // returns if the object can be inserted as the given type BOOL RLMIsObjectValidForProperty(id obj, RLMProperty *prop); // throw an exception if the object is not a valid value for the property void RLMValidateValueForProperty(id obj, RLMObjectSchema *objectSchema, RLMProperty *prop, bool validateObjects=false); id RLMValidateValue(id value, RLMPropertyType type, bool optional, bool collection, NSString *objectClassName); void RLMThrowTypeError(id obj, RLMObjectSchema *objectSchema, RLMProperty *prop); // gets default values for the given schema (+defaultPropertyValues) // merges with native property defaults if Swift class NSDictionary *RLMDefaultValuesForObjectSchema(RLMObjectSchema *objectSchema); BOOL RLMIsDebuggerAttached(); BOOL RLMIsRunningInPlayground(); // C version of isKindOfClass static inline BOOL RLMIsKindOfClass(Class class1, Class class2) { while (class1) { if (class1 == class2) return YES; class1 = class_getSuperclass(class1); } return NO; } template static inline T *RLMDynamicCast(__unsafe_unretained id obj) { if ([obj isKindOfClass:[T class]]) { return obj; } return nil; } static inline id RLMCoerceToNil(__unsafe_unretained id obj) { if (static_cast(obj) == NSNull.null) { return nil; } else if (__unsafe_unretained auto optional = RLMDynamicCast(obj)) { return RLMCoerceToNil(RLMGetSwiftValueStorage(optional)); } return obj; } template static inline T RLMCoerceToNil(__unsafe_unretained T obj) { return RLMCoerceToNil(static_cast(obj)); } id RLMAsFastEnumeration(id obj); id RLMBridgeSwiftValue(id obj); bool RLMIsSwiftObjectClass(Class cls); // String conversion utilities static inline NSString *RLMStringDataToNSString(realm::StringData stringData) { static_assert(sizeof(NSUInteger) >= sizeof(size_t), "Need runtime overflow check for size_t to NSUInteger conversion"); if (stringData.is_null()) { return nil; } else { return [[NSString alloc] initWithBytes:stringData.data() length:stringData.size() encoding:NSUTF8StringEncoding]; } } static inline NSString *RLMStringViewToNSString(std::string_view stringView) { if (stringView.size() == 0) { return nil; } return [[NSString alloc] initWithBytes:stringView.data() length:stringView.size() encoding:NSUTF8StringEncoding]; } static inline realm::StringData RLMStringDataWithNSString(__unsafe_unretained NSString *const string) { static_assert(sizeof(size_t) >= sizeof(NSUInteger), "Need runtime overflow check for NSUInteger to size_t conversion"); return realm::StringData(string.UTF8String, [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); } // Binary conversion utilities static inline NSData *RLMBinaryDataToNSData(realm::BinaryData binaryData) { return binaryData ? [NSData dataWithBytes:binaryData.data() length:binaryData.size()] : nil; } static inline realm::BinaryData RLMBinaryDataForNSData(__unsafe_unretained NSData *const data) { // this is necessary to ensure that the empty NSData isn't treated by core as the null realm::BinaryData // because data.bytes == 0 when data.length == 0 // the casting bit ensures that we create a data with a non-null pointer auto bytes = static_cast(data.bytes) ?: static_cast((__bridge void *)data); return realm::BinaryData(bytes, data.length); } // Date conversion utilities // These use the reference date and shift the seconds rather than just getting // the time interval since the epoch directly to avoid losing sub-second precision static inline NSDate *RLMTimestampToNSDate(realm::Timestamp ts) NS_RETURNS_RETAINED { if (ts.is_null()) return nil; auto timeInterval = ts.get_seconds() - NSTimeIntervalSince1970 + ts.get_nanoseconds() / 1'000'000'000.0; return [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:timeInterval]; } static inline realm::Timestamp RLMTimestampForNSDate(__unsafe_unretained NSDate *const date) { if (!date) return {}; auto timeInterval = date.timeIntervalSinceReferenceDate; if (isnan(timeInterval)) return {0, 0}; // Arbitrary choice // Clamp dates that we can't represent as a Timestamp to the maximum value if (timeInterval >= std::numeric_limits::max() - NSTimeIntervalSince1970) return {std::numeric_limits::max(), 1'000'000'000 - 1}; if (timeInterval - NSTimeIntervalSince1970 < std::numeric_limits::min()) return {std::numeric_limits::min(), -1'000'000'000 + 1}; auto seconds = static_cast(timeInterval); auto nanoseconds = static_cast((timeInterval - seconds) * 1'000'000'000.0); seconds += static_cast(NSTimeIntervalSince1970); // Seconds and nanoseconds have to have the same sign if (nanoseconds < 0 && seconds > 0) { nanoseconds += 1'000'000'000; --seconds; } return {seconds, nanoseconds}; } static inline NSUInteger RLMConvertNotFound(size_t index) { return index == realm::not_found ? NSNotFound : index; } static inline void RLMNSStringToStdString(std::string &out, NSString *in) { if (!in) return; out.resize([in maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]); if (out.empty()) { return; } NSUInteger size = out.size(); [in getBytes:&out[0] maxLength:size usedLength:&size encoding:NSUTF8StringEncoding options:0 range:{0, in.length} remainingRange:nullptr]; out.resize(size); } realm::Mixed RLMObjcToMixed(__unsafe_unretained id const value, __unsafe_unretained RLMRealm *const realm=nil, realm::CreatePolicy createPolicy={}); realm::Mixed RLMObjcToMixedPrimitives(__unsafe_unretained id const value, __unsafe_unretained RLMRealm *const realm, realm::CreatePolicy createPolicy); id RLMMixedToObjc(realm::Mixed const& value, __unsafe_unretained RLMRealm *realm=nil, RLMClassInfo *classInfo=nullptr, RLMProperty *property=nullptr, realm::Obj obj={}); realm::Decimal128 RLMObjcToDecimal128(id value); realm::UUID RLMObjcToUUID(__unsafe_unretained id const value); // Given a bundle identifier, return the base directory on the disk within which Realm database and support files should // be stored. FOUNDATION_EXTERN RLM_VISIBLE NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier); // Get a NSDateFormatter for ISO8601-formatted strings NSDateFormatter *RLMISO8601Formatter(); template static auto RLMTranslateError(Fn&& fn) { try { return fn(); } catch (std::exception const& e) { @throw RLMException(e); } } static inline bool numberIsInteger(__unsafe_unretained NSNumber *const obj) { char data_type = [obj objCType][0]; return data_type == *@encode(bool) || data_type == *@encode(char) || data_type == *@encode(short) || data_type == *@encode(int) || data_type == *@encode(long) || data_type == *@encode(long long) || data_type == *@encode(unsigned short) || data_type == *@encode(unsigned int) || data_type == *@encode(unsigned long) || data_type == *@encode(unsigned long long); } static inline bool numberIsBool(__unsafe_unretained NSNumber *const obj) { // @encode(BOOL) is 'B' on iOS 64 and 'c' // objcType is always 'c'. Therefore compare to "c". if ([obj objCType][0] == 'c') { return true; } if (numberIsInteger(obj)) { int value = [obj intValue]; return value == 0 || value == 1; } return false; } static inline bool numberIsFloat(__unsafe_unretained NSNumber *const obj) { char data_type = [obj objCType][0]; return data_type == *@encode(float) || data_type == *@encode(short) || data_type == *@encode(int) || data_type == *@encode(long) || data_type == *@encode(long long) || data_type == *@encode(unsigned short) || data_type == *@encode(unsigned int) || data_type == *@encode(unsigned long) || data_type == *@encode(unsigned long long) || // A double is like float if it fits within float bounds or is NaN. (data_type == *@encode(double) && (ABS([obj doubleValue]) <= FLT_MAX || isnan([obj doubleValue]))); } static inline bool numberIsDouble(__unsafe_unretained NSNumber *const obj) { char data_type = [obj objCType][0]; return data_type == *@encode(double) || data_type == *@encode(float) || data_type == *@encode(short) || data_type == *@encode(int) || data_type == *@encode(long) || data_type == *@encode(long long) || data_type == *@encode(unsigned short) || data_type == *@encode(unsigned int) || data_type == *@encode(unsigned long) || data_type == *@encode(unsigned long long); } class RLMUnfairMutex { public: RLMUnfairMutex() = default; void lock() noexcept { os_unfair_lock_lock(&_lock); } bool try_lock() noexcept { return os_unfair_lock_trylock(&_lock); } void unlock() noexcept { os_unfair_lock_unlock(&_lock); } private: os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT; RLMUnfairMutex(RLMUnfairMutex const&) = delete; RLMUnfairMutex& operator=(RLMUnfairMutex const&) = delete; }; RLM_HIDDEN_END ================================================ FILE: Realm/RLMUtil.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMUtil.hpp" #import "RLMArray_Private.hpp" #import "RLMAccessor.hpp" #import "RLMDecimal128_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMError_Private.hpp" #import "RLMObjectId_Private.hpp" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMProperty_Private.h" #import "RLMSwiftValueStorage.h" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftCollectionBase.h" #import "RLMSwiftSupport.h" #import "RLMUUID_Private.hpp" #import "RLMValue.h" #import #import #import #include #include #if !defined(REALM_COCOA_VERSION) #import "RLMVersion.h" #endif static inline RLMArray *asRLMArray(__unsafe_unretained id const value) { return RLMDynamicCast(value) ?: (RLMArray *)RLMDynamicCast(value)._rlmCollection; } static inline RLMSet *asRLMSet(__unsafe_unretained id const value) { return RLMDynamicCast(value) ?: RLMDynamicCast(value)._rlmCollection; } static inline RLMDictionary *asRLMDictionary(__unsafe_unretained id const value) { return RLMDynamicCast(value) ?: (RLMDictionary *)RLMDynamicCast(value)._rlmCollection; } static inline bool checkCollectionType(__unsafe_unretained id const collection, RLMPropertyType type, bool optional, __unsafe_unretained NSString *const objectClassName) { return collection.type == type && collection.optional == optional && (type != RLMPropertyTypeObject || [collection.objectClassName isEqualToString:objectClassName]); } static id (*s_bridgeValue)(id); id RLMAsFastEnumeration(__unsafe_unretained id obj) { if (!obj) { return nil; } if ([obj conformsToProtocol:@protocol(NSFastEnumeration)]) { return obj; } if (s_bridgeValue) { id bridged = s_bridgeValue(obj); if ([bridged conformsToProtocol:@protocol(NSFastEnumeration)]) { return bridged; } } return nil; } void RLMSetSwiftBridgeCallback(id (*callback)(id)) { s_bridgeValue = callback; } id RLMBridgeSwiftValue(__unsafe_unretained id value) { if (!value || !s_bridgeValue) { return nil; } return s_bridgeValue(value); } bool RLMIsSwiftObjectClass(Class cls) { return [cls isSubclassOfClass:RealmSwiftObject.class] || [cls isSubclassOfClass:RealmSwiftEmbeddedObject.class]; } static BOOL validateValue(__unsafe_unretained id const value, RLMPropertyType type, bool optional, bool collection, __unsafe_unretained NSString *const objectClassName) { if (optional && !RLMCoerceToNil(value)) { return YES; } if (collection) { if (auto rlmArray = asRLMArray(value)) { return checkCollectionType(rlmArray, type, optional, objectClassName); } else if (auto rlmSet = asRLMSet(value)) { return checkCollectionType(rlmSet, type, optional, objectClassName); } else if (auto rlmDictionary = asRLMDictionary(value)) { return checkCollectionType(rlmDictionary, type, optional, objectClassName); } if (id enumeration = RLMAsFastEnumeration(value)) { // check each element for compliance for (id el in enumeration) { if (!RLMValidateValue(el, type, optional, false, objectClassName)) { return NO; } } return YES; } if (!value || value == NSNull.null) { return YES; } return NO; } switch (type) { case RLMPropertyTypeString: return [value isKindOfClass:[NSString class]]; case RLMPropertyTypeBool: if ([value isKindOfClass:[NSNumber class]]) { return numberIsBool(value); } return NO; case RLMPropertyTypeDate: return [value isKindOfClass:[NSDate class]]; case RLMPropertyTypeInt: if (NSNumber *number = RLMDynamicCast(value)) { return numberIsInteger(number); } return NO; case RLMPropertyTypeFloat: if (NSNumber *number = RLMDynamicCast(value)) { return numberIsFloat(number); } return NO; case RLMPropertyTypeDouble: if (NSNumber *number = RLMDynamicCast(value)) { return numberIsDouble(number); } return NO; case RLMPropertyTypeData: return [value isKindOfClass:[NSData class]]; case RLMPropertyTypeAny: { return !value || [value conformsToProtocol:@protocol(RLMValue)]; } case RLMPropertyTypeLinkingObjects: return YES; case RLMPropertyTypeObject: { // only NSNull, nil, or objects which derive from RLMObject and match the given // object class are valid RLMObjectBase *objBase = RLMDynamicCast(value); return objBase && [objBase->_objectSchema.className isEqualToString:objectClassName]; } case RLMPropertyTypeObjectId: return [value isKindOfClass:[RLMObjectId class]]; case RLMPropertyTypeDecimal128: return [value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[RLMDecimal128 class]] || ([value isKindOfClass:[NSString class]] && realm::Decimal128::is_valid_str([value UTF8String])); case RLMPropertyTypeUUID: return [value isKindOfClass:[NSUUID class]] || ([value isKindOfClass:[NSString class]] && realm::UUID::is_valid_string([value UTF8String])); } @throw RLMException(@"Invalid RLMPropertyType specified"); } id RLMValidateValue(__unsafe_unretained id const value, RLMPropertyType type, bool optional, bool collection, __unsafe_unretained NSString *const objectClassName) { if (validateValue(value, type, optional, collection, objectClassName)) { return value ?: NSNull.null; } if (id bridged = RLMBridgeSwiftValue(value)) { if (validateValue(bridged, type, optional, collection, objectClassName)) { return bridged ?: NSNull.null; } } return nil; } void RLMThrowTypeError(__unsafe_unretained id const obj, __unsafe_unretained RLMObjectSchema *const objectSchema, __unsafe_unretained RLMProperty *const prop) { @throw RLMException(@"Invalid value '%@' of type '%@' for '%@%s'%s property '%@.%@'.", obj, [obj class], prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", prop.array ? " array" : prop.set ? " set" : prop.dictionary ? " dictionary" : "", objectSchema.className, prop.name); } void RLMValidateValueForProperty(__unsafe_unretained id const obj, __unsafe_unretained RLMObjectSchema *const objectSchema, __unsafe_unretained RLMProperty *const prop, bool validateObjects) { // This duplicates a lot of the checks in RLMIsObjectValidForProperty() // for the sake of more specific error messages if (prop.collection) { // nil is considered equivalent to an empty array for historical reasons // since we don't support null arrays (only arrays containing null), // it's not worth the BC break to change this if (!obj || obj == NSNull.null) { return; } id enumeration = RLMAsFastEnumeration(obj); if (!enumeration) { @throw RLMException(@"Invalid value (%@) for '%@%s' %@ property '%@.%@': value is not enumerable.", obj, prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", prop.array ? @"array" : @"set", objectSchema.className, prop.name); } if (!validateObjects && prop.type == RLMPropertyTypeObject) { return; } if (RLMArray *array = asRLMArray(obj)) { if (!checkCollectionType(array, prop.type, prop.optional, prop.objectClassName)) { @throw RLMException(@"RLMArray<%@%s> does not match expected type '%@%s' for property '%@.%@'.", array.objectClassName ?: RLMTypeToString(array.type), array.optional ? "?" : "", prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", objectSchema.className, prop.name); } return; } else if (RLMSet *set = asRLMSet(obj)) { if (!checkCollectionType(set, prop.type, prop.optional, prop.objectClassName)) { @throw RLMException(@"RLMSet<%@%s> does not match expected type '%@%s' for property '%@.%@'.", set.objectClassName ?: RLMTypeToString(set.type), set.optional ? "?" : "", prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", objectSchema.className, prop.name); } return; } else if (RLMDictionary *dictionary = asRLMDictionary(obj)) { if (!checkCollectionType(dictionary, prop.type, prop.optional, prop.objectClassName)) { @throw RLMException(@"RLMDictionary<%@, %@%s> does not match expected type '%@%s' for property '%@.%@'.", RLMTypeToString(dictionary.keyType), dictionary.objectClassName ?: RLMTypeToString(dictionary.type), dictionary.optional ? "?" : "", prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "", objectSchema.className, prop.name); } return; } if (prop.dictionary) { for (id key in enumeration) { id value = enumeration[key]; if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) { RLMThrowTypeError(value, objectSchema, prop); } } } else { for (id value in enumeration) { if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) { RLMThrowTypeError(value, objectSchema, prop); } } } return; } // For create() we want to skip the validation logic for objects because // we allow much fuzzier matching (any KVC-compatible object with at least // all the non-defaulted fields), and all the logic for that lives in the // object store rather than here if (prop.type == RLMPropertyTypeObject && !validateObjects) { return; } if (RLMIsObjectValidForProperty(obj, prop)) { return; } RLMThrowTypeError(obj, objectSchema, prop); } BOOL RLMIsObjectValidForProperty(__unsafe_unretained id const obj, __unsafe_unretained RLMProperty *const property) { return RLMValidateValue(obj, property.type, property.optional, property.collection, property.objectClassName) != nil; } NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) { if (!objectSchema.isSwiftClass) { return [objectSchema.objectClass defaultPropertyValues]; } NSMutableDictionary *defaults = nil; if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) { defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]]; } else { defaults = [NSMutableDictionary dictionary]; } RLMObject *defaultObject = [[objectSchema.objectClass alloc] init]; for (RLMProperty *prop in objectSchema.properties) { if (!defaults[prop.name] && defaultObject[prop.name]) { defaults[prop.name] = defaultObject[prop.name]; } } return defaults; } static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) { NSMutableDictionary *userInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION, RLMRealmCoreVersionKey: @REALM_VERSION}.mutableCopy; if (additionalUserInfo != nil) { [userInfo addEntriesFromDictionary:additionalUserInfo]; } NSException *e = [NSException exceptionWithName:RLMExceptionName reason:reason userInfo:userInfo]; return e; } NSException *RLMException(NSString *fmt, ...) { va_list args; va_start(args, fmt); NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{}); va_end(args); return e; } NSException *RLMException(std::exception const& exception) { return RLMException(@"%s", exception.what()); } NSException *RLMException(realm::Exception const& exception) { return RLMException(@(exception.what()), @{@"Error Code": @(exception.code()), @"Underlying": makeError(exception.to_status())}); } void RLMSetErrorOrThrow(NSError *error, NSError **outError) { if (outError) { *outError = error; } else { @throw RLMException(error.localizedDescription, @{NSUnderlyingErrorKey: error}); } } BOOL RLMIsDebuggerAttached() { int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; struct kinfo_proc info; size_t info_size = sizeof(info); if (sysctl(name, sizeof(name)/sizeof(name[0]), &info, &info_size, NULL, 0) == -1) { NSLog(@"sysctl() failed: %s", strerror(errno)); return false; } return (info.kp_proc.p_flag & P_TRACED) != 0; } BOOL RLMIsRunningInPlayground() { return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."]; } realm::Mixed RLMObjcToMixed(__unsafe_unretained id const value, __unsafe_unretained RLMRealm *const realm, realm::CreatePolicy createPolicy) { if (!value || value == NSNull.null) { return realm::Mixed(); } id v; if ([value conformsToProtocol:@protocol(RLMValue)]) { v = value; } else { v = RLMBridgeSwiftValue(value); if (v == NSNull.null) { return realm::Mixed(); } REALM_ASSERT([v conformsToProtocol:@protocol(RLMValue)]); } switch ([v rlm_anyValueType]) { case RLMAnyValueTypeList: return realm::Mixed(0, realm::CollectionType::List); case RLMAnyValueTypeDictionary: return realm::Mixed(0, realm::CollectionType::Dictionary); default: return RLMObjcToMixedPrimitives(v, realm, createPolicy); } } realm::Mixed RLMObjcToMixedPrimitives(__unsafe_unretained id const value, __unsafe_unretained RLMRealm *const realm, realm::CreatePolicy createPolicy) { RLMAnyValueType type = [value rlm_anyValueType]; return switch_on_type(static_cast(type), realm::util::overload{[&](realm::Obj*) { // The RLMObjectBase may be unmanaged and therefore has no RLMClassInfo attached. // So we fetch from the Realm instead. // If the Object is managed use it's RLMClassInfo instead so we do not have to do a // lookup in the table of schemas. RLMObjectBase *objBase = value; RLMAccessorContext c{objBase->_info ? *objBase->_info : realm->_info[objBase->_objectSchema.className]}; auto obj = c.unbox(value, createPolicy); return obj.is_valid() ? realm::Mixed(obj) : realm::Mixed(); }, [&](T *) { RLMStatelessAccessorContext c; return realm::Mixed(c.unbox(value)); }, [&](realm::Mixed*) -> realm::Mixed { REALM_UNREACHABLE(); }}); } id RLMMixedToObjc(realm::Mixed const& mixed, __unsafe_unretained RLMRealm *realm, RLMClassInfo *classInfo, RLMProperty *property, realm::Obj obj) { if (mixed.is_null()) { return NSNull.null; } switch (mixed.get_type()) { case realm::type_String: return RLMStringDataToNSString(mixed.get_string()); case realm::type_Int: return @(mixed.get_int()); case realm::type_Float: return @(mixed.get_float()); case realm::type_Double: return @(mixed.get_double()); case realm::type_Bool: return @(mixed.get_bool()); case realm::type_Timestamp: return RLMTimestampToNSDate(mixed.get_timestamp()); case realm::type_Binary: return RLMBinaryDataToNSData(mixed.get()); case realm::type_Decimal: return [[RLMDecimal128 alloc] initWithDecimal128:mixed.get()]; case realm::type_ObjectId: return [[RLMObjectId alloc] initWithValue:mixed.get()]; case realm::type_TypedLink: return RLMObjectFromObjLink(realm, mixed.get(), classInfo->isSwiftClass()); case realm::type_Link: { auto obj = classInfo->table()->get_object((mixed).get()); return RLMCreateObjectAccessor(*classInfo, std::move(obj)); } case realm::type_UUID: return [[NSUUID alloc] initWithRealmUUID:mixed.get()]; default: if (mixed.is_type(realm::type_Dictionary)) { return [[RLMManagedDictionary alloc] initWithParent:obj property:property parentInfo:*classInfo]; } else if (mixed.is_type(realm::type_List)) { return [[RLMManagedArray alloc] initWithParent:obj property:property parentInfo:*classInfo]; } else { @throw RLMException(@"Invalid data type for RLMPropertyTypeAny property."); } } } realm::UUID RLMObjcToUUID(__unsafe_unretained id const value) { try { if (auto uuid = RLMDynamicCast(value)) { return uuid.rlm_uuidValue; } if (auto string = RLMDynamicCast(value)) { return realm::UUID(string.UTF8String); } } catch (std::exception const& e) { @throw RLMException(@"Cannot convert value '%@' of type '%@' to uuid: %s", value, [value class], e.what()); } @throw RLMException(@"Cannot convert value '%@' of type '%@' to uuid", value, [value class]); } realm::Decimal128 RLMObjcToDecimal128(__unsafe_unretained id const value) { try { if (!value || value == NSNull.null) { return realm::Decimal128(realm::null()); } if (auto decimal = RLMDynamicCast(value)) { return decimal.decimal128Value; } if (auto string = RLMDynamicCast(value)) { return realm::Decimal128(string.UTF8String); } if (auto decimal = RLMDynamicCast(value)) { return realm::Decimal128(decimal.stringValue.UTF8String); } if (auto number = RLMDynamicCast(value)) { auto type = number.objCType[0]; if (type == *@encode(double) || type == *@encode(float)) { return realm::Decimal128(number.doubleValue); } else if (std::isupper(type)) { return realm::Decimal128(number.unsignedLongLongValue); } else { return realm::Decimal128(number.longLongValue); } } if (id bridged = RLMBridgeSwiftValue(value); bridged != value) { return RLMObjcToDecimal128(bridged); } } catch (std::exception const& e) { @throw RLMException(@"Cannot convert value '%@' of type '%@' to decimal128: %s", value, [value class], e.what()); } @throw RLMException(@"Cannot convert value '%@' of type '%@' to decimal128", value, [value class]); } NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier) { #if TARGET_OS_TV (void)bundleIdentifier; // tvOS prohibits writing to the Documents directory, so we use the Library/Caches directory instead. return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; #elif TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST (void)bundleIdentifier; // On iOS the Documents directory isn't user-visible, so put files there return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; #else // On OS X it is, so put files in Application Support. If we aren't running // in a sandbox, put it in a subdirectory based on the bundle identifier // to avoid accidentally sharing files between applications NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; if (![[NSProcessInfo processInfo] environment][@"APP_SANDBOX_CONTAINER_ID"]) { if (!bundleIdentifier) { bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; } if (!bundleIdentifier) { bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent; } path = [path stringByAppendingPathComponent:bundleIdentifier]; // create directory [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; } return path; #endif } NSDateFormatter *RLMISO8601Formatter() { // note: NSISO8601DateFormatter can't be used as it doesn't support milliseconds NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ"; dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; return dateFormatter; } ================================================ FILE: Realm/RLMValue.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 #import #import #import #import #import #import #import #pragma mark RLMValue /** RLMValue is a property type which represents a polymorphic Realm value. This is similar to the usage of `AnyObject` / `Any` in Swift. ``` // A property on `MyObject` @property (nonatomic) id myAnyValue; // A property on `AnotherObject` @property (nonatomic) id myAnyValue; MyObject *myObject = [MyObject createInRealm:realm withValue:@[]]; myObject.myAnyValue = @1234; // underlying type is NSNumber. myObject.myAnyValue = @"hello"; // underlying type is NSString. AnotherObject *anotherObject = [AnotherObject createInRealm:realm withValue:@[]]; myObject.myAnyValue = anotherObject; // underlying type is RLMObject. ``` The following types conform to RLMValue: `NSData` `NSDate` `NSNull` `NSNumber` `NSUUID` `NSString` `RLMObject `RLMObjectId` `RLMDecimal128` `RLMDictionary` `RLMArray` `NSArray` `NSDictionary` */ @protocol RLMValue /// Describes the type of property stored. @property (readonly) RLMAnyValueType rlm_valueType __attribute__((deprecated("Use `rlm_anyValueType` instead, which includes collection types as well"))); /// Describes the type of property stored. @property (readonly) RLMAnyValueType rlm_anyValueType; @end /// :nodoc: @interface NSNull (RLMValue) @end /// :nodoc: @interface NSNumber (RLMValue) @end /// :nodoc: @interface NSString (RLMValue) @end /// :nodoc: @interface NSData (RLMValue) @end /// :nodoc: @interface NSDate (RLMValue) @end /// :nodoc: @interface NSUUID (RLMValue) @end /// :nodoc: @interface RLMDecimal128 (RLMValue) @end /// :nodoc: @interface RLMObjectBase (RLMValue) @end /// :nodoc: @interface RLMObjectId (RLMValue) @end /// :nodoc: @interface NSDictionary (RLMValue) @end /// :nodoc: @interface NSArray (RLMValue) @end /// :nodoc: @interface RLMArray (RLMValue) @end /// :nodoc: @interface RLMDictionary (RLMValue) @end ================================================ FILE: Realm/RLMValue.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMValue.h" #import "RLMUtil.hpp" #pragma mark NSData @implementation NSData (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeData; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeData; } @end #pragma mark NSDate @implementation NSDate (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeDate; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeDate; } @end #pragma mark NSNumber @implementation NSNumber (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { if ([self objCType][0] == 'c' && (self.intValue == 0 || self.intValue == 1)) { return RLMPropertyTypeBool; } else if (numberIsInteger(self)) { return RLMPropertyTypeInt; } else if (*@encode(float) == [self objCType][0]) { return RLMPropertyTypeFloat; } else if (*@encode(double) == [self objCType][0]) { return RLMPropertyTypeDouble; } else { @throw RLMException(@"Unknown numeric type on type RLMValue."); } } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { if ([self objCType][0] == 'c' && (self.intValue == 0 || self.intValue == 1)) { return RLMAnyValueTypeBool; } else if (numberIsInteger(self)) { return RLMAnyValueTypeInt; } else if (*@encode(float) == [self objCType][0]) { return RLMAnyValueTypeFloat; } else if (*@encode(double) == [self objCType][0]) { return RLMAnyValueTypeDouble; } else { @throw RLMException(@"Unknown numeric type on type RLMValue."); } } @end #pragma mark NSNull @implementation NSNull (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeAny; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeAny; } @end #pragma mark NSString @implementation NSString (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeString; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeString; } @end #pragma mark NSUUID @implementation NSUUID (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeUUID; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeUUID; } @end #pragma mark RLMDecimal128 @implementation RLMDecimal128 (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeDecimal128; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeDecimal128; } @end #pragma mark RLMObjectBase @implementation RLMObjectBase (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeObject; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeObject; } @end #pragma mark RLMObjectId @implementation RLMObjectId (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeObjectId; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeObjectId; } @end #pragma mark Dictionary @implementation NSDictionary (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeAny; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeDictionary; } @end @implementation RLMDictionary (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeAny; return RLMPropertyTypeAny; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeDictionary; } @end #pragma mark Array @implementation NSArray (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeAny; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeList; } @end @implementation RLMArray (RLMValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (RLMPropertyType)rlm_valueType { return RLMPropertyTypeAny; } #pragma clang diagnostic pop - (RLMAnyValueType)rlm_anyValueType { return RLMAnyValueTypeList; } @end ================================================ FILE: Realm/Realm-Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 20.0.4 CFBundleSignature ???? CFBundleVersion 20.0.4 NSHumanReadableCopyright Copyright © 2014-2021 Realm. All rights reserved. NSPrincipalClass ================================================ FILE: Realm/Realm.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import ================================================ FILE: Realm/Realm.modulemap ================================================ framework module Realm { export Foundation umbrella header "Realm.h" header "RLMArray.h" header "RLMDecimal128.h" header "RLMDictionary.h" header "RLMEmbeddedObject.h" header "RLMGeospatial.h" header "RLMLogger.h" header "RLMMigration.h" header "RLMObject.h" header "RLMObjectId.h" header "RLMObjectSchema.h" header "RLMProperty.h" header "RLMRealm.h" header "RLMRealmConfiguration.h" header "RLMResults.h" header "RLMSchema.h" header "RLMSectionedResults.h" header "RLMSet.h" header "RLMValue.h" explicit module Private { header "RLMAccessor.h" header "RLMArray_Private.h" header "RLMAsyncTask_Private.h" header "RLMCollection_Private.h" header "RLMDictionary_Private.h" header "RLMLogger_Private.h" header "RLMObjectBase_Dynamic.h" header "RLMObjectBase_Private.h" header "RLMObjectSchema_Private.h" header "RLMObjectStore.h" header "RLMObject_Private.h" header "RLMProperty_Private.h" header "RLMRealmConfiguration_Private.h" header "RLMRealm_Private.h" header "RLMResults_Private.h" header "RLMScheduler.h" header "RLMSchema_Private.h" header "RLMSectionedResults.h" header "RLMSet_Private.h" header "RLMSwiftCollectionBase.h" header "RLMSwiftProperty.h" header "RLMSwiftValueStorage.h" } explicit module Dynamic { header "RLMRealm_Dynamic.h" header "RLMObjectBase_Dynamic.h" } explicit module Swift { header "RLMSwiftObject.h" } } ================================================ FILE: Realm/Swift/RLMSupport.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Realm extension RLMRealm { /** Returns the schema version for a Realm at a given local URL. - see: `+ [RLMRealm schemaVersionAtURL:encryptionKey:error:]` */ @nonobjc public class func schemaVersion(at url: URL, usingEncryptionKey key: Data? = nil) throws -> UInt64 { var error: NSError? let version = __schemaVersion(at: url, encryptionKey: key, error: &error) guard version != RLMNotVersioned else { throw error! } return version } /** Returns the same object as the one referenced when the `RLMThreadSafeReference` was first created, but resolved for the current Realm for this thread. Returns `nil` if this object was deleted after the reference was created. - see `- [RLMRealm resolveThreadSafeReference:]` */ @nonobjc public func resolve(reference: RLMThreadSafeReference) -> Confined? { return __resolve(reference as! RLMThreadSafeReference) as! Confined? } } extension RLMObject { /** Returns all objects of this object type matching the given predicate from the default Realm. - see `+ [RLMObject objectsWithPredicate:]` */ public class func objects(where predicateFormat: String, _ args: CVarArg...) -> RLMResults { return objects(with: NSPredicate(format: predicateFormat, arguments: getVaList(args))) as! RLMResults } /** Returns all objects of this object type matching the given predicate from the specified Realm. - see `+ [RLMObject objectsInRealm:withPredicate:]` */ public class func objects(in realm: RLMRealm, where predicateFormat: String, _ args: CVarArg...) -> RLMResults { return objects(in: realm, with: NSPredicate(format: predicateFormat, arguments: getVaList(args))) as! RLMResults } } /// A protocol defining iterator support for RLMArray, RLMSet & RLMResults. public protocol _RLMCollectionIterator: Sequence { /** Returns a `RLMCollectionIterator` that yields successive elements in the collection. This enables support for sequence-style enumeration of `RLMObject` subclasses in Swift. */ func makeIterator() -> RLMCollectionIterator } extension _RLMCollectionIterator where Self: RLMCollection { /// :nodoc: public func makeIterator() -> RLMCollectionIterator { return RLMCollectionIterator(self) } } /// :nodoc: public typealias RLMDictionarySingleEntry = (key: String, value: RLMObject) /// A protocol defining iterator support for RLMDictionary public protocol _RLMDictionaryIterator { /// :nodoc: func makeIterator() -> RLMDictionaryIterator } extension _RLMDictionaryIterator where Self: RLMCollection { /// :nodoc: public func makeIterator() -> RLMDictionaryIterator { return RLMDictionaryIterator(self) } } // Sequence conformance for RLMArray, RLMDictionary, RLMSet and RLMResults is provided by RLMCollection's // `makeIterator()` implementation. #if compiler(<6.0) extension RLMArray: Sequence, _RLMCollectionIterator { } extension RLMDictionary: Sequence, _RLMDictionaryIterator {} extension RLMSet: Sequence, _RLMCollectionIterator {} extension RLMResults: Sequence, _RLMCollectionIterator {} #else extension RLMArray: @retroactive Sequence, _RLMCollectionIterator { } extension RLMDictionary: @retroactive Sequence, _RLMDictionaryIterator {} extension RLMSet: @retroactive Sequence, _RLMCollectionIterator {} extension RLMResults: @retroactive Sequence, _RLMCollectionIterator {} #endif /** This struct enables sequence-style enumeration for RLMObjects in Swift via `RLMCollection.makeIterator` */ public struct RLMCollectionIterator: IteratorProtocol { private var iteratorBase: NSFastEnumerationIterator internal init(_ collection: RLMCollection) { iteratorBase = NSFastEnumerationIterator(collection) } public mutating func next() -> RLMObject? { return iteratorBase.next() as! RLMObject? } } /** This struct enables sequence-style enumeration for RLMDictionary in Swift via `RLMDictionary.makeIterator` */ public struct RLMDictionaryIterator: IteratorProtocol { private var iteratorBase: NSFastEnumerationIterator private let dictionary: RLMDictionary internal init(_ collection: RLMCollection) { dictionary = collection as! RLMDictionary iteratorBase = NSFastEnumerationIterator(collection) } public mutating func next() -> RLMDictionarySingleEntry? { let key = iteratorBase.next() if let key = key { return (key: key as Any, value: dictionary[key as AnyObject]) as? RLMDictionarySingleEntry } if key != nil { fatalError("unsupported key type") } return nil } } // Swift query convenience functions extension RLMCollection { /** Returns the index of the first object in the collection matching the predicate. */ public func indexOfObject(where predicateFormat: String, _ args: CVarArg...) -> UInt { guard let index = indexOfObject?(with: NSPredicate(format: predicateFormat, arguments: getVaList(args))) else { fatalError("This RLMCollection does not support indexOfObject(where:)") } return index } /** Returns all objects matching the given predicate in the collection. */ public func objects(where predicateFormat: String, _ args: CVarArg...) -> RLMResults { return objects(with: NSPredicate(format: predicateFormat, arguments: getVaList(args))) as! RLMResults } } extension RLMCollection { /// Allows for subscript support with RLMDictionary. public subscript(_ key: String) -> AnyObject? { get { (self as! RLMDictionary).object(forKey: key as NSString) } set { (self as! RLMDictionary).setObject(newValue, forKey: key as NSString) } } } ================================================ FILE: Realm/TestUtils/RLMChildProcessEnvironment.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMChildProcessEnvironment.h" @implementation RLMChildProcessEnvironment - (instancetype)init { if (self = [super init]) { _appIds = @[]; _identifier = 0; } return self; } - (instancetype)initWithAppIds:(NSArray *)appIds email:(NSString *)email password:(NSString *)password identifer:(NSInteger)identifier { if (self = [super init]) { _appIds = appIds ?: @[]; _email = email; _password = password; _identifier = identifier; } return self; } - (NSString *)_appId { return self.appIds == nil ? nil : [self.appIds firstObject]; } - (NSDictionary *)dictionaryValue { NSMutableDictionary *environment = [NSMutableDictionary new]; if ([self.appIds count] > 0) { environment[@"RLMParentAppId"] = [self.appIds firstObject]; environment[@"RLMParentAppIds"] = [self.appIds componentsJoinedByString:@","]; } if (self.email != nil) { environment[@"RLMChildEmail"] = self.email; } if (self.password != nil) { environment[@"RLMChildPassword"] = self.password; } environment[@"RLMChildIdentifier"] = [@(self.identifier) stringValue]; return environment; } + (RLMChildProcessEnvironment *)current { NSDictionary *environment = [NSProcessInfo processInfo].environment; NSString *identifier = [environment objectForKey:@"RLMChildIdentifier"] ?: @"0"; NSString *appIds = environment[@"RLMParentAppIds"] ?: @""; return [[RLMChildProcessEnvironment new] initWithAppIds:[appIds componentsSeparatedByString:@","] email:environment[@"RLMChildEmail"] password:environment[@"RLMChildPassword"] identifer:[identifier intValue]]; } @end ================================================ FILE: Realm/TestUtils/RLMMultiProcessTestCase.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMMultiProcessTestCase.h" #import "RLMChildProcessEnvironment.h" #include @interface RLMMultiProcessTestCase () @property (nonatomic) bool isParent; @property (nonatomic, strong) NSString *testName; @property (nonatomic, strong) NSString *xctestPath; @property (nonatomic, strong) NSString *testsPath; @end @interface RLMMultiProcessTestCase (Sync) - (NSString *)appId; @end @implementation RLMMultiProcessTestCase // Override all of the methods for creating a XCTestCase object to capture the current test name + (id)testCaseWithInvocation:(NSInvocation *)invocation { RLMMultiProcessTestCase *testCase = [super testCaseWithInvocation:invocation]; testCase.testName = NSStringFromSelector(invocation.selector); return testCase; } - (id)initWithInvocation:(NSInvocation *)invocation { self = [super initWithInvocation:invocation]; if (self) { self.testName = NSStringFromSelector(invocation.selector); } return self; } + (id)testCaseWithSelector:(SEL)selector { RLMMultiProcessTestCase *testCase = [super testCaseWithSelector:selector]; testCase.testName = NSStringFromSelector(selector); return testCase; } - (id)initWithSelector:(SEL)selector { self = [super initWithSelector:selector]; if (self) { self.testName = NSStringFromSelector(selector); } return self; } - (BOOL)encryptTests { return NO; } - (void)setUp { self.isParent = !getenv("RLMProcessIsChild"); self.xctestPath = [self locateXCTest]; self.testsPath = [NSBundle bundleForClass:[self class]].bundlePath; if (!self.isParent) { // For multi-process tests, the child's concept of a default path needs to match the parent. // RLMRealmConfiguration isn't aware of this, but our test's RLMDefaultRealmURL helper does. // Use it to reset the default configuration's path so it matches the parent. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMDefaultRealmURL(); [RLMRealmConfiguration setDefaultConfiguration:configuration]; } [super setUp]; } - (void)invokeTest { CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ [super invokeTest]; CFRunLoopStop(CFRunLoopGetCurrent()); }); CFRunLoopRun(); } - (void)deleteFiles { // Only the parent should delete files in setUp/tearDown if (self.isParent) { [super deleteFiles]; } } + (void)preintializeSchema { // Do nothing so that we can test global schema init in child processes } - (NSString *)locateXCTest { NSProcessInfo *info = [NSProcessInfo processInfo]; NSString *pathString = info.environment[@"PATH"]; NSFileManager *fileManager = [NSFileManager defaultManager]; for (NSString *directory in [pathString componentsSeparatedByString:@":"]) { NSString *candidatePath = [directory stringByAppendingPathComponent:@"xctest"]; if ([fileManager isExecutableFileAtPath:candidatePath]) return candidatePath; } return info.arguments[0]; } #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - (NSTask *)childTaskWithEnvironment:(RLMChildProcessEnvironment *)environment { NSString *testName = [NSString stringWithFormat:@"%@/%@", self.className, self.testName]; NSMutableDictionary *env = [NSProcessInfo.processInfo.environment mutableCopy]; env[@"RLMProcessIsChild"] = @"true"; env[@"RLMParentProcessBundleID"] = [NSBundle mainBundle].bundleIdentifier; [env addEntriesFromDictionary:[environment dictionaryValue]]; // If we're running with address sanitizer or thread sanitizer we need to // explicitly tell dyld to inject the appropriate runtime library into // the child process for (int i = 0, count = _dyld_image_count(); i < count; i++) { const char *imageName = _dyld_get_image_name(i); if (imageName && strstr(imageName, "libclang_rt")) { env[@"DYLD_INSERT_LIBRARIES"] = @(imageName); } } // Don't inherit the config file in the subprocess, as multiple XCTest // processes talking to a single Xcode instance doesn't work at all [env removeObjectForKey:@"XCTestConfigurationFilePath"]; [env removeObjectForKey:@"XCTestSessionIdentifier"]; [env removeObjectForKey:@"XPC_SERVICE_NAME"]; [env removeObjectForKey:@"XCTestBundlePath"]; NSTask *task = [[NSTask alloc] init]; task.launchPath = self.xctestPath; task.arguments = @[@"-XCTest", testName, self.testsPath]; task.environment = env; task.standardError = nil; return task; } - (NSTask *)childTaskWithAppIds:(NSArray *)appIds { return [self childTaskWithEnvironment:[[RLMChildProcessEnvironment new] initWithAppIds:appIds email:nil password:nil identifer:0]]; } - (NSTask *)childTask { return [self childTaskWithAppIds:@[]]; } - (NSPipe *)filterPipe { NSPipe *pipe = [NSPipe pipe]; NSMutableData *buffer = [[NSMutableData alloc] init]; // Filter the output from the child process to reduce xctest noise pipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *file) { [buffer appendData:[file availableData]]; const char *newline; const char *start = buffer.bytes; const char *end = start + buffer.length; while ((newline = memchr(start, '\n', end - start))) { if (newline < start + 17 || (memcmp(start, "Test Suite", 10) && memcmp(start, "Test Case", 9) && memcmp(start, " Executed 1 test", 17))) { fwrite(start, newline - start + 1, 1, stderr); } start = newline + 1; } // Remove everything up to the last newline, leaving any data not newline-terminated in the buffer [buffer replaceBytesInRange:NSMakeRange(0, start - (char *)buffer.bytes) withBytes:0 length:0]; }; return pipe; } - (int)runChildAndWaitWithEnvironment:(RLMChildProcessEnvironment *)environment { NSTask *task = [self childTaskWithEnvironment:environment]; task.standardError = self.filterPipe; [task launch]; [task waitUntilExit]; return task.terminationStatus; } - (int)runChildAndWaitWithAppIds:(NSArray *)appIds { return [self runChildAndWaitWithEnvironment:[[RLMChildProcessEnvironment new] initWithAppIds:appIds email:nil password:nil identifer:0]]; } - (int)runChildAndWait { NSTask *task = [self childTask]; task.standardError = self.filterPipe; [task launch]; [task waitUntilExit]; return task.terminationStatus; } #else - (NSTask *)childTask { return nil; } - (NSTask *)childTaskWithAppIds:(__unused NSArray *)appIds { return nil; } - (int)runChildAndWait { return 1; } - (int)runChildAndWaitWithAppIds:(__unused NSArray *)appIds { return 1; } - (int)runChildAndWaitWithEnvironment:(RLMChildProcessEnvironment *)environment { return 1; } #endif @end ================================================ FILE: Realm/TestUtils/RLMTestCase.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMRealmConfiguration_Private.h" #import #import #import static NSString *parentProcessBundleIdentifier(void) { static BOOL hasInitializedIdentifier; static NSString *identifier; if (!hasInitializedIdentifier) { identifier = [NSProcessInfo processInfo].environment[@"RLMParentProcessBundleID"]; hasInitializedIdentifier = YES; } return identifier; } NSURL *RLMDefaultRealmURL(void) { return [NSURL fileURLWithPath:RLMRealmPathForFileAndBundleIdentifier(@"default.realm", parentProcessBundleIdentifier())]; } NSURL *RLMTestRealmURL(void) { return [NSURL fileURLWithPath:RLMRealmPathForFileAndBundleIdentifier(@"test.realm", parentProcessBundleIdentifier())]; } static void deleteOrThrow(NSURL *fileURL) { NSError *error; if (![[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error]) { if (error.code != NSFileNoSuchFileError) { @throw [NSException exceptionWithName:@"RLMTestException" reason:[@"Unable to delete realm: " stringByAppendingString:error.description] userInfo:nil]; } } } NSData *RLMGenerateKey(void) { uint8_t buffer[64]; (void)SecRandomCopyBytes(kSecRandomDefault, 64, buffer); return [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)]; } static BOOL encryptTests(void) { static BOOL encryptAll = NO; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ const char *str = getenv("REALM_ENCRYPT_ALL"); if (str && *str) { encryptAll = YES; } }); return encryptAll; } @implementation RLMTestCaseBase + (void)setUp { [super setUp]; #if DEBUG || !TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR // Disable actually syncing anything to the disk to greatly speed up the // tests, but only when not running on device because it can't be // re-enabled and we need it enabled for performance tests RLMDisableSyncToDisk(); #endif // Don't bother disabling backups on our non-Realm files because it takes // a while and we're going to delete them anyway. RLMSetSkipBackupAttribute(false); if (!getenv("RLMProcessIsChild")) { [self preinitializeSchema]; // Clean up any potentially lingering Realm files from previous runs [NSFileManager.defaultManager removeItemAtPath:RLMRealmPathForFile(@"") error:nil]; } // Ensure the documents directory exists as it sometimes doesn't after // resetting the simulator [NSFileManager.defaultManager createDirectoryAtURL:RLMDefaultRealmURL().URLByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:nil]; } // This ensures the shared schema is initialized outside of of a test case, // so if an exception is thrown, it will kill the test process rather than // allowing hundreds of test cases to fail in strange ways // This is overridden by RLMMultiProcessTestCase to support testing the schema init + (void)preinitializeSchema { [RLMSchema sharedSchema]; } // A hook point for subclasses to override the cleanup - (void)resetRealmState { [RLMRealm resetRealmState]; } @end @implementation RLMTestCase { dispatch_queue_t _bgQueue; } - (void)deleteFiles { // Clear cache [self resetRealmState]; // Delete Realm files NSURL *directory = RLMDefaultRealmURL().URLByDeletingLastPathComponent; NSError *error = nil; for (NSString *file in [NSFileManager.defaultManager contentsOfDirectoryAtPath:directory.path error:&error]) { deleteOrThrow([directory URLByAppendingPathComponent:file]); } } - (void)deleteRealmFileAtURL:(NSURL *)fileURL { deleteOrThrow(fileURL); deleteOrThrow([fileURL URLByAppendingPathExtension:@"lock"]); deleteOrThrow([fileURL URLByAppendingPathExtension:@"note"]); } - (BOOL)encryptTests { return encryptTests(); } - (void)invokeTest { @autoreleasepool { [self deleteFiles]; if (self.encryptTests) { RLMRealmConfiguration *configuration = [RLMRealmConfiguration rawDefaultConfiguration]; configuration.encryptionKey = RLMGenerateKey(); } } @autoreleasepool { [super invokeTest]; } @autoreleasepool { if (_bgQueue) { dispatch_sync(_bgQueue, ^{}); _bgQueue = nil; } [self deleteFiles]; } } - (RLMRealm *)realmWithTestPath { return [RLMRealm realmWithURL:RLMTestRealmURL()]; } - (RLMRealm *)realmWithTestPathAndSchema:(RLMSchema *)schema { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); if (schema) configuration.customSchema = schema; else configuration.dynamic = true; return [RLMRealm realmWithConfiguration:configuration error:nil]; } - (RLMRealm *)inMemoryRealmWithIdentifier:(NSString *)identifier { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.encryptionKey = nil; configuration.inMemoryIdentifier = identifier; return [RLMRealm realmWithConfiguration:configuration error:nil]; } - (RLMRealm *)readOnlyRealmWithURL:(NSURL *)fileURL error:(NSError **)error { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = fileURL; configuration.readOnly = true; return [RLMRealm realmWithConfiguration:configuration error:error]; } - (void)waitForNotification:(NSString *)expectedNote realm:(RLMRealm *)realm block:(dispatch_block_t)block { XCTestExpectation *notificationFired = [self expectationWithDescription:@"notification fired"]; __block RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertNotNil(note, @"Note should not be nil"); XCTAssertNotNil(realm, @"Realm should not be nil"); if (note == expectedNote) { // Check pointer equality to ensure we're using the interned string constant [notificationFired fulfill]; [token invalidate]; } }]; dispatch_queue_t queue = dispatch_queue_create("background", 0); dispatch_async(queue, ^{ @autoreleasepool { block(); } }); [self waitForExpectationsWithTimeout:30.0 handler:nil]; // wait for queue to finish dispatch_sync(queue, ^{}); } - (dispatch_queue_t)bgQueue { if (!_bgQueue) { _bgQueue = dispatch_queue_create("test background queue", 0); } return _bgQueue; } - (void)dispatchAsync:(NS_SWIFT_SENDABLE dispatch_block_t)block { dispatch_async(self.bgQueue, ^{ @autoreleasepool { block(); } }); } - (void)dispatchAsyncAndWait:(NS_SWIFT_SENDABLE dispatch_block_t)block { [self dispatchAsync:block]; dispatch_sync(_bgQueue, ^{}); } - (id)nonLiteralNil { return nil; } @end ================================================ FILE: Realm/TestUtils/RLMTestObjects.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestObjects.h" #import #pragma mark - Abstract Objects #pragma mark - #pragma mark OneTypeObjects @implementation StringObject - (NSString *)firstLetter { return [self.stringCol substringToIndex:1]; } @end @implementation IntObject @end @implementation AllIntSizesObject @end @implementation FloatObject @end @implementation DoubleObject @end @implementation BoolObject @end @implementation DateObject @end @implementation BinaryObject @end @implementation DecimalObject @end @implementation MixedObject @end @implementation UTF8Object @end @implementation IndexedStringObject + (NSArray *)indexedProperties { return @[@"stringCol"]; } @end @implementation LinkStringObject @end @implementation LinkIndexedStringObject @end @implementation RequiredPropertiesObject + (NSArray *)requiredProperties { return @[@"stringCol", @"binaryCol"]; } @end @implementation IgnoredURLObject + (NSArray *)ignoredProperties { return @[@"url"]; } @end @implementation EmbeddedIntObject @end @implementation EmbeddedIntParentObject + (NSString *)primaryKey { return @"pk"; } @end @implementation UuidObject @end #pragma mark AllTypesObject @implementation AllTypesObject + (NSDictionary *)linkingObjectsProperties { return @{@"linkingObjectsCol": [RLMPropertyDescriptor descriptorWithClass:LinkToAllTypesObject.class propertyName:@"allTypesCol"]}; } + (NSArray *)requiredProperties { return @[@"stringCol", @"dateCol", @"binaryCol", @"decimalCol", @"objectIdCol", @"uuidCol"]; } + (NSDictionary *)values:(int)i stringObject:(StringObject *)so { char str[] = "a"; str[0] += i - 1; return @{ @"boolCol": @(i % 2), @"cBoolCol": @(i % 2), @"intCol": @(i), @"floatCol": @(1.1f * i), @"doubleCol": @(1.11 * i), @"stringCol": @(str), @"binaryCol": [@(str) dataUsingEncoding:NSUTF8StringEncoding], @"dateCol": [NSDate dateWithTimeIntervalSince1970:i], @"longCol": @((long long)i * INT_MAX + 1), @"decimalCol": [[RLMDecimal128 alloc] initWithNumber:@(i)], @"objectIdCol": [RLMObjectId objectId], @"objectCol": so ?: NSNull.null, @"uuidCol": i < 4 ? @[[[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"], [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"], [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"], [[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"]][i] : [[NSUUID alloc] initWithUUIDString:@"b9d325b0-3058-4838-8473-8f1aaae410db"], @"anyCol": @(i+1) }; } + (NSDictionary *)values:(int)i stringObject:(StringObject *)so mixedObject:(MixedObject *)mo { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[self values:i stringObject:so]]; dict[@"mixedObjectCol"] = mo ?: NSNull.null; return dict; } @end @implementation AllPrimitiveRLMValues @end @implementation ArrayOfAllTypesObject @end @implementation DictionaryOfAllTypesObject @end @implementation SetOfAllTypesObject @end @implementation LinkToAllTypesObject @end @implementation AllOptionalTypes @end @implementation AllPrimitiveArrays + (NSArray *)requiredProperties { return @[@"intObj", @"floatObj", @"doubleObj", @"boolObj", @"stringObj", @"dateObj", @"dataObj", @"decimalObj", @"objectIdObj", @"uuidObj"]; } @end @implementation AllPrimitiveSets + (NSArray *)requiredProperties { return @[@"intObj", @"floatObj", @"doubleObj", @"boolObj", @"stringObj", @"dateObj", @"dataObj", @"decimalObj", @"objectIdObj", @"uuidObj", @"intObj2", @"floatObj2", @"doubleObj2", @"boolObj2", @"stringObj2", @"dateObj2", @"dataObj2", @"decimalObj2", @"objectIdObj2", @"uuidObj2"]; } @end @implementation AllOptionalPrimitiveArrays @end @implementation AllDictionariesObject @end @implementation AllOptionalPrimitiveSets @end @implementation AllPrimitiveDictionaries + (NSArray *)requiredProperties { return @[@"intObj", @"floatObj", @"doubleObj", @"boolObj", @"stringObj", @"dateObj", @"dataObj", @"decimalObj", @"objectIdObj", @"uuidObj", @"intObj2", @"floatObj2", @"doubleObj2", @"boolObj2", @"stringObj2", @"dateObj2", @"dataObj2", @"decimalObj2", @"objectIdObj2", @"uuidObj2"]; } @end @implementation AllOptionalPrimitiveDictionaries @end @implementation AllOptionalTypesPK + (NSString *)primaryKey { return @"pk"; } + (NSDictionary *)defaultPropertyValues { return @{@"pk": NSUUID.UUID.UUIDString}; } @end #pragma mark - Real Life Objects #pragma mark - #pragma mark EmployeeObject @implementation EmployeeObject @end #pragma mark CompanyObject @implementation CompanyObject @end @implementation PrimaryEmployeeObject + (NSString *)primaryKey { return @"name"; } @end @implementation LinkToPrimaryEmployeeObject @end @implementation PrimaryCompanyObject + (NSString *)primaryKey { return @"name"; } @end @implementation ArrayOfPrimaryCompanies @end @implementation SetOfPrimaryCompanies @end #pragma mark LinkToCompanyObject @implementation LinkToCompanyObject @end #pragma mark DogObject @class OwnerObject; @implementation DogObject + (NSDictionary *)linkingObjectsProperties { return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:OwnerObject.class propertyName:@"dog"] }; } @end #pragma mark OwnerObject @implementation OwnerObject - (BOOL)isEqual:(id)other { return [self isEqualToObject:other]; } @end #pragma mark - Specific Use Objects #pragma mark - #pragma mark CustomAccessorsObject @implementation CustomAccessorsObject @end #pragma mark BaseClassStringObject @implementation BaseClassStringObject @end #pragma mark CircleObject @implementation CircleObject @end #pragma mark CircleArrayObject @implementation CircleArrayObject @end #pragma mark CircleSetObject @implementation CircleSetObject @end #pragma mark CircleDictionaryObject @implementation CircleDictionaryObject @end #pragma mark ArrayPropertyObject @implementation ArrayPropertyObject @end #pragma mark SetPropertyObject @implementation SetPropertyObject @end #pragma mark DictionaryPropertyObject @implementation DictionaryPropertyObject @end #pragma mark DynamicTestObject @implementation DynamicTestObject @end #pragma mark AggregateObject @implementation AggregateObject @end @implementation AggregateArrayObject @end @implementation AggregateSetObject @end @implementation AggregateDictionaryObject @end #pragma mark PrimaryStringObject @implementation PrimaryStringObject + (NSString *)primaryKey { return @"stringCol"; } + (NSArray *)requiredProperties { return @[@"stringCol"]; } @end @implementation PrimaryNullableStringObject + (NSString *)primaryKey { return @"stringCol"; } @end @implementation PrimaryIntObject + (NSString *)primaryKey { return @"intCol"; } @end @implementation PrimaryInt64Object + (NSString *)primaryKey { return @"int64Col"; } @end @implementation PrimaryNullableIntObject + (NSString *)primaryKey { return @"optIntCol"; } @end #pragma mark ReadOnlyPropertyObject @interface ReadOnlyPropertyObject () @property (readwrite) int readOnlyPropertyMadeReadWriteInClassExtension; @end @implementation ReadOnlyPropertyObject - (NSNumber *)readOnlyUnsupportedProperty { return nil; } @end #pragma mark IntegerArrayPropertyObject @implementation IntegerArrayPropertyObject @end #pragma mark IntegerSetPropertyObject @implementation IntegerSetPropertyObject @end #pragma mark IntegerDictionaryPropertyObject @implementation IntegerDictionaryPropertyObject @end @implementation NumberObject @end @implementation NumberDefaultsObject + (NSDictionary *)defaultPropertyValues { return @{@"intObj" : @1, @"floatObj" : @2.2f, @"doubleObj" : @3.3, @"boolObj" : @NO}; } @end @implementation RequiredNumberObject + (NSArray *)requiredProperties { return @[@"intObj", @"floatObj", @"doubleObj", @"boolObj"]; } @end #pragma mark CustomInitializerObject @implementation CustomInitializerObject - (instancetype)init { self = [super init]; if (self) { self.stringCol = @"test"; } return self; } @end #pragma mark AbstractObject @implementation AbstractObject @end #pragma mark PersonObject @implementation PersonObject + (NSDictionary *)linkingObjectsProperties { return @{ @"parents": [RLMPropertyDescriptor descriptorWithClass:PersonObject.class propertyName:@"children"] }; } - (BOOL)isEqual:(id)other { if (![other isKindOfClass:[PersonObject class]]) { return NO; } PersonObject *otherPerson = other; return [self.name isEqual:otherPerson.name] && self.age == otherPerson.age && [self.children isEqual:otherPerson.children]; } @end @implementation RenamedProperties + (NSDictionary *)_realmColumnNames { return @{@"intCol": @"custom_intCol", @"stringCol": @"custom_stringCol"}; } @end @implementation RenamedProperties1 + (NSString *)_realmObjectName { return @"Renamed Properties"; } + (NSDictionary *)_realmColumnNames { return @{@"propA": @"prop 1", @"propB": @"prop 2"}; } + (NSDictionary *)linkingObjectsProperties { return @{@"linking1": [RLMPropertyDescriptor descriptorWithClass:LinkToRenamedProperties1.class propertyName:@"linkA"], @"linking2": [RLMPropertyDescriptor descriptorWithClass:LinkToRenamedProperties2.class propertyName:@"linkD"]}; } @end @implementation RenamedProperties2 + (NSString *)_realmObjectName { return @"Renamed Properties"; } + (NSDictionary *)_realmColumnNames { return @{@"propC": @"prop 1", @"propD": @"prop 2"}; } + (NSDictionary *)linkingObjectsProperties { return @{@"linking1": [RLMPropertyDescriptor descriptorWithClass:LinkToRenamedProperties1.class propertyName:@"linkA"], @"linking2": [RLMPropertyDescriptor descriptorWithClass:LinkToRenamedProperties2.class propertyName:@"linkD"]}; } @end @implementation LinkToRenamedProperties + (NSDictionary *)_realmColumnNames { return @{@"link": @"Link"}; } @end @implementation LinkToRenamedProperties1 + (NSString *)_realmObjectName { return @"Link To Renamed Properties"; } + (NSDictionary *)_realmColumnNames { return @{@"linkA": @"Link A", @"linkB": @"Link B"}; } @end @implementation LinkToRenamedProperties2 + (NSString *)_realmObjectName { return @"Link To Renamed Properties"; } + (NSDictionary *)_realmColumnNames { return @{@"linkC": @"Link A", @"linkD": @"Link B"}; } @end @implementation RenamedPrimaryKey + (NSString *)primaryKey { return @"pk"; } + (NSDictionary *)_realmColumnNames { return @{@"pk": @"Primary Key", @"value": @"Value"}; } @end #pragma mark FakeObject @implementation FakeObject + (bool)_realmIgnoreClass { return true; } @end @implementation FakeEmbeddedObject + (bool)_realmIgnoreClass { return true; } @end #pragma mark ComputedPropertyNotExplicitlyIgnoredObject @implementation ComputedPropertyNotExplicitlyIgnoredObject - (NSURL *)URL { return [NSURL URLWithString:self._URLBacking]; } - (void)setURL:(NSURL *)URL { self._URLBacking = URL.absoluteString; } @end ================================================ FILE: Realm/TestUtils/RealmTestSupport.h ================================================ // A dummy module header to let us import RealmTestSupport in non-swiftpm builds #import "RLMChildProcessEnvironment.h" ================================================ FILE: Realm/TestUtils/TestUtils.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "TestUtils.h" #import "RLMAssertions.h" #import #import #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealmUtil.hpp" #import #import static void recordFailure(XCTestCase *self, NSString *message, NSString *fileName, NSUInteger lineNumber) { XCTSourceCodeLocation *loc = [[XCTSourceCodeLocation alloc] initWithFilePath:fileName lineNumber:lineNumber]; XCTIssue *issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure compactDescription:message detailedDescription:nil sourceCodeContext:[[XCTSourceCodeContext alloc] initWithLocation:loc] associatedError:nil attachments:@[]]; [self recordIssue:issue]; } void RLMAssertThrowsWithReasonMatchingSwift(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber) { BOOL didThrow = NO; @try { block(); } @catch (NSException *e) { didThrow = YES; NSString *reason = e.reason; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:(NSRegularExpressionOptions)0 error:nil]; if ([regex numberOfMatchesInString:reason options:(NSMatchingOptions)0 range:NSMakeRange(0, reason.length)] == 0) { NSString *msg = [NSString stringWithFormat:@"The given expression threw an exception with reason '%@', but expected to match '%@'", reason, regexString]; recordFailure(self, msg, fileName, lineNumber); } } if (!didThrow) { NSString *prefix = @"The given expression failed to throw an exception"; message = message ? [NSString stringWithFormat:@"%@ (%@)", prefix, message] : prefix; recordFailure(self, message, fileName, lineNumber); } } static void assertThrows(XCTestCase *self, dispatch_block_t block, NSString *message, NSString *fileName, NSUInteger lineNumber, NSString *(^condition)(NSException *)) { @try { block(); NSString *prefix = @"The given expression failed to throw an exception"; message = message ? [NSString stringWithFormat:@"%@ (%@)", prefix, message] : prefix; recordFailure(self, message, fileName, lineNumber); } @catch (NSException *e) { if (NSString *failure = condition(e)) { recordFailure(self, failure, fileName, lineNumber); } } } void (RLMAssertThrowsWithName)(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *name, NSString *message, NSString *fileName, NSUInteger lineNumber) { assertThrows(self, block, message, fileName, lineNumber, ^NSString *(NSException *e) { if ([name isEqualToString:e.name]) { return nil; } return [NSString stringWithFormat:@"The given expression threw an exception named '%@', but expected '%@'", e.name, name]; }); } void (RLMAssertThrowsWithReason)(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *expected, NSString *message, NSString *fileName, NSUInteger lineNumber) { assertThrows(self, block, message, fileName, lineNumber, ^NSString *(NSException *e) { if ([e.reason rangeOfString:expected].location != NSNotFound) { return nil; } return [NSString stringWithFormat:@"The given expression threw an exception with reason '%@', but expected '%@'", e.reason, expected]; }); } void (RLMAssertThrowsWithReasonMatching)(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber) { auto regex = [NSRegularExpression regularExpressionWithPattern:regexString options:(NSRegularExpressionOptions)0 error:nil]; assertThrows(self, block, message, fileName, lineNumber, ^NSString *(NSException *e) { if ([regex numberOfMatchesInString:e.reason options:(NSMatchingOptions)0 range:{0, e.reason.length}] > 0) { return nil; } return [NSString stringWithFormat:@"The given expression threw an exception with reason '%@', but expected to match '%@'", e.reason, regexString]; }); } void (RLMAssertMatches)(XCTestCase *self, __attribute__((noescape)) NSString *(^block)(), NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber) { NSString *result = block(); NSError *err; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:(NSRegularExpressionOptions)0 error:&err]; if (err) { recordFailure(self, err.localizedDescription, fileName, lineNumber); return; } if ([regex numberOfMatchesInString:result options:(NSMatchingOptions)0 range:NSMakeRange(0, result.length)] == 0) { NSString *msg = [NSString stringWithFormat:@"The given expression '%@' did not match '%@'%@", result, regexString, message ? [NSString stringWithFormat:@": %@", message] : @""]; recordFailure(self, msg, fileName, lineNumber); } } void (RLMAssertExceptionReason)(XCTestCase *self, NSException *exception, NSString *expected, NSString *expression, NSString *fileName, NSUInteger lineNumber) { if (!exception) { return; } if ([exception.reason rangeOfString:(expected)].location != NSNotFound) { return; } auto location = [[XCTSourceCodeContext alloc] initWithLocation:[[XCTSourceCodeLocation alloc] initWithFilePath:fileName lineNumber:lineNumber]]; NSString *desc = [NSString stringWithFormat:@"The expression %@ threw an exception with reason '%@', but expected to contain '%@'", expression, exception.reason ?: @"", expected]; auto issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure compactDescription:desc detailedDescription:nil sourceCodeContext:location associatedError:nil attachments:@[]]; [self recordIssue:issue]; } bool RLMHasCachedRealmForPath(NSString *path) { return RLMGetAnyCachedRealmForPath(path.UTF8String); } bool RLMThreadSanitizerEnabled() { #if __has_feature(thread_sanitizer) return true; #else return false; #endif } #if !REALM_TVOS && !REALM_WATCHOS && !REALM_APPLE_DEVICE bool RLMCanFork() { return true; } pid_t RLMFork() { return fork(); } #else bool RLMCanFork() { return false; } pid_t RLMFork() { abort(); } #endif ================================================ FILE: Realm/TestUtils/include/RLMAssertions.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 #if !TARGET_OS_IOS || __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_14_0 #define RLMConstantInt "NSConstantIntegerNumber" #define RLMConstantDouble "NSConstantDoubleNumber" #define RLMConstantFloat "NSConstantFloatNumber" #define RLMConstantString "__NSCFConstantString" #else #define RLMConstantInt "__NSCFNumber" #define RLMConstantDouble "__NSCFNumber" #define RLMConstantFloat "__NSCFNumber" #define RLMConstantString "__NSCFConstantString" #endif FOUNDATION_EXTERN void RLMAssertThrowsWithReasonMatchingSwift(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN void RLMAssertThrowsWithName(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *name, NSString *message, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN void RLMAssertThrowsWithReasonMatching(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN void RLMAssertMatches(XCTestCase *self, __attribute__((noescape)) NSString *(^block)(void), NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN void RLMAssertThrowsWithReason(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *message, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN void RLMAssertExceptionReason(XCTestCase *self, NSException *exception, NSString *expected, NSString *expression, NSString *fileName, NSUInteger lineNumber); FOUNDATION_EXTERN bool RLMHasCachedRealmForPath(NSString *path); #define RLMAssertThrows(expression, ...) \ RLMPrimitiveAssertThrows(self, expression, __VA_ARGS__) #define RLMPrimitiveAssertThrows(self, expression, format...) \ ({ \ NSException *caughtException = nil; \ @try { \ (void)(expression); \ } \ @catch (id exception) { \ caughtException = exception; \ } \ if (!caughtException) { \ _XCTRegisterFailure(self, _XCTFailureDescription(_XCTAssertion_Throws, 0, @#expression), format); \ } \ caughtException; \ }) #define RLMAssertMatches(expression, regex, ...) \ RLMPrimitiveAssertMatches(self, expression, regex, __VA_ARGS__) #define RLMPrimitiveAssertMatches(self, expression, regexString, format...) \ ({ \ NSString *string = (expression); \ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:(NSRegularExpressionOptions)0 error:nil]; \ if ([regex numberOfMatchesInString:string options:(NSMatchingOptions)0 range:NSMakeRange(0, string.length)] == 0) { \ _XCTRegisterFailure(self, [_XCTFailureDescription(_XCTAssertion_True, 0, @#expression @" (EXPR_STRING) matches " @#regexString) stringByReplacingOccurrencesOfString:@"EXPR_STRING" withString:string ?: @""], format); \ } \ }) #define RLMAssertThrowsWithReasonMatching(expression, regex, ...) \ ({ \ NSException *exception = RLMAssertThrows(expression, __VA_ARGS__); \ if (exception) { \ RLMAssertMatches(exception.reason, regex, __VA_ARGS__); \ } \ exception; \ }) #define RLMAssertThrowsWithReason(expression, expected) \ ({ \ NSException *exception = RLMAssertThrows(expression); \ RLMAssertExceptionReason(self, exception, expected, @#expression, @"" __FILE__, __LINE__); \ exception; \ }) #define RLMAssertThrowsWithCodeMatching(expression, expectedCode, ...) \ ({ \ NSException *exception = RLMAssertThrows(expression, __VA_ARGS__); \ XCTAssertEqual([(NSError *)exception.userInfo[NSUnderlyingErrorKey] code], expectedCode, __VA_ARGS__); \ }) #define RLMValidateError(error, errDomain, errCode, msg) do { \ XCTAssertNotNil(error); \ XCTAssertEqual(error.domain, errDomain); \ XCTAssertEqual(error.code, errCode); \ XCTAssertEqualObjects(error.localizedDescription, msg); \ } while (0) #define RLMValidateErrorContains(error, errDomain, errCode, msg) do { \ XCTAssertNotNil(error); \ XCTAssertEqual(error.domain, errDomain); \ XCTAssertEqual(error.code, errCode); \ XCTAssert([error.localizedDescription containsString:msg], \ @"'%@' should contain '%@'", error.localizedDescription, msg); \ } while (0) #define RLMValidateRealmError(macroError, errCode, msg, path) do { \ NSError *error2 = (NSError *)macroError; \ RLMValidateError(error2, RLMErrorDomain, errCode, ([NSString stringWithFormat:msg, path])); \ XCTAssertEqualObjects(error2.userInfo[NSFilePathErrorKey], path); \ } while (0) #define RLMValidateRealmErrorContains(macroError, errCode, msg, path) do { \ NSError *error2 = (NSError *)macroError; \ RLMValidateErrorContains(error2, RLMErrorDomain, errCode, ([NSString stringWithFormat:msg, path])); \ XCTAssertEqualObjects(error2.userInfo[NSFilePathErrorKey], path); \ } while (0) #define RLMAssertRealmException(expr, errCode, msg, path) do { \ NSException* exception = RLMAssertThrows(expr); \ XCTAssertEqual(exception.name, RLMExceptionName); \ NSString* reason = [NSString stringWithFormat:msg, path]; \ XCTAssertEqualObjects(exception.reason, reason); \ RLMValidateRealmError(exception.userInfo[NSUnderlyingErrorKey], errCode, msg, path); \ } while (0) #define RLMAssertRealmExceptionContains(expr, errCode, msg, path) do { \ NSException* exception = RLMAssertThrows(expr); \ XCTAssertEqual(exception.name, RLMExceptionName); \ NSString* reason = [NSString stringWithFormat:msg, path]; \ XCTAssert([exception.reason containsString:reason], \ @"'%@' should contain '%@'", exception.reason, reason); \ RLMValidateRealmErrorContains(exception.userInfo[NSUnderlyingErrorKey], errCode, msg, path); \ } while (0) // XCTest assertions wrap each assertion in a try/catch to provide nice // reporting if an assertion unexpectedly throws an exception. This is normally // quite nice, but becomes a problem with the very large number of assertions // in the primitive collection test files builds. Replacing these with // assertions which do not try/catch cuts those files' build times by about // 75%. The normal XCTest assertions should still be used by default in places // where it does not cause problems. #define uncheckedAssertEqual(ex1, ex2) do { \ __typeof__(ex1) value1 = (ex1); \ __typeof__(ex2) value2 = (ex2); \ if (value1 != value2) { \ NSValue *box1 = [NSValue value:&value1 withObjCType:@encode(__typeof__(ex1))]; \ NSValue *box2 = [NSValue value:&value2 withObjCType:@encode(__typeof__(ex2))]; \ _XCTRegisterFailure(nil, _XCTFailureDescription(_XCTAssertion_Equal, 0, @#ex1, @#ex2, _XCTDescriptionForValue(box1), _XCTDescriptionForValue(box2))); \ } \ } while (0) #define uncheckedAssertEqualObjects(ex1, ex2) do { \ id value1 = (ex1); \ id value2 = (ex2); \ if (value1 != value2 && ![(id)value1 isEqual:value2]) { \ _XCTRegisterFailure(nil, _XCTFailureDescription(_XCTAssertion_EqualObjects, 0, @#ex1, @#ex2, value1, value2)); \ } \ } while (0) #define uncheckedAssertTrue(ex) uncheckedAssertEqual(ex, true) #define uncheckedAssertFalse(ex) uncheckedAssertEqual(ex, false) #define uncheckedAssertNil(ex) uncheckedAssertEqual(ex, nil) ================================================ FILE: Realm/TestUtils/include/RLMChildProcessEnvironment.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 @interface RLMChildProcessEnvironment : NSObject /// A single app identifier provided by the parent process. @property (nonatomic, readonly, nullable) NSString *appId; /// A list of app identifiers provided by the parent process. @property (nonatomic, readonly, nonnull) NSArray *appIds; /// An email credential provided by the parent process. @property (nonatomic, readonly, nullable) NSString *email; /// A password credential provided by the parent process. @property (nonatomic, readonly, nullable) NSString *password; /// A unique identifier set by the user (this differs from the PID). @property (nonatomic, readonly) NSInteger identifier; - (nonnull instancetype)init; - (nonnull instancetype)initWithAppIds:(NSArray * _Nullable)appIds email:(NSString * _Nullable)email password:(NSString * _Nullable)password identifer:(NSInteger)identifier; - (NSDictionary * _Nonnull)dictionaryValue; + (instancetype _Nonnull)current; @end ================================================ FILE: Realm/TestUtils/include/RLMMultiProcessTestCase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" @class NSTask, RLMChildProcessEnvironment; @interface RLMMultiProcessTestCase : RLMTestCase // if true, this is running the main test process @property (nonatomic, readonly) bool isParent; // spawn a child process running the current test and wait for it complete // returns the return code of the process - (int)runChildAndWait; - (int)runChildAndWaitWithAppIds:(NSArray *)appIds; - (int)runChildAndWaitWithEnvironment:(RLMChildProcessEnvironment *)environment; - (NSTask *)childTask; - (NSTask *)childTaskWithAppIds:(NSArray *)appIds; @end #define RLMRunChildAndWait() \ XCTAssertEqual(0, [self runChildAndWait], @"Tests in child process failed") #define RLMRunChildAndWaitWithAppIds(...) \ XCTAssertEqual(0, [self runChildAndWaitWithAppIds:@[__VA_ARGS__]], @"Tests in child process failed") ================================================ FILE: Realm/TestUtils/include/RLMTestCase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "RLMAssertions.h" #import "RLMTestObjects.h" RLM_HEADER_AUDIT_BEGIN(nullability, sendability) #ifdef __cplusplus extern "C" { #endif NSURL *RLMTestRealmURL(void); NSURL *RLMDefaultRealmURL(void); NSString *RLMRealmPathForFile(NSString *); NSData *RLMGenerateKey(void); #ifdef __cplusplus } #endif @interface RLMTestCaseBase : XCTestCase - (void)resetRealmState; @end @interface RLMTestCase : RLMTestCaseBase - (RLMRealm *)realmWithTestPath; - (RLMRealm *)realmWithTestPathAndSchema:(nullable RLMSchema *)schema; - (RLMRealm *)inMemoryRealmWithIdentifier:(NSString *)identifier; - (RLMRealm *)readOnlyRealmWithURL:(NSURL *)fileURL error:(NSError **)error; - (void)deleteFiles; - (void)deleteRealmFileAtURL:(NSURL *)fileURL; - (void)waitForNotification:(RLMNotification)expectedNote realm:(RLMRealm *)realm block:(dispatch_block_t)block; - (nullable id)nonLiteralNil; - (BOOL)encryptTests; - (void)dispatchAsync:(NS_SWIFT_SENDABLE dispatch_block_t)block; - (void)dispatchAsyncAndWait:(NS_SWIFT_SENDABLE dispatch_block_t)block; @property (nonatomic, readonly) dispatch_queue_t bgQueue; @end RLM_HEADER_AUDIT_END(nullability, sendability) ================================================ FILE: Realm/TestUtils/include/RLMTestObjects.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #define RLM_GENERIC_ARRAY(CLASS) RLMArray #define RLM_GENERIC_SET(CLASS) RLMSet #pragma mark - Abstract Objects #pragma mark - #pragma mark SingleTypeObjects @interface StringObject : RLMObject @property NSString *stringCol; @property(readonly) NSString *firstLetter; @end @interface IntObject : RLMObject @property int intCol; @end @interface AllIntSizesObject : RLMObject // int8_t not supported due to being ambiguous with BOOL @property int16_t int16; @property int32_t int32; @property int64_t int64; @end @interface FloatObject : RLMObject @property float floatCol; @end @interface DoubleObject : RLMObject @property double doubleCol; @end @interface BoolObject : RLMObject @property BOOL boolCol; @end @interface DateObject : RLMObject @property NSDate *dateCol; @end @interface BinaryObject : RLMObject @property NSData *binaryCol; @end @interface DecimalObject : RLMObject @property RLMDecimal128 *decimalCol; @end @interface UTF8Object : RLMObject @property NSString *柱колоéнǢкƱаم; @end @interface IndexedStringObject : RLMObject @property NSString *stringCol; @end RLM_COLLECTION_TYPE(StringObject) RLM_COLLECTION_TYPE(IntObject) @interface LinkStringObject : RLMObject @property StringObject *objectCol; @end @interface LinkIndexedStringObject : RLMObject @property IndexedStringObject *objectCol; @end @interface RequiredPropertiesObject : RLMObject @property NSString *stringCol; @property NSData *binaryCol; @property NSDate *dateCol; @end @interface IgnoredURLObject : RLMObject @property NSString *name; @property NSURL *url; @end @interface EmbeddedIntObject : RLMEmbeddedObject @property int intCol; @end RLM_COLLECTION_TYPE(EmbeddedIntObject) @interface EmbeddedIntParentObject : RLMObject @property int pk; @property EmbeddedIntObject *object; @property RLMArray *array; @end @interface UuidObject: RLMObject @property NSUUID *uuidCol; @end @interface MixedObject: RLMObject @property id anyCol; @property RLMArray *anyArray; @end #pragma mark AllTypesObject @interface AllTypesObject : RLMObject @property BOOL boolCol; @property int intCol; @property float floatCol; @property double doubleCol; @property NSString *stringCol; @property NSData *binaryCol; @property NSDate *dateCol; @property bool cBoolCol; @property int64_t longCol; @property RLMDecimal128 *decimalCol; @property RLMObjectId *objectIdCol; @property NSUUID *uuidCol; @property StringObject *objectCol; @property MixedObject *mixedObjectCol; @property (readonly) RLMLinkingObjects *linkingObjectsCol; @property id anyCol; + (NSDictionary *)values:(int)i stringObject:(StringObject *)so; + (NSDictionary *)values:(int)i stringObject:(StringObject *)so mixedObject:(MixedObject *)mo; @end RLM_COLLECTION_TYPE(AllTypesObject) @interface LinkToAllTypesObject : RLMObject @property AllTypesObject *allTypesCol; @end @interface ArrayOfAllTypesObject : RLMObject @property RLM_GENERIC_ARRAY(AllTypesObject) *array; @end @interface SetOfAllTypesObject : RLMObject @property RLM_GENERIC_SET(AllTypesObject) *set; @end @interface DictionaryOfAllTypesObject : RLMObject @property RLMDictionary *dictionary; @end @interface AllOptionalTypes : RLMObject @property NSNumber *intObj; @property NSNumber *floatObj; @property NSNumber *doubleObj; @property NSNumber *boolObj; @property NSString *string; @property NSData *data; @property NSDate *date; @property RLMDecimal128 *decimal; @property RLMObjectId *objectId; @property NSUUID *uuidCol; @end @interface AllOptionalTypesPK : RLMObject @property int pk; @property NSNumber *intObj; @property NSNumber *floatObj; @property NSNumber *doubleObj; @property NSNumber *boolObj; @property NSString *string; @property NSData *data; @property NSDate *date; @property RLMDecimal128 *decimal; @property RLMObjectId *objectId; @property NSUUID *uuidCol; @end @interface AllPrimitiveArrays : RLMObject @property RLMArray *intObj; @property RLMArray *floatObj; @property RLMArray *doubleObj; @property RLMArray *boolObj; @property RLMArray *stringObj; @property RLMArray *dateObj; @property RLMArray *dataObj; @property RLMArray *decimalObj; @property RLMArray *objectIdObj; @property RLMArray *uuidObj; @property RLMArray *anyBoolObj; @property RLMArray *anyIntObj; @property RLMArray *anyFloatObj; @property RLMArray *anyDoubleObj; @property RLMArray *anyStringObj; @property RLMArray *anyDataObj; @property RLMArray *anyDateObj; @property RLMArray *anyDecimalObj; @property RLMArray *anyObjectIdObj; @property RLMArray *anyUUIDObj; @end @interface AllOptionalPrimitiveArrays : RLMObject @property RLMArray *intObj; @property RLMArray *floatObj; @property RLMArray *doubleObj; @property RLMArray *boolObj; @property RLMArray *stringObj; @property RLMArray *dateObj; @property RLMArray *dataObj; @property RLMArray *decimalObj; @property RLMArray *objectIdObj; @property RLMArray *uuidObj; @end @interface AllPrimitiveSets : RLMObject @property RLMSet *intObj; @property RLMSet *intObj2; @property RLMSet *floatObj; @property RLMSet *floatObj2; @property RLMSet *doubleObj; @property RLMSet *doubleObj2; @property RLMSet *boolObj; @property RLMSet *boolObj2; @property RLMSet *stringObj; @property RLMSet *stringObj2; @property RLMSet *dateObj; @property RLMSet *dateObj2; @property RLMSet *dataObj; @property RLMSet *dataObj2; @property RLMSet *decimalObj; @property RLMSet *decimalObj2; @property RLMSet *objectIdObj; @property RLMSet *objectIdObj2; @property RLMSet *uuidObj; @property RLMSet *uuidObj2; @property RLMSet *anyBoolObj; @property RLMSet *anyBoolObj2; @property RLMSet *anyIntObj; @property RLMSet *anyIntObj2; @property RLMSet *anyFloatObj; @property RLMSet *anyFloatObj2; @property RLMSet *anyDoubleObj; @property RLMSet *anyDoubleObj2; @property RLMSet *anyStringObj; @property RLMSet *anyStringObj2; @property RLMSet *anyDataObj; @property RLMSet *anyDataObj2; @property RLMSet *anyDateObj; @property RLMSet *anyDateObj2; @property RLMSet *anyDecimalObj; @property RLMSet *anyDecimalObj2; @property RLMSet *anyObjectIdObj; @property RLMSet *anyObjectIdObj2; @property RLMSet *anyUUIDObj; @property RLMSet *anyUUIDObj2; @end @interface AllOptionalPrimitiveSets : RLMObject @property RLMSet *intObj; @property RLMSet *intObj2; @property RLMSet *floatObj; @property RLMSet *floatObj2; @property RLMSet *doubleObj; @property RLMSet *doubleObj2; @property RLMSet *boolObj; @property RLMSet *boolObj2; @property RLMSet *stringObj; @property RLMSet *stringObj2; @property RLMSet *dateObj; @property RLMSet *dateObj2; @property RLMSet *dataObj; @property RLMSet *dataObj2; @property RLMSet *decimalObj; @property RLMSet *decimalObj2; @property RLMSet *objectIdObj; @property RLMSet *objectIdObj2; @property RLMSet *uuidObj; @property RLMSet *uuidObj2; @end @interface AllPrimitiveRLMValues : RLMObject @property id nullVal; @property id intVal; @property id floatVal; @property id doubleVal; @property id boolVal; @property id stringVal; @property id dateVal; @property id dataVal; @property id decimalVal; @property id objectIdVal; @property id uuidVal; @end @interface AllDictionariesObject : RLMObject @property RLMDictionary *intDict; @property RLMDictionary *floatDict; @property RLMDictionary *doubleDict; @property RLMDictionary *boolDict; @property RLMDictionary *stringDict; @property RLMDictionary *dateDict; @property RLMDictionary *dataDict; @property RLMDictionary *decimalDict; @property RLMDictionary *objectIdDict; @property RLMDictionary *uuidDict; @property RLMDictionary *stringObjDict; @end @interface AllPrimitiveDictionaries : RLMObject @property RLMDictionary *intObj; @property RLMDictionary *floatObj; @property RLMDictionary *doubleObj; @property RLMDictionary *boolObj; @property RLMDictionary *stringObj; @property RLMDictionary *dateObj; @property RLMDictionary *dataObj; @property RLMDictionary *decimalObj; @property RLMDictionary *objectIdObj; @property RLMDictionary *uuidObj; @property RLMDictionary *anyBoolObj; @property RLMDictionary *anyIntObj; @property RLMDictionary *anyFloatObj; @property RLMDictionary *anyDoubleObj; @property RLMDictionary *anyStringObj; @property RLMDictionary *anyDataObj; @property RLMDictionary *anyDateObj; @property RLMDictionary *anyDecimalObj; @property RLMDictionary *anyObjectIdObj; @property RLMDictionary *anyUUIDObj; @end @interface AllOptionalPrimitiveDictionaries : RLMObject @property RLMDictionary *intObj; @property RLMDictionary *floatObj; @property RLMDictionary *doubleObj; @property RLMDictionary *boolObj; @property RLMDictionary *stringObj; @property RLMDictionary *dateObj; @property RLMDictionary *dataObj; @property RLMDictionary *decimalObj; @property RLMDictionary *objectIdObj; @property RLMDictionary *uuidObj; @end #pragma mark - Real Life Objects #pragma mark - #pragma mark EmployeeObject @interface EmployeeObject : RLMObject @property NSString *name; @property int age; @property BOOL hired; @end RLM_COLLECTION_TYPE(EmployeeObject) #pragma mark CompanyObject @interface CompanyObject : RLMObject @property NSString *name; @property RLM_GENERIC_ARRAY(EmployeeObject) *employees; @property RLM_GENERIC_SET(EmployeeObject) *employeeSet; @property RLMDictionary *employeeDict; @end #pragma mark LinkToCompanyObject @interface LinkToCompanyObject : RLMObject @property CompanyObject *company; @end #pragma mark DogObject @interface DogObject : RLMObject @property NSString *dogName; @property int age; @property (readonly) RLMLinkingObjects *owners; @end RLM_COLLECTION_TYPE(DogObject) @interface DogArrayObject : RLMObject @property RLM_GENERIC_ARRAY(DogObject) *dogs; @end @interface DogSetObject : RLMObject @property RLM_GENERIC_SET(DogObject) *dogs; @end @interface DogDictionaryObject : RLMObject @property RLMDictionary *dogs; @end #pragma mark OwnerObject @interface OwnerObject : RLMObject @property NSString *name; @property DogObject *dog; @end #pragma mark - Specific Use Objects #pragma mark - #pragma mark CustomAccessorsObject @interface CustomAccessorsObject : RLMObject @property (getter = getThatName) NSString *name; @property (setter = setTheInt:) int age; @end #pragma mark BaseClassStringObject @interface BaseClassStringObject : RLMObject @property int intCol; @end @interface BaseClassStringObject () @property NSString *stringCol; @end #pragma mark CircleObject @interface CircleObject : RLMObject @property NSString *data; @property CircleObject *next; @end RLM_COLLECTION_TYPE(CircleObject); #pragma mark CircleArrayObject @interface CircleArrayObject : RLMObject @property RLM_GENERIC_ARRAY(CircleObject) *circles; @end #pragma mark CircleSetObject @interface CircleSetObject : RLMObject @property RLM_GENERIC_SET(CircleObject) *circles; @end #pragma mark CircleDictionaryObject @interface CircleDictionaryObject : RLMObject @property RLMDictionary *circles; @end #pragma mark ArrayPropertyObject @interface ArrayPropertyObject : RLMObject @property NSString *name; @property RLM_GENERIC_ARRAY(StringObject) *array; @property RLM_GENERIC_ARRAY(IntObject) *intArray; @end #pragma mark SetPropertyObject @interface SetPropertyObject : RLMObject @property NSString *name; @property RLM_GENERIC_SET(StringObject) *set; @property RLM_GENERIC_SET(IntObject) *intSet; @end #pragma mark DictionaryPropertyObject @interface DictionaryPropertyObject : RLMObject @property RLMDictionary *stringDictionary; @property RLMDictionary *intDictionary; @property RLMDictionary *primitiveStringDictionary; @property RLMDictionary *embeddedDictionary; @property RLMDictionary *intObjDictionary; @end #pragma mark DynamicObject @interface DynamicTestObject : RLMObject @property NSString *stringCol; @property int intCol; @end #pragma mark AggregateObject @interface AggregateObject : RLMObject @property int intCol; @property float floatCol; @property double doubleCol; @property BOOL boolCol; @property NSDate *dateCol; @property id anyCol; @end RLM_COLLECTION_TYPE(AggregateObject) @interface AggregateArrayObject : RLMObject @property RLMArray *array; @end @interface AggregateSetObject : RLMObject @property RLMSet *set; @end @interface AggregateDictionaryObject : RLMObject @property RLMDictionary *dictionary; @end #pragma mark PrimaryStringObject @interface PrimaryStringObject : RLMObject @property NSString *stringCol; @property int intCol; @end @interface PrimaryNullableStringObject : RLMObject @property NSString *stringCol; @property int intCol; @end @interface PrimaryIntObject : RLMObject @property int intCol; @end RLM_COLLECTION_TYPE(PrimaryIntObject); @interface PrimaryInt64Object : RLMObject @property int64_t int64Col; @end @interface PrimaryNullableIntObject : RLMObject @property NSNumber *optIntCol; @property int value; @end @interface ReadOnlyPropertyObject : RLMObject @property (readonly) NSNumber *readOnlyUnsupportedProperty; @property (readonly) int readOnlySupportedProperty; @property (readonly) int readOnlyPropertyMadeReadWriteInClassExtension; @end #pragma mark IntegerArrayPropertyObject @interface IntegerArrayPropertyObject : RLMObject @property NSInteger number; @property RLM_GENERIC_ARRAY(IntObject) *array; @end #pragma mark IntegerSetPropertyObject @interface IntegerSetPropertyObject : RLMObject @property NSInteger number; @property RLM_GENERIC_SET(IntObject) *set; @end #pragma mark IntegerDictionaryPropertyObject @interface IntegerDictionaryPropertyObject : RLMObject @property NSInteger number; @property RLMDictionary *dictionary; @end @interface NumberObject : RLMObject @property NSNumber *intObj; @property NSNumber *floatObj; @property NSNumber *doubleObj; @property NSNumber *boolObj; @end @interface NumberDefaultsObject : NumberObject @end @interface RequiredNumberObject : RLMObject @property NSNumber *intObj; @property NSNumber *floatObj; @property NSNumber *doubleObj; @property NSNumber *boolObj; @end #pragma mark CustomInitializerObject @interface CustomInitializerObject : RLMObject @property NSString *stringCol; @end #pragma mark AbstractObject @interface AbstractObject : RLMObject @end #pragma mark PersonObject @class PersonObject; RLM_COLLECTION_TYPE(PersonObject); @interface PersonObject : RLMObject @property NSString *name; @property NSInteger age; @property RLMArray *children; @property (readonly) RLMLinkingObjects *parents; @end @interface PrimaryEmployeeObject : EmployeeObject @end RLM_COLLECTION_TYPE(PrimaryEmployeeObject); @interface LinkToPrimaryEmployeeObject : RLMObject @property PrimaryEmployeeObject *wrapped; @end @interface PrimaryCompanyObject : RLMObject @property NSString *name; @property RLM_GENERIC_ARRAY(PrimaryEmployeeObject) *employees; @property RLM_GENERIC_SET(PrimaryEmployeeObject) *employeeSet; @property RLMDictionary *employeeDict; @property PrimaryEmployeeObject *intern; @property LinkToPrimaryEmployeeObject *wrappedIntern; @end RLM_COLLECTION_TYPE(PrimaryCompanyObject); @interface ArrayOfPrimaryCompanies : RLMObject @property RLM_GENERIC_ARRAY(PrimaryCompanyObject) *companies; @end @interface SetOfPrimaryCompanies : RLMObject @property RLM_GENERIC_SET(PrimaryCompanyObject) *companies; @end #pragma mark ComputedPropertyNotExplicitlyIgnoredObject @interface ComputedPropertyNotExplicitlyIgnoredObject : RLMObject @property NSString *_URLBacking; @property NSURL *URL; @end @interface RenamedProperties : RLMObject @property (nonatomic) int intCol; @property NSString *stringCol; @end @interface RenamedProperties1 : RLMObject @property (nonatomic) int propA; @property (nonatomic) NSString *propB; @property (readonly, nonatomic) RLMLinkingObjects *linking1; @property (readonly, nonatomic) RLMLinkingObjects *linking2; @end @interface RenamedProperties2 : RLMObject @property (nonatomic) int propC; @property (nonatomic) NSString *propD; @property (readonly, nonatomic) RLMLinkingObjects *linking1; @property (readonly, nonatomic) RLMLinkingObjects *linking2; @end RLM_COLLECTION_TYPE(RenamedProperties1) RLM_COLLECTION_TYPE(RenamedProperties2) RLM_COLLECTION_TYPE(RenamedProperties) @interface LinkToRenamedProperties : RLMObject @property (nonatomic) RenamedProperties *link; @property (nonatomic) RLM_GENERIC_ARRAY(RenamedProperties) *array; @property (nonatomic) RLM_GENERIC_SET(RenamedProperties) *set; @property (nonatomic) RLMDictionary *dictionary; @end @interface LinkToRenamedProperties1 : RLMObject @property (nonatomic) RenamedProperties1 *linkA; @property (nonatomic) RenamedProperties2 *linkB; @property (nonatomic) RLM_GENERIC_ARRAY(RenamedProperties1) *array; @property (nonatomic) RLM_GENERIC_SET(RenamedProperties1) *set; @property (nonatomic) RLMDictionary *dictionary; @end @interface LinkToRenamedProperties2 : RLMObject @property (nonatomic) RenamedProperties2 *linkC; @property (nonatomic) RenamedProperties1 *linkD; @property (nonatomic) RLM_GENERIC_ARRAY(RenamedProperties2) *array; @property (nonatomic) RLM_GENERIC_SET(RenamedProperties2) *set; @property (nonatomic) RLMDictionary *dictionary; @end @interface RenamedPrimaryKey : RLMObject @property (nonatomic) int pk; @property (nonatomic) int value; @end #pragma mark FakeObject @interface FakeObject : RLMObject @end @interface FakeEmbeddedObject : RLMEmbeddedObject @end ================================================ FILE: Realm/TestUtils/include/TestUtils.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 #import #import RLM_HEADER_AUDIT_BEGIN(nullability) FOUNDATION_EXTERN void RLMAssertThrowsWithReasonMatchingSwift(XCTestCase *self, __attribute__((noescape)) dispatch_block_t block, NSString *regexString, NSString *_Nullable message, NSString *fileName, NSUInteger lineNumber); // It appears to be impossible to check this from Swift so we need a helper function FOUNDATION_EXTERN bool RLMThreadSanitizerEnabled(void); FOUNDATION_EXTERN bool RLMCanFork(void); FOUNDATION_EXTERN pid_t RLMFork(void); RLM_HEADER_AUDIT_END(nullability) ================================================ FILE: Realm/Tests/ArrayPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" @implementation DogArrayObject @end @interface ArrayPropertyTests : RLMTestCase @end @implementation ArrayPropertyTests -(void)testPopulateEmptyArray { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[], @[]]]; XCTAssertNotNil(array.array, @"Should be able to get an empty array"); XCTAssertEqual(array.array.count, 0U, @"Should start with no array elements"); StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; [array.array addObject:obj]; [array.array addObject:[StringObject createInRealm:realm withValue:@[@"b"]]]; [array.array addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqual(array.array.count, 3U, @"Should have three elements in array"); XCTAssertEqualObjects([array.array[0] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([array.array[1] stringCol], @"b", @"Second element should have property value 'b'"); XCTAssertEqualObjects([array.array[2] stringCol], @"a", @"Third element should have property value 'a'"); RLMArray *arrayProp = array.array; RLMAssertThrowsWithReasonMatching([arrayProp addObject:obj], @"write transaction"); // make sure we can fast enumerate for (RLMObject *obj in array.array) { XCTAssertTrue(obj.description.length, @"Object should have description"); } } -(void)testModifyDetatchedArray { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *arObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[], @[]]]; XCTAssertNotNil(arObj.array, @"Should be able to get an empty array"); XCTAssertEqual(arObj.array.count, 0U, @"Should start with no array elements"); StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; RLMArray *array = arObj.array; [array addObject:obj]; [array addObject:[StringObject createInRealm:realm withValue:@[@"b"]]]; [realm commitWriteTransaction]; XCTAssertEqual(array.count, 2U, @"Should have two elements in array"); XCTAssertEqualObjects([array[0] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([arObj.array[1] stringCol], @"b", @"Second element should have property value 'b'"); RLMAssertThrowsWithReasonMatching([array addObject:obj], @"write transaction"); } - (void)testDeleteUnmanagedObjectWithArrayProperty { ArrayPropertyObject *arObj = [[ArrayPropertyObject alloc] initWithValue:@[@"arrayObject", @[@[@"a"]], @[]]]; RLMArray *stringArray = arObj.array; XCTAssertFalse(stringArray.isInvalidated, @"stringArray should be valid after creation."); arObj = nil; XCTAssertFalse(stringArray.isInvalidated, @"stringArray should still be valid after parent deletion."); } - (void)testDeleteObjectWithArrayProperty { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *arObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[@[@"a"]], @[]]]; RLMArray *stringArray = arObj.array; XCTAssertFalse(stringArray.isInvalidated, @"stringArray should be valid after creation."); [realm deleteObject:arObj]; XCTAssertTrue(stringArray.isInvalidated, @"stringArray should be invalid after parent deletion."); [realm commitWriteTransaction]; } - (void)testDeleteObjectInArrayProperty { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *arObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[@[@"a"]], @[]]]; RLMArray *stringArray = arObj.array; StringObject *firstObject = stringArray.firstObject; [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; XCTAssertFalse(stringArray.isInvalidated, @"stringArray should be valid after member object deletion."); XCTAssertTrue(firstObject.isInvalidated, @"firstObject should be invalid after deletion."); XCTAssertEqual(stringArray.count, 0U, @"stringArray.count should be zero after deleting its only member."); [realm commitWriteTransaction]; } -(void)testInsertMultiple { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[], @[]]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; child2.stringCol = @"b"; [obj.array addObjects:@[child2, child1]]; [realm commitWriteTransaction]; RLMResults *children = [StringObject allObjectsInRealm:realm]; XCTAssertEqualObjects([children[0] stringCol], @"a", @"First child should be 'a'"); XCTAssertEqualObjects([children[1] stringCol], @"b", @"Second child should be 'b'"); } -(void)testInsertAtIndex { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"arrayObject", @[], @[]]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; child2.stringCol = @"b"; [obj.array addObject:child2]; RLMAssertThrowsWithReasonMatching([obj.array insertObject:child1 atIndex:2], @"must be less than 2"); [realm commitWriteTransaction]; RLMArray *children = obj.array; XCTAssertEqual(children.count, 1U); XCTAssertEqualObjects([children[0] stringCol], @"b", @"Only child should be 'b'"); } - (void)testMove { RLMRealm *realm = [self realmWithTestPath]; ArrayPropertyObject *obj = [[ArrayPropertyObject alloc] initWithValue:@[@"arrayObject", @[@[@"a"], @[@"b"]], @[]]]; RLM_GENERIC_ARRAY(StringObject) *children = obj.array; [children moveObjectAtIndex:1 toIndex:0]; XCTAssertEqualObjects([children[0] stringCol], @"b"); XCTAssertEqualObjects([children[1] stringCol], @"a"); [children moveObjectAtIndex:0 toIndex:1]; XCTAssertEqualObjects([children[0] stringCol], @"a"); XCTAssertEqualObjects([children[1] stringCol], @"b"); [children moveObjectAtIndex:0 toIndex:0]; XCTAssertEqualObjects([children[0] stringCol], @"a"); XCTAssertEqualObjects([children[1] stringCol], @"b"); RLMAssertThrowsWithReasonMatching([children moveObjectAtIndex:0 toIndex:2], @"must be less than 2"); RLMAssertThrowsWithReasonMatching([children moveObjectAtIndex:2 toIndex:0], @"must be less than 2"); [realm beginWriteTransaction]; [realm addObject:obj]; children = obj.array; [children moveObjectAtIndex:1 toIndex:0]; XCTAssertEqualObjects([children[0] stringCol], @"b"); XCTAssertEqualObjects([children[1] stringCol], @"a"); [children moveObjectAtIndex:0 toIndex:1]; XCTAssertEqualObjects([children[0] stringCol], @"a"); XCTAssertEqualObjects([children[1] stringCol], @"b"); [children moveObjectAtIndex:0 toIndex:0]; XCTAssertEqualObjects([children[0] stringCol], @"a"); XCTAssertEqualObjects([children[1] stringCol], @"b"); RLMAssertThrowsWithReasonMatching([children moveObjectAtIndex:0 toIndex:2], @"must be less than 2"); RLMAssertThrowsWithReasonMatching([children moveObjectAtIndex:2 toIndex:0], @"must be less than 2"); [realm commitWriteTransaction]; RLMAssertThrowsWithReasonMatching([children moveObjectAtIndex:1 toIndex:0], @"write transaction"); } - (void)testAddInvalidated { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; [realm addObject:person]; [realm deleteObjects:[EmployeeObject allObjects]]; RLMAssertThrowsWithReasonMatching([company.employees addObject:person], @"invalidated"); RLMAssertThrowsWithReasonMatching([company.employees insertObject:person atIndex:0], @"invalidated"); [realm cancelWriteTransaction]; } - (void)testAddNil { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; RLMAssertThrowsWithReason([company.employees addObject:self.nonLiteralNil], @"Invalid nil value for array of 'EmployeeObject'."); [realm cancelWriteTransaction]; } - (void)testUnmanaged { RLMRealm *realm = [self realmWithTestPath]; ArrayPropertyObject *array = [[ArrayPropertyObject alloc] init]; array.name = @"name"; XCTAssertNotNil(array.array, @"RLMArray property should get created on access"); XCTAssertNil(array.array.firstObject, @"No objects added yet"); XCTAssertNil(array.array.lastObject, @"No objects added yet"); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; [array.array addObject:obj1]; [array.array addObject:obj2]; [array.array addObject:obj3]; XCTAssertEqualObjects(array.array.firstObject, obj1, @"Objects should be equal"); XCTAssertEqualObjects(array.array.lastObject, obj3, @"Objects should be equal"); XCTAssertEqualObjects([array.array objectAtIndex:1], obj2, @"Objects should be equal"); NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; [indexSet addIndex:0]; [indexSet addIndex:2]; XCTAssertEqualObjects([array.array objectsAtIndexes:indexSet], (@[obj1, obj3]), @"Objects should be equal"); [realm beginWriteTransaction]; [realm addObject:array]; [realm commitWriteTransaction]; XCTAssertEqual(array.array.count, 3U, @"Should have two elements in array"); XCTAssertEqualObjects([array.array[0] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([array.array[1] stringCol], @"b", @"Second element should have property value 'b'"); NSArray *objectsAtIndexes = [array.array objectsAtIndexes:indexSet]; XCTAssertEqualObjects([objectsAtIndexes[0] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([objectsAtIndexes[1] stringCol], @"c", @"Second element should have property value 'c'"); [realm beginWriteTransaction]; [array.array replaceObjectAtIndex:0 withObject:obj3]; XCTAssertTrue([[array.array objectAtIndex:0] isEqualToObject:obj3], @"Objects should be replaced"); array.array[0] = obj1; XCTAssertTrue([obj1 isEqualToObject:[array.array objectAtIndex:0]], @"Objects should be replaced"); [array.array removeLastObject]; XCTAssertEqual(array.array.count, 2U, @"2 objects left"); [array.array addObject:obj1]; [array.array removeAllObjects]; XCTAssertEqual(array.array.count, 0U, @"All objects removed"); [realm commitWriteTransaction]; ArrayPropertyObject *intArray = [[ArrayPropertyObject alloc] init]; IntObject *intObj = [[IntObject alloc] init]; intObj.intCol = 1; RLMAssertThrowsWithReasonMatching([intArray.array addObject:(id)intObj], @"IntObject.*StringObject"); [intArray.intArray addObject:intObj]; XCTAssertThrows([intArray.intArray objectsWhere:@"intCol == 1"], @"Should throw on unmanaged RLMArray"); XCTAssertThrows(([intArray.intArray objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol == %i", 1]]), @"Should throw on unmanaged RLMArray"); XCTAssertThrows([intArray.intArray sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"Should throw on unmanaged RLMArray"); XCTAssertEqual(0U, [intArray.intArray indexOfObjectWhere:@"intCol == 1"]); XCTAssertEqual(0U, ([intArray.intArray indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol == %i", 1]])); XCTAssertEqual([intArray.intArray indexOfObject:intObj], 0U, @"Should be first element"); XCTAssertEqual([intArray.intArray indexOfObject:intObj], 0U, @"Should be first element"); // test unmanaged with literals __unused ArrayPropertyObject *obj = [[ArrayPropertyObject alloc] initWithValue:@[@"n", @[], @[[[IntObject alloc] initWithValue:@[@1]]]]]; } - (void)testUnmanagedComparision { RLMRealm *realm = [self realmWithTestPath]; ArrayPropertyObject *array = [[ArrayPropertyObject alloc] init]; ArrayPropertyObject *array2 = [[ArrayPropertyObject alloc] init]; array.name = @"name"; array2.name = @"name2"; XCTAssertNotNil(array.array, @"RLMArray property should get created on access"); XCTAssertNotNil(array2.array, @"RLMArray property should get created on access"); XCTAssertTrue([array.array isEqual:array2.array], @"Empty arrays should be equal"); XCTAssertNil(array.array.firstObject, @"No objects added yet"); XCTAssertNil(array2.array.lastObject, @"No objects added yet"); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; [array.array addObject:obj1]; [array.array addObject:obj2]; [array.array addObject:obj3]; [array2.array addObject:obj1]; [array2.array addObject:obj2]; [array2.array addObject:obj3]; XCTAssertTrue([array.array isEqual:array2.array], @"Arrays should be equal"); [array2.array removeLastObject]; XCTAssertFalse([array.array isEqual:array2.array], @"Arrays should not be equal"); [array2.array addObject:obj3]; XCTAssertTrue([array.array isEqual:array2.array], @"Arrays should be equal"); [realm beginWriteTransaction]; [realm addObject:array]; [realm commitWriteTransaction]; XCTAssertFalse([array.array isEqual:array2.array], @"Comparing a managed array to an unmanaged one should fail"); XCTAssertFalse([array2.array isEqual:array.array], @"Comparing a managed array to an unmanaged one should fail"); } - (void)testUnmanagedPrimitive { AllPrimitiveArrays *obj = [[AllPrimitiveArrays alloc] init]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.uuidObj isKindOfClass:[RLMArray class]]); [obj.intObj addObject:@1]; XCTAssertEqualObjects(obj.intObj[0], @1); XCTAssertThrows([obj.intObj addObject:@""]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; obj = [AllPrimitiveArrays createInRealm:realm withValue:@[@[],@[],@[],@[],@[],@[],@[]]]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMArray class]]); XCTAssertTrue([obj.uuidObj isKindOfClass:[RLMArray class]]); [obj.intObj addObject:@5]; XCTAssertEqualObjects(obj.intObj.firstObject, @5); [realm cancelWriteTransaction]; } - (void)testReplaceObjectAtIndexInUnmanagedArray { ArrayPropertyObject *array = [[ArrayPropertyObject alloc] init]; array.name = @"name"; StringObject *stringObj1 = [[StringObject alloc] init]; stringObj1.stringCol = @"a"; StringObject *stringObj2 = [[StringObject alloc] init]; stringObj2.stringCol = @"b"; StringObject *stringObj3 = [[StringObject alloc] init]; stringObj3.stringCol = @"c"; [array.array addObject:stringObj1]; [array.array addObject:stringObj2]; [array.array addObject:stringObj3]; IntObject *intObj1 = [[IntObject alloc] init]; intObj1.intCol = 0; IntObject *intObj2 = [[IntObject alloc] init]; intObj2.intCol = 1; IntObject *intObj3 = [[IntObject alloc] init]; intObj3.intCol = 2; [array.intArray addObject:intObj1]; [array.intArray addObject:intObj2]; [array.intArray addObject:intObj3]; XCTAssertEqualObjects(array.array[0], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects(array.array[1], stringObj2, @"Objects should be equal"); XCTAssertEqualObjects(array.array[2], stringObj3, @"Objects should be equal"); XCTAssertEqual(array.array.count, 3U, @"Should have 3 elements in string array"); XCTAssertEqualObjects(array.intArray[0], intObj1, @"Objects should be equal"); XCTAssertEqualObjects(array.intArray[1], intObj2, @"Objects should be equal"); XCTAssertEqualObjects(array.intArray[2], intObj3, @"Objects should be equal"); XCTAssertEqual(array.intArray.count, 3U, @"Should have 3 elements in int array"); StringObject *stringObj4 = [[StringObject alloc] init]; stringObj4.stringCol = @"d"; [array.array replaceObjectAtIndex:0 withObject:stringObj4]; XCTAssertTrue([[array.array objectAtIndex:0] isEqualToObject:stringObj4], @"Objects should be replaced"); XCTAssertEqual(array.array.count, 3U, @"Should have 3 elements in int array"); IntObject *intObj4 = [[IntObject alloc] init]; intObj4.intCol = 3; [array.intArray replaceObjectAtIndex:1 withObject:intObj4]; XCTAssertTrue([[array.intArray objectAtIndex:1] isEqualToObject:intObj4], @"Objects should be replaced"); XCTAssertEqual(array.intArray.count, 3U, @"Should have 3 elements in int array"); RLMAssertThrowsWithReasonMatching([array.array replaceObjectAtIndex:0 withObject:(id)intObj4], @"IntObject.*StringObject"); RLMAssertThrowsWithReasonMatching([array.intArray replaceObjectAtIndex:1 withObject:(id)stringObj4], @"StringObject.*IntObject"); } - (void)testDeleteObjectInUnmanagedArray { ArrayPropertyObject *array = [[ArrayPropertyObject alloc] init]; array.name = @"name"; StringObject *stringObj1 = [[StringObject alloc] init]; stringObj1.stringCol = @"a"; StringObject *stringObj2 = [[StringObject alloc] init]; stringObj2.stringCol = @"b"; StringObject *stringObj3 = [[StringObject alloc] init]; stringObj3.stringCol = @"c"; [array.array addObject:stringObj1]; [array.array addObject:stringObj2]; [array.array addObject:stringObj3]; IntObject *intObj1 = [[IntObject alloc] init]; intObj1.intCol = 0; IntObject *intObj2 = [[IntObject alloc] init]; intObj2.intCol = 1; IntObject *intObj3 = [[IntObject alloc] init]; intObj3.intCol = 2; [array.intArray addObject:intObj1]; [array.intArray addObject:intObj2]; [array.intArray addObject:intObj3]; XCTAssertEqualObjects(array.array[0], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects(array.array[1], stringObj2, @"Objects should be equal"); XCTAssertEqualObjects(array.array[2], stringObj3, @"Objects should be equal"); XCTAssertEqual(array.array.count, 3U, @"Should have 3 elements in string array"); XCTAssertEqualObjects(array.intArray[0], intObj1, @"Objects should be equal"); XCTAssertEqualObjects(array.intArray[1], intObj2, @"Objects should be equal"); XCTAssertEqualObjects(array.intArray[2], intObj3, @"Objects should be equal"); XCTAssertEqual(array.intArray.count, 3U, @"Should have 3 elements in int array"); [array.array removeLastObject]; XCTAssertEqualObjects(array.array[0], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects(array.array[1], stringObj2, @"Objects should be equal"); XCTAssertEqual(array.array.count, 2U, @"Should have 2 elements in string array"); [array.array removeLastObject]; XCTAssertEqualObjects(array.array[0], stringObj1, @"Objects should be equal"); XCTAssertEqual(array.array.count, 1U, @"Should have 1 elements in string array"); [array.array removeLastObject]; XCTAssertEqual(array.array.count, 0U, @"Should have 0 elements in string array"); [array.intArray removeAllObjects]; XCTAssertEqual(array.intArray.count, 0U, @"Should have 0 elements in int array"); } - (void)testExchangeObjectAtIndexWithObjectAtIndex { void (^test)(RLMArray *) = ^(RLMArray *array) { [array exchangeObjectAtIndex:0 withObjectAtIndex:1]; XCTAssertEqual(2U, array.count); XCTAssertEqualObjects(@"b", [array[0] stringCol]); XCTAssertEqualObjects(@"a", [array[1] stringCol]); [array exchangeObjectAtIndex:1 withObjectAtIndex:1]; XCTAssertEqual(2U, array.count); XCTAssertEqualObjects(@"b", [array[0] stringCol]); XCTAssertEqualObjects(@"a", [array[1] stringCol]); [array exchangeObjectAtIndex:1 withObjectAtIndex:0]; XCTAssertEqual(2U, array.count); XCTAssertEqualObjects(@"a", [array[0] stringCol]); XCTAssertEqualObjects(@"b", [array[1] stringCol]); RLMAssertThrowsWithReasonMatching([array exchangeObjectAtIndex:1 withObjectAtIndex:20], @"less than 2"); }; ArrayPropertyObject *array = [[ArrayPropertyObject alloc] initWithValue:@[@"foo", @[@[@"a"], @[@"b"]], @[]]]; test(array.array); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:array]; test(array.array); [realm commitWriteTransaction]; } - (void)testIndexOfObject { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; EmployeeObject *deleted = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; EmployeeObject *indirectlyDeleted = [EmployeeObject allObjectsInRealm:realm].lastObject; [realm deleteObject:deleted]; // create company CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employees addObjects:[EmployeeObject allObjects]]; [company.employees removeObjectAtIndex:1]; // test unmanaged XCTAssertEqual(0U, [company.employees indexOfObject:po1]); XCTAssertEqual(1U, [company.employees indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [company.employees indexOfObject:po2]); // add to realm [realm addObject:company]; [realm commitWriteTransaction]; // test LinkView RLMArray XCTAssertEqual(0U, [company.employees indexOfObject:po1]); XCTAssertEqual(1U, [company.employees indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [company.employees indexOfObject:po2]); // non realm employee EmployeeObject *notInRealm = [[EmployeeObject alloc] initWithValue:@[@"NoName", @1, @NO]]; XCTAssertEqual((NSUInteger)NSNotFound, [company.employees indexOfObject:notInRealm]); // invalid object XCTAssertThrows([company.employees indexOfObject:(EmployeeObject *)company]); RLMAssertThrowsWithReasonMatching([company.employees indexOfObject:deleted], @"invalidated"); RLMAssertThrowsWithReasonMatching([company.employees indexOfObject:indirectlyDeleted], @"invalidated"); RLMResults *employees = [company.employees objectsWhere:@"age = %@", @40]; XCTAssertEqual(0U, [employees indexOfObject:po1]); XCTAssertEqual((NSUInteger)NSNotFound, [employees indexOfObject:po3]); } - (void)testIndexOfObjectWhere { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; EmployeeObject *po4 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Bill", @"age": @55, @"hired": @YES}]; // create company CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employees addObjects:@[po3, po1, po4]]; // test unmanaged XCTAssertEqual(0U, [company.employees indexOfObjectWhere:@"name = 'Jill'"]); XCTAssertEqual(1U, [company.employees indexOfObjectWhere:@"name = 'Joe'"]); XCTAssertEqual((NSUInteger)NSNotFound, [company.employees indexOfObjectWhere:@"name = 'John'"]); // add to realm [realm addObject:company]; [realm commitWriteTransaction]; // test LinkView RLMArray XCTAssertEqual(0U, [company.employees indexOfObjectWhere:@"name = 'Jill'"]); XCTAssertEqual(1U, [company.employees indexOfObjectWhere:@"name = 'Joe'"]); XCTAssertEqual((NSUInteger)NSNotFound, [company.employees indexOfObjectWhere:@"name = 'John'"]); RLMResults *results = [company.employees objectsWhere:@"age > 30"]; XCTAssertEqual(0U, [results indexOfObjectWhere:@"name = 'Joe'"]); XCTAssertEqual(1U, [results indexOfObjectWhere:@"name = 'Bill'"]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObjectWhere:@"name = 'John'"]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObjectWhere:@"name = 'Jill'"]); } - (void)testFastEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; [realm commitWriteTransaction]; // enumerate empty array for (__unused id obj in company.employees) { XCTFail(@"Should be empty"); } [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [company.employees addObject:eo]; } [realm commitWriteTransaction]; XCTAssertEqual(company.employees.count, 30U); __weak id objects[30]; NSInteger count = 0; for (EmployeeObject *e in company.employees) { XCTAssertNotNil(e, @"Object is not nil and accessible"); if (count > 16) { // 16 is the size of blocks fast enumeration happens to ask for at // the moment, but of course that's just an implementation detail // that may change XCTAssertNil(objects[count - 16]); } objects[count++] = e; } XCTAssertEqual(count, 30, @"should have enumerated 30 objects"); for (int i = 0; i < count; i++) { XCTAssertNil(objects[i], @"Object should have been released"); } @autoreleasepool { for (EmployeeObject *e in company.employees) { objects[0] = e; break; } } XCTAssertNil(objects[0], @"Object should have been released"); } - (void)testModifyDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; const size_t totalCount = 40; for (size_t i = 0; i < totalCount; ++i) { [company.employees addObject:[EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]]; } size_t count = 0; for (EmployeeObject *eo in company.employees) { ++count; [company.employees addObject:eo]; } XCTAssertEqual(totalCount, count); XCTAssertEqual(totalCount * 2, company.employees.count); [realm cancelWriteTransaction]; // Unmanaged array company = [[CompanyObject alloc] init]; for (size_t i = 0; i < totalCount; ++i) { [company.employees addObject:[[EmployeeObject alloc] initWithValue:@[@"name", @(i), @NO]]]; } count = 0; for (EmployeeObject *eo in company.employees) { ++count; [company.employees addObject:eo]; } XCTAssertEqual(totalCount, count); XCTAssertEqual(totalCount * 2, company.employees.count); } - (void)testDeleteDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; const size_t totalCount = 40; for (size_t i = 0; i < totalCount; ++i) { [company.employees addObject:[EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]]; } [realm commitWriteTransaction]; [realm beginWriteTransaction]; for (__unused EmployeeObject *eo in company.employees) { [realm deleteObjects:company.employees]; } [realm commitWriteTransaction]; } - (void)testValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; XCTAssertEqualObjects([company.employees valueForKey:@"name"], @[]); [realm addObject:company]; [realm commitWriteTransaction]; XCTAssertEqualObjects([company.employees valueForKey:@"age"], @[]); // managed NSMutableArray *ages = [NSMutableArray array]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { [ages addObject:@(i)]; EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employees addObject:eo]; } [realm commitWriteTransaction]; RLM_GENERIC_ARRAY(EmployeeObject) *employeeObjects = [company valueForKey:@"employees"]; NSMutableArray *kvcAgeProperties = [NSMutableArray array]; for (EmployeeObject *employee in employeeObjects) { [kvcAgeProperties addObject:@(employee.age)]; } XCTAssertEqualObjects(kvcAgeProperties, ages); XCTAssertEqualObjects([company.employees valueForKey:@"age"], ages); XCTAssertTrue([[[company.employees valueForKey:@"self"] firstObject] isEqualToObject:company.employees.firstObject]); XCTAssertTrue([[[company.employees valueForKey:@"self"] lastObject] isEqualToObject:company.employees.lastObject]); XCTAssertEqual([[company.employees valueForKeyPath:@"@count"] integerValue], 30); XCTAssertEqual([[company.employees valueForKeyPath:@"@min.age"] integerValue], 0); XCTAssertEqual([[company.employees valueForKeyPath:@"@max.age"] integerValue], 29); XCTAssertEqualWithAccuracy([[company.employees valueForKeyPath:@"@avg.age"] doubleValue], 14.5, 0.1f); XCTAssertEqualObjects([company.employees valueForKeyPath:@"@unionOfObjects.age"], (@[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29])); XCTAssertEqualObjects([company.employees valueForKeyPath:@"@distinctUnionOfObjects.name"], (@[@"Joe"])); RLMAssertThrowsWithReasonMatching([company.employees valueForKeyPath:@"@sum.dogs.@sum.age"], @"Nested key paths.*not supported"); // unmanaged object company = [[CompanyObject alloc] init]; ages = [NSMutableArray array]; for (int i = 0; i < 30; ++i) { [ages addObject:@(i)]; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employees addObject:eo]; } XCTAssertEqualObjects([company.employees valueForKey:@"age"], ages); XCTAssertTrue([[[company.employees valueForKey:@"self"] firstObject] isEqualToObject:company.employees.firstObject]); XCTAssertTrue([[[company.employees valueForKey:@"self"] lastObject] isEqualToObject:company.employees.lastObject]); XCTAssertEqual([[company.employees valueForKeyPath:@"@count"] integerValue], 30); XCTAssertEqual([[company.employees valueForKeyPath:@"@min.age"] integerValue], 0); XCTAssertEqual([[company.employees valueForKeyPath:@"@max.age"] integerValue], 29); XCTAssertEqualWithAccuracy([[company.employees valueForKeyPath:@"@avg.age"] doubleValue], 14.5, 0.1f); XCTAssertEqualObjects([company.employees valueForKeyPath:@"@unionOfObjects.age"], (@[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29])); XCTAssertEqualObjects([company.employees valueForKeyPath:@"@distinctUnionOfObjects.name"], (@[@"Joe"])); RLMAssertThrowsWithReasonMatching([company.employees valueForKeyPath:@"@sum.dogs.@sum.age"], @"Nested key paths.*not supported"); } - (void)testSetValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employees setValue:@"name" forKey:@"name"]; XCTAssertEqualObjects([company.employees valueForKey:@"name"], @[]); [realm addObject:company]; [realm commitWriteTransaction]; XCTAssertThrows([company.employees setValue:@10 forKey:@"age"]); XCTAssertEqualObjects([company.employees valueForKey:@"age"], @[]); // managed NSMutableArray *ages = [NSMutableArray array]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employees addObject:eo]; } [company.employees setValue:@20 forKey:@"age"]; [realm commitWriteTransaction]; XCTAssertEqualObjects([company.employees valueForKey:@"age"], ages); // unmanaged object company = [[CompanyObject alloc] init]; ages = [NSMutableArray array]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employees addObject:eo]; } [company.employees setValue:@20 forKey:@"age"]; XCTAssertEqualObjects([company.employees valueForKey:@"age"], ages); } - (void)testObjectAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; AggregateArrayObject *obj = [AggregateArrayObject new]; XCTAssertEqual(0, [obj.array sumOfProperty:@"intCol"].intValue); XCTAssertNil([obj.array averageOfProperty:@"intCol"]); XCTAssertNil([obj.array minOfProperty:@"intCol"]); XCTAssertNil([obj.array maxOfProperty:@"intCol"]); NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [realm transactionWithBlock:^{ [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [obj.array addObjects:[AggregateObject allObjectsInRealm:realm]]; }]; void (^test)(void) = ^{ RLMArray *array = obj.array; // SUM XCTAssertEqual([array sumOfProperty:@"intCol"].integerValue, 4); XCTAssertEqualWithAccuracy([array sumOfProperty:@"floatCol"].floatValue, 7.2f, 0.1f); XCTAssertEqualWithAccuracy([array sumOfProperty:@"doubleCol"].doubleValue, 10.0, 0.1f); RLMAssertThrowsWithReasonMatching([array sumOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([array sumOfProperty:@"boolCol"], @"sum.*bool"); RLMAssertThrowsWithReasonMatching([array sumOfProperty:@"dateCol"], @"sum.*date"); // Average XCTAssertEqualWithAccuracy([array averageOfProperty:@"intCol"].doubleValue, 0.4, 0.1f); XCTAssertEqualWithAccuracy([array averageOfProperty:@"floatCol"].doubleValue, 0.72, 0.1f); XCTAssertEqualWithAccuracy([array averageOfProperty:@"doubleCol"].doubleValue, 1.0, 0.1f); RLMAssertThrowsWithReasonMatching([array averageOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([array averageOfProperty:@"boolCol"], @"average.*bool"); RLMAssertThrowsWithReasonMatching([array averageOfProperty:@"dateCol"], @"average.*date"); // MIN XCTAssertEqual(0, [[array minOfProperty:@"intCol"] intValue]); XCTAssertEqual(0.0f, [[array minOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(0.0, [[array minOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMinInput, [array minOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([array minOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([array minOfProperty:@"boolCol"], @"min.*bool"); // MAX XCTAssertEqual(1, [[array maxOfProperty:@"intCol"] intValue]); XCTAssertEqual(1.2f, [[array maxOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(2.5, [[array maxOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMaxInput, [array maxOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([array maxOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([array maxOfProperty:@"boolCol"], @"max.*bool"); }; test(); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; test(); } - (void)testRenamedPropertyAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; LinkToRenamedProperties1 *obj = [LinkToRenamedProperties1 new]; XCTAssertEqual(0, [obj.array sumOfProperty:@"propA"].intValue); XCTAssertNil([obj.array averageOfProperty:@"propA"]); XCTAssertNil([obj.array minOfProperty:@"propA"]); XCTAssertNil([obj.array maxOfProperty:@"propA"]); XCTAssertThrows([obj.array sumOfProperty:@"prop 1"]); [realm transactionWithBlock:^{ [RenamedProperties1 createInRealm:realm withValue:@[@1, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@2, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@3, @""]]; [obj.array addObjects:[RenamedProperties1 allObjectsInRealm:realm]]; }]; XCTAssertEqual(6, [obj.array sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.array averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.array minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.array maxOfProperty:@"propA"] intValue]); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; XCTAssertEqual(6, [obj.array sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.array averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.array minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.array maxOfProperty:@"propA"] intValue]); } - (void)testRenamedPropertyObservation { RLMRealm *realm = self.realmWithTestPath; __block LinkToRenamedProperties *obj; [realm transactionWithBlock:^{ [RenamedProperties1 createInRealm:realm withValue:@[@1, @""]]; obj = [LinkToRenamedProperties createInRealm:realm withValue:@[]]; RenamedProperties *linkedObject = [RenamedProperties createInRealm:realm withValue:@[@1, @""]]; [obj.array addObject:linkedObject]; }]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [obj.array addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; } keyPaths:@[@"stringCol"]]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMArray *array = [(LinkToRenamedProperties *)[LinkToRenamedProperties allObjectsInRealm:realm].firstObject array]; [array setValue:@"newValue" forKey:@"stringCol"]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testValueForCollectionOperationKeyPath { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *e1 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; EmployeeObject *e2 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; EmployeeObject *e3 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; EmployeeObject *e4 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"D", @"age": @50, @"hired": @YES}]; PrimaryCompanyObject *c1 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG", @"employees": @[e1, e2, e3, e2], @"employeeSet": @[]}]; PrimaryCompanyObject *c2 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG 2", @"employees": @[e1, e4], @"employeeSet": @[]}]; ArrayOfPrimaryCompanies *companies = [ArrayOfPrimaryCompanies createInRealm:realm withValue:@[@[c1, c2]]]; [realm commitWriteTransaction]; // count operator XCTAssertEqual([[c1.employees valueForKeyPath:@"@count"] integerValue], 4); // numeric operators XCTAssertEqual([[c1.employees valueForKeyPath:@"@min.age"] intValue], 20); XCTAssertEqual([[c1.employees valueForKeyPath:@"@max.age"] intValue], 40); XCTAssertEqual([[c1.employees valueForKeyPath:@"@sum.age"] integerValue], 120); XCTAssertEqualWithAccuracy([[c1.employees valueForKeyPath:@"@avg.age"] doubleValue], 30, 0.1f); // collection XCTAssertEqualObjects([c1.employees valueForKeyPath:@"@unionOfObjects.name"], (@[@"A", @"B", @"C", @"B"])); XCTAssertEqualObjects([[c1.employees valueForKeyPath:@"@distinctUnionOfObjects.name"] sortedArrayUsingSelector:@selector(compare:)], (@[@"A", @"B", @"C"])); XCTAssertEqualObjects([companies.companies valueForKeyPath:@"@unionOfArrays.employees"], (@[e1, e2, e3, e2, e1, e4])); NSComparator cmp = ^NSComparisonResult(id obj1, id obj2) { return [[obj1 name] compare:[obj2 name]]; }; XCTAssertEqualObjects([[companies.companies valueForKeyPath:@"@distinctUnionOfArrays.employees"] sortedArrayUsingComparator:cmp], (@[e1, e2, e3, e4])); // invalid key paths RLMAssertThrowsWithReasonMatching([c1.employees valueForKeyPath:@"@invalid.name"], @"Unsupported KVC collection operator found in key path '@invalid.name'"); RLMAssertThrowsWithReasonMatching([c1.employees valueForKeyPath:@"@sum"], @"Missing key path for KVC collection operator sum in key path '@sum'"); RLMAssertThrowsWithReasonMatching([c1.employees valueForKeyPath:@"@sum."], @"Missing key path for KVC collection operator sum in key path '@sum.'"); RLMAssertThrowsWithReasonMatching([c1.employees valueForKeyPath:@"@sum.employees.@sum.age"], @"Nested key paths.*not supported"); } - (void)testCrossThreadAccess { CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; EmployeeObject *eo = [[EmployeeObject alloc] init]; eo.name = @"Joe"; eo.age = 40; eo.hired = YES; [company.employees addObject:eo]; RLMArray *employees = company.employees; // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(company.employees); XCTAssertNoThrow([employees lastObject]); }]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [realm addObject:company]; [realm commitWriteTransaction]; employees = company.employees; XCTAssertNoThrow(company.employees); XCTAssertNoThrow([employees lastObject]); [self dispatchAsyncAndWait:^{ XCTAssertThrows(company.employees); XCTAssertThrows([employees lastObject]); }]; } - (void)testSortByNoColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; RLMArray *array = [DogArrayObject createInDefaultRealmWithValue:@[@[a2, b1, a1, b2]]].dogs; [realm commitWriteTransaction]; RLMResults *notActuallySorted = [array sortedResultsUsingDescriptors:@[]]; XCTAssertTrue([array[0] isEqualToObject:notActuallySorted[0]]); XCTAssertTrue([array[1] isEqualToObject:notActuallySorted[1]]); XCTAssertTrue([array[2] isEqualToObject:notActuallySorted[2]]); XCTAssertTrue([array[3] isEqualToObject:notActuallySorted[3]]); } - (void)testSortByMultipleColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; DogArrayObject *array = [DogArrayObject createInDefaultRealmWithValue:@[@[a1, a2, b1, b2]]]; [realm commitWriteTransaction]; bool (^checkOrder)(NSArray *, NSArray *, NSArray *) = ^bool(NSArray *properties, NSArray *ascending, NSArray *dogs) { NSArray *sort = @[[RLMSortDescriptor sortDescriptorWithKeyPath:properties[0] ascending:[ascending[0] boolValue]], [RLMSortDescriptor sortDescriptorWithKeyPath:properties[1] ascending:[ascending[1] boolValue]]]; RLMResults *actual = [array.dogs sortedResultsUsingDescriptors:sort]; return [actual[0] isEqualToObject:dogs[0]] && [actual[1] isEqualToObject:dogs[1]] && [actual[2] isEqualToObject:dogs[2]] && [actual[3] isEqualToObject:dogs[3]]; }; // Check each valid sort XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @YES], @[a1, a2, b1, b2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @NO], @[a2, a1, b2, b1])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @YES], @[b1, b2, a1, a2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @NO], @[b2, b1, a2, a1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @YES], @[a1, b1, a2, b2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @NO], @[b1, a1, b2, a2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @YES], @[a2, b2, a1, b1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @NO], @[b2, a2, b1, a1])); } - (void)testSortByRenamedColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; id value = @{@"array": @[@[@1, @"c"], @[@2, @"b"], @[@3, @"a"]], @"set": @[]}; LinkToRenamedProperties *obj = [LinkToRenamedProperties createInRealm:realm withValue:value]; XCTAssertEqualObjects([[obj.array sortedResultsUsingKeyPath:@"intCol" ascending:YES] valueForKeyPath:@"intCol"], (@[@1, @2, @3])); XCTAssertEqualObjects([[obj.array sortedResultsUsingKeyPath:@"intCol" ascending:NO] valueForKeyPath:@"intCol"], (@[@3, @2, @1])); [realm cancelWriteTransaction]; } - (void)testDeleteLinksAndObjectsInArray { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@[@"Joe", @40, @YES]]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@[@"John", @30, @NO]]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@[@"Jill", @25, @YES]]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employees addObjects:[EmployeeObject allObjects]]; [realm addObject:company]; [realm commitWriteTransaction]; RLMArray *peopleInCompany = company.employees; // Delete link to employee XCTAssertThrowsSpecificNamed([peopleInCompany removeObjectAtIndex:1], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertEqual(peopleInCompany.count, 3U, @"No links should have been deleted"); [realm beginWriteTransaction]; XCTAssertThrowsSpecificNamed([peopleInCompany removeObjectAtIndex:3], NSException, @"RLMException", @"Out of bounds"); XCTAssertNoThrow([peopleInCompany removeObjectAtIndex:1], @"Should delete link to employee"); [realm commitWriteTransaction]; XCTAssertEqual(peopleInCompany.count, 2U, @"link deleted when accessing via links"); EmployeeObject *test = peopleInCompany[0]; XCTAssertEqual(test.age, po1.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po1.name, @"Should be equal"); XCTAssertEqual(test.hired, po1.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po1], @"Should be equal"); test = peopleInCompany[1]; XCTAssertEqual(test.age, po3.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po3.name, @"Should be equal"); XCTAssertEqual(test.hired, po3.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po3], @"Should be equal"); XCTAssertThrowsSpecificNamed([peopleInCompany removeLastObject], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed([peopleInCompany removeAllObjects], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed([peopleInCompany replaceObjectAtIndex:0 withObject:po2], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed([peopleInCompany insertObject:po2 atIndex:0], NSException, @"RLMException", @"Not allowed in read transaction"); [realm beginWriteTransaction]; XCTAssertNoThrow([peopleInCompany removeLastObject], @"Should delete last link"); XCTAssertEqual(peopleInCompany.count, 1U, @"1 remaining link"); [peopleInCompany replaceObjectAtIndex:0 withObject:po2]; XCTAssertEqual(peopleInCompany.count, 1U, @"1 link replaced"); [peopleInCompany insertObject:po1 atIndex:0]; XCTAssertEqual(peopleInCompany.count, 2U, @"2 links"); XCTAssertNoThrow([peopleInCompany removeAllObjects], @"Should delete all links"); XCTAssertEqual(peopleInCompany.count, 0U, @"0 remaining links"); [realm commitWriteTransaction]; RLMResults *allPeople = [EmployeeObject allObjects]; XCTAssertEqual(allPeople.count, 3U, @"Only links should have been deleted, not the employees"); } - (void)testArrayDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMArray *employees = [CompanyObject createInDefaultRealmWithValue:@[@"company"]].employees; RLMArray *ints = [AllPrimitiveArrays createInDefaultRealmWithValue:@[]].intObj; for (NSInteger i = 0; i < 1012; ++i) { EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; person.age = 24; person.hired = YES; [employees addObject:person]; [ints addObject:@(i + 100)]; } [realm commitWriteTransaction]; RLMAssertMatches(employees.description, @"(?s)RLMArray\\ \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[0\\] EmployeeObject \\{\n" @"\t\tname = Mary;\n" @"\t\tage = 24;\n" @"\t\thired = 1;\n" @"\t\\},\n" @".*\n" @"\t... 912 objects skipped.\n" @"\\)"); RLMAssertMatches(ints.description, @"(?s)RLMArray\\ \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[0\\] 100,\n" @"\t\\[1\\] 101,\n" @"\t\\[2\\] 102,\n" @".*\n" @"\t... 912 objects skipped.\n" @"\\)"); } - (void)testUnmanagedAssignment { IntObject *io1 = [[IntObject alloc] init]; IntObject *io2 = [[IntObject alloc] init]; IntObject *io3 = [[IntObject alloc] init]; ArrayPropertyObject *array1 = [[ArrayPropertyObject alloc] init]; ArrayPropertyObject *array2 = [[ArrayPropertyObject alloc] init]; // Assigning NSArray shallow copies array1.intArray = (id)@[io1, io2]; XCTAssertEqualObjects([array1.intArray valueForKey:@"self"], (@[io1, io2])); [array1 setValue:@[io3, io1] forKey:@"intArray"]; XCTAssertEqualObjects([array1.intArray valueForKey:@"self"], (@[io3, io1])); array1[@"intArray"] = @[io2, io3]; XCTAssertEqualObjects([array1.intArray valueForKey:@"self"], (@[io2, io3])); // Assigning RLMArray shallow copies array2.intArray = array1.intArray; XCTAssertEqualObjects([array2.intArray valueForKey:@"self"], (@[io2, io3])); [array1.intArray removeAllObjects]; XCTAssertEqualObjects([array2.intArray valueForKey:@"self"], (@[io2, io3])); // Self-assignment is a no-op array2.intArray = array2.intArray; XCTAssertEqualObjects([array2.intArray valueForKey:@"self"], (@[io2, io3])); array2[@"intArray"] = array2[@"intArray"]; XCTAssertEqualObjects([array2[@"intArray"] valueForKey:@"self"], (@[io2, io3])); } - (void)testManagedAssignment { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; IntObject *io1 = [IntObject createInRealm:realm withValue:@[@1]]; IntObject *io2 = [IntObject createInRealm:realm withValue:@[@2]]; IntObject *io3 = [IntObject createInRealm:realm withValue:@[@3]]; ArrayPropertyObject *array1 = [ArrayPropertyObject createInRealm:realm withValue:@[@""]]; ArrayPropertyObject *array2 = [ArrayPropertyObject createInRealm:realm withValue:@[@""]]; // Assigning NSArray shallow copies array1.intArray = (id)@[io1, io2]; XCTAssertEqualObjects([array1.intArray valueForKey:@"intCol"], (@[@1, @2])); [array1 setValue:@[io3, io1] forKey:@"intArray"]; XCTAssertEqualObjects([array1.intArray valueForKey:@"intCol"], (@[@3, @1])); array1[@"intArray"] = @[io2, io3]; XCTAssertEqualObjects([array1.intArray valueForKey:@"intCol"], (@[@2, @3])); // Assigning RLMArray shallow copies array2.intArray = array1.intArray; XCTAssertEqualObjects([array2.intArray valueForKey:@"intCol"], (@[@2, @3])); [array1.intArray removeAllObjects]; XCTAssertEqualObjects([array2.intArray valueForKey:@"intCol"], (@[@2, @3])); // Self-assignment is a no-op array2.intArray = array2.intArray; XCTAssertEqualObjects([array2.intArray valueForKey:@"intCol"], (@[@2, @3])); array2[@"intArray"] = array2[@"intArray"]; XCTAssertEqualObjects([array2[@"intArray"] valueForKey:@"intCol"], (@[@2, @3])); [realm cancelWriteTransaction]; } - (void)testAssignIncorrectType { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[@[@"a"]], @[@[@0]]]]; RLMAssertThrowsWithReason(array.intArray = (id)array.array, @"RLMArray does not match expected type 'IntObject' for property 'ArrayPropertyObject.intArray'."); RLMAssertThrowsWithReason(array[@"intArray"] = array[@"array"], @"RLMArray does not match expected type 'IntObject' for property 'ArrayPropertyObject.intArray'."); [realm cancelWriteTransaction]; } - (void)testNotificationSentInitially { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [array.array addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); XCTAssertNil(change); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [array.array addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMArray *array = [(ArrayPropertyObject *)[ArrayPropertyObject allObjectsInRealm:realm].firstObject array]; [array addObject:[[StringObject alloc] init]]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [array.array addNotificationBlock:^(__unused RLMArray *array, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [array.array addNotificationBlock:^(RLMArray *array, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); XCTAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMArray *array = [(ArrayPropertyObject *)[ArrayPropertyObject allObjectsInRealm:realm].firstObject array]; [array addObject:[[StringObject alloc] init]]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [array.array addNotificationBlock:^(RLMArray *array, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:array]; [realm commitWriteTransaction]; [(RLMNotificationToken *)token invalidate]; } static RLMArray *managedTestArray(void) { RLMRealm *realm = [RLMRealm defaultRealm]; __block RLMArray *array; [realm transactionWithBlock:^{ ArrayPropertyObject *obj = [ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], @[@[@0], @[@1]]]]; array = obj.intArray; }]; return array; } - (void)testAllMethodsCheckThread { RLMArray *array = managedTestArray(); IntObject *io = array.firstObject; RLMRealm *realm = array.realm; [realm beginWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReasonMatching([array count], @"thread"); RLMAssertThrowsWithReasonMatching([array objectAtIndex:0], @"thread"); RLMAssertThrowsWithReasonMatching([array firstObject], @"thread"); RLMAssertThrowsWithReasonMatching([array lastObject], @"thread"); RLMAssertThrowsWithReasonMatching([array addObject:io], @"thread"); RLMAssertThrowsWithReasonMatching([array addObjects:@[io]], @"thread"); RLMAssertThrowsWithReasonMatching([array insertObject:io atIndex:0], @"thread"); RLMAssertThrowsWithReasonMatching([array removeObjectAtIndex:0], @"thread"); RLMAssertThrowsWithReasonMatching([array removeLastObject], @"thread"); RLMAssertThrowsWithReasonMatching([array removeAllObjects], @"thread"); RLMAssertThrowsWithReasonMatching([array replaceObjectAtIndex:0 withObject:io], @"thread"); RLMAssertThrowsWithReasonMatching([array moveObjectAtIndex:0 toIndex:1], @"thread"); RLMAssertThrowsWithReasonMatching([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"thread"); RLMAssertThrowsWithReasonMatching([array indexOfObject:[IntObject allObjects].firstObject], @"thread"); RLMAssertThrowsWithReasonMatching([array indexOfObjectWhere:@"intCol = 0"], @"thread"); RLMAssertThrowsWithReasonMatching([array indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"thread"); RLMAssertThrowsWithReasonMatching([array objectsWhere:@"intCol = 0"], @"thread"); RLMAssertThrowsWithReasonMatching([array objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"thread"); RLMAssertThrowsWithReasonMatching([array sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"thread"); RLMAssertThrowsWithReasonMatching([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"thread"); RLMAssertThrowsWithReasonMatching(array[0], @"thread"); RLMAssertThrowsWithReasonMatching(array[0] = io, @"thread"); RLMAssertThrowsWithReasonMatching([array valueForKey:@"intCol"], @"thread"); RLMAssertThrowsWithReasonMatching([array setValue:@1 forKey:@"intCol"], @"thread"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in array);}), @"thread"); }]; [realm cancelWriteTransaction]; } - (void)testAllMethodsCheckForInvalidation { RLMArray *array = managedTestArray(); IntObject *io = array.firstObject; RLMRealm *realm = array.realm; [realm beginWriteTransaction]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); XCTAssertNoThrow([array count]); XCTAssertNoThrow([array objectAtIndex:0]); XCTAssertNoThrow([array firstObject]); XCTAssertNoThrow([array lastObject]); XCTAssertNoThrow([array addObject:io]); XCTAssertNoThrow([array addObjects:@[io]]); XCTAssertNoThrow([array insertObject:io atIndex:0]); XCTAssertNoThrow([array removeObjectAtIndex:0]); XCTAssertNoThrow([array removeLastObject]); XCTAssertNoThrow([array removeAllObjects]); [array addObjects:@[io, io, io]]; XCTAssertNoThrow([array replaceObjectAtIndex:0 withObject:io]); XCTAssertNoThrow([array moveObjectAtIndex:0 toIndex:1]); XCTAssertNoThrow([array exchangeObjectAtIndex:0 withObjectAtIndex:1]); XCTAssertNoThrow([array indexOfObject:[IntObject allObjects].firstObject]); XCTAssertNoThrow([array indexOfObjectWhere:@"intCol = 0"]); XCTAssertNoThrow([array indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([array objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([array objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([array sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow(array[0]); XCTAssertNoThrow(array[0] = io); XCTAssertNoThrow([array valueForKey:@"intCol"]); XCTAssertNoThrow([array setValue:@1 forKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in array);})); [realm cancelWriteTransaction]; [realm invalidate]; [realm beginWriteTransaction]; io = [IntObject createInDefaultRealmWithValue:@[@0]]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); RLMAssertThrowsWithReasonMatching([array count], @"invalidated"); RLMAssertThrowsWithReasonMatching([array objectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReasonMatching([array firstObject], @"invalidated"); RLMAssertThrowsWithReasonMatching([array lastObject], @"invalidated"); RLMAssertThrowsWithReasonMatching([array addObject:io], @"invalidated"); RLMAssertThrowsWithReasonMatching([array addObjects:@[io]], @"invalidated"); RLMAssertThrowsWithReasonMatching([array insertObject:io atIndex:0], @"invalidated"); RLMAssertThrowsWithReasonMatching([array removeObjectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReasonMatching([array removeLastObject], @"invalidated"); RLMAssertThrowsWithReasonMatching([array removeAllObjects], @"invalidated"); RLMAssertThrowsWithReasonMatching([array replaceObjectAtIndex:0 withObject:io], @"invalidated"); RLMAssertThrowsWithReasonMatching([array moveObjectAtIndex:0 toIndex:1], @"invalidated"); RLMAssertThrowsWithReasonMatching([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"invalidated"); RLMAssertThrowsWithReasonMatching([array indexOfObject:[IntObject allObjects].firstObject], @"invalidated"); RLMAssertThrowsWithReasonMatching([array indexOfObjectWhere:@"intCol = 0"], @"invalidated"); RLMAssertThrowsWithReasonMatching([array indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"invalidated"); RLMAssertThrowsWithReasonMatching([array objectsWhere:@"intCol = 0"], @"invalidated"); RLMAssertThrowsWithReasonMatching([array objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"invalidated"); RLMAssertThrowsWithReasonMatching([array sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"invalidated"); RLMAssertThrowsWithReasonMatching([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReasonMatching(array[0], @"invalidated"); RLMAssertThrowsWithReasonMatching(array[0] = io, @"invalidated"); RLMAssertThrowsWithReasonMatching([array valueForKey:@"intCol"], @"invalidated"); RLMAssertThrowsWithReasonMatching([array setValue:@1 forKey:@"intCol"], @"invalidated"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in array);}), @"invalidated"); [realm cancelWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMArray *array = managedTestArray(); IntObject *io = array.firstObject; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); XCTAssertNoThrow([array count]); XCTAssertNoThrow([array objectAtIndex:0]); XCTAssertNoThrow([array firstObject]); XCTAssertNoThrow([array lastObject]); XCTAssertNoThrow([array indexOfObject:[IntObject allObjects].firstObject]); XCTAssertNoThrow([array indexOfObjectWhere:@"intCol = 0"]); XCTAssertNoThrow([array indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([array objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([array objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([array sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow(array[0]); XCTAssertNoThrow([array valueForKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in array);})); RLMAssertThrowsWithReasonMatching([array addObject:io], @"write transaction"); RLMAssertThrowsWithReasonMatching([array addObjects:@[io]], @"write transaction"); RLMAssertThrowsWithReasonMatching([array insertObject:io atIndex:0], @"write transaction"); RLMAssertThrowsWithReasonMatching([array removeObjectAtIndex:0], @"write transaction"); RLMAssertThrowsWithReasonMatching([array removeLastObject], @"write transaction"); RLMAssertThrowsWithReasonMatching([array removeAllObjects], @"write transaction"); RLMAssertThrowsWithReasonMatching([array replaceObjectAtIndex:0 withObject:io], @"write transaction"); RLMAssertThrowsWithReasonMatching([array moveObjectAtIndex:0 toIndex:1], @"write transaction"); RLMAssertThrowsWithReasonMatching([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"write transaction"); RLMAssertThrowsWithReasonMatching(array[0] = io, @"write transaction"); RLMAssertThrowsWithReasonMatching([array setValue:@1 forKey:@"intCol"], @"write transaction"); } - (void)testIsFrozen { RLMArray *unfrozen = managedTestArray(); RLMArray *frozen = [unfrozen freeze]; XCTAssertFalse(unfrozen.isFrozen); XCTAssertTrue(frozen.isFrozen); } - (void)testFreezingFrozenObjectReturnsSelf { RLMArray *array = managedTestArray(); RLMArray *frozen = [array freeze]; XCTAssertNotEqual(array, frozen); XCTAssertNotEqual(array.freeze, frozen); XCTAssertEqual(frozen, frozen.freeze); } - (void)testFreezeFromWrongThread { RLMArray *array = managedTestArray(); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([array freeze], @"Realm accessed from incorrect thread"); }]; } - (void)testAccessFrozenFromDifferentThread { RLMArray *frozen = [managedTestArray() freeze]; [self dispatchAsyncAndWait:^{ XCTAssertEqualObjects([frozen valueForKey:@"intCol"], (@[@0, @1])); }]; } - (void)testObserveFrozenArray { RLMArray *frozen = [managedTestArray() freeze]; id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {}; RLMAssertThrowsWithReason([frozen addNotificationBlock:block], @"Frozen Realms do not change and do not have change notifications."); } - (void)testQueryFrozenArray { RLMArray *frozen = [managedTestArray() freeze]; XCTAssertEqualObjects([[frozen objectsWhere:@"intCol > 0"] valueForKey:@"intCol"], (@[@1])); } - (void)testFrozenArraysDoNotUpdate { RLMArray *array = managedTestArray(); RLMArray *frozen = [array freeze]; XCTAssertEqual(frozen.count, 2); [array.realm transactionWithBlock:^{ [array removeLastObject]; }]; XCTAssertEqual(frozen.count, 2); } @end ================================================ FILE: Realm/Tests/AsyncTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealm_Private.hpp" #import #import // A whole bunch of blocks don't use their RLMResults parameter #pragma clang diagnostic ignored "-Wunused-parameter" @interface ManualRefreshRealm : RLMRealm @end @implementation ManualRefreshRealm - (void)verifyNotificationsAreSupported:(__unused bool)isCollection { // The normal implementation of this will reject realms with automatic change notifications disabled } @end @interface AsyncTests : RLMTestCase @end @implementation AsyncTests - (void)createObject:(int)value { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@(value)]]; }]; } } - (void)testInitialResultsAreDelivered { [self createObject:1]; XCTestExpectation *expectation = [self expectationWithDescription:@""]; auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertNil(e); XCTAssertEqualObjects(results.objectClassName, @"IntObject"); XCTAssertEqual(results.count, 1U); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testNewResultsAreDeliveredAfterLocalCommit { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger expected = 0; auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual(results.count, expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self createObject:1]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self createObject:2]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testNewResultsAreDeliveredAfterBackgroundCommit { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger expected = 0; auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual(results.count, expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testResultsPerserveQuery { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger expected = 0; auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual(results.count, expected); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; ++expected; [self dispatchAsyncAndWait:^{ [self createObject:1]; [self createObject:-11]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testResultsPerserveSort { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block int expected = 0; auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual([results.firstObject intCol], expected); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; expected = 1; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:-1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; expected = 2; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testQueryingDeliveredQueryResults { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger expected = 0; auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testQueryingDeliveredTableResults { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger expected = 0; auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testQueryingDeliveredSortedResults { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block int expected = 0; auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual([[results objectsWhere:@"intCol < 10"].firstObject intCol], expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testSortingDeliveredResults { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block int expected = 0; auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { XCTAssertEqual([[results sortedResultsUsingKeyPath:@"intCol" ascending:NO].firstObject intCol], expected++); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:1]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ [self createObject:2]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testQueryingLinkList { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block int expected = 0; auto token = [[array.intArray objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { // NSLog(@"IntArray: %d", (int)array.intArray.count); XCTAssertNil(e); XCTAssertNotNil(results); XCTAssertEqual((int)results.count, expected); for (int i = 0; i < expected; ++i) { XCTAssertEqual(results[i].intCol, i + 1); } ++expected; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; for (int i = 0; i < 3; ++i) { expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; ArrayPropertyObject *array = [[ArrayPropertyObject allObjectsInRealm:realm] firstObject]; // Create two objects, one in the list and one not, to verify that the // LinkList is actually be used [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@(i + 1)]]; [array.intArray addObject:[IntObject createInRealm:realm withValue:@[@(i + 1)]]]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } [token invalidate]; } - (RLMNotificationToken *)subscribeAndWaitForInitial:(id)query block:(void (^)(id))block { __block XCTestExpectation *exp = [self expectationWithDescription:@"wait for initial results"]; auto token = [query addNotificationBlock:^(id results, RLMCollectionChange *change, NSError *e) { XCTAssertNotNil(results); XCTAssertNil(e); if (exp) { [exp fulfill]; exp = nil; } else { block(results); } }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; return token; } - (void)testManualRefreshUsesAsyncResultsWhenPossible { __block bool called = false; auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) { called = true; }]; RLMRealm *realm = [RLMRealm defaultRealm]; realm.autorefresh = NO; [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsync:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; }]; }]; }]; XCTAssertFalse(called); [realm refresh]; XCTAssertTrue(called); [token invalidate]; } - (void)testModifyingUnrelatedTableDoesNotTriggerResend { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { // will throw if called a second time [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [StringObject createInDefaultRealmWithValue:@[@""]]; }]; }]; [token invalidate]; } - (void)testStaleResultsAreDiscardedWhenThreadIsBlocked { XCTestExpectation *expectation = [self expectationWithDescription:@""]; auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { // Will fail if this is called with the initial results XCTAssertEqual(1U, results.count); // Will fail if it's called twice [expectation fulfill]; }]; // Advance the version on a different thread, and then wait for async work // to complete for that new version [self dispatchAsyncAndWait:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; } error:nil]; __block RLMNotificationToken *token; CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ token = [IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { [token invalidate]; token = nil; CFRunLoopStop(CFRunLoopGetCurrent()); }]; }); CFRunLoopRun(); }]; // Only now let the main thread pick up the notifications [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testCommitInOneNotificationDoesNotCancelOtherNotifications { __block XCTestExpectation *exp1 = nil; __block XCTestExpectation *exp2 = nil; __block int firstBlockCalls = 0; __block int secondBlockCalls = 0; auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) { ++firstBlockCalls; if (firstBlockCalls == 2) { [exp1 fulfill]; } else { [results.realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@1]]; [results.realm commitWriteTransaction]; } }]; auto token2 = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) { ++secondBlockCalls; if (secondBlockCalls == 2) { [exp2 fulfill]; } }]; exp1 = [self expectationWithDescription:@""]; exp2 = [self expectationWithDescription:@""]; [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; } error:nil]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertEqual(2, firstBlockCalls); XCTAssertEqual(2, secondBlockCalls); [token invalidate]; [token2 invalidate]; } - (void)testRLMResultsInstanceIsReused { __weak __block RLMResults *prev; __block bool first = true; XCTestExpectation *expectation = [self expectationWithDescription:@""]; auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { if (first) { prev = results; first = false; } else { XCTAssertEqual(prev, results); // deliberately not EqualObjects } [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertNotNil(prev); [token invalidate]; } - (void)testCancellationTokenKeepsSubscriptionAlive { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token; @autoreleasepool { token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) { XCTAssertNotNil(results); XCTAssertNil(err); [expectation fulfill]; }]; } [self waitForExpectationsWithTimeout:2.0 handler:nil]; // at this point there are no strong references to anything other than the // token, so verify that things haven't magically gone away // this would be better as a multi-process tests with the commit done // from a different process expectation = [self expectationWithDescription:@""]; @autoreleasepool { [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; }]; } [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testCancellationTokenPreventsOpeningRealmWithMismatchedConfig { __block XCTestExpectation *expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token; @autoreleasepool { token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) { XCTAssertNotNil(results); XCTAssertNil(err); [expectation fulfill]; }]; } [self waitForExpectationsWithTimeout:2.0 handler:nil]; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.readOnly = true; @autoreleasepool { XCTAssertThrows([RLMRealm realmWithConfiguration:config error:nil]); } [token invalidate]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } - (void)testAddAndRemoveQueries { RLMRealm *realm = [RLMRealm defaultRealm]; @autoreleasepool { RLMResults *results = IntObject.allObjects; [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; // Readd same results at same version [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; // Add different results at same version [[self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ }]; }]; // Readd at later version [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; // Add different results at later version [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; } // Add different results after all of the previous async queries have been // removed entirely [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) { XCTFail(@"results delivered after removal"); }] invalidate]; } - (void)testMultipleSourceVersionsForAsyncQueries { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.cache = false; // Create ten RLMRealm instances, each with a different read version RLMRealm *realms[10]; for (int i = 0; i < 10; ++i) { RLMRealm *realm = realms[i] = [RLMRealm realmWithConfiguration:config error:nil]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@(i)]]; }]; } // Each Realm should see a different number of objects as they're on different versions for (NSUInteger i = 0; i < 10; ++i) { XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count); } RLMNotificationToken *tokens[10]; // asyncify them in reverse order so that the version pin has to go backwards for (int i = 9; i >= 0; --i) { XCTestExpectation *exp = [self expectationWithDescription:@(i).stringValue]; tokens[i] = [[IntObject allObjectsInRealm:realms[i]] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTAssertEqual(10U, results.count); XCTAssertNil(error); [exp fulfill]; }]; } [self waitForExpectationsWithTimeout:2.0 handler:nil]; for (int i = 0; i < 10; ++i) { [tokens[i] invalidate]; } } - (void)testMultipleSourceVersionsWithNotifiersRemovedBeforeRunning { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.cache = false; config.configRef.automatic_change_notifications = false; // Create ten RLMRealm instances, each with a different read version RLMRealm *realms[10]; for (int i = 0; i < 10; ++i) { RLMRealm *realm = realms[i] = [ManualRefreshRealm realmWithConfiguration:config error:nil]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@(i)]]; }]; } __block int calls = 0; RLMNotificationToken *tokens[10]; @autoreleasepool { for (int i = 0; i < 10; ++i) { tokens[i] = [[IntObject allObjectsInRealm:realms[i]] addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { ++calls; }]; } // Each Realm should see a different number of objects as they're on different versions for (NSUInteger i = 0; i < 10; ++i) { XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count); } // remove all but the last two so that the version pin is for a version // that doesn't have a notifier anymore for (int i = 0; i < 7; ++i) { [tokens[i] invalidate]; } } // Let the background job run now auto coord = realm::_impl::RealmCoordinator::get_coordinator(config.path); coord->on_change(); for (int i = 7; i < 10; ++i) { realms[i]->_realm->notify(); XCTAssertEqual(calls, i - 6); } for (int i = 7; i < 10; ++i) { [tokens[i] invalidate]; } } - (void)testMultipleCallbacksForOneQuery { RLMResults *results = IntObject.allObjects; __block int calls1 = 0; auto token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) { ++calls1; }]; XCTAssertEqual(calls1, 0); __block int calls2 = 0; auto token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) { ++calls2; }]; XCTAssertEqual(calls1, 0); XCTAssertEqual(calls2, 0); [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls1, 1); XCTAssertEqual(calls2, 1); [token1 invalidate]; [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls1, 1); XCTAssertEqual(calls2, 2); [token2 invalidate]; [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls1, 1); XCTAssertEqual(calls2, 2); } - (void)testRemovingBlockFromWithinNotificationBlock { RLMResults *results = IntObject.allObjects; __block int calls = 0; __block RLMNotificationToken *token1, *token2; token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) { [token1 invalidate]; ++calls; }]; token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) { [token2 invalidate]; ++calls; }]; [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls, 2); } - (void)testAddingBlockFromWithinNotificationBlock { RLMResults *results = IntObject.allObjects; __block int calls = 0; __block RLMNotificationToken *token1, *token2; token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) { if (++calls == 1) { token2 = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { ++calls; }]; } }]; // Triggers one call on each block. Nested call is deferred until next refresh. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls, 1); [results.realm refresh]; XCTAssertEqual(calls, 2); // Triggers one call on each block [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls, 4); [token1 invalidate]; [token2 invalidate]; } - (void)testAddingNewQueryWithinNotificationBlock { RLMResults *results1 = IntObject.allObjects; RLMResults *results2 = IntObject.allObjects; __block int calls = 0; __block RLMNotificationToken *token1, *token2; token1 = [self subscribeAndWaitForInitial:results1 block:^(RLMResults *results) { ++calls; if (calls == 1) { CFRunLoopStop(CFRunLoopGetCurrent()); token2 = [results2 addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); ++calls; }]; } }]; // Triggers one call on outer block, but inner does not get a chance to deliver [self dispatchAsync:^{ [self createObject:0]; }]; CFRunLoopRun(); XCTAssertEqual(calls, 1); // Pick up the initial run of the inner block CFRunLoopRun(); assert(calls == 2); XCTAssertEqual(calls, 2); // Triggers a call on each block [self dispatchAsync:^{ [self createObject:0]; }]; CFRunLoopRun(); XCTAssertEqual(calls, 4); [token1 invalidate]; [token2 invalidate]; } - (void)testAddingNewQueryWithinRealmNotificationBlock { __block RLMNotificationToken *queryToken; __block XCTestExpectation *exp; auto realmToken = [RLMRealm.defaultRealm addNotificationBlock:^(RLMNotification notification, RLMRealm *realm) { CFRunLoopStop(CFRunLoopGetCurrent()); exp = [self expectationWithDescription:@"query notification"]; queryToken = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) { [exp fulfill]; }]; }]; // Make a background commit to trigger a Realm notification [self dispatchAsync:^{ [RLMRealm.defaultRealm transactionWithBlock:^{}]; }]; // Wait for the notification CFRunLoopRun(); [realmToken invalidate]; // Wait for the initial async query results created within the notification [self waitForExpectationsWithTimeout:2.0 handler:nil]; [queryToken invalidate]; } - (void)testBlockedThreadWithNotificationsDoesNotPreventDeliveryOnOtherThreads { dispatch_group_t group1 = dispatch_group_create(); dispatch_group_t group2 = dispatch_group_create(); // Add a notification block on a background thread, run the runloop // until the initial results are ready, and then block the thread without // running the runloop until the main thread is done testing things __block RLMNotificationToken *token; dispatch_group_enter(group1); dispatch_group_enter(group2); token = [IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { dispatch_group_leave(group1); dispatch_group_wait(group2, DISPATCH_TIME_FOREVER); } queue:self.bgQueue]; dispatch_group_wait(group1, DISPATCH_TIME_FOREVER); __block int calls = 0; auto token2 = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) { ++calls; }]; XCTAssertEqual(calls, 0); [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ [self createObject:0]; }]; XCTAssertEqual(calls, 1); [token invalidate]; [token2 invalidate]; dispatch_group_leave(group2); } - (void)testAddNotificationBlockFromWrongThread { RLMResults *results = [IntObject allObjects]; [self dispatchAsyncAndWait:^{ CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ XCTAssertThrows([results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTFail(@"should not be called"); }]); CFRunLoopStop(CFRunLoopGetCurrent()); }); CFRunLoopRun(); }]; } - (void)testAddNotificationBlockFromWrongQueue { auto queue = dispatch_queue_create("background queue", DISPATCH_QUEUE_SERIAL); __block RLMResults *results; dispatch_sync(queue, ^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:queue]; results = [IntObject allObjectsInRealm:realm]; }); XCTAssertThrows([results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTFail(@"should not be called"); }]); } - (void)testRemoveNotificationBlockFromWrongThread { // Unlike adding this is allowed, because it can happen due to capturing // tokens in blocks and users are very confused by errors from deallocation // on the wrong thread RLMResults *results = [IntObject allObjects]; auto token = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTFail(@"should not be called"); }]; [self dispatchAsyncAndWait:^{ [token invalidate]; }]; } - (void)testSimultaneouslyRemoveCallbacksFromCallbacksForOtherResults { dispatch_semaphore_t sema1 = dispatch_semaphore_create(0); dispatch_semaphore_t sema2 = dispatch_semaphore_create(0); __block RLMNotificationToken *token1, *token2; [self dispatchAsync:^{ CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ __block bool first = true; token1 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTAssertTrue(first); first = false; dispatch_semaphore_signal(sema1); dispatch_semaphore_wait(sema2, DISPATCH_TIME_FOREVER); [token2 invalidate]; CFRunLoopStop(CFRunLoopGetCurrent()); }]; }); CFRunLoopRun(); }]; CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ __block bool first = true; token2 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTAssertTrue(first); first = false; dispatch_semaphore_signal(sema2); dispatch_semaphore_wait(sema1, DISPATCH_TIME_FOREVER); [token1 invalidate]; CFRunLoopStop(CFRunLoopGetCurrent()); }]; }); CFRunLoopRun(); } - (void)testAsyncNotSupportedForReadOnlyRealms { @autoreleasepool { [RLMRealm defaultRealm]; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.readOnly = true; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertThrows([[IntObject allObjectsInRealm:realm] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { XCTFail(@"should not be called"); }]); } - (void)testAsyncNotSupportedAfterMakingChangesInWriteTransactions { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ XCTAssertNoThrow([IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {}]); [IntObject createInRealm:realm withValue:@[@0]]; RLMAssertThrowsWithReason([IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {}], @"Cannot create asynchronous query after making changes in a write transaction."); RLMAssertThrowsWithReason([IntObject.allObjects[0] addNotificationBlock:^(BOOL, NSArray *, NSError *) {}], @"Cannot create asynchronous query after making changes in a write transaction."); }]; } - (void)testTransactionsAfterDeletingArrayLinkView { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; IntObject *io = [IntObject createInRealm:realm withValue:@[@5]]; ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]]; [realm commitWriteTransaction]; RLMNotificationToken *token1 = [self subscribeAndWaitForInitial:apo.intArray block:^(RLMArray *array) { XCTAssertTrue(array.invalidated); }]; RLMResults *asResults = [apo.intArray objectsWhere:@"intCol = 5"]; RLMNotificationToken *token2 = [self subscribeAndWaitForInitial:asResults block:^(RLMResults *results) { XCTAssertEqual(results.count, 0U); }]; // Delete the object containing the RLMArray with notifiers [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteObject:[ArrayPropertyObject allObjectsInRealm:realm].firstObject]; }]; }]; // Perform another transaction while the notifiers are still alive as // transactions deleting the RLMArray and transactions with the RLMArray // already deleted hit different code paths [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; }]; }]; [token1 invalidate]; [token2 invalidate]; } - (void)testTransactionsAfterDeletingSetLinkView { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; IntObject *io = [IntObject createInRealm:realm withValue:@[@5]]; SetPropertyObject *spo = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]]; [realm commitWriteTransaction]; RLMNotificationToken *token1 = [self subscribeAndWaitForInitial:spo.intSet block:^(RLMSet *set) { XCTAssertTrue(set.invalidated); }]; RLMResults *asResults = [spo.intSet objectsWhere:@"intCol = 5"]; RLMNotificationToken *token2 = [self subscribeAndWaitForInitial:asResults block:^(RLMResults *results) { XCTAssertEqual(results.count, 0U); }]; // Delete the object containing the RLMArray with notifiers [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteObject:[SetPropertyObject allObjectsInRealm:realm].firstObject]; }]; }]; // Perform another transaction while the notifiers are still alive as // transactions deleting the RLMArray and transactions with the RLMArray // already deleted hit different code paths [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; }]; }]; [token1 invalidate]; [token2 invalidate]; } - (void)testInitialResultDiscardsChanges { auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) { XCTAssertEqual(results.count, 1U); XCTAssertNil(changes); CFRunLoopStop(CFRunLoopGetCurrent()); }]; // Make a write on a background thread, and then wait for the notification // for that write to be delivered to ensure that the notification we get on // the main thread actually would include changes dispatch_semaphore_t sema = dispatch_semaphore_create(0); [self dispatchAsync:^{ CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) { if (changes) { dispatch_semaphore_signal(sema); CFRunLoopStop(CFRunLoopGetCurrent()); } }]; [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; }]; CFRunLoopRun(); [token invalidate]; CFRunLoopStop(CFRunLoopGetCurrent()); }); CFRunLoopRun(); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); CFRunLoopRun(); [token invalidate]; } - (void)testNotificationDeliveryToQueue { RLMRealm *realm = [RLMRealm defaultRealm]; __block RLMNotificationToken *token; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [self dispatchAsync:^{ RLMRealm *bgRealm = [RLMRealm defaultRealmForQueue:self.bgQueue]; token = [[IntObject allObjectsInRealm:bgRealm] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *, NSError *) { XCTAssertNotNil(results); XCTAssertNoThrow(results.count); dispatch_semaphore_signal(sema); }]; }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [token invalidate]; } @end ================================================ FILE: Realm/Tests/CompactionTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMTestCase.h" @interface RLMRealm () - (BOOL)compact; @end @interface CompactionTests : RLMTestCase @end @implementation CompactionTests { uint64_t _expectedTotalBytesBefore; } static const NSUInteger expectedUsedBytesBeforeMin = 50000; static const NSUInteger count = 1000; #pragma mark - Helpers - (unsigned long long)fileSize:(NSURL *)fileURL { NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fileURL.path error:nil]; return [attributes[NSFileSize] unsignedLongLongValue]; } - (void)setUp { [super setUp]; @autoreleasepool { // Make compactable Realm RLMRealm *realm = self.realmWithTestPath; NSString *uuid = [[NSUUID UUID] UUIDString]; [realm transactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"A"]]; for (NSUInteger i = 0; i < count; ++i) { [StringObject createInRealm:realm withValue:@[uuid]]; } [StringObject createInRealm:realm withValue:@[@"B"]]; }]; } _expectedTotalBytesBefore = [self fileSize:RLMTestRealmURL()]; } #pragma mark - Tests - (void)testCompact { RLMRealm *realm = self.realmWithTestPath; unsigned long long fileSizeBefore = [self fileSize:realm.configuration.fileURL]; StringObject *object = [StringObject allObjectsInRealm:realm].firstObject; XCTAssertTrue([realm compact]); XCTAssertTrue(object.isInvalidated); XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); unsigned long long fileSizeAfter = [self fileSize:realm.configuration.fileURL]; XCTAssertGreaterThan(fileSizeBefore, fileSizeAfter); } - (void)testSuccessfulCompactOnLaunch { // Configure the Realm to compact on launch RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); configuration.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){ // The reported size is the logical size of the file and not the size // on disk for encrypted Realms if (!self.encryptTests) { XCTAssertEqual(totalBytes, _expectedTotalBytesBefore); } XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin)); return true; }; // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertLessThan([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); // Validate that the file still contains what it should XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); } - (void)testNoBlockCompactOnLaunch { // Configure the Realm to compact on launch RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); // Validate that the file still contains what it should XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); } - (void)testCachedRealmCompactOnLaunch { // Test that the compaction block never gets called if there are cached Realms // Access Realm before opening it with a compaction block RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); __unused RLMRealm *firstRealm = [RLMRealm realmWithConfiguration:configuration error:nil]; // Configure the Realm to compact on launch RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy]; __block BOOL compactBlockInvoked = NO; configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ compactBlockInvoked = YES; // Always attempt to compact return YES; }; // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil]; XCTAssertFalse(compactBlockInvoked); XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); // Validate that the file still contains what it should XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); } - (void)testCachedRealmOtherThreadCompactOnLaunch { // Test that the compaction block never gets called if the Realm is open on a different thread // Access Realm before opening it with a compaction block RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); dispatch_semaphore_t failedCompactTestCompleteSema = dispatch_semaphore_create(0); dispatch_semaphore_t bgRealmClosedSema = dispatch_semaphore_create(0); XCTestExpectation *realmOpenedExpectation = [self expectationWithDescription:@"Realm was opened on background thread"]; [self dispatchAsync:^{ @autoreleasepool { __unused RLMRealm *firstRealm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realmOpenedExpectation fulfill]; dispatch_semaphore_wait(failedCompactTestCompleteSema, DISPATCH_TIME_FOREVER); } dispatch_semaphore_signal(bgRealmClosedSema); }]; [self waitForExpectationsWithTimeout:2 handler:nil]; @autoreleasepool { // Configure the Realm to compact on launch RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy]; __block BOOL compactBlockInvoked = NO; configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ compactBlockInvoked = YES; // Always attempt to compact return YES; }; // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); __unused RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil]; XCTAssertFalse(compactBlockInvoked); XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); dispatch_semaphore_signal(failedCompactTestCompleteSema); } dispatch_semaphore_wait(bgRealmClosedSema, DISPATCH_TIME_FOREVER); // Configure the Realm to compact on launch RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy]; __block BOOL compactBlockInvoked = NO; configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){ // The reported size is the logical size of the file and not the size // on disk for encrypted Realms if (!self.encryptTests) { XCTAssertEqual(totalBytes, _expectedTotalBytesBefore); } XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin)); compactBlockInvoked = YES; return true; }; // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil]; XCTAssertTrue(compactBlockInvoked); XCTAssertLessThan([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); // Validate that the file still contains what it should XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); } - (void)testReturnNoCompactOnLaunch { // Configure the Realm to compact on launch RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); configuration.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){ // The reported size is the logical size of the file and not the size // on disk for encrypted Realms if (!self.encryptTests) { XCTAssertEqual(totalBytes, _expectedTotalBytesBefore); } XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin)); // Don't compact. return NO; }; // Confirm expected sizes before and after opening the Realm XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore); // Validate that the file still contains what it should XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2); XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]); } - (void)testCompactOnLaunchValidation { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.readOnly = YES; BOOL (^compactBlock)(NSUInteger, NSUInteger) = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ return NO; }; RLMAssertThrowsWithReasonMatching(configuration.shouldCompactOnLaunch = compactBlock, @"Cannot set `shouldCompactOnLaunch` when `readOnly` is set."); configuration.readOnly = NO; configuration.shouldCompactOnLaunch = compactBlock; RLMAssertThrowsWithReasonMatching(configuration.readOnly = YES, @"Cannot set `readOnly` when `shouldCompactOnLaunch` is set."); } - (void)testAccessDeniedOnTemporaryFile { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); configuration.shouldCompactOnLaunch = ^(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ return YES; }; NSURL *tmpURL = [configuration.fileURL URLByAppendingPathExtension:@"tmp_compaction_space"]; [NSData.data writeToURL:tmpURL atomically:NO]; [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @YES} ofItemAtPath:tmpURL.path error:nil]; RLMAssertRealmException([RLMRealm realmWithConfiguration:configuration error:nil], RLMErrorFilePermissionDenied, @"Failed to delete file at '%@': Operation not permitted", tmpURL.path); [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @NO} ofItemAtPath:tmpURL.path error:nil]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:configuration error:nil]); } @end ================================================ FILE: Realm/Tests/Decimal128Tests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" #import @interface Decimal128Tests : RLMTestCase @end @implementation Decimal128Tests #pragma mark - Initialization - (void)testDecimal128Initialization { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"1.2.3"]; RLMDecimal128 *d4 = [[RLMDecimal128 alloc] initWithValue:@3.14159]; RLMDecimal128 *d5 = [[RLMDecimal128 alloc] initWithValue:@"123.456"]; RLMDecimal128 *d6 = [[RLMDecimal128 alloc] initWithNumber:@123456789]; RLMDecimal128 *d7 = [[RLMDecimal128 alloc] init]; XCTAssertEqual(d1.doubleValue, 3.14159); XCTAssertTrue([d1.stringValue isEqualToString:@"3.14159"]); XCTAssertEqual(d2.doubleValue, 3.14159); XCTAssertTrue([d2.stringValue isEqualToString:@"3.14159"]); XCTAssertTrue(d3.isNaN); XCTAssertEqual(d4.doubleValue, 3.14159); XCTAssertTrue([d5.stringValue isEqualToString:@"123.456"]); XCTAssertTrue([d6.stringValue isEqualToString:@"123456789"]); XCTAssertEqual(d7.doubleValue, 0); } - (void)testDecimal128Decimal { NSNumber *n1 = @3.14159; RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertEqual((int)n1.decimalValue._exponent, (int)d1.decimalValue._exponent); XCTAssertEqual((int)n1.decimalValue._isCompact, (int)d1.decimalValue._isCompact); XCTAssertEqual((int)n1.decimalValue._isNegative, (int)d1.decimalValue._isNegative); XCTAssertEqual((int)n1.decimalValue._length, (int)d1.decimalValue._length); XCTAssertEqual(n1.decimalValue._mantissa[0], d1.decimalValue._mantissa[0]); XCTAssertEqual((int)n1.decimalValue._reserved, (int)d1.decimalValue._reserved); } #pragma mark - Arithmetic - (void)testDecimal128Addition { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; RLMDecimal128 *d2 = [d1 decimalNumberByAdding:[[RLMDecimal128 alloc] initWithString:@"3.14159"]]; XCTAssertEqual(d2.doubleValue, 6.28318); XCTAssertTrue([d2.stringValue isEqualToString:@"6.28318"]); XCTAssertEqual(d2.doubleValue, 6.28318); } - (void)testDecimal128Subtraction { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@2.5]; RLMDecimal128 *d2 = [d1 decimalNumberBySubtracting:[[RLMDecimal128 alloc] initWithString:@"5.5"]]; XCTAssertEqual(d2.doubleValue, -3.0); XCTAssertTrue([d2.stringValue isEqualToString:@"-3"]); } - (void)testDecimal128Division { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@0.21]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"0.7"]; RLMDecimal128 *result = [d1 decimalNumberByDividingBy:d2]; XCTAssertEqual(result.doubleValue, 0.3); XCTAssertTrue([result.stringValue isEqualToString:@"3E-1"]); } - (void)testDecimal128Multiplication { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@1.5]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"2.5"]; RLMDecimal128 *result = [d1 decimalNumberByMultiplyingBy:d2]; XCTAssertEqual(result.doubleValue, 3.75); XCTAssertTrue([result.stringValue isEqualToString:@"3.75"]); } #pragma mark - Comparison - (void)testDecimal128InitializationEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertTrue([d1 isEqual:d2]); } - (void)testDecimal128InitializationGreaterThan { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14160]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertTrue([d1 isGreaterThan:d2]); } - (void)testDecimal128InitializationGreaterThanEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14158]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14159"]; XCTAssertFalse([d1 isGreaterThanOrEqualTo:d2]); XCTAssertTrue([d2 isLessThanOrEqualTo:d3]); } - (void)testDecimal128InitializationLessThan { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; XCTAssertTrue([d1 isLessThan:d2]); } - (void)testDecimal128InitializationLessThanEquals { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@3.14159]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; RLMDecimal128 *d3 = [[RLMDecimal128 alloc] initWithString:@"3.14160"]; XCTAssertTrue([d1 isLessThanOrEqualTo:d2]); XCTAssertTrue([d2 isLessThanOrEqualTo:d3]); } #pragma mark - Miscellaneous - (void)testNaN { RLMDecimal128 *nan = [[RLMDecimal128 alloc] initWithValue:[NSNull null]]; XCTAssertTrue(nan.isNaN); } - (void)testMinimumMaximumValue { RLMDecimal128 *min = RLMDecimal128.minimumDecimalNumber; RLMDecimal128 *max = RLMDecimal128.maximumDecimalNumber; XCTAssertTrue([min isLessThan:max]); XCTAssertTrue([max isGreaterThan:min]); } - (void)testMagnitude { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@-123.321]; RLMDecimal128 *exp1 = [RLMDecimal128 decimalWithNumber:@123.321]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithNumber:@456.321]; RLMDecimal128 *exp2 = [RLMDecimal128 decimalWithNumber:@456.321]; XCTAssertTrue([d1.magnitude isEqual:exp1]); XCTAssertTrue([d2.magnitude isEqual:exp2]); } - (void)testNegate { RLMDecimal128 *d1 = [[RLMDecimal128 alloc] initWithNumber:@-123.321]; RLMDecimal128 *exp1 = [RLMDecimal128 decimalWithNumber:@123.321]; RLMDecimal128 *d2 = [[RLMDecimal128 alloc] initWithNumber:@456.321]; RLMDecimal128 *exp2 = [RLMDecimal128 decimalWithNumber:@-456.321]; [d1 negate]; [d2 negate]; XCTAssertTrue([d1 isEqual:exp1]); XCTAssertTrue([d2 isEqual:exp2]); } @end ================================================ FILE: Realm/Tests/DictionaryPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMTestCase.h" @interface DictionaryPropertyTests : RLMTestCase @end @implementation DogDictionaryObject @end @implementation DictionaryPropertyTests - (void)testPopulateEmptyDictionary { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@[@{}, @{}]]; XCTAssertNotNil(dict.stringDictionary, @"Should be able to get an empty dictionary"); XCTAssertEqual(dict.stringDictionary.count, 0U, @"Should start with no dictionary elements"); StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; dict.stringDictionary[@"one"] = obj; dict.stringDictionary[@"two"] = [StringObject createInRealm:realm withValue:@[@"b"]]; dict.stringDictionary[@"three"] = obj; [realm commitWriteTransaction]; XCTAssertEqual(dict.stringDictionary.count, 3U, @"Should have three elements in the dictionary"); XCTAssertEqualObjects([dict.stringDictionary[@"one"] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([dict.stringDictionary[@"two"] stringCol], @"b", @"Second element should have property value 'b'"); XCTAssertEqualObjects([dict.stringDictionary[@"three"] stringCol], @"a", @"Third element should have property value 'a'"); RLMDictionary *dictionaryProp = dict.stringDictionary; RLMAssertThrowsWithReasonMatching([dictionaryProp setObject:obj forKey:@"four"], @"write transaction"); for (NSString *key in dictionaryProp) { XCTAssertTrue(((RLMDictionary *)dictionaryProp[key]).description.length, @"Object should have description"); } } - (void)testKeyType { DictionaryPropertyObject *unmanaged = [[DictionaryPropertyObject alloc] init]; XCTAssertEqual(unmanaged.intDictionary.keyType, RLMPropertyTypeString); RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *managed = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; [realm commitWriteTransaction]; XCTAssertEqual(managed.intDictionary.keyType, RLMPropertyTypeString); } -(void)testModifyDetatchedDictionary { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *dictObj = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; XCTAssertNotNil(dictObj.stringDictionary, @"Should be able to get an empty dictionary"); XCTAssertEqual(dictObj.stringDictionary.count, 0U, @"Should start with no dictionary elements"); StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; RLMDictionary *dict = dictObj.stringDictionary; dict[@"one"] = obj; [dict setObject:[StringObject createInRealm:realm withValue:@[@"b"]] forKey:@"two"]; [realm commitWriteTransaction]; XCTAssertEqual(dictObj.stringDictionary.count, 2U, @"Should have two elements in dictionary"); XCTAssertEqualObjects([dictObj.stringDictionary[@"one"] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([dictObj.stringDictionary[@"two"] stringCol], @"b", @"Second element should have property value 'b'"); RLMAssertThrowsWithReasonMatching([dictObj.stringDictionary setObject:obj forKey:@"one"], @"write transaction"); } - (void)testDeleteUnmanagedObjectWithDictionaryProperty { DictionaryPropertyObject *dictObj = [[DictionaryPropertyObject alloc] initWithValue:@[]]; RLMDictionary *stringDictionary = dictObj.stringDictionary; XCTAssertFalse(stringDictionary.isInvalidated, @"stringDictionary should be valid after creation."); dictObj = nil; XCTAssertFalse(stringDictionary.isInvalidated, @"stringDictionary should still be valid after parent deletion."); } - (void)testDeleteObjectWithDictionaryProperty { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *dictObj = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; RLMDictionary *dictionary = dictObj.stringDictionary; XCTAssertFalse(dictionary.isInvalidated, @"dictionary should be valid after creation."); [realm deleteObject:dictObj]; XCTAssertTrue(dictionary.isInvalidated, @"dictionary should be invalid after parent deletion."); [realm commitWriteTransaction]; } - (void)testDeleteObjectInDictionaryProperty { RLMRealm *realm = [self realmWithTestPath]; StringObject *obj = [[StringObject alloc] init]; [realm beginWriteTransaction]; DictionaryPropertyObject *dictObj = [DictionaryPropertyObject createInRealm:realm withValue: @{@"stringDictionary": @{@"one": obj}}]; RLMDictionary *stringDictionary = dictObj.stringDictionary; StringObject *one = stringDictionary[@"one"]; [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; XCTAssertFalse(stringDictionary.isInvalidated, @"stringDictionary should be valid after member object deletion."); XCTAssertTrue(one.isInvalidated, @"firstObject should be invalid after deletion."); XCTAssertEqual(stringDictionary.count, 1U, @"stringDictionary.count should be one as it holds onto the invalidated object."); [realm commitWriteTransaction]; } -(void)testKeyedSubscript { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *obj = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; StringObject *child3 = [StringObject createInRealm:realm withValue:@[@"c"]]; obj.stringDictionary[@"one"] = child1; XCTAssertTrue([[obj.stringDictionary[@"one"] stringCol] isEqualToString:@"a"]); obj.stringDictionary[@"two"] = child2; XCTAssertNil([obj.stringDictionary[@"two"] stringCol]); // reassign obj.stringDictionary[@"two"] = child3; XCTAssertTrue([[obj.stringDictionary[@"two"] stringCol] isEqualToString:@"c"]); [realm commitWriteTransaction]; } -(void)testRemoveObject { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *obj = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; StringObject *child3 = [StringObject createInRealm:realm withValue:@[@"c"]]; obj.stringDictionary[@"one"] = child1; XCTAssertTrue([[obj.stringDictionary[@"one"] stringCol] isEqualToString:@"a"]); obj.stringDictionary[@"two"] = child2; XCTAssertNil([obj.stringDictionary[@"two"] stringCol]); // reassign obj.stringDictionary[@"two"] = child3; XCTAssertTrue([[obj.stringDictionary[@"two"] stringCol] isEqualToString:@"c"]); [realm commitWriteTransaction]; [realm beginWriteTransaction]; [obj.stringDictionary removeObjectForKey:@"one"]; XCTAssertNil(obj.stringDictionary[@"one"]); [obj.stringDictionary removeObjectForKey:@"two"]; XCTAssertNil(obj.stringDictionary[@"two"]); obj.stringDictionary[@"three"] = child3; XCTAssertTrue([[obj.stringDictionary[@"three"] stringCol] isEqualToString:@"c"]); [obj.stringDictionary removeAllObjects]; XCTAssertNil(obj.stringDictionary[@"three"]); obj.stringDictionary[@"one"] = child1; XCTAssertNotNil(obj.stringDictionary[@"one"]); obj.stringDictionary[@"two"] = child2; XCTAssertNotNil(obj.stringDictionary[@"two"]); [obj.stringDictionary removeObjectsForKeys:@[@"one", @"two"]]; XCTAssertNil(obj.stringDictionary[@"one"]); XCTAssertNil(obj.stringDictionary[@"two"]); obj.stringDictionary[@"one"] = child1; XCTAssertNotNil(obj.stringDictionary[@"one"]); obj.stringDictionary[@"one"] = nil; XCTAssertNil(obj.stringDictionary[@"one"]); [realm commitWriteTransaction]; } -(void)testAddInvalidated { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; [realm addObject:person]; [realm deleteObjects:[EmployeeObject allObjects]]; RLMAssertThrowsWithReasonMatching([company.employeeDict setObject:person forKey:@"person1"], @"invalidated"); [realm cancelWriteTransaction]; } - (void)testAddNil { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; RLMAssertThrowsWithReason([company.employeeDict setObject:self.nonLiteralNil forKey:@"blah"], @"Must provide a non-nil value."); [realm cancelWriteTransaction]; } - (void)testUnmanaged { RLMRealm *realm = [self realmWithTestPath]; DictionaryPropertyObject *dict = [[DictionaryPropertyObject alloc] init]; XCTAssertNotNil(dict.stringDictionary, @"RLMDictionary property should get created on access"); XCTAssertEqual(dict.stringDictionary.allValues.count, 0U, @"No objects added yet"); XCTAssertEqual(dict.stringDictionary.allKeys.count, 0U, @"No objects added yet"); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; dict.stringDictionary[@"one"] = obj1; dict.stringDictionary[@"two"] = obj2; dict.stringDictionary[@"three"] = obj3; XCTAssertEqual(dict.stringDictionary.allValues.count, 3U); XCTAssertEqual(dict.stringDictionary.allKeys.count, 3U); XCTAssertEqualObjects(dict.stringDictionary[@"one"], obj1, @"Objects should be equal"); XCTAssertEqualObjects(dict.stringDictionary[@"three"], obj3, @"Objects should be equal"); XCTAssertEqualObjects(dict.stringDictionary.allValues[1], obj2, @"Objects should be equal"); [realm beginWriteTransaction]; [realm addObject:dict]; [realm commitWriteTransaction]; XCTAssertEqual(dict.stringDictionary.allValues.count, 3U); XCTAssertEqual(dict.stringDictionary.allKeys.count, 3U); XCTAssertEqual(dict.stringDictionary.count, 3U, @"Should have two elements in dictionary"); XCTAssertEqualObjects([dict.stringDictionary[@"one"] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([dict.stringDictionary[@"two"] stringCol], @"b", @"Second element should have property value 'b'"); [realm beginWriteTransaction]; dict.stringDictionary[@"one"] = obj3; XCTAssertTrue([dict.stringDictionary[@"one"] isEqualToObject:obj3], @"Objects should be replaced"); dict.stringDictionary[@"one"] = obj1; XCTAssertTrue([obj1 isEqualToObject:dict.stringDictionary[@"one"]], @"Objects should be replaced"); [dict.stringDictionary removeObjectForKey:@"one"]; XCTAssertEqual(dict.stringDictionary.count, 2U, @"2 objects left"); [dict.stringDictionary removeAllObjects]; XCTAssertEqual(dict.stringDictionary.count, 0U, @"All objects removed"); [realm commitWriteTransaction]; DictionaryPropertyObject *intDictionary = [[DictionaryPropertyObject alloc] init]; IntObject *intObj = [[IntObject alloc] init]; intObj.intCol = 1; [intDictionary.intObjDictionary setObject:intObj forKey:@"one"]; [intDictionary.intObjDictionary setObject:intObj forKey:@"two"]; XCTAssertThrows([intDictionary.intObjDictionary objectsWhere:@"intCol == 1"], @"Should throw on unmanaged RLMDictionary"); XCTAssertThrows(([intDictionary.intObjDictionary objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol == %i", 1]]), @"Should throw on unmanaged RLMDictionary"); XCTAssertThrows([intDictionary.intObjDictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"Should throw on unmanaged RLMDictionary"); // test unmanaged with literals __unused DictionaryPropertyObject *obj = [[DictionaryPropertyObject alloc] initWithValue:@[@{}, @{}, @{}, @{@"one": [[IntObject alloc] initWithValue:@[@1]]}]]; } - (void)testUnmanagedComparision { RLMRealm *realm = [self realmWithTestPath]; DictionaryPropertyObject *dict = [[DictionaryPropertyObject alloc] init]; DictionaryPropertyObject *dict2 = [[DictionaryPropertyObject alloc] init]; XCTAssertNotNil(dict.stringDictionary, @"RLMDictionary property should get created on access"); XCTAssertNotNil(dict2.stringDictionary, @"RLMDictionary property should get created on access"); XCTAssertTrue([dict.stringDictionary isEqual:dict2.stringDictionary], @"Empty dictionaries should be equal"); XCTAssertEqual(dict.stringDictionary.count, 0U); XCTAssertEqual(dict2.stringDictionary.count, 0U); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; [dict.stringDictionary setObject:obj1 forKey:@"one"]; dict.stringDictionary[@"two"] = obj2; [dict.stringDictionary setObject:obj3 forKey:@"three"]; [dict2.stringDictionary setObject:obj1 forKey:@"one"]; dict2.stringDictionary[@"two"] = obj2; [dict2.stringDictionary setObject:obj3 forKey:@"three"]; XCTAssertTrue([dict.stringDictionary isEqual:dict2.stringDictionary], @"Dictionaries should be equal"); [dict2.stringDictionary removeObjectForKey:@"three"]; XCTAssertFalse([dict.stringDictionary isEqual:dict2.stringDictionary], @"Dictionaries should not be equal"); dict2.stringDictionary[@"three"] = obj3; XCTAssertTrue([dict.stringDictionary isEqual:dict2.stringDictionary], @"Dictionaries should be equal"); [realm beginWriteTransaction]; [realm addObject:dict]; [realm commitWriteTransaction]; XCTAssertFalse([dict.stringDictionary isEqual:dict2.stringDictionary], @"Comparing a managed dictionary to an unmanaged one should fail"); XCTAssertFalse([dict2.stringDictionary isEqual:dict.stringDictionary], @"Comparing a managed dictionary to an unmanaged one should fail"); } - (void)testUnmanagedPrimitive { AllPrimitiveDictionaries *obj = [[AllPrimitiveDictionaries alloc] init]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.uuidObj isKindOfClass:[RLMDictionary class]]); [obj.intObj setObject:@1 forKey:@"one"]; XCTAssertEqualObjects(obj.intObj[@"one"], @1); id nilValue; XCTAssertThrows([obj.intObj setObject:nilValue forKey:@"one"]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; obj = [AllPrimitiveDictionaries createInRealm:realm withValue:@[]]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMDictionary class]]); XCTAssertTrue([obj.uuidObj isKindOfClass:[RLMDictionary class]]); obj.intObj[@"two"] = @2; XCTAssertEqualObjects(obj.intObj[@"two"], @2); [realm cancelWriteTransaction]; } - (void)testDeleteObjectInUnmanagedDictionary { DictionaryPropertyObject *dict = [[DictionaryPropertyObject alloc] init]; StringObject *stringObj1 = [[StringObject alloc] init]; stringObj1.stringCol = @"a"; StringObject *stringObj2 = [[StringObject alloc] init]; stringObj2.stringCol = @"b"; StringObject *stringObj3 = [[StringObject alloc] init]; stringObj3.stringCol = @"c"; dict.stringDictionary[@"one"] = stringObj1; dict.stringDictionary[@"two"] = stringObj2; [dict.stringDictionary setObject:stringObj3 forKey:@"three"]; IntObject *intObj1 = [[IntObject alloc] init]; intObj1.intCol = 0; IntObject *intObj2 = [[IntObject alloc] init]; intObj2.intCol = 1; IntObject *intObj3 = [[IntObject alloc] init]; intObj3.intCol = 2; dict.intObjDictionary[@"one"] = intObj1; dict.intObjDictionary[@"two"] = intObj2; [dict.intObjDictionary setObject:intObj3 forKey:@"three"]; XCTAssertEqualObjects(dict.stringDictionary[@"one"], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects([dict.stringDictionary objectForKey:@"two"], stringObj2, @"Objects should be equal"); XCTAssertEqualObjects(dict.stringDictionary[@"three"], stringObj3, @"Objects should be equal"); XCTAssertEqual(dict.stringDictionary.count, 3U, @"Should have 3 elements in string dictionary"); XCTAssertEqualObjects(dict.intObjDictionary[@"one"], intObj1, @"Objects should be equal"); XCTAssertEqualObjects([dict.intObjDictionary objectForKey:@"two"], intObj2, @"Objects should be equal"); XCTAssertEqualObjects(dict.intObjDictionary[@"three"], intObj3, @"Objects should be equal"); XCTAssertEqual(dict.intObjDictionary.count, 3U, @"Should have 3 elements in int dictionary"); [dict.stringDictionary removeObjectForKey:@"three"]; XCTAssertEqualObjects(dict.stringDictionary[@"one"], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects([dict.stringDictionary objectForKey:@"two"], stringObj2, @"Objects should be equal"); XCTAssertEqual(dict.stringDictionary.count, 2U, @"Should have 2 elements in string dictionary"); [dict.stringDictionary removeObjectForKey:@"three"]; // already deleted [dict.stringDictionary removeObjectForKey:@"two"]; XCTAssertEqualObjects(dict.stringDictionary[@"one"], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects([dict.stringDictionary objectForKey:@"one"], stringObj1, @"Objects should be equal"); XCTAssertEqual(dict.stringDictionary.count, 1U, @"Should have 1 elements in string dictionary"); [dict.stringDictionary removeAllObjects]; XCTAssertEqual(dict.stringDictionary.count, 0U, @"Should have 0 elements in string dictionary"); [dict.intDictionary removeObjectsForKeys:@[@"one", @"two", @"three"]]; XCTAssertEqual(dict.intDictionary.count, 0U, @"Should have 0 elements in int dictionary"); } - (void)testFastEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; [realm commitWriteTransaction]; // enumerate empty dictionary for (__unused id obj in company.employeeDict) { XCTFail(@"Should be empty"); } [company.employeeDict enumerateKeysAndObjectsUsingBlock:^(__unused id _Nonnull key, __unused id _Nonnull value, __unused BOOL * _Nonnull stop) { XCTFail(@"Should be empty"); }]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; NSString *key = [NSString stringWithFormat:@"item%d", i]; company.employeeDict[key] = eo; } [realm commitWriteTransaction]; XCTAssertEqual(company.employeeDict.count, 30U); NSInteger count = 0; for (id key in company.employeeDict) { XCTAssertNotNil(key, @"Object is not nil and accessible"); count++; } XCTAssertEqual(count, 30, @"should have enumerated 30 objects"); [company.employeeDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, EmployeeObject * _Nonnull obj, __unused BOOL * _Nonnull stop) { XCTAssertEqualObjects([company.employeeDict[key] name], [obj name]); XCTAssertEqual(((EmployeeObject *)company.employeeDict[key]).age, [obj age]); XCTAssertEqual([company.employeeDict[key] hired], [obj hired]); }]; } - (void)testDeleteDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; const size_t totalCount = 40; for (size_t i = 0; i < totalCount; ++i) { NSString *key = [NSString stringWithFormat:@"item%zu", i]; company.employeeDict[key] = [EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]; } [realm commitWriteTransaction]; [realm beginWriteTransaction]; for (NSString *key in company.employeeDict) { [realm deleteObject:company.employeeDict[key]]; } [realm commitWriteTransaction]; [realm beginWriteTransaction]; for (size_t i = 0; i < totalCount; ++i) { NSString *key = [NSString stringWithFormat:@"item%zu", i]; company.employeeDict[key] = [EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]; } [realm commitWriteTransaction]; [realm beginWriteTransaction]; [company.employeeDict enumerateKeysAndObjectsUsingBlock:^(__unused id _Nonnull key, __unused id _Nonnull obj, __unused BOOL * _Nonnull stop) { [realm deleteObjects:company.employees]; }]; [realm commitWriteTransaction]; } - (void)testValueForKey { // unmanaged DictionaryPropertyObject *unmanObj = [DictionaryPropertyObject new]; StringObject *unmanChild1 = [[StringObject alloc] initWithValue:@[@"a"]]; EmbeddedIntObject *unmanChild2 = [[EmbeddedIntObject alloc] initWithValue:@[@123]]; [unmanObj.stringDictionary setValue:unmanChild1 forKey:@"one"]; XCTAssertTrue([[unmanObj.stringDictionary valueForKey:@"one"][@"stringCol"] isEqualToString:unmanChild1.stringCol]); [unmanObj.embeddedDictionary setValue:unmanChild2 forKey:@"two"]; XCTAssertEqual([[unmanObj.embeddedDictionary valueForKey:@"two"][@"intCol"] integerValue], unmanChild2.intCol); unmanObj.intDictionary[@"one"] = @1; XCTAssertEqualObjects([unmanObj.intDictionary valueForKey:@"invalidated"], @NO); // managed RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *obj = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; EmbeddedIntObject *child2 = [[EmbeddedIntObject alloc] initWithValue:@[@123]]; [obj.stringDictionary setValue:child1 forKey:@"one"]; XCTAssertTrue([[obj.stringDictionary valueForKey:@"one"][@"stringCol"] isEqualToString:child1.stringCol]); [obj.embeddedDictionary setValue:child2 forKey:@"two"]; XCTAssertEqual([[obj.embeddedDictionary valueForKey:@"two"][@"intCol"] integerValue], child2.intCol); [realm commitWriteTransaction]; XCTAssertEqualObjects([obj.stringDictionary valueForKey:@"invalidated"], @NO); } - (void)testObjectAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; AggregateDictionaryObject *obj = [AggregateDictionaryObject new]; XCTAssertEqual(0, [obj.dictionary sumOfProperty:@"intCol"].intValue); XCTAssertNil([obj.dictionary averageOfProperty:@"intCol"]); XCTAssertNil([obj.dictionary minOfProperty:@"intCol"]); XCTAssertNil([obj.dictionary maxOfProperty:@"intCol"]); NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [realm transactionWithBlock:^{ [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; RLMResults *allObjects = [AggregateObject allObjectsInRealm:realm]; for (NSUInteger i = 0; i < allObjects.count; i++) { NSString *key = [NSString stringWithFormat:@"item%lu", (unsigned long)i]; obj.dictionary[key] = allObjects[i]; } }]; void (^test)(void) = ^{ RLMDictionary *dictionary = obj.dictionary; // SUM XCTAssertEqual([dictionary sumOfProperty:@"intCol"].integerValue, 4); XCTAssertEqualWithAccuracy([dictionary sumOfProperty:@"floatCol"].floatValue, 7.2f, 0.1f); XCTAssertEqualWithAccuracy([dictionary sumOfProperty:@"doubleCol"].doubleValue, 10.0, 0.1f); RLMAssertThrowsWithReasonMatching([dictionary sumOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([dictionary sumOfProperty:@"boolCol"], @"sum.*bool"); RLMAssertThrowsWithReasonMatching([dictionary sumOfProperty:@"dateCol"], @"sum.*date"); // Average XCTAssertEqualWithAccuracy([dictionary averageOfProperty:@"intCol"].doubleValue, 0.4, 0.1f); XCTAssertEqualWithAccuracy([dictionary averageOfProperty:@"floatCol"].doubleValue, 0.72, 0.1f); XCTAssertEqualWithAccuracy([dictionary averageOfProperty:@"doubleCol"].doubleValue, 1.0, 0.1f); RLMAssertThrowsWithReasonMatching([dictionary averageOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([dictionary averageOfProperty:@"boolCol"], @"average.*bool"); RLMAssertThrowsWithReasonMatching([dictionary averageOfProperty:@"dateCol"], @"average.*date"); // MIN XCTAssertEqual(0, [[dictionary minOfProperty:@"intCol"] intValue]); XCTAssertEqual(0.0f, [[dictionary minOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(0.0, [[dictionary minOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMinInput, [dictionary minOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([dictionary minOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([dictionary minOfProperty:@"boolCol"], @"min.*bool"); // MAX XCTAssertEqual(1, [[dictionary maxOfProperty:@"intCol"] intValue]); XCTAssertEqual(1.2f, [[dictionary maxOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(2.5, [[dictionary maxOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMaxInput, [dictionary maxOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([dictionary maxOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([dictionary maxOfProperty:@"boolCol"], @"max.*bool"); }; test(); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; test(); } - (void)testRenamedPropertyAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; LinkToRenamedProperties1 *obj = [LinkToRenamedProperties1 new]; XCTAssertEqual(0, [obj.dictionary sumOfProperty:@"propA"].intValue); XCTAssertNil([obj.dictionary averageOfProperty:@"propA"]); XCTAssertNil([obj.dictionary minOfProperty:@"propA"]); XCTAssertNil([obj.dictionary maxOfProperty:@"propA"]); XCTAssertThrows([obj.dictionary sumOfProperty:@"prop 1"]); [realm transactionWithBlock:^{ [RenamedProperties1 createInRealm:realm withValue:@[@1, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@2, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@3, @""]]; RLMResults *allObjects = [RenamedProperties1 allObjectsInRealm:realm]; for (NSUInteger i = 0; i < allObjects.count; i++) { NSString *key = [NSString stringWithFormat:@"item%lu", (unsigned long)i]; obj.dictionary[key] = allObjects[i]; } }]; XCTAssertEqual(6, [obj.dictionary sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.dictionary averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.dictionary minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.dictionary maxOfProperty:@"propA"] intValue]); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; XCTAssertEqual(6, [obj.dictionary sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.dictionary averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.dictionary minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.dictionary maxOfProperty:@"propA"] intValue]); } -(void)testRenamedPropertyObservation { RLMRealm *realm = self.realmWithTestPath; __block LinkToRenamedProperties *obj; [realm transactionWithBlock:^{ obj = [LinkToRenamedProperties createInRealm:realm withValue:@[]]; RenamedProperties *linkedObject = [RenamedProperties createInRealm:realm withValue:@[@1, @""]]; obj.dictionary[@"item"] = linkedObject; }]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [obj.dictionary addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; } keyPaths:@[@"stringCol"]]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMDictionary *dict = [(LinkToRenamedProperties *)[LinkToRenamedProperties allObjectsInRealm:realm].firstObject dictionary]; RenamedProperties *property = dict[@"item"]; property.stringCol = @"newValue"; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } -(void)testInsertMultiple { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; DictionaryPropertyObject *obj = [DictionaryPropertyObject createInRealm:realm withValue: @{@"stringDictionary": @{}}]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; child2.stringCol = @"b"; [obj.stringDictionary setValuesForKeysWithDictionary:@{@"a": child1, @"b": child2}]; [realm commitWriteTransaction]; RLMResults *children = [StringObject allObjectsInRealm:realm]; XCTAssertEqualObjects([children[0] stringCol], @"a", @"First child should be 'a'"); XCTAssertEqualObjects([children[1] stringCol], @"b", @"Second child should be 'b'"); } - (void)testReplaceObjectInUnmanagedDictionary { DictionaryPropertyObject *dict = [[DictionaryPropertyObject alloc] init]; StringObject *stringObj1 = [[StringObject alloc] initWithValue:@{@"stringCol": @"a"}]; StringObject *stringObj2 = [[StringObject alloc] initWithValue:@{@"stringCol": @"b"}]; StringObject *stringObj3 = [[StringObject alloc] initWithValue:@{@"stringCol": @"c"}]; dict.stringDictionary[@"a"] = stringObj1; dict.stringDictionary[@"b"] = stringObj2; dict.stringDictionary[@"c"] = stringObj3; IntObject *intObj1 = [[IntObject alloc] initWithValue:@{@"intCol": @0}]; IntObject *intObj2 = [[IntObject alloc] initWithValue:@{@"intCol": @1}]; IntObject *intObj3 = [[IntObject alloc] initWithValue:@{@"intCol": @2}]; dict.intObjDictionary[@"a"] = intObj1; dict.intObjDictionary[@"b"] = intObj2; dict.intObjDictionary[@"c"] = intObj3; XCTAssertEqualObjects(dict.stringDictionary[@"a"], stringObj1, @"Objects should be equal"); XCTAssertEqualObjects(dict.stringDictionary[@"b"], stringObj2, @"Objects should be equal"); XCTAssertEqualObjects(dict.stringDictionary[@"c"], stringObj3, @"Objects should be equal"); XCTAssertEqual(dict.stringDictionary.count, 3U, @"Should have 3 elements in stringDictionary"); XCTAssertEqualObjects(dict.intObjDictionary[@"a"], intObj1, @"Objects should be equal"); XCTAssertEqualObjects(dict.intObjDictionary[@"b"], intObj2, @"Objects should be equal"); XCTAssertEqualObjects(dict.intObjDictionary[@"c"], intObj3, @"Objects should be equal"); XCTAssertEqual(dict.intObjDictionary.count, 3U, @"Should have 3 elements in intDictionary"); StringObject *stringObj4 = [[StringObject alloc] initWithValue:@{@"stringCol": @"d"}]; dict.stringDictionary[@"a"] = stringObj4; XCTAssertEqualObjects(dict.stringDictionary[@"a"], stringObj4, @"Objects should be replaced"); XCTAssertEqual(dict.stringDictionary.count, 3U, @"Should have 3 elements in stringDictionary"); IntObject *intObj4 = [[IntObject alloc] initWithValue:@{@"intCol": @3}]; dict.intObjDictionary[@"a"] = intObj4; XCTAssertEqualObjects(dict.intObjDictionary[@"a"], intObj4, @"Objects should be replaced"); XCTAssertEqual(dict.intObjDictionary.count, 3U, @"Should have 3 elements in intDictionary"); RLMAssertThrowsWithReasonMatching([dict.stringDictionary setObject:(id)intObj4 forKey:@"a"], @"IntObject.*StringObject"); RLMAssertThrowsWithReasonMatching([dict.intObjDictionary setObject:(id)stringObj4 forKey:@"a"], @"StringObject.*IntObject"); } - (void)testExchangeObjectForKeyWithObjectForKey { void (^test)(RLMDictionary *) = ^(RLMDictionary *dict) { id obj = dict[@"a"]; dict[@"a"] = dict[@"b"]; dict[@"b"] = obj; XCTAssertEqual(2U, dict.count); XCTAssertEqualObjects(@"b", [dict[@"a"] stringCol]); XCTAssertEqualObjects(@"a", [dict[@"b"] stringCol]); obj = dict[@"a"]; dict[@"a"] = dict[@"b"]; dict[@"b"] = obj; XCTAssertEqual(2U, dict.count); XCTAssertEqualObjects(@"a", [dict[@"a"] stringCol]); XCTAssertEqualObjects(@"b", [dict[@"b"] stringCol]); }; DictionaryPropertyObject *dict = [[DictionaryPropertyObject alloc] initWithValue:@{@"stringDictionary": @{@"a": [[StringObject alloc] initWithValue:@{@"stringCol": @"a"}], @"b": [[StringObject alloc] initWithValue:@{@"stringCol": @"b"}]}}]; test(dict.stringDictionary); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:dict]; test(dict.stringDictionary); [realm commitWriteTransaction]; } - (void)testObjectForKey { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; EmployeeObject *deleted = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; EmployeeObject *indirectlyDeleted = [EmployeeObject allObjectsInRealm:realm].lastObject; [realm deleteObject:deleted]; // create company CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employeeDict setObject:po1 forKey:@"po1"]; [company.employeeDict setObject:po2 forKey:@"po2"]; [company.employeeDict setObject:po3 forKey:@"po3"]; [company.employeeDict setObject:deleted forKey:@"deleted"]; // test unmanaged XCTAssertNotNil(company.employeeDict[@"deleted"]); XCTAssertTrue(deleted.isInvalidated); [company.employeeDict removeObjectForKey:@"deleted"]; // add to realm [realm addObject:company]; [realm commitWriteTransaction]; // test LinkView XCTAssertEqual(3U, company.employeeDict.count); XCTAssertEqualObjects(po2.name, [company.employeeDict[@"po2"] name]); // invalid object XCTAssertTrue(indirectlyDeleted.isInvalidated); RLMResults *employees = [company.employeeDict objectsWhere:@"age = %@", @40]; XCTAssertEqual(0U, [employees indexOfObject:po1]); XCTAssertEqual((NSUInteger)NSNotFound, [employees indexOfObject:po3]); } - (void)testObjectWhere { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Bill", @"age": @55, @"hired": @YES}]; // create company CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; for(EmployeeObject *eo in [EmployeeObject allObjectsInRealm:realm]) { company.employeeDict[eo.name] = eo; } // test unmanaged RLMAssertThrowsWithReasonMatching([company.employeeDict objectsWhere:@"name = 'Jill'"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); // add to realm [realm addObject:company]; [realm commitWriteTransaction]; // test LinkView RLMDictionary XCTAssertEqualObjects(@"Jill", [[company.employeeDict objectsWhere:@"name = 'Jill'"].firstObject name]); XCTAssertEqualObjects(@"Joe", [[company.employeeDict objectsWhere:@"name = 'Joe'"].firstObject name]); XCTAssertEqual([company.employeeDict objectsWhere:@"name = 'JoJo'"].count, 0U); RLMResults *results = [company.employeeDict objectsWhere:@"age > 30"]; XCTAssertEqual(1U, [results indexOfObjectWhere:@"name = 'Joe'"]); XCTAssertEqual(0U, [results indexOfObjectWhere:@"name = 'Bill'"]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObjectWhere:@"name = 'John'"]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObjectWhere:@"name = 'Jill'"]); } - (void)testSetValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; RLMAssertThrowsWithReasonMatching([company.employeeDict setValue:@"name" forKey:@"name"], @"Value of type '__NSCFConstantString' does not match RLMDictionary value type 'EmployeeObject'."); EmployeeObject *e = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jane", @"age": @(1), @"hired": @YES}]; [company.employeeDict setValue:e forKey:@"e"]; XCTAssertEqualObjects(((EmployeeObject *)[company.employeeDict valueForKey:@"e"]).name, @"Jane"); [realm addObject:company]; [realm commitWriteTransaction]; XCTAssertThrows([company.employeeDict setValue:@{} forKey:@"e2"]); XCTAssertNil([company.employeeDict valueForKey:@"e2"]); // managed NSMutableArray *ages = [NSMutableArray array]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; company.employeeDict[[NSString stringWithFormat:@"%d", i]] = eo; } [realm commitWriteTransaction]; EmployeeObject *o = [[EmployeeObject objectsInRealm:realm where:@"name = 'Jane'"] firstObject]; XCTAssertEqualObjects(((EmployeeObject *)[company.employeeDict valueForKey:@"e"]).name, o.name); // unmanaged object company = [[CompanyObject alloc] init]; ages = [NSMutableArray array]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Jamie", @"age": @(i), @"hired": @YES}]; company.employeeDict[[NSString stringWithFormat:@"%d", i]] = eo; } XCTAssertEqualObjects(((EmployeeObject *)[company.employeeDict valueForKey:@"0"]).name, @"Jamie"); } - (void)testValueForCollectionOperationKeyPath { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *e1 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; EmployeeObject *e2 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; EmployeeObject *e3 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; EmployeeObject *e4 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"D", @"age": @50, @"hired": @YES}]; PrimaryCompanyObject *c1 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG", @"employeeDict": @{@"e1": e1, @"e2": e2, @"e3": e3, @"e4": e2}, @"employee": @[], @"employeeSet": @[]}]; PrimaryCompanyObject *c2 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG 2", @"employeeDict": @{@"e1": e1, @"e4": e4}, @"employee": @[], @"employeeSet": @[]}]; ArrayOfPrimaryCompanies *companies = [ArrayOfPrimaryCompanies createInRealm:realm withValue:@[@[c1, c2]]]; [realm commitWriteTransaction]; // count operator XCTAssertEqual([[c1.employeeDict valueForKeyPath:@"@count"] integerValue], 4); // numeric operators XCTAssertEqual([[c1.employeeDict valueForKeyPath:@"@min.age"] intValue], 20); XCTAssertEqual([[c1.employeeDict valueForKeyPath:@"@max.age"] intValue], 40); XCTAssertEqual([[c1.employeeDict valueForKeyPath:@"@sum.age"] integerValue], 120); XCTAssertEqualWithAccuracy([[c1.employeeDict valueForKeyPath:@"@avg.age"] doubleValue], 30, 0.1f); // collection XCTAssertEqualObjects([[c1.employeeDict valueForKeyPath:@"@unionOfObjects.name"] sortedArrayUsingSelector:@selector(compare:)], ([@[@"A", @"B", @"C", @"B"] sortedArrayUsingSelector:@selector(compare:)])); XCTAssertEqualObjects([[c1.employeeDict valueForKeyPath:@"@distinctUnionOfObjects.name"] sortedArrayUsingSelector:@selector(compare:)], (@[@"A", @"B", @"C"])); NSComparator cmp = ^NSComparisonResult(id obj1, id obj2) { return [obj1 compare: obj2]; }; XCTAssertEqualObjects([[companies.companies valueForKeyPath:@"@unionOfArrays.employeeDict"] sortedArrayUsingComparator:cmp], (@[@"e1", @"e1", @"e2", @"e3", @"e4", @"e4"])); XCTAssertEqualObjects([[companies.companies valueForKeyPath:@"@distinctUnionOfArrays.employeeDict"] sortedArrayUsingComparator:cmp], (@[@"e1", @"e2", @"e3", @"e4"])); // invalid key paths RLMAssertThrowsWithReasonMatching([c1.employeeDict valueForKeyPath:@"@invalid.name"], @"Unsupported KVC collection operator found in key path '@invalid.name'"); RLMAssertThrowsWithReasonMatching([c1.employeeDict valueForKeyPath:@"@sum"], @"Missing key path for KVC collection operator sum in key path '@sum'"); RLMAssertThrowsWithReasonMatching([c1.employeeDict valueForKeyPath:@"@sum."], @"Missing key path for KVC collection operator sum in key path '@sum.'"); RLMAssertThrowsWithReasonMatching([c1.employeeDict valueForKeyPath:@"@sum.employees.@sum.age"], @"Nested key paths.*not supported"); } - (void)testCrossThreadAccess { CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; company.employeeDict[@"eo"] = eo; RLMDictionary *employees = company.employeeDict; // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(company.employeeDict); XCTAssertNoThrow(employees[@"eo"]); }]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [realm addObject:company]; [realm commitWriteTransaction]; employees = company.employeeDict; XCTAssertNoThrow(company.employeeDict); XCTAssertNoThrow(employees[@"eo"]); [self dispatchAsyncAndWait:^{ XCTAssertThrows(company.employeeDict); XCTAssertThrows(employees.allValues); XCTAssertThrows(employees.allKeys); XCTAssertThrows(employees[@"eo"]); }]; } - (void)testSortByNoColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; RLMDictionary *dict = [DogDictionaryObject createInDefaultRealmWithValue:@[@{@"a1": a1, @"b1": b1, @"a2": a2, @"b2": b2}]].dogs; [realm commitWriteTransaction]; RLMResults *notActuallySorted = [dict sortedResultsUsingDescriptors:@[]]; XCTAssertEqual(notActuallySorted.count, dict.count); XCTAssertTrue([dict[@"a1"] isEqualToObject:notActuallySorted[0]]); XCTAssertTrue([dict[@"a2"] isEqualToObject:notActuallySorted[1]]); XCTAssertTrue([dict[@"b1"] isEqualToObject:notActuallySorted[2]]); XCTAssertTrue([dict[@"b2"] isEqualToObject:notActuallySorted[3]]); } - (void)testSortByMultipleColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; DogDictionaryObject *ddo = [DogDictionaryObject createInDefaultRealmWithValue:@[@{@"a1": a1, @"b1": b1, @"a2": a2, @"b2": b2}]]; [realm commitWriteTransaction]; bool (^checkOrder)(NSArray *, NSArray *, NSArray *) = ^bool(NSArray *properties, NSArray *ascending, NSArray *dogs) { NSArray *sort = @[[RLMSortDescriptor sortDescriptorWithKeyPath:properties[0] ascending:[ascending[0] boolValue]], [RLMSortDescriptor sortDescriptorWithKeyPath:properties[1] ascending:[ascending[1] boolValue]]]; RLMResults *actual = [ddo.dogs sortedResultsUsingDescriptors:sort]; return [actual[0] isEqualToObject:dogs[0]] && [actual[1] isEqualToObject:dogs[1]] && [actual[2] isEqualToObject:dogs[2]] && [actual[3] isEqualToObject:dogs[3]]; }; // Check each valid sort XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @YES], @[a1, a2, b1, b2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @NO], @[a2, a1, b2, b1])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @YES], @[b1, b2, a1, a2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @NO], @[b2, b1, a2, a1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @YES], @[a1, b1, a2, b2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @NO], @[b1, a1, b2, a2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @YES], @[a2, b2, a1, b1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @NO], @[b2, a2, b1, a1])); } - (void)testSortByRenamedColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; id value = @{@"dictionary": @{@"0": @[@1, @"c"], @"1": @[@2, @"b"], @"2": @[@3, @"a"]}, @"set": @[]}; LinkToRenamedProperties *obj = [LinkToRenamedProperties createInRealm:realm withValue:value]; XCTAssertEqualObjects([[obj.dictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES] valueForKeyPath:@"intCol"], (@[@1, @2, @3])); XCTAssertEqualObjects([[obj.dictionary sortedResultsUsingKeyPath:@"intCol" ascending:NO] valueForKeyPath:@"intCol"], (@[@3, @2, @1])); [realm cancelWriteTransaction]; } - (void)testDeleteLinksAndObjectsInDictionary { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@[@"Joe", @40, @YES]]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@[@"John", @30, @NO]]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@[@"Jill", @25, @YES]]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; for (EmployeeObject *eo in [EmployeeObject allObjects]) { company.employeeDict[eo.name] = eo; } [realm addObject:company]; [realm commitWriteTransaction]; RLMDictionary *peopleInCompany = company.employeeDict; // Delete link to employee XCTAssertThrowsSpecificNamed([peopleInCompany removeObjectForKey:@"Joe"], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertEqual(peopleInCompany.count, 3U, @"No links should have been deleted"); [realm beginWriteTransaction]; XCTAssertNoThrow([peopleInCompany removeObjectForKey:@"John"], @"Should delete link to employee"); [realm commitWriteTransaction]; XCTAssertEqual(peopleInCompany.count, 2U, @"link deleted when accessing via links"); EmployeeObject *test = peopleInCompany[@"Joe"]; XCTAssertEqual(test.age, po1.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po1.name, @"Should be equal"); XCTAssertEqual(test.hired, po1.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po1], @"Should be equal"); test = peopleInCompany[@"Jill"]; XCTAssertEqual(test.age, po3.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po3.name, @"Should be equal"); XCTAssertEqual(test.hired, po3.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po3], @"Should be equal"); XCTAssertThrowsSpecificNamed([peopleInCompany removeAllObjects], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed(peopleInCompany[@"Joe"] = po2, NSException, @"RLMException", @"Replace not allowed in read transaction"); XCTAssertThrowsSpecificNamed(peopleInCompany[@"John"] = po2, NSException, @"RLMException", @"Add not allowed in read transaction"); [realm beginWriteTransaction]; XCTAssertNoThrow(peopleInCompany[@"Jill"] = nil, @"Should delete value for key"); XCTAssertEqual(peopleInCompany.count, 1U, @"1 remaining link"); peopleInCompany[@"Joe"] = po2; XCTAssertEqual(peopleInCompany.count, 1U, @"1 link replaced"); peopleInCompany[@"Jill"] = po3; XCTAssertEqual(peopleInCompany.count, 2U, @"2 links"); XCTAssertNoThrow([peopleInCompany removeAllObjects], @"Should delete all links"); XCTAssertEqual(peopleInCompany.count, 0U, @"0 remaining links"); [realm commitWriteTransaction]; RLMResults *allPeople = [EmployeeObject allObjects]; XCTAssertEqual(allPeople.count, 3U, @"Only links should have been deleted, not the employees"); } - (void)testDictionaryDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMDictionary *employees = [CompanyObject createInDefaultRealmWithValue:@[@"company"]].employeeDict; RLMDictionary *ints = [AllPrimitiveDictionaries createInDefaultRealmWithValue:@[]].intObj; for (NSInteger i = 0; i < 1012; ++i) { EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; person.age = 24; person.hired = YES; NSString *key = [NSString stringWithFormat:@"%li", (long)i]; employees[key] = person; ints[key] = @(i + 100); } [realm commitWriteTransaction]; RLMAssertMatches(employees.description, @"(?s)RLMDictionary\\ \\<0x[a-z0-9]+\\> \\(\n" @"\\[[0-9]+\\]: EmployeeObject \\{\n" @"\t\tname = Mary;\n" @"\t\tage = 24;\n" @"\t\thired = 1;\n" @"\t\\},\n" @".*\n" @"\\)"); RLMAssertMatches(ints.description, @"(?s)RLMDictionary\\ \\<0x[a-z0-9]+\\> \\(\n" @"\\[[0-9]+\\]: [0-9]+,\n" @"\\[[0-9]+\\]: [0-9]+,\n" @".*\n" @"\\[[0-9]+\\]: [0-9]+\n" @"\\)"); } - (void)testUnmanagedAssignment { IntObject *io1 = [[IntObject alloc] init]; IntObject *io2 = [[IntObject alloc] init]; IntObject *io3 = [[IntObject alloc] init]; DictionaryPropertyObject *dict1 = [[DictionaryPropertyObject alloc] init]; DictionaryPropertyObject *dict2 = [[DictionaryPropertyObject alloc] init]; // Assigning NSDictionary shallow copies dict1.intObjDictionary = (id)@{@"io1": io1, @"io2": io2}; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io1"], io1); [dict1 setValue:@{@"io1": io1, @"io3": io3} forKey:@"intObjDictionary"]; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io3"], io3); dict1[@"intObjDictionary"] = @{@"io2": io2, @"io3": io3}; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io3"], io3); // Assigning RLMDictionary shallow copies dict2.intObjDictionary = dict1.intObjDictionary; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io3"], io3); [dict1.intObjDictionary removeAllObjects]; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io3"], io3); // Self-assignment is a no-op dict2.intObjDictionary = dict2.intObjDictionary; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io3"], io3); dict2[@"intObjDictionary"] = dict2[@"intObjDictionary"]; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io3"], io3); } - (void)testManagedAssignment { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; IntObject *io1 = [IntObject createInRealm:realm withValue:@[@1]]; IntObject *io2 = [IntObject createInRealm:realm withValue:@[@2]]; IntObject *io3 = [IntObject createInRealm:realm withValue:@[@3]]; DictionaryPropertyObject *dict1 = [[DictionaryPropertyObject alloc] init]; DictionaryPropertyObject *dict2 = [[DictionaryPropertyObject alloc] init]; // Assigning NSDictonary shallow copies dict1.intObjDictionary = (id)@{@"io1": io1, @"io2": io2}; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io1"], io1); [dict1 setValue:@{@"io1": io1, @"io3": io3} forKey:@"intObjDictionary"]; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io3"], io3); dict1[@"intObjDictionary"] = @{@"io2": io2, @"io3": io3}; XCTAssertEqualObjects([dict1.intObjDictionary valueForKey:@"io2"], io2); // Assigning RLMDictionary shallow copies dict2.intObjDictionary = dict1.intObjDictionary; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io2"], io2); [dict1.intObjDictionary removeAllObjects]; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io2"], io2); // Self-assignment is a no-op dict2.intObjDictionary = dict2.intObjDictionary; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io2"], io2); dict2[@"intObjDictionary"] = dict2[@"intObjDictionary"]; XCTAssertEqualObjects([dict2.intObjDictionary valueForKey:@"io2"], io2); [realm cancelWriteTransaction]; } - (void)testAssignIncorrectType { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{@"stringDictionary": @{@"a": [[StringObject alloc] initWithValue:@[@"a"]]}}]; RLMAssertThrowsWithReason(dict.intObjDictionary = (id)dict.stringDictionary, @"RLMDictionary does not match expected type 'IntObject?' for property 'DictionaryPropertyObject.intObjDictionary'."); RLMAssertThrowsWithReason(dict[@"intObjDictionary"] = dict[@"stringDictionary"], @"RLMDictionary does not match expected type 'IntObject?' for property 'DictionaryPropertyObject.intObjDictionary'."); [realm cancelWriteTransaction]; } - (void)testNotificationSentInitially { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{@"stringDictionary": @{@"a": [[StringObject alloc] initWithValue:@[@"a"]]}}]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [dict.stringDictionary addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); XCTAssertNil(change); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{}]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [dict.stringDictionary addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMDictionary *dict = [(DictionaryPropertyObject *)[DictionaryPropertyObject allObjectsInRealm:realm].firstObject stringDictionary]; dict[@"new"] = [[StringObject alloc] init]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{}]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [dict.stringDictionary addNotificationBlock:^(__unused RLMDictionary *dictionary, __unused RLMDictionaryChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [DictionaryPropertyObject createInRealm:realm withValue:@{}]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{}]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [dict.stringDictionary addNotificationBlock:^(__unused RLMDictionary *dictionary, __unused RLMDictionaryChange *change, __unused NSError *error) { XCTAssertNotNil(dictionary); XCTAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMDictionary *dict = [(DictionaryPropertyObject *)[DictionaryPropertyObject allObjectsInRealm:realm].firstObject stringDictionary]; dict[@"new"] = [[StringObject alloc] init]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{}]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [dict.stringDictionary addNotificationBlock:^(__unused RLMDictionary *dictionary, __unused RLMDictionaryChange *change, __unused NSError *error) { XCTAssertNotNil(dictionary); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:dict]; [realm commitWriteTransaction]; [(RLMNotificationToken *)token invalidate]; } static RLMDictionary *managedTestDictionary(void) { RLMRealm *realm = [RLMRealm defaultRealm]; RLMDictionary *dict; [realm beginWriteTransaction]; dict = [DictionaryPropertyObject createInDefaultRealmWithValue: @{@"intObjDictionary": @{@"0": @[@0], @"1": @[@1]}}].intObjDictionary; [realm commitWriteTransaction]; return dict; } - (void)testAllMethodsCheckThread { RLMDictionary *dict = managedTestDictionary(); IntObject *io = dict.allValues.firstObject; RLMRealm *realm = dict.realm; [realm beginWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReasonMatching([dict count], @"thread"); RLMAssertThrowsWithReasonMatching([dict objectForKey:@"thread"], @"thread"); RLMAssertThrowsWithReasonMatching([dict setObject:io forKey:@"thread"], @"thread"); RLMAssertThrowsWithReasonMatching([dict removeObjectForKey:@"thread"], @"thread"); RLMAssertThrowsWithReasonMatching([dict setObject:nil forKey:@"thread"], @"thread"); RLMAssertThrowsWithReasonMatching([dict removeAllObjects], @"thread"); RLMAssertThrowsWithReasonMatching([dict objectsWhere:@"intCol = 0"], @"thread"); RLMAssertThrowsWithReasonMatching([dict objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"thread"); RLMAssertThrowsWithReasonMatching([dict sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"thread"); RLMAssertThrowsWithReasonMatching([dict sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"thread"); RLMAssertThrowsWithReasonMatching(dict[@"thread"], @"thread"); RLMAssertThrowsWithReasonMatching(dict[@"thread"] = io, @"thread"); RLMAssertThrowsWithReasonMatching([dict valueForKey:@"intCol"], @"thread"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in dict);}), @"thread"); }]; [realm cancelWriteTransaction]; } - (void)testAllMethodsCheckForInvalidation { RLMDictionary *dictionary = managedTestDictionary(); IntObject *io = dictionary[@"0"]; RLMRealm *realm = dictionary.realm; [realm beginWriteTransaction]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); XCTAssertNoThrow([dictionary count]); XCTAssertNoThrow([dictionary allValues]); XCTAssertNoThrow([dictionary allKeys]); XCTAssertNoThrow(dictionary[@"new"] = io); XCTAssertNoThrow([dictionary setObject:io forKey:@"another"]); XCTAssertNoThrow(dictionary[@"new"] = nil); XCTAssertNoThrow([dictionary removeObjectForKey:@"another"]); XCTAssertNoThrow([dictionary objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([dictionary objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([dictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow([dictionary valueForKey:@"0"]); XCTAssertNoThrow([dictionary setValue:io forKey:@"foo"]); XCTAssertNoThrow(({for (__unused id obj in dictionary);})); [realm cancelWriteTransaction]; [realm invalidate]; [realm beginWriteTransaction]; io = [IntObject createInDefaultRealmWithValue:@[@0]]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); RLMAssertThrowsWithReasonMatching([dictionary count], @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary allValues], @"invalidated"); XCTAssertNil(dictionary[@"new"]); RLMAssertThrowsWithReasonMatching(dictionary[@"new"] = io, @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary setObject:io forKey:@"another"], @"invalidated"); RLMAssertThrowsWithReasonMatching(dictionary[@"new"] = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary removeObjectForKey:@"another"], @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary objectsWhere:@"intCol = 0"], @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"invalidated"); RLMAssertThrowsWithReasonMatching([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"invalidated"); XCTAssertNil([dictionary valueForKey:@"new"]); RLMAssertThrowsWithReasonMatching([dictionary setValue:io forKey:@"foo"], @"invalidated"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in dictionary);}), @"invalidated"); [realm cancelWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMDictionary *dict = managedTestDictionary(); IntObject *io = dict.allValues.firstObject; XCTAssertNoThrow([dict objectClassName]); XCTAssertNoThrow([dict realm]); XCTAssertNoThrow([dict isInvalidated]); XCTAssertNoThrow([dict count]); XCTAssertNoThrow([dict objectForKey:@"0"]); XCTAssertNoThrow([dict objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([dict objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([dict sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([dict sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow(dict[@"0"]); XCTAssertNoThrow([dict valueForKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in dict);})); RLMAssertThrowsWithReasonMatching([dict setObject:io forKey:@"thread"], @"write transaction"); RLMAssertThrowsWithReasonMatching([dict removeObjectForKey:@"thread"], @"write transaction"); RLMAssertThrowsWithReasonMatching([dict setObject:nil forKey:@"thread"], @"write transaction"); RLMAssertThrowsWithReasonMatching([dict removeAllObjects], @"write transaction"); RLMAssertThrowsWithReasonMatching(dict[@"0"] = io, @"write transaction"); RLMAssertThrowsWithReasonMatching([dict setValue:io forKey:@"intCol"], @"write transaction"); } - (void)testDeleteObjectFromOutsideDictionary { RLMDictionary *dict = managedTestDictionary(); RLMRealm *realm = dict.realm; [realm beginWriteTransaction]; XCTAssertNotNil(dict[@"0"]); IntObject *o = dict.allValues[0]; IntObject *o2 = dict.allValues[1]; [realm deleteObject:o]; [realm deleteObject:o2]; XCTAssertEqualObjects(dict[@"0"], NSNull.null); XCTAssertEqualObjects(dict[@"1"], NSNull.null); [realm commitWriteTransaction]; } - (void)testIsFrozen { RLMDictionary *unfrozen = managedTestDictionary(); RLMDictionary *frozen = [unfrozen freeze]; XCTAssertFalse(unfrozen.isFrozen); XCTAssertTrue(frozen.isFrozen); } - (void)testFreezingFrozenObjectReturnsSelf { RLMDictionary *dict = managedTestDictionary(); RLMDictionary *frozen = [dict freeze]; XCTAssertNotEqual(dict, frozen); XCTAssertNotEqual(dict.freeze, frozen); XCTAssertEqual(frozen, frozen.freeze); } - (void)testFreezeFromWrongThread { RLMDictionary *dict = managedTestDictionary(); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([dict freeze], @"Realm accessed from incorrect thread"); }]; } - (void)testAccessFrozenFromDifferentThread { RLMDictionary *frozen = [managedTestDictionary() freeze]; [self dispatchAsyncAndWait:^{ XCTAssertEqualObjects(@([[frozen valueForKey:@"0"] intCol]), (@0)); }]; } - (void)testObserveFrozenDictionary { RLMDictionary *frozen = [managedTestDictionary() freeze]; id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {}; RLMAssertThrowsWithReason([frozen addNotificationBlock:block], @"Frozen Realms do not change and do not have change notifications."); } - (void)testQueryFrozenDictionary { RLMDictionary *frozen = [managedTestDictionary() freeze]; XCTAssertEqualObjects([[frozen objectsWhere:@"intCol > 0"] valueForKey:@"intCol"], (@[@1])); } - (void)testFrozenDictionarysDoNotUpdate { RLMDictionary *dict = managedTestDictionary(); RLMDictionary *frozen = [dict freeze]; XCTAssertEqual(frozen.count, 2); [dict.realm transactionWithBlock:^{ [dict removeObjectForKey:dict.allKeys.lastObject]; }]; XCTAssertEqual(frozen.count, 2); } - (void)testAddEntriesFromDictionaryUnmanaged { RLMDictionary *dict = [[DictionaryPropertyObject alloc] init].stringDictionary; RLMAssertThrowsWithReasonMatching([dict addEntriesFromDictionary:@[@"string"]], @"Cannot add entries from object of class '.*Array.*'"); RLMAssertThrowsWithReason([dict addEntriesFromDictionary:@{@"": [[IntObject alloc] init]}], @"Value of type 'IntObject' does not match RLMDictionary value type 'StringObject'."); RLMAssertThrowsWithReason([dict addEntriesFromDictionary:@{@1: [[StringObject alloc] init]}], @"Invalid key '1' of type '" RLMConstantInt "' for expected type 'string'."); // Adding nil is a no-op XCTAssertNoThrow([dict addEntriesFromDictionary:self.nonLiteralNil]); XCTAssertEqual(dict.count, 0U); // Add into empty adds those entries [dict addEntriesFromDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"1"]], @"b": [[StringObject alloc] initWithValue:@[@"2"]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqualObjects(dict[@"a"].stringCol, @"1"); XCTAssertEqualObjects(dict[@"b"].stringCol, @"2"); // Duplicate keys overwrite the old values and leave any non-duplicates [dict addEntriesFromDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"3"]], @"c": [[StringObject alloc] initWithValue:@[@"4"]]}]; XCTAssertEqual(dict.count, 3U); XCTAssertEqualObjects(dict[@"a"].stringCol, @"3"); XCTAssertEqualObjects(dict[@"b"].stringCol, @"2"); XCTAssertEqualObjects(dict[@"c"].stringCol, @"4"); // Add from a RLMDictionary rather than a NSDictionary RLMDictionary *dict2 = [[DictionaryPropertyObject alloc] init].stringDictionary; dict2[@"d"] = [[StringObject alloc] initWithValue:@[@"5"]]; [dict addEntriesFromDictionary:dict2]; XCTAssertEqual(dict.count, 4U); XCTAssertEqualObjects(dict[@"a"].stringCol, @"3"); XCTAssertEqualObjects(dict[@"b"].stringCol, @"2"); XCTAssertEqualObjects(dict[@"c"].stringCol, @"4"); XCTAssertEqualObjects(dict[@"d"].stringCol, @"5"); } - (void)testAddEntriesFromDictionaryManaged { RLMDictionary *dict = managedTestDictionary(); [dict.realm beginWriteTransaction]; [dict removeAllObjects]; RLMAssertThrowsWithReasonMatching([dict addEntriesFromDictionary:@[@"string"]], @"Cannot add entries from object of class '.*Array.*'"); RLMAssertThrowsWithReason([dict addEntriesFromDictionary:@{@"": [[StringObject alloc] init]}], @"Value of type 'StringObject' does not match RLMDictionary value type 'IntObject'."); RLMAssertThrowsWithReason([dict addEntriesFromDictionary:@{@1: [[IntObject alloc] init]}], @"Invalid key '1' of type '" RLMConstantInt "' for expected type 'string'."); // Adding nil is a no-op XCTAssertNoThrow([dict addEntriesFromDictionary:self.nonLiteralNil]); XCTAssertEqual(dict.count, 0U); // Add into empty adds those entries [dict addEntriesFromDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@1]], @"b": [[IntObject alloc] initWithValue:@[@2]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqual(dict[@"a"].intCol, 1); XCTAssertEqual(dict[@"b"].intCol, 2); // Duplicate keys overwrite the old values and leave any non-duplicates [dict addEntriesFromDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@3]], @"c": [[IntObject alloc] initWithValue:@[@4]]}]; XCTAssertEqual(dict.count, 3U); XCTAssertEqual(dict[@"a"].intCol, 3); XCTAssertEqual(dict[@"b"].intCol, 2); XCTAssertEqual(dict[@"c"].intCol, 4); // Add from a RLMDictionary rather than a NSDictionary RLMDictionary *dict2 = [[DictionaryPropertyObject alloc] init].intObjDictionary; dict2[@"d"] = [[IntObject alloc] initWithValue:@[@5]]; [dict addEntriesFromDictionary:dict2]; XCTAssertEqual(dict.count, 4U); XCTAssertEqual(dict[@"a"].intCol, 3); XCTAssertEqual(dict[@"b"].intCol, 2); XCTAssertEqual(dict[@"c"].intCol, 4); XCTAssertEqual(dict[@"d"].intCol, 5); [dict.realm cancelWriteTransaction]; } - (void)testSetDictionaryUnmanaged { RLMDictionary *dict = [[DictionaryPropertyObject alloc] init].stringDictionary; RLMAssertThrowsWithReasonMatching([dict setDictionary:@[@"string"]], @"Cannot set dictionary to object of class '.*Array.*'"); RLMAssertThrowsWithReason([dict setDictionary:@{@"": [[IntObject alloc] init]}], @"Value of type 'IntObject' does not match RLMDictionary value type 'StringObject'."); RLMAssertThrowsWithReason([dict setDictionary:@{@1: [[StringObject alloc] init]}], @"Invalid key '1' of type '" RLMConstantInt "' for expected type 'string'."); // Set into empty adds those entries [dict setDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"1"]], @"b": [[StringObject alloc] initWithValue:@[@"2"]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqualObjects(dict[@"a"].stringCol, @"1"); XCTAssertEqualObjects(dict[@"b"].stringCol, @"2"); // New dictionary replaces the old one entirely [dict setDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"3"]], @"c": [[StringObject alloc] initWithValue:@[@"4"]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqualObjects(dict[@"a"].stringCol, @"3"); XCTAssertEqualObjects(dict[@"c"].stringCol, @"4"); // Setting to nil clears XCTAssertNoThrow([dict setDictionary:self.nonLiteralNil]); XCTAssertEqual(dict.count, 0U); // Self-setting clears [dict setDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"3"]], @"c": [[StringObject alloc] initWithValue:@[@"4"]]}]; XCTAssertEqual(dict.count, 2U); [dict setDictionary:dict]; XCTAssertEqual(dict.count, 0U); // Type error clears [dict setDictionary:@{@"a": [[StringObject alloc] initWithValue:@[@"3"]], @"c": [[StringObject alloc] initWithValue:@[@"4"]]}]; XCTAssertEqual(dict.count, 2U); RLMAssertThrowsWithReason([dict setDictionary:@{@"": [[IntObject alloc] init]}], @"Value of type 'IntObject' does not match RLMDictionary value type 'StringObject'."); XCTAssertEqual(dict.count, 0U); } - (void)testSetDictionaryManaged { RLMDictionary *dict = managedTestDictionary(); [dict.realm beginWriteTransaction]; [dict removeAllObjects]; RLMAssertThrowsWithReasonMatching([dict setDictionary:@[@"string"]], @"Cannot set dictionary to object of class '.*Array.*'"); RLMAssertThrowsWithReason([dict setDictionary:@{@"": [[StringObject alloc] init]}], @"Value of type 'StringObject' does not match RLMDictionary value type 'IntObject'."); RLMAssertThrowsWithReason([dict setDictionary:@{@1: [[IntObject alloc] init]}], @"Invalid key '1' of type '" RLMConstantInt "' for expected type 'string'."); // Set into empty adds those entries [dict setDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@1]], @"b": [[IntObject alloc] initWithValue:@[@2]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqual(dict[@"a"].intCol, 1); XCTAssertEqual(dict[@"b"].intCol, 2); // New dictionary replaces the old one entirely [dict setDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@3]], @"c": [[IntObject alloc] initWithValue:@[@4]]}]; XCTAssertEqual(dict.count, 2U); XCTAssertEqual(dict[@"a"].intCol, 3); XCTAssertEqual(dict[@"c"].intCol, 4); // Setting to nil clears XCTAssertNoThrow([dict setDictionary:self.nonLiteralNil]); XCTAssertEqual(dict.count, 0U); // Self-setting clears [dict setDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@3]], @"c": [[IntObject alloc] initWithValue:@[@4]]}]; XCTAssertEqual(dict.count, 2U); [dict setDictionary:dict]; XCTAssertEqual(dict.count, 0U); // Type error clears [dict setDictionary:@{@"a": [[IntObject alloc] initWithValue:@[@3]], @"c": [[IntObject alloc] initWithValue:@[@4]]}]; XCTAssertEqual(dict.count, 2U); RLMAssertThrowsWithReason([dict setDictionary:@{@"": [[StringObject alloc] init]}], @"Value of type 'StringObject' does not match RLMDictionary value type 'IntObject'."); XCTAssertEqual(dict.count, 0U); [dict.realm cancelWriteTransaction]; } - (void)testInitWithNullLink { id value = @{@"stringDictionary": @{@"1": NSNull.null}, @"intDictionary": @{@"2": NSNull.null}, @"primitiveStringDictionary": @{@"3": NSNull.null}, @"embeddedDictionary": @{@"4": NSNull.null}, @"intObjDictionary": @{@"5": NSNull.null}}; DictionaryPropertyObject *obj = [[DictionaryPropertyObject alloc] initWithValue:value]; XCTAssertEqual(obj.stringDictionary[@"1"], (id)NSNull.null); XCTAssertEqual(obj.intDictionary[@"2"], (id)NSNull.null); XCTAssertEqual(obj.primitiveStringDictionary[@"3"], (id)NSNull.null); XCTAssertEqual(obj.embeddedDictionary[@"4"], (id)NSNull.null); XCTAssertEqual(obj.intObjDictionary[@"5"], (id)NSNull.null); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; obj = [DictionaryPropertyObject createInRealm:realm withValue:value]; XCTAssertEqual(obj.stringDictionary[@"1"], (id)NSNull.null); XCTAssertEqual(obj.intDictionary[@"2"], (id)NSNull.null); XCTAssertEqual(obj.primitiveStringDictionary[@"3"], (id)NSNull.null); XCTAssertEqual(obj.embeddedDictionary[@"4"], (id)NSNull.null); XCTAssertEqual(obj.intObjDictionary[@"5"], (id)NSNull.null); [realm cancelWriteTransaction]; } @end ================================================ FILE: Realm/Tests/DynamicTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.h" #import "RLMProperty_Private.h" #import "RLMObjectSchema_Private.h" #import "RLMSchema_Private.h" @interface DynamicTests : RLMTestCase @end @implementation DynamicTests #pragma mark - Tests - (void)testDynamicRealmExists { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose RLMRealm *realm = [RLMRealm realmWithURL:RLMTestRealmURL()]; [realm beginWriteTransaction]; [DynamicTestObject createInRealm:realm withValue:@[@"column1", @1]]; [DynamicTestObject createInRealm:realm withValue:@[@"column2", @2]]; [realm commitWriteTransaction]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; XCTAssertNotNil(dyrealm, @"realm should not be nil"); // verify schema RLMObjectSchema *dynSchema = dyrealm.schema[@"DynamicTestObject"]; XCTAssertNotNil(dynSchema, @"Should be able to get object schema dynamically"); XCTAssertEqual(dynSchema.properties.count, (NSUInteger)2, @"DynamicTestObject should have 2 properties"); XCTAssertEqualObjects([dynSchema.properties[0] name], @"stringCol", @"Invalid property name"); XCTAssertEqual([(RLMProperty *)dynSchema.properties[1] type], RLMPropertyTypeInt, @"Invalid type"); // verify object type RLMResults *results = [dyrealm allObjects:@"DynamicTestObject"]; XCTAssertEqual(results.count, (NSUInteger)2, @"Array should have 2 elements"); XCTAssertNotEqual(results.objectClassName, DynamicTestObject.className, @"Array class should by a dynamic object class"); } - (void)testDynamicObjectRetrieval { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [PrimaryStringObject createInRealm:realm withValue:@[@"key", @1]]; [realm commitWriteTransaction]; } RLMRealm *testRealm = [self realmWithTestPath]; RLMObject *object = [testRealm objectWithClassName:@"PrimaryStringObject" forPrimaryKey:@"key"]; XCTAssertNotNil(object, @"Should be able to retrieve object by primary key dynamically"); XCTAssert([[object valueForKey:@"stringCol"] isEqualToString:@"key"],@"stringCol should equal 'key'"); XCTAssert([[[object class] className] isEqualToString:@"PrimaryStringObject"],@"Object class name should equal 'PrimaryStringObject'"); XCTAssert([object isKindOfClass:[PrimaryStringObject class]], @"Object should be of class 'PrimaryStringObject'"); } - (void)testDynamicSchemaMatchesRegularSchema { RLMSchema *expectedSchema = nil; @autoreleasepool { expectedSchema = self.realmWithTestPath.schema; } XCTAssertNotNil(expectedSchema); RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.fileURL = RLMTestRealmURL(); config.dynamic = YES; NSError *error = nil; RLMSchema *dynamicSchema = [[RLMRealm realmWithConfiguration:config error:&error] schema]; XCTAssertNil(error); for (RLMObjectSchema *expectedObjectSchema in expectedSchema.objectSchema) { Class cls = expectedObjectSchema.objectClass; if ([cls _realmObjectName] || [cls _realmColumnNames]) { // Class overrides names, so the dynamic schema for it shoudn't match continue; } if (expectedObjectSchema.primaryKeyProperty.index != 0) { // The dynamic schema will always put the primary key first, so it // won't match if the static schema doesn't have it there continue; } RLMObjectSchema *dynamicObjectSchema = dynamicSchema[expectedObjectSchema.className]; XCTAssertEqual(dynamicObjectSchema.properties.count, expectedObjectSchema.properties.count); for (NSUInteger propertyIndex = 0; propertyIndex < expectedObjectSchema.properties.count; propertyIndex++) { RLMProperty *dynamicProperty = dynamicObjectSchema.properties[propertyIndex]; RLMProperty *expectedProperty = expectedObjectSchema.properties[propertyIndex]; XCTAssertEqualObjects(dynamicProperty, expectedProperty); } } } - (void)testDynamicSchema { RLMSchema *schema = [[RLMSchema alloc] init]; RLMProperty *prop = [[RLMProperty alloc] initWithName:@"a" type:RLMPropertyTypeInt objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO]; RLMObjectSchema *objectSchema = [[RLMObjectSchema alloc] initWithClassName:@"TrulyDynamicObject" objectClass:RLMObject.class properties:@[prop]]; schema.objectSchema = @[objectSchema]; RLMRealm *dyrealm = [self realmWithTestPathAndSchema:schema]; XCTAssertNotNil(dyrealm, @"dynamic realm shouldn't be nil"); } - (void)testDynamicProperties { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose RLMRealm *realm = [RLMRealm realmWithURL:RLMTestRealmURL()]; [realm beginWriteTransaction]; [DynamicTestObject createInRealm:realm withValue:@[@"column1", @1]]; [DynamicTestObject createInRealm:realm withValue:@[@"column2", @2]]; [realm commitWriteTransaction]; } // verify properties RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; RLMResults *results = [dyrealm allObjects:@"DynamicTestObject"]; RLMObject *o1 = results[0], *o2 = results[1]; XCTAssertEqualObjects(o1[@"intCol"], @1); XCTAssertEqualObjects(o2[@"stringCol"], @"column2"); RLMAssertThrowsWithReason(o1[@"invalid"], @"Invalid property name"); RLMAssertThrowsWithReason(o1[@"invalid"] = nil, @"Invalid property name"); } - (void)testDynamicTypes { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"string"; id obj1 = [AllTypesObject values:0 stringObject:nil]; id obj2 = [AllTypesObject values:1 stringObject:so]; @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose RLMRealm *realm = [RLMRealm realmWithURL:RLMTestRealmURL()]; [realm beginWriteTransaction]; [AllTypesObject createInRealm:realm withValue:obj1]; [AllTypesObject createInRealm:realm withValue:obj2]; [realm commitWriteTransaction]; } // verify properties RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; RLMResults *results = [dyrealm allObjects:AllTypesObject.className]; XCTAssertEqual(results.count, 2U, @"Should have 2 objects"); RLMObjectSchema *schema = dyrealm.schema[AllTypesObject.className]; for (int i = 0; i < 12; i++) { NSString *propName = [schema.properties[i] name]; XCTAssertEqualObjects(obj1[propName], results[0][propName]); XCTAssertEqualObjects(obj2[propName], results[1][propName]); } // check sub object type XCTAssertEqualObjects([schema.properties[12] objectClassName], @"StringObject", @"Sub-object type in schema should be 'StringObject'"); // check object equality XCTAssertNil(results[0][@"objectCol"], @"object should be nil"); XCTAssertEqualObjects(results[1][@"objectCol"][@"stringCol"], @"string", @"Child object should have string value 'string'"); [dyrealm beginWriteTransaction]; RLMObject *o = results[0]; for (int i = 0; i < 12; i++) { RLMProperty *prop = schema.properties[i]; id value = prop.type == RLMPropertyTypeString ? @1 : @""; RLMAssertThrowsWithReason(o[prop.name] = value, @"Invalid value '"); RLMAssertThrowsWithReason(o[prop.name] = NSNull.null, @"Invalid value '' of type 'NSNull' for"); RLMAssertThrowsWithReason(o[prop.name] = nil, @"Invalid value '(null)' of type '(null)' for"); } RLMProperty *prop = schema.properties[12]; RLMAssertThrowsWithReason(o[prop.name] = @"str", @"Invalid value 'str' of type '__NSCFConstantString' for 'StringObject?' property 'AllTypesObject.objectCol'."); XCTAssertNoThrow(o[prop.name] = nil); XCTAssertNoThrow(o[prop.name] = NSNull.null); id otherObjectType = [dyrealm createObject:IntObject.className withValue:@[@1]]; RLMAssertThrowsWithReason(o[prop.name] = otherObjectType, @"Invalid value 'IntObject"); [dyrealm cancelWriteTransaction]; } - (void)testDynamicAdd { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose [RLMRealm realmWithURL:RLMTestRealmURL()]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; [dyrealm beginWriteTransaction]; id stringObject = [dyrealm createObject:StringObject.className withValue:@[@"string"]]; [dyrealm createObject:AllTypesObject.className withValue:[AllTypesObject values:0 stringObject:stringObject]]; [dyrealm commitWriteTransaction]; XCTAssertEqual(1U, [dyrealm allObjects:StringObject.className].count); XCTAssertEqual(1U, [dyrealm allObjects:AllTypesObject.className].count); } - (void)testDynamicArray { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose [RLMRealm realmWithURL:RLMTestRealmURL()]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; [dyrealm beginWriteTransaction]; RLMObject *stringObject = [dyrealm createObject:StringObject.className withValue:@[@"string"]]; RLMObject *stringObject1 = [dyrealm createObject:StringObject.className withValue:@[@"string1"]]; [dyrealm createObject:ArrayPropertyObject.className withValue:@[@"name", @[stringObject, stringObject1], @[]]]; RLMResults *results = [dyrealm allObjects:ArrayPropertyObject.className]; XCTAssertEqual(1U, results.count); RLMObject *arrayObj = results.firstObject; RLMArray *array = arrayObj[@"array"]; XCTAssertEqual(2U, array.count); XCTAssertEqualObjects(array[0][@"stringCol"], stringObject[@"stringCol"]); [array removeObjectAtIndex:0]; [array addObject:stringObject]; XCTAssertEqual(2U, array.count); XCTAssertEqualObjects(array[0][@"stringCol"], stringObject1[@"stringCol"]); XCTAssertEqualObjects(array[1][@"stringCol"], stringObject[@"stringCol"]); arrayObj[@"array"] = NSNull.null; XCTAssertEqual(0U, array.count); [array addObject:stringObject]; XCTAssertEqual(1U, array.count); arrayObj[@"array"] = nil; XCTAssertEqual(0U, array.count); arrayObj[@"array"] = @[stringObject, stringObject1]; XCTAssertEqualObjects(array[0][@"stringCol"], stringObject[@"stringCol"]); XCTAssertEqualObjects(array[1][@"stringCol"], stringObject1[@"stringCol"]); [dyrealm commitWriteTransaction]; } - (void)testDynamicSet { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose [RLMRealm realmWithURL:RLMTestRealmURL()]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; [dyrealm beginWriteTransaction]; RLMObject *stringObject = [dyrealm createObject:StringObject.className withValue:@[@"string"]]; RLMObject *stringObject1 = [dyrealm createObject:StringObject.className withValue:@[@"string1"]]; [dyrealm createObject:SetPropertyObject.className withValue:@[@"name", @[stringObject, stringObject1], @[]]]; RLMResults *results = [dyrealm allObjects:SetPropertyObject.className]; XCTAssertEqual(1U, results.count); RLMObject *setObj = results.firstObject; RLMSet *set = setObj[@"set"]; XCTAssertEqual(2U, set.count); XCTAssertEqualObjects(set.allObjects[0][@"stringCol"], stringObject[@"stringCol"]); [set addObject:stringObject]; XCTAssertEqual(2U, set.count); XCTAssertEqualObjects(set.allObjects[0][@"stringCol"], stringObject[@"stringCol"]); XCTAssertEqualObjects(set.allObjects[1][@"stringCol"], stringObject1[@"stringCol"]); setObj[@"set"] = NSNull.null; XCTAssertEqual(0U, set.count); [set addObject:stringObject]; XCTAssertEqual(1U, set.count); setObj[@"set"] = nil; XCTAssertEqual(0U, set.count); setObj[@"set"] = @[stringObject, stringObject1]; XCTAssertEqualObjects(set.allObjects[0][@"stringCol"], stringObject[@"stringCol"]); XCTAssertEqualObjects(set.allObjects[1][@"stringCol"], stringObject1[@"stringCol"]); [dyrealm commitWriteTransaction]; } - (void)testDynamicDictionary { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose [RLMRealm realmWithURL:RLMTestRealmURL()]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; [dyrealm beginWriteTransaction]; RLMObject *stringObject = [dyrealm createObject:StringObject.className withValue:@[@"string"]]; RLMObject *stringObject1 = [dyrealm createObject:StringObject.className withValue:@[@"string1"]]; [dyrealm createObject:DictionaryPropertyObject.className withValue:@{ @"stringDictionary": @{@"0": stringObject, @"1": stringObject1}, @"intObjDictionary": @{@"0": @{@"intCol":@0}, @"1": @{@"intCol":@1}}, @"primitiveStringDictionary": @{}, @"embeddedDictionary": @{} }]; RLMResults *results = [dyrealm allObjects:DictionaryPropertyObject.className]; XCTAssertEqual(1U, results.count); RLMObject *dictionaryObj = results.firstObject; RLMDictionary *dictionary = dictionaryObj[@"stringDictionary"]; XCTAssertEqual(2U, dictionary.count); XCTAssertEqualObjects(dictionary[@"0"][@"stringCol"], stringObject[@"stringCol"]); XCTAssertEqualObjects(dictionary[@"1"][@"stringCol"], stringObject1[@"stringCol"]); dictionaryObj[@"stringDictionary"][@"0"] = nil; XCTAssertEqual(1U, dictionary.count); dictionaryObj[@"stringDictionary"] = @{}; XCTAssertEqual(0U, dictionary.count); dictionaryObj[@"stringDictionary"] = @{@"0": stringObject, @"1": stringObject1}; XCTAssertEqualObjects(dictionary.allValues[0][@"stringCol"], stringObject[@"stringCol"]); XCTAssertEqualObjects(dictionary.allValues[1][@"stringCol"], stringObject1[@"stringCol"]); [dyrealm commitWriteTransaction]; } - (void)testOptionalProperties { @autoreleasepool { // open realm in autoreleasepool to create tables and then dispose [RLMRealm realmWithURL:RLMTestRealmURL()]; } RLMRealm *dyrealm = [self realmWithTestPathAndSchema:nil]; [dyrealm beginWriteTransaction]; RLMObject *object = [dyrealm createObject:AllOptionalTypes.className withValue:@[]]; XCTAssertNil(object[@"intObj"]); XCTAssertNil(object[@"floatObj"]); XCTAssertNil(object[@"doubleObj"]); XCTAssertNil(object[@"boolObj"]); XCTAssertNil(object[@"string"]); XCTAssertNil(object[@"data"]); XCTAssertNil(object[@"date"]); XCTAssertNil(object[@"uuidCol"]); NSDate *date = [NSDate date]; NSData *data = [NSData data]; NSUUID *uuid = [NSUUID new]; object[@"intObj"] = @1; object[@"floatObj"] = @2.2f; object[@"doubleObj"] = @3.3; object[@"boolObj"] = @YES; object[@"string"] = @"str"; object[@"date"] = date; object[@"data"] = data; object[@"uuidCol"] = uuid; XCTAssertEqualObjects(object[@"intObj"], @1); XCTAssertEqualObjects(object[@"floatObj"], @2.2f); XCTAssertEqualObjects(object[@"doubleObj"], @3.3); XCTAssertEqualObjects(object[@"boolObj"], @YES); XCTAssertEqualObjects(object[@"string"], @"str"); XCTAssertEqualObjects(object[@"date"], date); XCTAssertEqualObjects(object[@"data"], data); XCTAssertEqualObjects(object[@"uuidCol"], uuid); object[@"intObj"] = NSNull.null; object[@"floatObj"] = NSNull.null; object[@"doubleObj"] = NSNull.null; object[@"boolObj"] = NSNull.null; object[@"string"] = NSNull.null; object[@"date"] = NSNull.null; object[@"data"] = NSNull.null; object[@"uuidCol"] = NSNull.null; XCTAssertNil(object[@"intObj"]); XCTAssertNil(object[@"floatObj"]); XCTAssertNil(object[@"doubleObj"]); XCTAssertNil(object[@"boolObj"]); XCTAssertNil(object[@"string"]); XCTAssertNil(object[@"data"]); XCTAssertNil(object[@"date"]); XCTAssertNil(object[@"uuidCol"]); object[@"intObj"] = nil; object[@"floatObj"] = nil; object[@"doubleObj"] = nil; object[@"boolObj"] = nil; object[@"string"] = nil; object[@"date"] = nil; object[@"data"] = nil; object[@"uuidCol"] = nil; XCTAssertNil(object[@"intObj"]); XCTAssertNil(object[@"floatObj"]); XCTAssertNil(object[@"doubleObj"]); XCTAssertNil(object[@"boolObj"]); XCTAssertNil(object[@"string"]); XCTAssertNil(object[@"data"]); XCTAssertNil(object[@"date"]); XCTAssertNil(object[@"uuidCol"]); [dyrealm commitWriteTransaction]; } - (void)testLinkingObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMObject *person = [realm createObject:PersonObject.className withValue:@[@"parent", @10, @[@[@"child", @5, @[]]]]]; XCTAssertEqual([person[@"parents"] count], 0U); RLMObject *child = [person[@"children"] firstObject]; XCTAssertEqual([child[@"parents"] count], 1U); XCTAssertTrue([person isEqualToObject:[child[@"parents"] firstObject]]); RLMAssertThrowsWithReason(person[@"parents"] = @[], @"Cannot modify read-only property 'PersonObject.parents'"); [realm commitWriteTransaction]; } - (void)testDynamicSetEmbeddedLink { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMObject *parent = [realm createObject:@"EmbeddedIntParentObject" withValue:@[@1]]; XCTAssertNil(parent[@"object"]); XCTAssertEqual(0U, [parent[@"array"] count]); XCTAssertNoThrow(parent[@"object"] = @[@1]); XCTAssertEqualObjects(parent[@"object"][@"intCol"], @1); XCTAssertNoThrow(parent[@"object"] = nil); XCTAssertNil(parent[@"object"]); XCTAssertNoThrow(parent[@"object"] = @[@1]); XCTAssertNoThrow(parent[@"object"] = NSNull.null); XCTAssertNil(parent[@"object"]); [parent[@"array"] addObject:@[@2]]; RLMAssertThrowsWithReason(parent[@"object"] = [parent[@"array"] firstObject], @"Cannot set a link to an existing managed embedded object"); [realm cancelWriteTransaction]; } @end ================================================ FILE: Realm/Tests/EncryptionTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #import "RLMRealmConfiguration_Private.h" #import "RLMRealm_Private.h" #import "RLMSchema_Private.h" #import "RLMUtil.hpp" @interface EncryptionTests : RLMTestCase @end @implementation EncryptionTests - (RLMRealmConfiguration *)configurationWithKey:(NSData *)key { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.fileURL = RLMDefaultRealmURL(); configuration.encryptionKey = key; return configuration; } - (RLMRealm *)realmWithKey:(NSData *)key { return [RLMRealm realmWithConfiguration:[self configurationWithKey:key] error:nil]; } #pragma mark - Key validation - (void)testBadEncryptionKeys { XCTAssertThrows([RLMRealm.defaultRealm writeCopyToURL:RLMTestRealmURL() encryptionKey:NSData.data error:nil]); } - (void)testValidEncryptionKeys { XCTAssertNoThrow([RLMRealm.defaultRealm writeCopyToURL:RLMTestRealmURL() encryptionKey:self.nonLiteralNil error:nil]); NSData *key = [[NSMutableData alloc] initWithLength:64]; XCTAssertNoThrow([RLMRealm.defaultRealm writeCopyToURL:RLMTestRealmURL() encryptionKey:key error:nil]); } #pragma mark - realmWithURL: - (void)testReopenWithSameKeyWorks { NSData *key = RLMGenerateKey(); @autoreleasepool { RLMRealm *realm = [self realmWithKey:key]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; } @autoreleasepool { RLMRealm *realm = [self realmWithKey:key]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } } - (void)testReopenWithNoKeyThrows { @autoreleasepool { [self realmWithKey:RLMGenerateKey()]; } RLMAssertRealmExceptionContains([self realmWithKey:nil], RLMErrorInvalidDatabase, @"Failed to open Realm file at path '%@': header has invalid mnemonic.", RLMDefaultRealmURL().path); } - (void)testReopenWithWrongKeyThrows { @autoreleasepool { [self realmWithKey:RLMGenerateKey()]; } NSData *key = RLMGenerateKey(); RLMAssertRealmExceptionContains([self realmWithKey:key], RLMErrorInvalidDatabase, @"Failed to open Realm file at path '%@': Realm file decryption failed (Decryption failed: page 0 in file of size", RLMDefaultRealmURL().path); } - (void)testOpenUnencryptedWithKeyThrows { @autoreleasepool { [self realmWithKey:nil]; } NSData *key = RLMGenerateKey(); RLMAssertRealmExceptionContains([self realmWithKey:key], RLMErrorInvalidDatabase, @"Failed to open Realm file at path '%@': Realm file decryption failed (Decryption failed: page 0 in file of size", RLMDefaultRealmURL().path); } - (void)testOpenWithNewKeyWhileAlreadyOpenThrows { [self realmWithKey:RLMGenerateKey()]; RLMAssertThrows([self realmWithKey:RLMGenerateKey()], @"already opened with different encryption key"); } #pragma mark - writeCopyToURL: - (void)testWriteCopyToPathWithNoKeyWritesDecrypted { NSData *key = RLMGenerateKey(); @autoreleasepool { RLMRealm *realm = [self realmWithKey:key]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; [realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:nil]; } @autoreleasepool { RLMRealmConfiguration *config = [self configurationWithKey:nil]; config.fileURL = RLMTestRealmURL(); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } } - (void)testWriteCopyToPathWithNewKey { NSData *key1 = RLMGenerateKey(); NSData *key2 = RLMGenerateKey(); @autoreleasepool { RLMRealm *realm = [self realmWithKey:key1]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; [realm writeCopyToURL:RLMTestRealmURL() encryptionKey:key2 error:nil]; } @autoreleasepool { RLMRealmConfiguration *config = [self configurationWithKey:key2]; config.fileURL = RLMTestRealmURL(); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } } - (void)testWriteCopyForConfigurationAndKey { NSData *key1 = RLMGenerateKey(); NSData *key2 = RLMGenerateKey(); RLMRealmConfiguration *destinationConfig = [self configurationWithKey:key2]; destinationConfig.encryptionKey = key2; destinationConfig.fileURL = RLMTestRealmURL(); @autoreleasepool { RLMRealm *realm = [self realmWithKey:key1]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; [realm writeCopyForConfiguration:destinationConfig error:nil]; } @autoreleasepool { RLMRealm *realm = [RLMRealm realmWithConfiguration:destinationConfig error:nil]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } } #pragma mark - Migrations - (void)createRealmRequiringMigrationWithKey:(NSData *)key migrationRun:(BOOL *)migrationRun { // Create an object schema which requires migration to the shared schema RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:IntObject.class]; objectSchema.properties = @[]; RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = @[objectSchema]; // Create the Realm file on disk @autoreleasepool { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = key; config.customSchema = schema; [RLMRealm realmWithConfiguration:config error:nil]; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 1; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { *migrationRun = YES; }; [RLMRealmConfiguration setDefaultConfiguration:config]; } - (void)testImplicitMigration { NSData *key = RLMGenerateKey(); BOOL migrationRan = NO; [self createRealmRequiringMigrationWithKey:key migrationRun:&migrationRan]; XCTAssertThrows([RLMRealm defaultRealm]); XCTAssertFalse(migrationRan); RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = key; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); XCTAssertTrue(migrationRan); } - (void)testExplicitMigration { NSData *key = RLMGenerateKey(); __block BOOL migrationRan = NO; [self createRealmRequiringMigrationWithKey:key migrationRun:&migrationRan]; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.schemaVersion = 1; configuration.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { migrationRan = YES; }; XCTAssertFalse([RLMRealm performMigrationForConfiguration:configuration error:nil]); XCTAssertFalse(migrationRan); configuration.encryptionKey = key; XCTAssertTrue([RLMRealm performMigrationForConfiguration:configuration error:nil]); XCTAssertTrue(migrationRan); } @end ================================================ FILE: Realm/Tests/EnumeratorTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" @interface EnumeratorTests : RLMTestCase @end @implementation EnumeratorTests - (void)testEnum { RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *emptyPeople = [EmployeeObject allObjects]; // Enum for zero rows added for (EmployeeObject *row in emptyPeople) { XCTFail(@"No objects should have been added %@", row); } NSArray *rowsArray = @[@{@"name": @"John", @"age": @20, @"hired": @YES}, @{@"name": @"Mary", @"age": @21, @"hired": @NO}, @{@"name": @"Lars", @"age": @21, @"hired": @YES}, @{@"name": @"Phil", @"age": @43, @"hired": @NO}, @{@"name": @"Anni", @"age": @54, @"hired": @YES}]; // Add objects [realm beginWriteTransaction]; for (NSArray *rowArray in rowsArray) { [EmployeeObject createInRealm:realm withValue:rowArray]; } [realm commitWriteTransaction]; // Get all objects RLMResults *people = [EmployeeObject allObjects]; // Iterate using for...in NSUInteger index = 0; for (EmployeeObject *row in people) { XCTAssertEqualObjects(row.name, rowsArray[index][@"name"], @"Name in iteration should be equal to what was set."); XCTAssertEqualObjects(@(row.age), rowsArray[index][@"age"], @"Age in iteration should be equal to what was set."); XCTAssertEqualObjects(@(row.hired), rowsArray[index][@"hired"], @"Hired in iteration should be equal to what was set."); index++; } NSPredicate *pred = [NSPredicate predicateWithFormat:@"hired = YES && age BETWEEN {20, 30}"]; NSArray *filteredArray = [rowsArray filteredArrayUsingPredicate:pred]; // Do a query, and get all matches as RLMResults RLMResults *res = [EmployeeObject objectsWithPredicate:pred]; // Iterate over the resulting RLMResults index = 0; for (EmployeeObject *row in res) { XCTAssertEqualObjects(row.name, filteredArray[index][@"name"], @"Name in iteration should be equal to what was set."); XCTAssertEqualObjects(@(row.age), filteredArray[index][@"age"], @"Age in iteration should be equal to what was set."); XCTAssertEqualObjects(@(row.hired), filteredArray[index][@"hired"], @"Hired in iteration should be equal to what was set."); index++; } } @end ================================================ FILE: Realm/Tests/InterprocessTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMMultiProcessTestCase.h" #import "RLMConstants.h" #if TARGET_OS_OSX && !TARGET_OS_MACCATALYST @interface InterprocessTest : RLMMultiProcessTestCase @end @implementation InterprocessTest - (void)setUp { [super setUp]; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IntObject.class, DoubleObject.class]; [RLMRealmConfiguration setDefaultConfiguration:config]; } - (void)testCreateInitialRealmInChild { if (self.isParent) { RLMRunChildAndWait(); RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testCreateInitialRealmInParent { RLMRealm *realm = [RLMRealm defaultRealm]; if (self.isParent) { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; RLMRunChildAndWait(); } else { XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } } - (void)testCompactOnLaunchSuccessful { if (self.isParent) { @autoreleasepool { RLMRealm *realm = RLMRealm.defaultRealm; [realm transactionWithBlock:^{ for (int i = 0; i < 1000; ++i) { [IntObject createInRealm:realm withValue:@[@(i)]]; } }]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; }]; } RLMRunChildAndWait(); // runs the event loop } else { unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) { NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue]; }; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ return YES; }; unsigned long long sizeBefore = fileSize(config.fileURL.path); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; unsigned long long sizeAfter = fileSize(config.fileURL.path); XCTAssertGreaterThan(sizeBefore, sizeAfter); XCTAssertTrue(realm.isEmpty); } } - (void)testCompactOnLaunchBeginWriteFailed { if (self.isParent) { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMRunChildAndWait(); // runs the event loop [realm cancelWriteTransaction]; } else { unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) { NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue]; }; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ return YES; }; unsigned long long sizeBefore = fileSize(config.fileURL.path); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; unsigned long long sizeAfter = fileSize(config.fileURL.path); XCTAssertEqual(sizeBefore, sizeAfter); XCTAssertTrue(realm.isEmpty); } } - (void)testCompactOnLaunchFailSilently { if (self.isParent) { RLMRealm *realm = [RLMRealm defaultRealm]; RLMRunChildAndWait(); // runs the event loop (void)[realm configuration]; // ensure the Realm stays open while the child process runs } else { unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) { NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue]; }; __block BOOL blockCalled = NO; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){ blockCalled = YES; return YES; }; unsigned long long sizeBefore = fileSize(config.fileURL.path); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; unsigned long long sizeAfter = fileSize(config.fileURL.path); XCTAssertLessThanOrEqual(sizeBefore, sizeAfter); XCTAssertTrue(realm.isEmpty); XCTAssertTrue(blockCalled); } } - (void)testOpenInParentThenAddObjectInChild { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); if (self.isParent) { RLMRunChildAndWait(); // runs the event loop XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testOpenInParentThenAddObjectInChildWithoutAutorefresh { RLMRealm *realm = [RLMRealm defaultRealm]; realm.autorefresh = NO; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); if (self.isParent) { RLMRunChildAndWait(); XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); [realm refresh]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testOpenInParentThenAddObjectInChildWithNoChanceToAutorefresh { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); if (self.isParent) { // Wait on a different thread so that this thread doesn't get the chance // to autorefresh [self dispatchAsyncAndWait:^{ RLMRunChildAndWait(); }]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); [realm refresh]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testChangeInChildTriggersNotificationInParent { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); if (self.isParent) { [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRunChildAndWait(); }]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testBackgroundProcessDoesNotTriggerSpuriousNotifications { RLMRealm *realm = [RLMRealm defaultRealm]; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) { XCTFail(@"Notification should not have been triggered"); }]; if (self.isParent) { RLMRunChildAndWait(); } else { // Just a meaningless thing that reads from the realm XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); } [token invalidate]; } // FIXME: Re-enable this test when it can be made to pass reliably. - (void)DISABLED_testShareInMemoryRealm { RLMRealm *realm = [self inMemoryRealmWithIdentifier:@"test"]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); if (self.isParent) { [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRunChildAndWait(); }]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); } else { [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } } - (void)testBidirectionalCommunication { const int stopValue = 100; RLMRealm *realm = [self inMemoryRealmWithIdentifier:@"test"]; [realm beginWriteTransaction]; IntObject *obj = [IntObject allObjectsInRealm:realm].firstObject; if (!obj) { obj = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; } else { [realm cancelWriteTransaction]; } RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { if (obj.intCol % 2 == self.isParent && obj.intCol < stopValue) { [realm transactionWithBlock:^{ obj.intCol++; }]; } }]; if (self.isParent) { dispatch_queue_t queue = dispatch_queue_create("background", 0); dispatch_async(queue, ^{ RLMRunChildAndWait(); }); while (obj.intCol < stopValue) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } dispatch_sync(queue, ^{}); } else { [realm transactionWithBlock:^{ obj.intCol++; }]; while (obj.intCol < stopValue) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } } [token invalidate]; } - (void)testManyWriters { const int stopValue = 100; const int workers = 10; const RLMRealm *realm = RLMRealm.defaultRealm; if (self.isParent) { [realm beginWriteTransaction]; IntObject *obj = [IntObject createInDefaultRealmWithValue:@[@(-workers)]]; [realm commitWriteTransaction]; dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < workers; ++i) { dispatch_async(queue, ^{ RLMRunChildAndWait(); }); } dispatch_barrier_sync(queue, ^{}); [realm refresh]; XCTAssertEqual(stopValue, obj.intCol); XCTAssertEqual(stopValue, [DoubleObject allObjects].count); XCTAssertEqual(stopValue / 2 + 1, [[DoubleObject.allObjects minOfProperty:@"doubleCol"] intValue]); return; } // Run the run loop until someone else makes a commit dispatch_block_t waitForExternalChange = ^{ RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [token invalidate]; }; IntObject *obj = [IntObject allObjects].firstObject; int nextRun = -1; // Wait for all of the workers to start up while (obj.intCol < 0) { if (nextRun == -1) { [realm beginWriteTransaction]; ++obj.intCol; [realm commitWriteTransaction]; nextRun = 0; } waitForExternalChange(); } while (true) { // Wait for someone else to run if it's not our turn yet if (obj.intCol < nextRun && nextRun < 100) { waitForExternalChange(); continue; } [realm beginWriteTransaction]; if (obj.intCol == stopValue) { [realm commitWriteTransaction]; break; } ++obj.intCol; // Do some stuff [DoubleObject createInDefaultRealmWithValue:@[@(obj.intCol)]]; [DoubleObject createInDefaultRealmWithValue:@[@(obj.intCol)]]; RLMResults *min = [DoubleObject objectsWhere:@"doubleCol = %@", [DoubleObject.allObjects minOfProperty:@"doubleCol"]]; [realm deleteObject:min.firstObject]; [realm commitWriteTransaction]; // Wait for a random number of other workers to do some work to avoid // having a strict order that processes run in and to avoid having a // single process do everything nextRun = obj.intCol + arc4random() % 10; } } - (void)testRecoverAfterCrash { if (self.isParent) { [self runChildAndWait]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [IntObject allObjects].count); } else { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; _Exit(1); } } - (void)testRecoverAfterCrashWithFileAlreadyOpen { if (self.isParent) { RLMRealm *realm = RLMRealm.defaultRealm; [self runChildAndWait]; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [IntObject allObjects].count); } else { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; _Exit(1); } } - (void)testCanOpenAndReadWhileOtherProcessHoldsWriteLock { RLMRealm *realm = RLMRealm.defaultRealm; if (self.isParent) { [realm beginWriteTransaction]; RLMRunChildAndWait(); [realm commitWriteTransaction]; } else { XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); } } @end #endif ================================================ FILE: Realm/Tests/KVOTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import #import #import #import RLM_COLLECTION_TYPE(KVOObject) RLM_COLLECTION_TYPE(KVOLinkObject1) @interface KVOObject : RLMObject @property int pk; // Primary key for isEqual: @property int ignored; @property BOOL boolCol; @property int16_t int16Col; @property int32_t int32Col; @property int64_t int64Col; @property float floatCol; @property double doubleCol; @property bool cBoolCol; @property NSString *stringCol; @property NSData *binaryCol; @property NSDate *dateCol; @property RLMObjectId *objectIdCol; @property RLMDecimal128 *decimal128Col; @property NSUUID *uuidCol; @property id anyCol; @property KVOObject *objectCol; @property RLMArray *boolArray; @property RLMArray *intArray; @property RLMArray *floatArray; @property RLMArray *doubleArray; @property RLMArray *stringArray; @property RLMArray *dataArray; @property RLMArray *dateArray; @property RLMArray *objectIdArray; @property RLMArray *decimal128Array; @property RLMArray *uuidArray; @property RLMArray *anyArray; @property RLMArray *objectArray; @property RLMSet *boolSet; @property RLMSet *intSet; @property RLMSet *floatSet; @property RLMSet *doubleSet; @property RLMSet *stringSet; @property RLMSet *dataSet; @property RLMSet *dateSet; @property RLMSet *objectIdSet; @property RLMSet *decimal128Set; @property RLMSet *uuidSet; @property RLMSet *anySet; @property RLMSet *objectSet; @property RLMDictionary *boolDictionary; @property RLMDictionary *intDictionary; @property RLMDictionary *floatDictionary; @property RLMDictionary *doubleDictionary; @property RLMDictionary *stringDictionary; @property RLMDictionary *dataDictionary; @property RLMDictionary *dateDictionary; @property RLMDictionary *objectIdDictionary; @property RLMDictionary *decimal128Dictionary; @property RLMDictionary *uuidDictionary; @property RLMDictionary *anyDictionary; @property RLMDictionary *objectDictionary; @property NSNumber *optIntCol; @property NSNumber *optFloatCol; @property NSNumber *optDoubleCol; @property NSNumber *optBoolCol; @end @implementation KVOObject + (NSString *)primaryKey { return @"pk"; } + (NSArray *)ignoredProperties { return @[@"ignored"]; } @end @interface KVOLinkObject1 : RLMObject @property int pk; // Primary key for isEqual: @property KVOObject *obj; @property RLMArray *array; @property RLMSet *set; @end @implementation KVOLinkObject1 + (NSString *)primaryKey { return @"pk"; } @end @interface KVOLinkObject2 : RLMObject @property int pk; // Primary key for isEqual: @property KVOLinkObject1 *obj; @property RLMArray *array; @property RLMSet *set; @property RLMDictionary *dictionary; @end @implementation KVOLinkObject2 + (NSString *)primaryKey { return @"pk"; } @end @interface PlainKVOObject : NSObject @property int ignored; @property BOOL boolCol; @property int16_t int16Col; @property int32_t int32Col; @property int64_t int64Col; @property float floatCol; @property double doubleCol; @property bool cBoolCol; @property NSString *stringCol; @property NSData *binaryCol; @property NSDate *dateCol; @property PlainKVOObject *objectCol; @property RLMObjectId *objectIdCol; @property RLMDecimal128 *decimal128Col; @property NSUUID *uuidCol; @property id anyCol; @property NSMutableArray *boolArray; @property NSMutableArray *intArray; @property NSMutableArray *floatArray; @property NSMutableArray *doubleArray; @property NSMutableArray *stringArray; @property NSMutableArray *dataArray; @property NSMutableArray *dateArray; @property NSMutableArray *objectArray; @property NSMutableArray *objectIdArray; @property NSMutableArray *decimal128Array; @property NSMutableArray *uuidArray; @property NSMutableArray *anyArray; @property NSMutableSet *boolSet; @property NSMutableSet *intSet; @property NSMutableSet *floatSet; @property NSMutableSet *doubleSet; @property NSMutableSet *stringSet; @property NSMutableSet *dataSet; @property NSMutableSet *dateSet; @property NSMutableSet *objectSet; @property NSMutableSet *objectIdSet; @property NSMutableSet *decimal128Set; @property NSMutableSet *uuidSet; @property NSMutableSet *anySet; @property NSMutableDictionary *boolDictionary; @property NSMutableDictionary *intDictionary; @property NSMutableDictionary *floatDictionary; @property NSMutableDictionary *doubleDictionary; @property NSMutableDictionary *stringDictionary; @property NSMutableDictionary *dataDictionary; @property NSMutableDictionary *dateDictionary; @property NSMutableDictionary *objectDictionary; @property NSMutableDictionary *objectIdDictionary; @property NSMutableDictionary *decimal128Dictionary; @property NSMutableDictionary *uuidDictionary; @property NSMutableDictionary *anyDictionary; @property NSNumber *optIntCol; @property NSNumber *optFloatCol; @property NSNumber *optDoubleCol; @property NSNumber *optBoolCol; @end @implementation PlainKVOObject @end @interface PlainLinkObject1 : NSObject @property PlainKVOObject *obj; @property NSMutableArray *array; @property NSMutableSet *set; @property NSMutableDictionary *dictionary; @end @implementation PlainLinkObject1 @end @interface PlainLinkObject2 : NSObject @property PlainLinkObject1 *obj; @property NSMutableArray *array; @property NSMutableSet *set; @property NSMutableDictionary *dictionary; @end @implementation PlainLinkObject2 @end // Tables with no links (or backlinks) preserve the order of rows on // insertion/deletion, while tables with links do not, so we need an object // class known to have no links to test the ordered case @interface ObjectWithNoLinksToOrFrom : RLMObject @property int value; @end @implementation ObjectWithNoLinksToOrFrom @end // An object which removes a KVO registration when it's deallocated, for use // as an associated object @interface KVOUnregisterHelper : NSObject @end @implementation KVOUnregisterHelper { __unsafe_unretained id _obj; __unsafe_unretained id _observer; NSString *_keyPath; } + (void)automaticallyUnregister:(id)observer object:(id)obj keyPath:(NSString *)keyPath { KVOUnregisterHelper *helper = [self new]; helper->_observer = observer; helper->_obj = obj; helper->_keyPath = keyPath; objc_setAssociatedObject(obj, (__bridge void *)helper, helper, OBJC_ASSOCIATION_RETAIN); } - (void)dealloc { [_obj removeObserver:_observer forKeyPath:_keyPath]; } @end // A KVO observer which retains the given object until it observes a change @interface ReleaseOnObservation : NSObject @property (strong) id object; @end @implementation ReleaseOnObservation - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context { [object removeObserver:self forKeyPath:keyPath context:context]; _object = nil; } @end @interface KVOTests : RLMTestCase // get an object that should be observed for the given object being mutated // used by some of the subclasses to observe a different accessor for the same row - (id)observableForObject:(id)obj; @end // subscribes to kvo notifications on the passed object on creation, records // all change notifications sent and makes them available in `notifications`, // and automatically unsubscribes on destruction class KVORecorder { id _observer; id _obj; NSString *_keyPath; RLMRealm *_mutationRealm; RLMRealm *_observationRealm; NSMutableArray *_notifications; public: // construct a new recorder for the given `keyPath` on `obj`, using `observer` // as the NSObject helper to actually add as an observer KVORecorder(id observer, id obj, NSString *keyPath, int options = NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew) : _observer(observer) , _obj([observer observableForObject:obj]) , _keyPath(keyPath) , _mutationRealm([obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[obj realm] : nil) , _observationRealm([_obj respondsToSelector:@selector(realm)] ? (RLMRealm *)[_obj realm] : nil) , _notifications([NSMutableArray new]) { [_obj addObserver:observer forKeyPath:keyPath options:options context:this]; } ~KVORecorder() { @try { [_obj removeObserver:_observer forKeyPath:_keyPath context:this]; } @catch (NSException *e) { XCTFail(@"%@", e.description); } XCTAssertEqual(0U, _notifications.count); } // record a single notification void operator()(NSString *key, id obj, NSDictionary *changeDictionary) { XCTAssertEqual(obj, _obj); XCTAssertEqualObjects(key, _keyPath); [_notifications addObject:[NSDictionary dictionaryWithDictionary:changeDictionary]]; } // ensure that the observed object is updated for any changes made to the // object being mutated if they are different void refresh() { if (_mutationRealm != _observationRealm) { [_mutationRealm commitWriteTransaction]; [_observationRealm refresh]; [_mutationRealm beginWriteTransaction]; } } NSDictionary *pop_front() { NSDictionary *value = [_notifications firstObject]; if (value) { [_notifications removeObjectAtIndex:0U]; } return value; } NSUInteger size() const { return _notifications.count; } bool empty() const { return _notifications.count == 0; } }; // Assert that `recorder` has a notification at `index` and return it if so #define AssertNotification(recorder) ([&]{ \ (recorder).refresh(); \ NSDictionary *value = recorder.pop_front(); \ XCTAssertNotNil(value, @"Did not get a notification when expected"); \ return value; \ })() // Validate that `recorder` has at least one notification, and that the first // notification is the expected one #define AssertChanged(recorder, from, to) do { \ if (NSDictionary *note = AssertNotification((recorder))) { \ XCTAssertEqualObjects(@(NSKeyValueChangeSetting), note[NSKeyValueChangeKindKey]); \ XCTAssertEqualObjects((from), note[NSKeyValueChangeOldKey]); \ XCTAssertEqualObjects((to), note[NSKeyValueChangeNewKey]); \ } \ else { \ return; \ } \ } while (false) #define AssertCollectionChanged(s) do { \ AssertNotification(r); \ XCTAssertTrue(r.empty()); \ } while (false) // Validate that `r` has a notification with the given kind and changed indexes, // remove it, and verify that there are no more notifications #define AssertIndexChange(kind, indexes) do { \ if (NSDictionary *note = AssertNotification(r)) { \ XCTAssertEqual([note[NSKeyValueChangeKindKey] intValue], static_cast(kind)); \ XCTAssertEqualObjects(note[NSKeyValueChangeIndexesKey], indexes); \ } \ XCTAssertTrue(r.empty()); \ } while (0) // Tests for plain Foundation key-value observing to verify that we correctly // match the standard semantics. Each of the subclasses of KVOTests runs the // same set of tests on RLMObjects in difference scenarios @implementation KVOTests // forward a KVO notification to the KVORecorder stored in the context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { (*static_cast(context))(keyPath, object, change); } // overridden in the multiple accessors, one realm and multiple realms cases - (id)observableForObject:(id)obj { return obj; } // overridden in the multiple realms case because `-refresh` does not send // notifications for intermediate states - (bool)collapsesNotifications { return false; } // overridden in all subclases to return the appropriate object // base class runs the tests on a plain NSObject using stock KVO to ensure that // the tests are actually covering the correct behavior, since there's a great // deal that the documentation doesn't specify - (id)createObject { PlainKVOObject *obj = [PlainKVOObject new]; obj.int16Col = 1; obj.int32Col = 2; obj.int64Col = 3; obj.binaryCol = NSData.data; obj.stringCol = @""; obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; obj.boolArray = [NSMutableArray array]; obj.intArray = [NSMutableArray array]; obj.floatArray = [NSMutableArray array]; obj.doubleArray = [NSMutableArray array]; obj.stringArray = [NSMutableArray array]; obj.dataArray = [NSMutableArray array]; obj.dateArray = [NSMutableArray array]; obj.objectIdArray = [NSMutableArray array]; obj.decimal128Array = [NSMutableArray array]; obj.objectArray = [NSMutableArray array]; obj.uuidArray = [NSMutableArray array]; obj.anyArray = [NSMutableArray array]; obj.boolSet = [NSMutableSet set]; obj.intSet = [NSMutableSet set]; obj.floatSet = [NSMutableSet set]; obj.doubleSet = [NSMutableSet set]; obj.stringSet = [NSMutableSet set]; obj.dataSet = [NSMutableSet set]; obj.dateSet = [NSMutableSet set]; obj.objectIdSet = [NSMutableSet set]; obj.decimal128Set = [NSMutableSet set]; obj.objectSet = [NSMutableSet set]; obj.uuidSet = [NSMutableSet set]; obj.anySet = [NSMutableSet set]; obj.boolDictionary = [NSMutableDictionary dictionary]; obj.intDictionary = [NSMutableDictionary dictionary]; obj.floatDictionary = [NSMutableDictionary dictionary]; obj.doubleDictionary = [NSMutableDictionary dictionary]; obj.stringDictionary = [NSMutableDictionary dictionary]; obj.dataDictionary = [NSMutableDictionary dictionary]; obj.dateDictionary = [NSMutableDictionary dictionary]; obj.objectIdDictionary = [NSMutableDictionary dictionary]; obj.decimal128Dictionary = [NSMutableDictionary dictionary]; obj.objectDictionary = [NSMutableDictionary dictionary]; obj.uuidDictionary = [NSMutableDictionary dictionary]; obj.anyDictionary = [NSMutableDictionary dictionary]; return obj; } - (id)createLinkObject { PlainLinkObject1 *obj1 = [PlainLinkObject1 new]; obj1.obj = [self createObject]; obj1.array = [NSMutableArray new]; obj1.set = [NSMutableSet new]; obj1.dictionary = [NSMutableDictionary new]; PlainLinkObject2 *obj2 = [PlainLinkObject2 new]; obj2.obj = obj1; obj2.array = [NSMutableArray new]; obj2.set = [NSMutableSet new]; obj2.dictionary = [NSMutableDictionary new]; return obj2; } // actual tests follow - (void)testRegisterForUnknownProperty { KVOObject *obj = [self createObject]; XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:0 context:nullptr]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionOld context:nullptr]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"non-existent" options:NSKeyValueObservingOptionPrior context:nullptr]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"non-existent"]); } - (void)testRemoveObserver { // iOS 14.2 beta 2 has stopped throwing exceptions when a KVO observer is removed that does not exist // FIXME: revisit this once 14.2 is out to see if this was an intended change #if REALM_PLATFORM_IOS if (@available(iOS 14.2, *)) { return; } #endif KVOObject *obj = [self createObject]; XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException); XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col" context:nullptr], NSException, NSRangeException); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:nullptr]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]); XCTAssertThrowsSpecificNamed([obj removeObserver:self forKeyPath:@"int32Col"], NSException, NSRangeException); // `context` parameter must match if it's passed, but the overload that doesn't // take one will unregister any context void *context1 = (void *)1; void *context2 = (void *)2; XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col" context:context1]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col" context:context1]); // no context version should only unregister one (unspecified) observer XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context1]); XCTAssertNoThrow([obj addObserver:self forKeyPath:@"int32Col" options:0 context:context2]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]); XCTAssertNoThrow([obj removeObserver:self forKeyPath:@"int32Col"]); XCTAssertThrows([obj removeObserver:self forKeyPath:@"int32Col"]); } - (void)testRemoveObserverInObservation { auto helper = [ReleaseOnObservation new]; __unsafe_unretained id obj; __weak id weakObj; @autoreleasepool { obj = weakObj = helper.object = [self createObject]; [obj addObserver:helper forKeyPath:@"int32Col" options:NSKeyValueObservingOptionOld context:nullptr]; } [obj setInt32Col:0]; XCTAssertNil(helper.object); XCTAssertNil(weakObj); } - (void)testSimple { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col"); obj.int32Col = 10; AssertChanged(r, @2, @10); } { KVORecorder r(self, obj, @"int32Col"); obj.int32Col = 1; AssertChanged(r, @10, @1); } } - (void)testSelfAssignmentNotifies { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col"); obj.int32Col = obj.int32Col; AssertChanged(r, @2, @2); } } - (void)testMultipleObserversAreNotified { KVOObject *obj = [self createObject]; { KVORecorder r1(self, obj, @"int32Col"); KVORecorder r2(self, obj, @"int32Col"); KVORecorder r3(self, obj, @"int32Col"); obj.int32Col = 10; AssertChanged(r1, @2, @10); AssertChanged(r2, @2, @10); AssertChanged(r3, @2, @10); } } - (void)testOnlyObserversForTheCorrectPropertyAreNotified { KVOObject *obj = [self createObject]; { KVORecorder r16(self, obj, @"int16Col"); KVORecorder r32(self, obj, @"int32Col"); KVORecorder r64(self, obj, @"int64Col"); obj.int16Col = 2; AssertChanged(r16, @1, @2); XCTAssertTrue(r16.empty()); XCTAssertTrue(r32.empty()); XCTAssertTrue(r64.empty()); obj.int32Col = 2; AssertChanged(r32, @2, @2); XCTAssertTrue(r16.empty()); XCTAssertTrue(r32.empty()); XCTAssertTrue(r64.empty()); obj.int64Col = 2; AssertChanged(r64, @3, @2); XCTAssertTrue(r16.empty()); XCTAssertTrue(r32.empty()); XCTAssertTrue(r64.empty()); } } - (void)testMultipleChangesWithSingleObserver { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"int32Col"); obj.int32Col = 1; obj.int32Col = 2; obj.int32Col = 3; obj.int32Col = 3; if (self.collapsesNotifications) { AssertChanged(r, @2, @3); } else { AssertChanged(r, @2, @1); AssertChanged(r, @1, @2); AssertChanged(r, @2, @3); AssertChanged(r, @3, @3); } } - (void)testOnlyObserversForTheCorrectObjectAreNotified { KVOObject *obj1 = [self createObject]; KVOObject *obj2 = [self createObject]; KVORecorder r1(self, obj1, @"int32Col"); KVORecorder r2(self, obj2, @"int32Col"); obj1.int32Col = 10; AssertChanged(r1, @2, @10); XCTAssertEqual(0U, r2.size()); obj2.int32Col = 5; AssertChanged(r2, @2, @5); } - (void)testOptionsInitial { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col", 0); XCTAssertEqual(0U, r.size()); } { KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionInitial); r.pop_front(); } } - (void)testOptionsOld { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col", 0); obj.int32Col = 0; if (NSDictionary *note = AssertNotification(r)) { XCTAssertNil(note[NSKeyValueChangeOldKey]); } } { KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionOld); obj.int32Col = 0; if (NSDictionary *note = AssertNotification(r)) { XCTAssertNotNil(note[NSKeyValueChangeOldKey]); } } } - (void)testOptionsNew { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col", 0); obj.int32Col = 0; if (NSDictionary *note = AssertNotification(r)) { XCTAssertNil(note[NSKeyValueChangeNewKey]); } } { KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew); obj.int32Col = 0; if (NSDictionary *note = AssertNotification(r)) { XCTAssertNotNil(note[NSKeyValueChangeNewKey]); } } } - (void)testOptionsPrior { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"int32Col", NSKeyValueObservingOptionNew|NSKeyValueObservingOptionPrior); obj.int32Col = 0; r.refresh(); XCTAssertEqual(2U, r.size()); if (NSDictionary *note = AssertNotification(r)) { XCTAssertNil(note[NSKeyValueChangeNewKey]); XCTAssertEqualObjects(@YES, note[NSKeyValueChangeNotificationIsPriorKey]); } if (NSDictionary *note = AssertNotification(r)) { XCTAssertNotNil(note[NSKeyValueChangeNewKey]); XCTAssertNil(note[NSKeyValueChangeNotificationIsPriorKey]); } } - (void)testAllPropertyTypes { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"boolCol"); obj.boolCol = YES; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"int16Col"); obj.int16Col = 0; AssertChanged(r, @1, @0); } { KVORecorder r(self, obj, @"int32Col"); obj.int32Col = 0; AssertChanged(r, @2, @0); } { KVORecorder r(self, obj, @"int64Col"); obj.int64Col = 0; AssertChanged(r, @3, @0); } { KVORecorder r(self, obj, @"floatCol"); obj.floatCol = 1.0f; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"doubleCol"); obj.doubleCol = 1.0; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"cBoolCol"); obj.cBoolCol = YES; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"stringCol"); obj.stringCol = @"abc"; AssertChanged(r, @"", @"abc"); obj.stringCol = nil; AssertChanged(r, @"abc", NSNull.null); } { KVORecorder r(self, obj, @"binaryCol"); NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; obj.binaryCol = data; AssertChanged(r, NSData.data, data); obj.binaryCol = nil; AssertChanged(r, data, NSNull.null); } { KVORecorder r(self, obj, @"dateCol"); NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1]; obj.dateCol = date; AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date); obj.dateCol = nil; AssertChanged(r, date, NSNull.null); } { KVORecorder r(self, obj, @"objectIdCol"); RLMObjectId *objectId = [RLMObjectId objectId]; obj.objectIdCol = objectId; AssertChanged(r, NSNull.null, objectId); obj.objectIdCol = nil; AssertChanged(r, objectId, NSNull.null); } { KVORecorder r(self, obj, @"decimal128Col"); RLMDecimal128 *decimal128 = [[RLMDecimal128 alloc] initWithNumber:@1]; obj.decimal128Col = decimal128; AssertChanged(r, NSNull.null, decimal128); obj.decimal128Col = nil; AssertChanged(r, decimal128, NSNull.null); } { KVORecorder r(self, obj, @"objectCol"); obj.objectCol = obj; AssertChanged(r, NSNull.null, [self observableForObject:obj]); obj.objectCol = nil; AssertChanged(r, [self observableForObject:obj], NSNull.null); } { KVORecorder r(self, obj, @"uuidCol"); NSUUID *uuid = [NSUUID UUID]; obj.uuidCol = uuid; AssertChanged(r, NSNull.null, uuid); obj.uuidCol = nil; AssertChanged(r, uuid, NSNull.null); } { KVORecorder r(self, obj, @"anyCol"); obj.anyCol = @"abc"; AssertChanged(r, NSNull.null, @"abc"); obj.anyCol = nil; AssertChanged(r, @"abc", NSNull.null); } // Array { KVORecorder r(self, obj, @"intArray"); obj.intArray = obj.intArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"boolArray"); obj.boolArray = obj.boolArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"floatArray"); obj.floatArray = obj.floatArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"doubleArray"); obj.doubleArray = obj.doubleArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"stringArray"); obj.stringArray = obj.stringArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dataArray"); obj.dataArray = obj.dataArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dateArray"); obj.dateArray = obj.dateArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectIdArray"); obj.objectIdArray = obj.objectIdArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"decimal128Array"); obj.decimal128Array = obj.decimal128Array; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectArray"); obj.objectArray = obj.objectArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"uuidArray"); obj.uuidArray = obj.uuidArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"anyArray"); obj.anyArray = obj.anyArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } // Set { KVORecorder r(self, obj, @"intSet"); obj.intSet = obj.intSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"boolSet"); obj.boolSet = obj.boolSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"floatSet"); obj.floatSet = obj.floatSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"doubleSet"); obj.doubleSet = obj.doubleSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"stringSet"); obj.stringSet = obj.stringSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dataSet"); obj.dataSet = obj.dataSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dateSet"); obj.dateSet = obj.dateSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectIdSet"); obj.objectIdSet = obj.objectIdSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"decimal128Set"); obj.decimal128Set = obj.decimal128Set; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectSet"); obj.objectSet = obj.objectSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"uuidSet"); obj.uuidSet = obj.uuidSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"anySet"); obj.anySet = obj.anySet; r.refresh(); r.pop_front(); // asserts that there's something to pop } // Dictionary { KVORecorder r(self, obj, @"intDictionary"); obj.intDictionary = obj.intDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"boolDictionary"); obj.boolDictionary = obj.boolDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"floatDictionary"); obj.floatDictionary = obj.floatDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"doubleDictionary"); obj.doubleDictionary = obj.doubleDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"stringDictionary"); obj.stringDictionary = obj.stringDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dataDictionary"); obj.dataDictionary = obj.dataDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"dateDictionary"); obj.dateDictionary = obj.dateDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectIdDictionary"); obj.objectIdDictionary = obj.objectIdDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"decimal128Dictionary"); obj.decimal128Dictionary = obj.decimal128Dictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectDictionary"); obj.objectDictionary = obj.objectDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"uuidDictionary"); obj.uuidDictionary = obj.uuidDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"anyDictionary"); obj.anyDictionary = obj.anyDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"optIntCol"); obj.optIntCol = @1; AssertChanged(r, NSNull.null, @1); obj.optIntCol = nil; AssertChanged(r, @1, NSNull.null); } { KVORecorder r(self, obj, @"optFloatCol"); obj.optFloatCol = @1.1f; AssertChanged(r, NSNull.null, @1.1f); obj.optFloatCol = nil; AssertChanged(r, @1.1f, NSNull.null); } { KVORecorder r(self, obj, @"optDoubleCol"); obj.optDoubleCol = @1.1; AssertChanged(r, NSNull.null, @1.1); obj.optDoubleCol = nil; AssertChanged(r, @1.1, NSNull.null); } { KVORecorder r(self, obj, @"optBoolCol"); obj.optBoolCol = @YES; AssertChanged(r, NSNull.null, @YES); obj.optBoolCol = nil; AssertChanged(r, @YES, NSNull.null); } } - (void)testAllPropertyTypesKVC { KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"boolCol"); [obj setValue:@YES forKey:@"boolCol"]; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"int16Col"); [obj setValue:@0 forKey:@"int16Col"]; AssertChanged(r, @1, @0); } { KVORecorder r(self, obj, @"int32Col"); [obj setValue:@0 forKey:@"int32Col"]; AssertChanged(r, @2, @0); } { KVORecorder r(self, obj, @"int64Col"); [obj setValue:@0 forKey:@"int64Col"]; AssertChanged(r, @3, @0); } { KVORecorder r(self, obj, @"floatCol"); [obj setValue:@1.0f forKey:@"floatCol"]; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"doubleCol"); [obj setValue:@1.0 forKey:@"doubleCol"]; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"cBoolCol"); [obj setValue:@YES forKey:@"cBoolCol"]; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"stringCol"); [obj setValue:@"abc" forKey:@"stringCol"]; AssertChanged(r, @"", @"abc"); [obj setValue:nil forKey:@"stringCol"]; AssertChanged(r, @"abc", NSNull.null); } { KVORecorder r(self, obj, @"binaryCol"); NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; [obj setValue:data forKey:@"binaryCol"]; AssertChanged(r, NSData.data, data); [obj setValue:nil forKey:@"binaryCol"]; AssertChanged(r, data, NSNull.null); } { KVORecorder r(self, obj, @"dateCol"); NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1]; [obj setValue:date forKey:@"dateCol"]; AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date); [obj setValue:nil forKey:@"dateCol"]; AssertChanged(r, date, NSNull.null); } { KVORecorder r(self, obj, @"objectIdCol"); RLMObjectId *objectId = [RLMObjectId objectId]; [obj setValue:objectId forKey:@"objectIdCol"]; AssertChanged(r, NSNull.null, objectId); [obj setValue:nil forKey:@"objectIdCol"]; AssertChanged(r, objectId, NSNull.null); } { KVORecorder r(self, obj, @"decimal128Col"); RLMDecimal128 *decimal128 = [[RLMDecimal128 alloc] initWithNumber:@1]; [obj setValue:decimal128 forKey:@"decimal128Col"]; AssertChanged(r, NSNull.null, decimal128); [obj setValue:nil forKey:@"decimal128Col"]; AssertChanged(r, decimal128, NSNull.null); } { KVORecorder r(self, obj, @"objectCol"); [obj setValue:obj forKey:@"objectCol"]; AssertChanged(r, NSNull.null, [self observableForObject:obj]); [obj setValue:nil forKey:@"objectCol"]; AssertChanged(r, [self observableForObject:obj], NSNull.null); } { KVORecorder r(self, obj, @"uuidCol"); NSUUID *uuid = [NSUUID UUID]; [obj setValue:uuid forKey:@"uuidCol"]; AssertChanged(r, NSNull.null, uuid); [obj setValue:nil forKey:@"uuidCol"]; AssertChanged(r, uuid, NSNull.null); } { KVORecorder r(self, obj, @"objectArray"); [obj setValue:obj.objectArray forKey:@"objectArray"]; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"optIntCol"); [obj setValue:@1 forKey:@"optIntCol"]; AssertChanged(r, NSNull.null, @1); [obj setValue:nil forKey:@"optIntCol"]; AssertChanged(r, @1, NSNull.null); } { KVORecorder r(self, obj, @"optFloatCol"); [obj setValue:@1.1f forKey:@"optFloatCol"]; AssertChanged(r, NSNull.null, @1.1f); [obj setValue:nil forKey:@"optFloatCol"]; AssertChanged(r, @1.1f, NSNull.null); } { KVORecorder r(self, obj, @"optDoubleCol"); [obj setValue:@1.1 forKey:@"optDoubleCol"]; AssertChanged(r, NSNull.null, @1.1); [obj setValue:nil forKey:@"optDoubleCol"]; AssertChanged(r, @1.1, NSNull.null); } { KVORecorder r(self, obj, @"optBoolCol"); [obj setValue:@YES forKey:@"optBoolCol"]; AssertChanged(r, NSNull.null, @YES); [obj setValue:nil forKey:@"optBoolCol"]; AssertChanged(r, @YES, NSNull.null); } } - (void)testAllPropertyTypesDynamic { KVOObject *obj = [self createObject]; if (![obj respondsToSelector:@selector(setObject:forKeyedSubscript:)]) { return; } { KVORecorder r(self, obj, @"boolCol"); obj[@"boolCol"] = @YES; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"int16Col"); obj[@"int16Col"] = @0; AssertChanged(r, @1, @0); } { KVORecorder r(self, obj, @"int32Col"); obj[@"int32Col"] = @0; AssertChanged(r, @2, @0); } { KVORecorder r(self, obj, @"int64Col"); obj[@"int64Col"] = @0; AssertChanged(r, @3, @0); } { KVORecorder r(self, obj, @"floatCol"); obj[@"floatCol"] = @1.0f; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"doubleCol"); obj[@"doubleCol"] = @1.0; AssertChanged(r, @0, @1); } { KVORecorder r(self, obj, @"cBoolCol"); obj[@"cBoolCol"] = @YES; AssertChanged(r, @NO, @YES); } { KVORecorder r(self, obj, @"stringCol"); obj[@"stringCol"] = @"abc"; AssertChanged(r, @"", @"abc"); obj[@"stringCol"] = nil; AssertChanged(r, @"abc", NSNull.null); } { KVORecorder r(self, obj, @"binaryCol"); NSData *data = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; obj[@"binaryCol"] = data; AssertChanged(r, NSData.data, data); obj[@"binaryCol"] = nil; AssertChanged(r, data, NSNull.null); } { KVORecorder r(self, obj, @"dateCol"); NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:1]; obj[@"dateCol"] = date; AssertChanged(r, [NSDate dateWithTimeIntervalSinceReferenceDate:0], date); obj[@"dateCol"] = nil; AssertChanged(r, date, NSNull.null); } { KVORecorder r(self, obj, @"objectCol"); obj[@"objectCol"] = obj; AssertChanged(r, NSNull.null, [self observableForObject:obj]); obj[@"objectCol"] = nil; AssertChanged(r, [self observableForObject:obj], NSNull.null); } { KVORecorder r(self, obj, @"uuidCol"); NSUUID *uuid = [NSUUID UUID]; obj[@"uuidCol"] = uuid; AssertChanged(r, NSNull.null, uuid); obj[@"uuidCol"] = nil; AssertChanged(r, uuid, NSNull.null); } { KVORecorder r(self, obj, @"anyCol"); obj[@"anyCol"] = @"abc"; AssertChanged(r, NSNull.null, @"abc"); obj[@"anyCol"] = nil; AssertChanged(r, @"abc", NSNull.null); } { KVORecorder r(self, obj, @"objectArray"); obj[@"objectArray"] = obj.objectArray; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectSet"); obj[@"objectSet"] = obj.objectSet; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"objectDictionary"); obj[@"objectDictionary"] = obj.objectDictionary; r.refresh(); r.pop_front(); // asserts that there's something to pop } { KVORecorder r(self, obj, @"optIntCol"); obj[@"optIntCol"] = @1; AssertChanged(r, NSNull.null, @1); obj[@"optIntCol"] = nil; AssertChanged(r, @1, NSNull.null); } { KVORecorder r(self, obj, @"optFloatCol"); obj[@"optFloatCol"] = @1.1f; AssertChanged(r, NSNull.null, @1.1f); obj[@"optFloatCol"] = nil; AssertChanged(r, @1.1f, NSNull.null); } { KVORecorder r(self, obj, @"optDoubleCol"); obj[@"optDoubleCol"] = @1.1; AssertChanged(r, NSNull.null, @1.1); obj[@"optDoubleCol"] = nil; AssertChanged(r, @1.1, NSNull.null); } { KVORecorder r(self, obj, @"optBoolCol"); obj[@"optBoolCol"] = @YES; AssertChanged(r, NSNull.null, @YES); obj[@"optBoolCol"] = nil; AssertChanged(r, @YES, NSNull.null); } } - (void)testArrayDiffs { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"array"); id mutator = [obj mutableArrayValueForKey:@"array"]; [mutator addObject:obj.obj]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); [mutator addObject:obj.obj]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]); [mutator removeObjectAtIndex:0]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]); [mutator replaceObjectAtIndex:0 withObject:obj.obj]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]); NSMutableIndexSet *indexes = [NSMutableIndexSet new]; [indexes addIndex:0]; [indexes addIndex:2]; [mutator insertObjects:@[obj.obj, obj.obj] atIndexes:indexes]; AssertIndexChange(NSKeyValueChangeInsertion, indexes); [mutator removeObjectsAtIndexes:indexes]; AssertIndexChange(NSKeyValueChangeRemoval, indexes); if (![obj.array isKindOfClass:[NSArray class]]) { // We deliberately diverge from NSMutableArray for `removeAllObjects` and // `addObjectsFromArray:`, because generating a separate notification for // each object added or removed is needlessly pessimal. [mutator addObjectsFromArray:@[obj.obj, obj.obj]]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]); // NSArray sends multiple notifications for exchange, which we can't do // on refresh [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); // NSArray doesn't have move [mutator moveObjectAtIndex:1 toIndex:0]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); [mutator removeLastObject]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]); [mutator removeAllObjects]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); } } - (void)testPrimitiveArrayDiffs { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"intArray"); id mutator = [obj mutableArrayValueForKey:@"intArray"]; [mutator addObject:@1]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); [mutator addObject:@2]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:1]); [mutator removeObjectAtIndex:0]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]); [mutator replaceObjectAtIndex:0 withObject:@3]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]); NSMutableIndexSet *indexes = [NSMutableIndexSet new]; [indexes addIndex:0]; [indexes addIndex:2]; [mutator insertObjects:@[@4, @5] atIndexes:indexes]; AssertIndexChange(NSKeyValueChangeInsertion, indexes); [mutator removeObjectsAtIndexes:indexes]; AssertIndexChange(NSKeyValueChangeRemoval, indexes); if (![obj.intArray isKindOfClass:[NSArray class]]) { // We deliberately diverge from NSMutableArray for `removeAllObjects` and // `addObjectsFromArray:`, because generating a separate notification for // each object added or removed is needlessly pessimal. [mutator addObjectsFromArray:@[@6, @7]]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]); // NSArray sends multiple notifications for exchange, which we can't do // on refresh [mutator exchangeObjectAtIndex:0 withObjectAtIndex:1]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); // NSArray doesn't have move [mutator moveObjectAtIndex:1 toIndex:0]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); [mutator removeLastObject]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:2]); [mutator removeAllObjects]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]); } } - (void)testSetKVO { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; KVORecorder r(self, obj, @"set"); id mutator = [obj mutableSetValueForKey:@"set"]; id mutator2 = [obj2 mutableSetValueForKey:@"set"]; id set2 = [obj2 valueForKey:@"set"]; [mutator addObject:obj.obj]; AssertCollectionChanged(); [mutator removeObject:obj.obj]; AssertCollectionChanged(); [mutator addObject:obj.obj]; AssertCollectionChanged(); [mutator2 addObject:obj2.obj]; [mutator setSet:set2]; AssertCollectionChanged(); [mutator intersectSet:set2]; AssertCollectionChanged(); [mutator minusSet:set2]; AssertCollectionChanged(); [mutator unionSet:set2]; AssertCollectionChanged(); } - (void)testPrimitiveSetKVO { KVOObject *obj = [self createObject]; KVOObject *obj2 = [self createObject]; KVORecorder r(self, obj, @"intSet"); id mutator = [obj mutableSetValueForKey:@"intSet"]; id mutator2 = [obj2 mutableSetValueForKey:@"intSet"]; [mutator addObject:@1]; AssertCollectionChanged(); [mutator removeObject:@1]; AssertCollectionChanged(); [mutator addObject:@1]; AssertCollectionChanged(); [mutator2 addObject:@2]; [mutator setSet:mutator2]; AssertCollectionChanged(); [mutator intersectSet:mutator2]; AssertCollectionChanged(); [mutator minusSet:mutator2]; AssertCollectionChanged(); [mutator unionSet:mutator2]; AssertCollectionChanged(); } - (void)testDictionaryKVO { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; KVORecorder r(self, obj, @"dictionary"); id mutator = [obj valueForKey:@"dictionary"]; id mutator2 = [obj2 valueForKey:@"dictionary"]; // Foundation doesn't expose any notifying proxy classes for NSMutableDictionary // and it doesnt really make sense to create a wrapper purely for testing. // So if `mutator` is NSMutableDictionary return. if ([mutator isKindOfClass:[NSMutableDictionary class]]) { return; } [mutator setObject:obj.obj forKey:@"key"]; AssertCollectionChanged(); [mutator removeObjectForKey:@"key"]; AssertCollectionChanged(); [mutator setObject:obj.obj forKey:@"key2"]; AssertCollectionChanged(); [mutator2 setObject:obj2.obj forKey:@"key"]; [mutator removeAllObjects]; AssertCollectionChanged(); } - (void)testPrimitiveDictionaryKVO { KVOObject *obj = [self createObject]; KVOObject *obj2 = [self createObject]; KVORecorder r(self, obj, @"intDictionary"); id mutator = [obj valueForKey:@"intDictionary"]; id mutator2 = [obj2 valueForKey:@"intDictionary"]; if ([mutator isKindOfClass:[NSMutableDictionary class]]) { return; } [mutator setObject:@1 forKey:@"key"]; AssertCollectionChanged(); [mutator removeObjectForKey:@"key"]; AssertCollectionChanged(); [mutator setObject:@2 forKey:@"key2"]; AssertCollectionChanged(); [mutator2 setObject:@3 forKey:@"key"]; [mutator removeAllObjects]; AssertCollectionChanged(); } - (void)testIgnoredProperty { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"ignored"); obj.ignored = 10; AssertChanged(r, @0, @10); } - (void)testChangeEndOfKeyPath { KVOLinkObject2 *obj = [self createLinkObject]; std::unique_ptr r; @autoreleasepool { r = std::make_unique(self, obj, @"obj.obj.boolCol"); } obj.obj.obj.boolCol = YES; AssertChanged(*r, @NO, @YES); } - (void)testChangeMiddleOfKeyPath { KVOLinkObject2 *obj = [self createLinkObject]; KVOObject *oldObj = obj.obj.obj; KVOObject *newObj = [self createObject]; newObj.boolCol = YES; KVORecorder r(self, obj, @"obj.obj.boolCol"); obj.obj.obj = newObj; AssertChanged(r, @NO, @YES); newObj.boolCol = NO; AssertChanged(r, @YES, @NO); oldObj.boolCol = YES; } - (void)testNullifyMiddleOfKeyPath { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"obj.obj.boolCol"); obj.obj = nil; AssertChanged(r, @NO, NSNull.null); } - (void)testChangeMiddleOfKeyPathToNonNil { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *obj2 = obj.obj; obj.obj = nil; obj2.obj.boolCol = YES; KVORecorder r(self, obj, @"obj.obj.boolCol"); obj.obj = obj2; AssertChanged(r, NSNull.null, @YES); } - (void)testArrayKVC { KVOObject *obj = [self createObject]; [obj.objectArray addObject:obj]; KVORecorder r(self, obj, @"boolCol"); [obj.objectArray setValue:@YES forKey:@"boolCol"]; AssertChanged(r, @NO, @YES); } - (void)testSetKVC { KVOObject *obj = [self createObject]; [obj.objectSet addObject:obj]; KVORecorder r(self, obj, @"boolCol"); [obj.objectSet setValue:@YES forKey:@"boolCol"]; AssertChanged(r, @NO, @YES); } - (void)testSharedSchemaOnObservedObjectGivesOriginalSchema { KVOObject *obj = [self createObject]; if (![obj isKindOfClass:RLMObjectBase.class]) { return; } RLMObjectSchema *original = [obj.class sharedSchema]; KVORecorder r(self, obj, @"boolCol"); XCTAssertEqual(original, [obj.class sharedSchema]); // note: intentionally not EqualObjects } // RLMArray doesn't support @count at all - (void)testObserveArrayCount { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"objectArray.@count"); id mutator = [obj mutableArrayValueForKey:@"objectArray"]; [mutator addObject:obj]; AssertChanged(r, @0, @1); } - (void)testMixedCollectionKVC { KVOObject *obj = [self createObject]; NSDictionary *d = @{ @"key2" : @"hello2", @"key3" : @YES, @"key4" : @123, @"key5" : @456.789 }; NSArray *a = @[ @"hello2", @YES, @123, @456.789 ]; { KVORecorder r(self, obj, @"anyCol"); obj.anyCol = d; AssertCollectionChanged(); } { KVORecorder r(self, obj, @"anyCol"); [obj setValue:d forKey:@"anyCol"]; AssertCollectionChanged(); [obj setValue:nil forKey:@"anyCol"]; AssertCollectionChanged(); } { KVORecorder r(self, obj, @"anyCol"); obj.anyCol = a; AssertCollectionChanged(); } { KVORecorder r(self, obj, @"anyCol"); [obj setValue:a forKey:@"anyCol"]; AssertCollectionChanged(); [obj setValue:nil forKey:@"anyCol"]; AssertCollectionChanged(); } if (![obj respondsToSelector:@selector(setObject:forKeyedSubscript:)]) { return; } { KVORecorder r(self, obj, @"anyCol"); obj[@"anyCol"] = d; AssertCollectionChanged(); obj[@"anyCol"] = nil; AssertCollectionChanged(); } { KVORecorder r(self, obj, @"anyCol"); obj[@"anyCol"] = a; AssertCollectionChanged(); obj[@"anyCol"] = nil; AssertCollectionChanged(); } } @end // Run tests on an unmanaged RLMObject instance @interface KVOUnmanagedObjectTests : KVOTests @end @implementation KVOUnmanagedObjectTests - (id)createObject { static int pk = 0; KVOObject *obj = [KVOObject new]; obj.pk = pk++; obj.int16Col = 1; obj.int32Col = 2; obj.int64Col = 3; obj.binaryCol = NSData.data; obj.stringCol = @""; obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; return obj; } - (id)createLinkObject { static int pk = 0; KVOLinkObject1 *obj1 = [KVOLinkObject1 new]; obj1.pk = pk++; obj1.obj = [self createObject]; KVOLinkObject2 *obj2 = [KVOLinkObject2 new]; obj2.pk = pk++; obj2.obj = obj1; return obj2; } - (void)testAddToRealmAfterAddingObservers { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; KVOObject *obj = [self createObject]; { KVORecorder r(self, obj, @"int32Col"); XCTAssertThrows([realm addObject:obj]); } XCTAssertNoThrow([realm addObject:obj]); [realm cancelWriteTransaction]; } - (void)testObserveInvalidArrayProperty { KVOObject *obj = [self createObject]; XCTAssertThrows([obj.objectArray addObserver:self forKeyPath:@"self" options:0 context:0]); XCTAssertNoThrow([obj.objectArray addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]); XCTAssertNoThrow([obj.objectArray removeObserver:self forKeyPath:RLMInvalidatedKey context:0]); } - (void)testUnregisteringViaAnAssociatedObject { @autoreleasepool { __attribute__((objc_precise_lifetime)) KVOObject *obj = [self createObject]; [obj addObserver:self forKeyPath:@"boolCol" options:0 context:0]; [KVOUnregisterHelper automaticallyUnregister:self object:obj keyPath:@"boolCol"]; } // Throws if the unregistration doesn't succeed } @end // Run tests on a managed object, modifying the actual object instance being // observed @interface KVOManagedObjectTests : KVOTests @property (nonatomic, strong) RLMRealm *realm; @end @implementation KVOManagedObjectTests - (void)setUp { [super setUp]; _realm = [self getRealm]; [_realm beginWriteTransaction]; } - (void)tearDown { [self.realm cancelWriteTransaction]; self.realm = nil; [super tearDown]; } - (RLMRealm *)getRealm { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.inMemoryIdentifier = @"test"; configuration.schemaMode = realm::SchemaMode::AdditiveDiscovered; return [RLMRealm realmWithConfiguration:configuration error:nil]; } - (id)createObject { static std::atomic pk{0}; return [KVOObject createInRealm:_realm withValue:@[@(++pk), @NO, @1, @2, @3, @0, @0, @NO, @"", NSData.data, [NSDate dateWithTimeIntervalSinceReferenceDate:0]]]; } - (id)createLinkObject { static std::atomic pk{0}; return [KVOLinkObject2 createInRealm:_realm withValue:@[@(++pk), @[@(++pk), [self createObject], @[]], @[]]]; } - (EmbeddedIntParentObject *)createEmbeddedObject { return [EmbeddedIntParentObject createInRealm:_realm withValue:@[@1, @[@2], @[@[@3]]]]; } - (void)testDeleteObservedObject { KVOObject *obj = [self createObject]; KVORecorder r1(self, obj, @"boolCol"); KVORecorder r2(self, obj, RLMInvalidatedKey); [self.realm deleteObject:obj]; AssertChanged(r2, @NO, @YES); // should not crash } - (void)testDeleteMultipleObservedObjects { KVOObject *obj1 = [self createObject]; KVOObject *obj2 = [self createObject]; KVOObject *obj3 = [self createObject]; KVORecorder r1(self, obj1, RLMInvalidatedKey); KVORecorder r2(self, obj2, RLMInvalidatedKey); KVORecorder r3(self, obj3, RLMInvalidatedKey); [self.realm deleteObject:obj2]; AssertChanged(r2, @NO, @YES); XCTAssertTrue(r1.empty()); XCTAssertTrue(r3.empty()); [self.realm deleteObject:obj3]; AssertChanged(r3, @NO, @YES); XCTAssertTrue(r1.empty()); XCTAssertTrue(r2.empty()); [self.realm deleteObject:obj1]; AssertChanged(r1, @NO, @YES); XCTAssertTrue(r2.empty()); XCTAssertTrue(r3.empty()); } - (void)testDeleteMiddleOfKeyPath { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"obj.obj.boolCol"); [self.realm deleteObject:obj.obj]; AssertChanged(r, @NO, NSNull.null); } - (void)testDeleteParentOfObservedRLMArray { KVOObject *obj = [self createObject]; KVORecorder r1(self, obj, @"objectArray"); KVORecorder r2(self, obj, @"objectArray.invalidated"); KVORecorder r3(self, obj.objectArray, RLMInvalidatedKey); [self.realm deleteObject:obj]; AssertChanged(r2, @NO, @YES); AssertChanged(r3, @NO, @YES); } - (void)testDeleteAllObjects { KVOObject *obj = [self createObject]; KVORecorder r1(self, obj, @"boolCol"); KVORecorder r2(self, obj, RLMInvalidatedKey); [self.realm deleteAllObjects]; AssertChanged(r2, @NO, @YES); // should not crash } - (void)testClearTable { KVOObject *obj = [self createObject]; KVORecorder r1(self, obj, @"boolCol"); KVORecorder r2(self, obj, RLMInvalidatedKey); [self.realm deleteObjects:[KVOObject allObjectsInRealm:self.realm]]; AssertChanged(r2, @NO, @YES); // should not crash } - (void)testClearQuery { KVOObject *obj = [self createObject]; KVORecorder r1(self, obj, @"boolCol"); KVORecorder r2(self, obj, RLMInvalidatedKey); [self.realm deleteObjects:[KVOObject objectsInRealm:self.realm where:@"TRUEPREDICATE"]]; AssertChanged(r2, @NO, @YES); // should not crash } - (void)testClearLinkView { KVOObject *obj = [self createObject]; KVOObject *obj2 = [self createObject]; [obj2.objectArray addObject:obj]; KVORecorder r1(self, obj, @"boolCol"); KVORecorder r2(self, obj, RLMInvalidatedKey); [self.realm deleteObjects:obj2.objectArray]; AssertChanged(r2, @NO, @YES); // should not crash } - (void)testCreateObserverAfterDealloc { @autoreleasepool { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"boolCol"); obj.boolCol = YES; AssertChanged(r, @NO, @YES); } @autoreleasepool { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, @"boolCol"); obj.boolCol = YES; AssertChanged(r, @NO, @YES); } } - (void)testDirectlyDeleteLinkedToObject { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; KVORecorder r(self, obj, @"obj"); KVORecorder r2(self, obj, @"obj.invalidated"); [self.realm deleteObject:linked]; if (NSDictionary *note = AssertNotification(r)) { XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]); XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null); } AssertChanged(r2, @NO, NSNull.null); } - (void)testDeleteLinkedToObjectViaTableClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"obj"); KVORecorder r2(self, obj, @"obj.invalidated"); [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]]; if (NSDictionary *note = AssertNotification(r)) { XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]); XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null); } AssertChanged(r2, @NO, NSNull.null); } - (void)testDeleteLinkedToObjectViaQueryClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"obj"); KVORecorder r2(self, obj, @"obj.invalidated"); [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]]; if (NSDictionary *note = AssertNotification(r)) { XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]); XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null); } AssertChanged(r2, @NO, NSNull.null); } - (void)testDeleteObjectInArray { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; [obj.array addObject:linked]; KVORecorder r(self, obj, @"array"); [self.realm deleteObject:linked]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]); } - (void)testDeleteObjectsInArrayViaTableClear { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; [obj.array addObject:obj.obj]; [obj.array addObject:obj.obj]; [obj.array addObject:obj2.obj]; KVORecorder r(self, obj, @"array"); [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]]; AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}])); } - (void)testDeleteObjectsInArrayViaTableViewClear { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; [obj.array addObject:obj2.obj]; [obj.array addObject:obj.obj]; [obj.array addObject:obj.obj]; KVORecorder r(self, obj, @"array"); RLMResults *results = [KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]; [results lastObject]; [self.realm deleteObjects:results]; AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}])); } - (void)testDeleteObjectsInArrayViaQueryClear { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; [obj.array addObject:obj.obj]; [obj.array addObject:obj.obj]; [obj.array addObject:obj2.obj]; KVORecorder r(self, obj, @"array"); [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]]; AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}])); } - (void)testObserveInvalidArrayProperty { KVOObject *obj = [self createObject]; RLMArray *array = obj.objectArray; XCTAssertThrows([array addObserver:self forKeyPath:@"self" options:0 context:0]); XCTAssertNoThrow([array addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]); XCTAssertNoThrow([array removeObserver:self forKeyPath:RLMInvalidatedKey context:0]); } - (void)testInvalidOperationOnObservedArray { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; [obj.array addObject:linked]; KVORecorder r(self, obj, @"array"); XCTAssertThrows([obj.array exchangeObjectAtIndex:2 withObjectAtIndex:3]); // A KVO notification is still sent to observers on the same thread since we // can't cancel willChange, but the data is not very meaningful so don't check it if (!self.collapsesNotifications) { AssertNotification(r); } } - (void)testDeleteObjectInSet { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; [obj.set addObject:linked]; KVORecorder r(self, obj, @"set"); [self.realm deleteObject:linked]; AssertCollectionChanged(); } - (void)testDeleteObjectsInSetViaTableClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"set"); [obj.set addObject:obj.obj]; AssertCollectionChanged(); [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]]; AssertCollectionChanged(); } - (void)testDeleteObjectsInSetViaTableViewClear { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; KVORecorder r(self, obj, @"set"); [obj.set addObject:obj2.obj]; AssertCollectionChanged(); RLMResults *results = [KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]; [results lastObject]; [self.realm deleteObjects:results]; AssertCollectionChanged(); } - (void)testDeleteObjectsInSetViaQueryClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"set"); [obj.set addObject:obj.obj]; AssertCollectionChanged(); [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]]; AssertCollectionChanged(); } - (void)testObserveInvalidSetProperty { KVOObject *obj = [self createObject]; RLMSet *set = obj.objectSet; XCTAssertThrows([set addObserver:self forKeyPath:@"self" options:0 context:0]); XCTAssertNoThrow([set addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]); XCTAssertNoThrow([set removeObserver:self forKeyPath:RLMInvalidatedKey context:0]); } - (void)testInvalidOperationOnObservedSet { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; [obj.set addObject:linked]; KVORecorder r(self, obj, @"set"); XCTAssertThrows([obj.set addObject:(id)@1]); // A KVO notification is still sent to observers on the same thread since we // can't cancel willChange, but the data is not very meaningful so don't check it if (!self.collapsesNotifications) { AssertNotification(r); } } - (void)testDeleteObjectInDictionary { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject1 *linked = obj.obj; [obj.dictionary setObject:linked forKey:@"key"]; KVORecorder r(self, obj, @"dictionary"); [self.realm deleteObject:linked]; AssertCollectionChanged(); } - (void)testDeleteObjectsInDictionaryViaTableClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"dictionary"); [obj.dictionary setObject:obj.obj forKey:@"key"]; AssertCollectionChanged(); [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]]; AssertCollectionChanged(); } - (void)testDeleteObjectsInDictionaryViaTableViewClear { KVOLinkObject2 *obj = [self createLinkObject]; KVOLinkObject2 *obj2 = [self createLinkObject]; KVORecorder r(self, obj, @"dictionary"); [obj.dictionary setObject:obj2.obj forKey:@"key"]; AssertCollectionChanged(); RLMResults *results = [KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]; [results lastObject]; [self.realm deleteObjects:results]; AssertCollectionChanged(); } - (void)testDeleteObjectsInDictionaryViaQueryClear { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"dictionary"); [obj.dictionary setObject:obj.obj forKey:@"key"]; AssertCollectionChanged(); [self.realm deleteObjects:[KVOLinkObject1 objectsInRealm:self.realm where:@"TRUEPREDICATE"]]; AssertCollectionChanged(); } - (void)testObserveInvalidDictionaryProperty { KVOObject *obj = [self createObject]; RLMDictionary *dictionary = obj.objectDictionary; XCTAssertThrows([dictionary addObserver:self forKeyPath:@"self" options:0 context:0]); XCTAssertNoThrow([dictionary addObserver:self forKeyPath:RLMInvalidatedKey options:0 context:0]); XCTAssertNoThrow([dictionary removeObserver:self forKeyPath:RLMInvalidatedKey context:0]); } - (void)testInvalidOperationOnObservedDictionary { KVOLinkObject2 *obj = [self createLinkObject]; KVORecorder r(self, obj, @"dictionary"); XCTAssertThrows([obj.dictionary setObject:(id)@1 forKey:@"key"]); // A KVO notification is still sent to observers on the same thread since we // can't cancel willChange, but the data is not very meaningful so don't check it if (!self.collapsesNotifications) { AssertNotification(r); } } - (void)testDeleteParentOfObservedEmbeddedObject { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj, @"object"); KVORecorder r2(self, obj, @"object.invalidated"); KVORecorder r3(self, obj.object, RLMInvalidatedKey); [self.realm deleteObject:obj]; AssertChanged(r2, @NO, @YES); AssertChanged(r3, @NO, @YES); } - (void)testSetLinkToEmbeddedObjectToNil { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj, @"object.invalidated"); KVORecorder r2(self, obj.object, RLMInvalidatedKey); obj.object = nil; AssertChanged(r1, @NO, NSNull.null); AssertChanged(r2, @NO, @YES); } - (void)testSetLinkToEmbeddedObjectToNewObject { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj, @"object.invalidated"); KVORecorder r2(self, obj.object, RLMInvalidatedKey); obj.object = [[EmbeddedIntObject alloc] init]; AssertChanged(r1, @NO, @NO); AssertChanged(r2, @NO, @YES); } - (void)testDynamicSetLinkToEmbeddedObjectToNil { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj, @"object.invalidated"); KVORecorder r2(self, obj.object, RLMInvalidatedKey); obj[@"object"] = nil; AssertChanged(r1, @NO, NSNull.null); AssertChanged(r2, @NO, @YES); } - (void)testDynamicSetLinkToEmbeddedObjectToNewObject { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj, @"object.invalidated"); KVORecorder r2(self, obj.object, RLMInvalidatedKey); obj[@"object"] = [[EmbeddedIntObject alloc] init]; AssertChanged(r1, @NO, @NO); AssertChanged(r2, @NO, @YES); } - (void)testRemoveEmbeddedObjectFromArray { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r(self, obj.array[0], RLMInvalidatedKey); [obj.array removeAllObjects]; AssertChanged(r, @NO, @YES); } - (void)testOverwriteEmbeddedObjectInArray { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r(self, obj, @"array"); KVORecorder r2(self, obj.array[0], RLMInvalidatedKey); obj.array[0] = [[EmbeddedIntObject alloc] init]; AssertIndexChange(NSKeyValueChangeReplacement, ([NSIndexSet indexSetWithIndexesInRange:{0, 1}])); AssertChanged(r2, @NO, @YES); } - (void)testOverwriteEmbeddedObjectViaAddParent { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj.object, RLMInvalidatedKey); KVORecorder r2(self, obj.array[0], RLMInvalidatedKey); [self.realm addOrUpdateObject:[[EmbeddedIntParentObject alloc] initWithValue:@[@1]]]; AssertChanged(r1, @NO, @YES); AssertChanged(r2, @NO, @YES); } - (void)testOverwriteEmbeddedObjectViaCreateParent { EmbeddedIntParentObject *obj = [self createEmbeddedObject]; KVORecorder r1(self, obj.object, RLMInvalidatedKey); KVORecorder r2(self, obj.array[0], RLMInvalidatedKey); [EmbeddedIntParentObject createOrUpdateInRealm:self.realm withValue:@[@1, NSNull.null, NSNull.null]]; AssertChanged(r1, @NO, @YES); AssertChanged(r2, @NO, @YES); } @end // Mutate a different accessor backed by the same row as the accessor being observed @interface KVOMultipleAccessorsTests : KVOManagedObjectTests @end @implementation KVOMultipleAccessorsTests - (id)observableForObject:(id)value { if (RLMObjectBase *obj = RLMDynamicCast(value)) { RLMObject *copy = RLMCreateManagedAccessor(RLMObjectBaseObjectSchema(obj).accessorClass, obj->_info); copy->_row = obj->_row; return copy; } else if (RLMArray *array = RLMDynamicCast(value)) { return array; } else { XCTFail(@"unsupported type"); return nil; } } - (void)testIgnoredProperty { // ignored properties do not notify other accessors for the same row } - (void)testAddOrUpdate { KVOObject *obj = [self createObject]; KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj]; KVORecorder r(self, obj, @"boolCol"); obj2.boolCol = true; XCTAssertTrue(r.empty()); [self.realm addOrUpdateObject:obj2]; AssertChanged(r, @NO, @YES); } - (void)testCreateOrUpdate { KVOObject *obj = [self createObject]; KVOObject *obj2 = [[KVOObject alloc] initWithValue:obj]; KVORecorder r(self, obj, @"boolCol"); obj2.boolCol = true; XCTAssertTrue(r.empty()); [KVOObject createOrUpdateInRealm:self.realm withValue:obj2]; AssertChanged(r, @NO, @YES); } // The following tests aren't really multiple-accessor-specific, but they're // conceptually similar and don't make sense in the multiple realm instances case - (void)testCancelWriteTransactionWhileObservingNewObject { KVOObject *obj = [self createObject]; KVORecorder r(self, obj, RLMInvalidatedKey); KVORecorder r2(self, obj, @"boolCol"); [self.realm cancelWriteTransaction]; AssertChanged(r, @NO, @YES); r2.pop_front(); [self.realm beginWriteTransaction]; } - (void)testCancelWriteTransactionWhileObservingChangedProperty { KVOObject *obj = [self createObject]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; obj.boolCol = YES; KVORecorder r(self, obj, @"boolCol"); [self.realm cancelWriteTransaction]; AssertChanged(r, @YES, @NO); [self.realm beginWriteTransaction]; } - (void)testCancelWriteTransactionWhileObservingLinkToExistingObject { KVOObject *obj = [self createObject]; KVOObject *obj2 = [self createObject]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; obj.objectCol = obj2; KVORecorder r(self, obj, @"objectCol"); [self.realm cancelWriteTransaction]; AssertChanged(r, obj2, NSNull.null); [self.realm beginWriteTransaction]; } - (void)testCancelWriteTransactionWhileObservingLinkToNewObject { KVOObject *obj = [self createObject]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; obj.objectCol = [self createObject]; KVORecorder r(self, obj, @"objectCol"); [self.realm cancelWriteTransaction]; if (NSDictionary *note = AssertNotification(r)) { XCTAssertTrue([note[NSKeyValueChangeOldKey] isKindOfClass:[RLMObjectBase class]]); XCTAssertEqualObjects(note[NSKeyValueChangeNewKey], NSNull.null); } [self.realm beginWriteTransaction]; } - (void)testCancelWriteTransactionWhileObservingNewObjectLinkingToNewObject { KVOObject *obj = [self createObject]; obj.objectCol = [self createObject]; KVORecorder r(self, obj, RLMInvalidatedKey); KVORecorder r2(self, obj, @"objectCol"); KVORecorder r3(self, obj, @"objectCol.boolCol"); [self.realm cancelWriteTransaction]; AssertChanged(r, @NO, @YES); [self.realm beginWriteTransaction]; } - (void)testCancelWriteWithArrayChanges { KVOObject *obj = [self createObject]; [obj.objectArray addObject:obj]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; { [obj.objectArray addObject:obj]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]); } { [obj.objectArray removeLastObject]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); } { obj.objectArray[0] = obj; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]); } // test batching with multiple items changed [obj.objectArray addObject:obj]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; { [obj.objectArray removeAllObjects]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}])); } { [obj.objectArray removeLastObject]; [obj.objectArray removeLastObject]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}])); } { [obj.objectArray insertObject:obj atIndex:1]; [obj.objectArray insertObject:obj atIndex:0]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; NSMutableIndexSet *expected = [NSMutableIndexSet new]; [expected addIndex:0]; [expected addIndex:2]; // shifted due to inserting at 0 after 1 AssertIndexChange(NSKeyValueChangeRemoval, expected); } { [obj.objectArray insertObject:obj atIndex:0]; [obj.objectArray removeLastObject]; KVORecorder r(self, obj, @"objectArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertChanged(r, obj.objectArray, obj.objectArray); } } - (void)testCancelWriteWithPrimitiveArrayChanges { KVOObject *obj = [self createObject]; [obj.intArray addObject:@1]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; { [obj.intArray addObject:@2]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:1]); } { [obj.intArray removeLastObject]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); } { obj.intArray[0] = @3; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeReplacement, [NSIndexSet indexSetWithIndex:0]); } // test batching with multiple items changed [obj.intArray addObject:@4]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; { [obj.intArray removeAllObjects]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}])); } { [obj.intArray removeLastObject]; [obj.intArray removeLastObject]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, ([NSIndexSet indexSetWithIndexesInRange:{0, 2}])); } { [obj.intArray insertObject:@5 atIndex:1]; [obj.intArray insertObject:@6 atIndex:0]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; NSMutableIndexSet *expected = [NSMutableIndexSet new]; [expected addIndex:0]; [expected addIndex:2]; // shifted due to inserting at 0 after 1 AssertIndexChange(NSKeyValueChangeRemoval, expected); } { [obj.intArray insertObject:@7 atIndex:0]; [obj.intArray removeLastObject]; KVORecorder r(self, obj, @"intArray"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertChanged(r, obj.intArray, obj.intArray); } } - (void)testCancelWriteWithLinkedObjectedRemoved { KVOLinkObject2 *obj = [self createLinkObject]; [obj.array addObject:obj.obj]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; { [self.realm deleteObject:obj.obj]; KVORecorder r(self, obj, @"array"); KVORecorder r2(self, obj, @"obj"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject); } { [self.realm deleteObjects:[KVOLinkObject1 allObjectsInRealm:self.realm]]; KVORecorder r(self, obj, @"array"); KVORecorder r2(self, obj, @"obj"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject); } { [self.realm deleteObjects:obj.array]; KVORecorder r(self, obj, @"array"); KVORecorder r2(self, obj, @"obj"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertIndexChange(NSKeyValueChangeInsertion, [NSIndexSet indexSetWithIndex:0]); AssertChanged(r2, NSNull.null, [KVOLinkObject1 allObjectsInRealm:self.realm].firstObject); } } - (void)testInvalidateRealm { KVOObject *obj = [self createObject]; [self.realm commitWriteTransaction]; KVORecorder r1(self, obj, RLMInvalidatedKey); KVORecorder r2(self, obj, @"objectArray.invalidated"); [self.realm invalidate]; [self.realm beginWriteTransaction]; AssertChanged(r1, @NO, @YES); AssertChanged(r2, @NO, @YES); } - (void)testRenamedProperties { auto obj = [RenamedProperties1 createInRealm:self.realm withValue:@[@1, @"a"]]; [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; KVORecorder r(self, obj, @"propA"); obj.propA = 2; AssertChanged(r, @1, @2); obj[@"propA"] = @3; AssertChanged(r, @2, @3); [obj setValue:@4 forKey:@"propA"]; AssertChanged(r, @3, @4); // Only rollback will notify objects of different types with the same table, // not direct modification. Probably not worth fixing this. RenamedProperties2 *obj2 = [RenamedProperties2 allObjectsInRealm:self.realm].firstObject; KVORecorder r2(self, obj2, @"propC"); [self.realm cancelWriteTransaction]; [self.realm beginWriteTransaction]; AssertChanged(r, @4, @1); AssertChanged(r2, @4, @1); } @end // Observing an object from a different RLMRealm instance backed by the same // row as the managed object being mutated @interface KVOMultipleRealmsTests : KVOManagedObjectTests @property RLMRealm *secondaryRealm; @end @implementation KVOMultipleRealmsTests - (void)setUp { [super setUp]; RLMRealmConfiguration *config = self.realm.configuration; config.cache = false; self.secondaryRealm = [RLMRealm realmWithConfiguration:config error:nil]; } - (void)tearDown { self.secondaryRealm = nil; [super tearDown]; } - (id)observableForObject:(id)value { [self.realm commitWriteTransaction]; [self.realm beginWriteTransaction]; [self.secondaryRealm refresh]; if (RLMObjectBase *obj = RLMDynamicCast(value)) { RLMObjectSchema *objectSchema = RLMObjectBaseObjectSchema(obj); RLMObject *copy = RLMCreateManagedAccessor(objectSchema.accessorClass, &self.secondaryRealm->_info[objectSchema.className]); copy->_row = (*copy->_info->table()).get_object(obj->_row.get_key()); return copy; } else if (RLMArray *array = RLMDynamicCast(value)) { return array; } else { XCTFail(@"unsupported type"); return nil; } } - (bool)collapsesNotifications { return true; } - (void)testIgnoredProperty { // ignored properties do not notify other accessors for the same row } - (void)testBatchArrayChanges { KVOObject *obj = [self createObject]; [obj.objectArray addObject:obj]; [obj.objectArray addObject:obj]; [obj.objectArray addObject:obj]; { KVORecorder r(self, obj, @"objectArray"); [obj.objectArray insertObject:obj atIndex:1]; [obj.objectArray insertObject:obj atIndex:0]; NSMutableIndexSet *expected = [NSMutableIndexSet new]; [expected addIndex:0]; [expected addIndex:2]; // shifted due to inserting at 0 after 1 AssertIndexChange(NSKeyValueChangeInsertion, expected); } { KVORecorder r(self, obj, @"objectArray"); [obj.objectArray removeObjectAtIndex:3]; [obj.objectArray removeObjectAtIndex:3]; AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{3, 2}])); } { KVORecorder r(self, obj, @"objectArray"); [obj.objectArray removeObjectAtIndex:0]; [obj.objectArray removeAllObjects]; AssertIndexChange(NSKeyValueChangeRemoval, ([NSIndexSet indexSetWithIndexesInRange:{0, 3}])); } [obj.objectArray addObject:obj]; { KVORecorder r(self, obj, @"objectArray"); [obj.objectArray addObject:obj]; [obj.objectArray removeAllObjects]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]); } [obj.objectArray addObject:obj]; { KVORecorder r(self, obj, @"objectArray"); obj.objectArray[0] = obj; [obj.objectArray removeAllObjects]; AssertIndexChange(NSKeyValueChangeRemoval, [NSIndexSet indexSetWithIndex:0]); } } - (void)testOrderedErase { NSMutableArray *objects = [NSMutableArray arrayWithCapacity:10]; for (int i = 0; i < 10; ++i) @autoreleasepool { [objects addObject:[ObjectWithNoLinksToOrFrom createInRealm:self.realm withValue:@[@(i)]]]; } // deleteObject: always uses move_last_over(), but TableView::clear() uses // erase() if there's no links auto deleteObject = ^(int value) { [self.realm deleteObjects:[ObjectWithNoLinksToOrFrom objectsInRealm:self.realm where:@"value = %d", value]]; }; { // delete object before observed, then observed KVORecorder r(self, objects[2], @"invalidated"); deleteObject(1); deleteObject(2); AssertChanged(r, @NO, @YES); } { // delete object after observed, then observed KVORecorder r(self, objects[3], @"invalidated"); deleteObject(4); deleteObject(3); AssertChanged(r, @NO, @YES); } { // delete observed, then object before observed KVORecorder r(self, objects[6], @"invalidated"); deleteObject(6); deleteObject(5); AssertChanged(r, @NO, @YES); } { // delete observed, then object after observed KVORecorder r(self, objects[7], @"invalidated"); deleteObject(7); deleteObject(8); AssertChanged(r, @NO, @YES); } } @end // Test with the table column order not matching the order of the properties @interface KVOManagedObjectWithReorderedPropertiesTests : KVOManagedObjectTests @end @implementation KVOManagedObjectWithReorderedPropertiesTests - (RLMRealm *)getRealm { // Initialize the file with the properties in reverse order, then re-open // with it in the normal order while the reversed one is still open (as // otherwise it'll recreate the file due to being in-memory) RLMSchema *schema = [RLMSchema new]; schema.objectSchema = @[[self reverseProperties:KVOObject.sharedSchema], [self reverseProperties:KVOLinkObject1.sharedSchema], [self reverseProperties:KVOLinkObject2.sharedSchema]]; RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.cache = false; configuration.inMemoryIdentifier = @"test"; configuration.customSchema = schema; RLMRealm *reversedRealm = [RLMRealm realmWithConfiguration:configuration error:nil]; configuration.customSchema = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertNotEqualObjects(realm.schema, reversedRealm.schema); return realm; } - (RLMObjectSchema *)reverseProperties:(RLMObjectSchema *)source { RLMObjectSchema *objectSchema = [source copy]; objectSchema.properties = objectSchema.properties.reverseObjectEnumerator.allObjects; return objectSchema; } @end ================================================ FILE: Realm/Tests/LinkTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMRealm_Dynamic.h" RLM_COLLECTION_TYPE(CircularArrayObject) @interface CircularArrayObject : RLMObject @property RLM_GENERIC_ARRAY(CircularArrayObject) *array; @end @implementation CircularArrayObject @end RLM_COLLECTION_TYPE(CircularSetObject) @interface CircularSetObject : RLMObject @property RLM_GENERIC_SET(CircularSetObject) *set; @end @implementation CircularSetObject @end @interface LinkTests : RLMTestCase @end @implementation LinkTests - (void)makeDogWithName:(NSString *)name owner:(NSString *)ownerName { RLMRealm *realm = [self realmWithTestPath]; OwnerObject *owner = [[OwnerObject alloc] init]; owner.name = ownerName; owner.dog = [[DogObject alloc] init]; owner.dog.dogName = name; owner.dog.age = 0; [realm beginWriteTransaction]; [realm addObject:owner]; [realm commitWriteTransaction]; } - (void)testBasicLink { [self makeDogWithName:@"Harvie" owner:@"Tim"]; RLMRealm *realm = [self realmWithTestPath]; RLMResults *owners = [OwnerObject objectsInRealm:realm withPredicate:nil]; RLMResults *dogs = [DogObject objectsInRealm:realm withPredicate:nil]; XCTAssertEqual(owners.count, 1U); XCTAssertEqual(dogs.count, 1U); XCTAssertEqualObjects([owners[0] name], @"Tim", @"Tim is named Tim"); XCTAssertEqualObjects([dogs[0] dogName], @"Harvie", @"Harvie is named Harvie"); OwnerObject *tim = owners[0]; XCTAssertEqualObjects(tim.dog.dogName, @"Harvie", @"Tim's dog should be Harvie"); } -(void)testBasicLinkWithNil { RLMRealm *realm = [self realmWithTestPath]; OwnerObject *owner = [[OwnerObject alloc] init]; owner.name = @"Tim"; owner.dog = nil; [realm beginWriteTransaction]; [realm addObject:owner]; [realm commitWriteTransaction]; RLMResults *owners = [OwnerObject objectsInRealm:realm withPredicate:nil]; RLMResults *dogs = [DogObject objectsInRealm:realm withPredicate:nil]; XCTAssertEqual(owners.count, 1U); XCTAssertEqual(dogs.count, 0U); XCTAssertEqualObjects([owners[0] name], @"Tim", @"Tim is named Tim"); OwnerObject *tim = owners[0]; XCTAssertEqualObjects(tim.dog, nil, @"Tim does not have a dog"); } - (void)testMultipleOwnerLink { [self makeDogWithName:@"Harvie" owner:@"Tim"]; RLMRealm *realm = [self realmWithTestPath]; XCTAssertEqual([OwnerObject allObjectsInRealm:realm].count, 1U); XCTAssertEqual([DogObject allObjectsInRealm:realm].count, 1U); [realm beginWriteTransaction]; OwnerObject *fiel = [OwnerObject createInRealm:realm withValue:@[@"Fiel", [NSNull null]]]; fiel.dog = [DogObject allObjectsInRealm:realm].firstObject; [realm commitWriteTransaction]; XCTAssertEqual([OwnerObject objectsInRealm:realm withPredicate:nil].count, 2U); XCTAssertEqual([DogObject objectsInRealm:realm withPredicate:nil].count, 1U); } - (void)testLinkRemoval { [self makeDogWithName:@"Harvie" owner:@"Tim"]; RLMRealm *realm = [self realmWithTestPath]; XCTAssertEqual([OwnerObject objectsInRealm:realm withPredicate:nil].count, 1U); XCTAssertEqual([DogObject objectsInRealm:realm withPredicate:nil].count, 1U); DogObject *dog = [DogObject allObjectsInRealm:realm].firstObject; OwnerObject *owner = [OwnerObject allObjectsInRealm:realm].firstObject; [realm beginWriteTransaction]; [realm deleteObject:dog]; [realm commitWriteTransaction]; XCTAssertNil(owner.dog, @"Dog should be nullified when deleted"); XCTAssertThrows(dog.dogName, @"Dog object should be invalid after being deleted from the realm"); // refresh owner and check owner = [OwnerObject allObjectsInRealm:realm].firstObject; XCTAssertNotNil(owner, @"Should have 1 owner"); XCTAssertNil(owner.dog, @"Dog should be nullified when deleted"); XCTAssertEqual([DogObject objectsInRealm:realm withPredicate:nil].count, 0U); } - (void)testInvalidLinks { RLMRealm *realm = [self realmWithTestPath]; LinkToAllTypesObject *linkObject = [[LinkToAllTypesObject alloc] init]; linkObject.allTypesCol = [[AllTypesObject alloc] init]; [realm beginWriteTransaction]; XCTAssertThrows([realm addObject:linkObject], @"dateCol not set on linked object"); StringObject *to = [StringObject createInRealm:realm withValue:@[@"testObject"]]; NSArray *args = @[@"Tim", to]; XCTAssertThrows([OwnerObject createInRealm:realm withValue:args], @"Inserting wrong object type should throw"); [realm commitWriteTransaction]; } - (void)testLinkTooManyRelationships { RLMRealm *realm = [self realmWithTestPath]; OwnerObject *owner = [[OwnerObject alloc] init]; owner.name = @"Tim"; owner.dog = [[DogObject alloc] init]; owner.dog.dogName = @"Harvie"; [realm beginWriteTransaction]; [realm addObject:owner]; [realm commitWriteTransaction]; XCTAssertThrows([OwnerObject objectsInRealm:realm where:@"dog.dogName.first = 'Fifo'"], @"3 levels of relationship"); } - (void)testBidirectionalRelationship { RLMRealm *realm = [RLMRealm defaultRealm]; CircleObject *obj0 = [[CircleObject alloc] initWithValue:@[@"a", NSNull.null]]; CircleObject *obj1 = [[CircleObject alloc] initWithValue:@[@"b", obj0]]; obj0.next = obj1; [realm beginWriteTransaction]; [realm addObject:obj0]; [realm addObject:obj1]; [realm commitWriteTransaction]; RLMResults *results = [CircleObject allObjects]; XCTAssertEqualObjects(@"a", [results[0] data]); XCTAssertEqualObjects(@"b", [results[1] data]); } - (void)testAddingCircularReferenceDoesNotLeakSourceObjects { CircleObject __weak *weakObj0, __weak *weakObj1; @autoreleasepool { CircleObject *obj0 = [[CircleObject alloc] initWithValue:@[@"a", NSNull.null]]; CircleObject *obj1 = [[CircleObject alloc] initWithValue:@[@"b", obj0]]; obj0.next = obj1; weakObj0 = obj0; weakObj1 = obj1; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj0]; obj0.next = nil; obj1.next = nil; [realm commitWriteTransaction]; } XCTAssertNil(weakObj0); XCTAssertNil(weakObj1); } - (void)testAddingCircularReferenceInArrayDoesNotLeakSourceObjects { CircularArrayObject __weak *weakObj0, __weak *weakObj1; @autoreleasepool { CircularArrayObject *obj0 = [[CircularArrayObject alloc] initWithValue:@[@[]]]; CircularArrayObject *obj1 = [[CircularArrayObject alloc] initWithValue:@[@[obj0]]]; [obj0.array addObject:obj1]; weakObj0 = obj0; weakObj1 = obj1; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj0]; [realm commitWriteTransaction]; } XCTAssertNil(weakObj0); XCTAssertNil(weakObj1); } - (void)testAddingCircularReferenceInSetDoesNotLeakSourceObjects { CircularSetObject __weak *weakObj0, __weak *weakObj1; @autoreleasepool { CircularSetObject *obj0 = [[CircularSetObject alloc] initWithValue:@[@[]]]; CircularSetObject *obj1 = [[CircularSetObject alloc] initWithValue:@[@[obj0]]]; [obj0.set addObject:obj1]; weakObj0 = obj0; weakObj1 = obj1; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj0]; [realm commitWriteTransaction]; } XCTAssertNil(weakObj0); XCTAssertNil(weakObj1); } - (void)testCircularLinks { RLMRealm *realm = [self realmWithTestPath]; CircleObject *obj = [[CircleObject alloc] init]; obj.data = @"a"; obj.next = obj; [realm beginWriteTransaction]; [realm addObject:obj]; obj.next.data = @"b"; [realm commitWriteTransaction]; XCTAssertEqual(1U, [CircleObject allObjectsInRealm:realm].count); CircleObject *obj1 = [CircleObject allObjectsInRealm:realm].firstObject; XCTAssertEqualObjects(obj1.data, @"b", @"data should be 'b'"); XCTAssertEqualObjects(obj1.data, obj.next.data, @"objects should be equal"); } @end ================================================ FILE: Realm/Tests/LinkingObjectsTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMTestCase.h" @interface LinkingObjectsTests : RLMTestCase @end @implementation LinkingObjectsTests - (void)testBasics { NSArray *(^asArray)(id) = ^(id arrayLike) { return [arrayLike valueForKeyPath:@"self"]; }; RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]]; RLMLinkingObjects *hannahsParents = hannah.parents; XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark ])); [realm commitWriteTransaction]; XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark ])); [realm beginWriteTransaction]; PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark, diane ])); [realm beginWriteTransaction]; [realm deleteObject:hannah]; [realm commitWriteTransaction]; XCTAssertEqualObjects(asArray(hannahsParents), (@[ ])); } - (void)testLinkingObjectsOnUnmanagedObject { PersonObject *don = [[PersonObject alloc] initWithValue:@[ @"Don", @60, @[] ]]; XCTAssertEqual(0u, don.parents.count); XCTAssertNil(don.parents.firstObject); XCTAssertNil(don.parents.lastObject); for (__unused id parent in don.parents) { XCTFail(@"Got an item in empty linking objects"); } XCTAssertEqual(0u, [don.parents sortedResultsUsingKeyPath:@"age" ascending:YES].count); XCTAssertEqual(0u, [don.parents objectsWhere:@"TRUEPREDICATE"].count); XCTAssertNil([don.parents minOfProperty:@"age"]); XCTAssertNil([don.parents maxOfProperty:@"age"]); XCTAssertEqualObjects(@0, [don.parents sumOfProperty:@"age"]); XCTAssertNil([don.parents averageOfProperty:@"age"]); XCTAssertEqualObjects(@[], [don.parents valueForKey:@"age"]); XCTAssertEqualObjects(@0, [don.parents valueForKeyPath:@"@count"]); XCTAssertNil([don.parents valueForKeyPath:@"@min.age"]); XCTAssertNil([don.parents valueForKeyPath:@"@max.age"]); XCTAssertEqualObjects(@0, [don.parents valueForKeyPath:@"@sum.age"]); XCTAssertNil([don.parents valueForKeyPath:@"@avg.age"]); PersonObject *mark = [[PersonObject alloc] initWithValue:@[ @"Mark", @30, @[] ]]; XCTAssertEqual(NSNotFound, [don.parents indexOfObject:mark]); XCTAssertEqual(NSNotFound, [don.parents indexOfObjectWhere:@"TRUEPREDICATE"]); RLMAssertThrowsWithReason(([don.parents addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { }]), @"Change notifications are only supported on managed collections."); } - (void)testLinkingObjectsOnFrozenObject { NSArray *(^asArray)(id) = ^(id arrayLike) { return [arrayLike valueForKeyPath:@"self"]; }; RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[@"Hannah", @0]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[@"Mark", @30, @[hannah]]]; [realm commitWriteTransaction]; PersonObject *frozenHannah = hannah.freeze; PersonObject *frozenMark = mark.freeze; XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark])); [realm beginWriteTransaction]; PersonObject *diane = [PersonObject createInRealm:realm withValue:@[@"Diane", @29, @[hannah]]]; [realm commitWriteTransaction]; PersonObject *frozenHannah2 = hannah.freeze; PersonObject *frozenMark2 = mark.freeze; PersonObject *frozenDiane = diane.freeze; XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark])); XCTAssertEqualObjects(asArray(frozenHannah2.parents), (@[frozenMark2, frozenDiane])); [realm beginWriteTransaction]; [realm deleteObject:hannah]; [realm commitWriteTransaction]; XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark])); XCTAssertEqualObjects(asArray(frozenHannah2.parents), (@[frozenMark2, frozenDiane])); } - (void)testFilteredLinkingObjects { NSArray *(^asArray)(id) = ^(id arrayLike) { return [arrayLike valueForKeyPath:@"self"]; }; RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]]; PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]]; RLMLinkingObjects *hannahsParents = hannah.parents; // Three separate queries so that accessing a property on one doesn't invalidate testing of other properties. RLMResults *resultsA = [hannahsParents objectsWhere:@"age > 25"]; RLMResults *resultsB = [hannahsParents objectsWhere:@"age > 25"]; RLMResults *resultsC = [hannahsParents objectsWhere:@"age > 25"]; [mark.children removeAllObjects]; [realm commitWriteTransaction]; XCTAssertEqual(resultsA.count, 1u); XCTAssertEqual(NSNotFound, [resultsB indexOfObjectWhere:@"name = 'Mark'"]); XCTAssertEqualObjects(asArray(resultsC), (@[ diane ])); } - (void)testNotificationSentInitially { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) { XCTAssertEqualObjects([linkingObjects valueForKeyPath:@"self"], (@[ mark ])); XCTAssertNil(change); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testNotificationSentAfterCommit { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(linkingObjects); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ [self.realmWithTestPath transactionWithBlock:^{ }]; }]; }]; [token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) { XCTAssertNotNil(linkingObjects); XCTAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) { XCTAssertNotNil(linkingObjects); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:mark]; [realm commitWriteTransaction]; [token invalidate]; } - (void)testRenamedProperties { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; auto obj1 = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]]; auto obj2 = [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]]; auto link = [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, obj2, @[obj1, obj1]]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(obj1.linking1.objectClassName, @"LinkToRenamedProperties1"); XCTAssertEqualObjects(obj1.linking2.objectClassName, @"LinkToRenamedProperties2"); XCTAssertTrue([obj1.linking1[0] isEqualToObject:link]); XCTAssertTrue([obj2.linking2[0] isEqualToObject:link]); } @end ================================================ FILE: Realm/Tests/MigrationTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMMigration.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObjectStore.h" #import "RLMObject_Private.h" #import "RLMProperty_Private.h" #import "RLMRealmConfiguration_Private.h" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMUtil.hpp" #import "RLMRealmUtil.hpp" #import #import #import #import #import using namespace realm; static void RLMAssertRealmSchemaMatchesTable(id self, RLMRealm *realm) { for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) { auto& info = realm->_info[objectSchema.className]; TableRef table = ObjectStore::table_for_object_type(realm.group, objectSchema.objectStoreName); for (RLMProperty *property in objectSchema.properties) { auto column = info.tableColumn(property); XCTAssertEqual(column, table->get_column_key(RLMStringDataWithNSString(property.columnName))); if (property.isPrimary) XCTAssertTrue(property.indexed); XCTAssertEqual(property.indexed, table->has_search_index(column)); } } static_cast(self); } @interface MigrationTestObject : RLMObject @property int intCol; @property NSString *stringCol; @end RLM_COLLECTION_TYPE(MigrationTestObject); @implementation MigrationTestObject @end @interface MigrationPrimaryKeyObject : RLMObject @property int intCol; @end @implementation MigrationPrimaryKeyObject + (NSString *)primaryKey { return @"intCol"; } @end @interface MigrationStringPrimaryKeyObject : RLMObject @property NSString * stringCol; @end @implementation MigrationStringPrimaryKeyObject + (NSString *)primaryKey { return @"stringCol"; } @end @interface ThreeFieldMigrationTestObject : RLMObject @property int col1; @property int col2; @property int col3; @end @implementation ThreeFieldMigrationTestObject @end @interface MigrationTwoStringObject : RLMObject @property NSString *col1; @property NSString *col2; @end @implementation MigrationTwoStringObject @end @interface MigrationLinkObject : RLMObject @property MigrationTestObject *object; @property RLMArray *array; @property RLMSet *set; @property RLMDictionary *dictionary; @end @implementation MigrationLinkObject @end @interface MigrationTests : RLMTestCase @end @interface DateMigrationObject : RLMObject @property (nonatomic, strong) NSDate *nonNullNonIndexed; @property (nonatomic, strong) NSDate *nullNonIndexed; @property (nonatomic, strong) NSDate *nonNullIndexed; @property (nonatomic, strong) NSDate *nullIndexed; @property (nonatomic) int cookie; @end #define RLM_OLD_DATE_FORMAT (REALM_VER_MAJOR < 1 && REALM_VER_MINOR < 100) @implementation DateMigrationObject + (NSArray *)requiredProperties { return @[@"nonNullNonIndexed", @"nonNullIndexed"]; } + (NSArray *)indexedProperties { return @[@"nonNullIndexed", @"nullIndexed"]; } @end @implementation MigrationTests #pragma mark - Helper methods - (RLMSchema *)schemaWithObjects:(NSArray *)objects { RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = objects; return schema; } - (RLMRealm *)realmWithSingleObject:(RLMObjectSchema *)objectSchema { return [self realmWithTestPathAndSchema:[self schemaWithObjects:@[objectSchema]]]; } - (RLMRealmConfiguration *)config { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.fileURL = RLMTestRealmURL(); config.encryptionKey = RLMRealmConfiguration.rawDefaultConfiguration.encryptionKey; return config; } - (void)createTestRealmWithClasses:(NSArray *)classes block:(void (^)(RLMRealm *realm))block { NSMutableArray *objectSchema = [NSMutableArray arrayWithCapacity:classes.count]; for (::Class cls in classes) { [objectSchema addObject:[RLMObjectSchema schemaForObjectClass:cls]]; } [self createTestRealmWithSchema:objectSchema block:block]; } - (void)createTestRealmWithSchema:(NSArray *)objectSchema block:(void (^)(RLMRealm *realm))block { @autoreleasepool { RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:objectSchema]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm beginWriteTransaction]; block(realm); [realm commitWriteTransaction]; } } - (RLMRealm *)migrateTestRealmWithBlock:(RLMMigrationBlock)block NS_RETURNS_RETAINED { @autoreleasepool { RLMRealmConfiguration *config = self.config; config.schemaVersion = 1; config.migrationBlock = block; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); return realm; } } - (void)failToMigrateTestRealmWithBlock:(RLMMigrationBlock)block { @autoreleasepool { RLMRealmConfiguration *config = self.config; config.schemaVersion = 1; config.migrationBlock = block; XCTAssertFalse([RLMRealm performMigrationForConfiguration:config error:nil]); } } - (void)assertMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.customSchema = [self schemaWithObjects:from]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } config.customSchema = [self schemaWithObjects:to]; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorSchemaMismatch); __block bool migrationCalled = false; config.schemaVersion = 1; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { migrationCalled = true; }; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); XCTAssertTrue(migrationCalled); RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]); } - (void)assertNoMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to { RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:from]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } config.customSchema = [self schemaWithObjects:to]; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]); } - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas migrationBlock:(RLMMigrationBlock)block { RLMRealmConfiguration *configuration = self.config; configuration.schemaVersion = 1; configuration.customSchema = [self schemaWithObjects:objectSchemas]; configuration.migrationBlock = block; return configuration; } - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas className:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName { return [self renameConfigurationWithObjectSchemas:objectSchemas migrationBlock:^(RLMMigration *migration, uint64_t) { [migration renamePropertyForClass:className oldName:oldName newName:newName]; [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject[oldName]); RLMAssertThrowsWithReasonMatching(newObject[newName], @"Invalid property name"); XCTAssertEqualObjects(oldObject[oldName], newObject[newName]); XCTAssertEqualObjects([oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""], newObject.description); }]; }]; } - (void)assertPropertyRenameError:(NSString *)errorMessage objectSchemas:(NSArray *)objectSchemas className:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName { RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:objectSchemas className:className oldName:oldName newName:newName]; NSError *error; [RLMRealm performMigrationForConfiguration:config error:&error]; XCTAssertTrue([error.localizedDescription rangeOfString:errorMessage].location != NSNotFound, @"\"%@\" should contain \"%@\"", error.localizedDescription, errorMessage); } - (void)assertPropertyRenameError:(NSString *)errorMessage firstSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform1 secondSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform2 { RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; RLMProperty *afterProperty = schema.properties.firstObject; RLMProperty *beforeProperty = [afterProperty copyWithNewName:@"before_stringCol"]; schema.properties = @[beforeProperty]; if (transform1) { transform1(schema, beforeProperty, afterProperty); } [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) { if (errorMessage == nil) { [StringObject createInRealm:realm withValue:@[@"0"]]; } }]; schema.properties = @[afterProperty]; if (transform2) { transform2(schema, beforeProperty, afterProperty); } auto config = [self renameConfigurationWithObjectSchemas:@[schema] className:StringObject.className oldName:beforeProperty.name newName:afterProperty.name]; if (errorMessage) { NSError *error; [RLMRealm performMigrationForConfiguration:config error:&error]; XCTAssertEqualObjects([error localizedDescription], errorMessage); } else { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]); } } #pragma mark - Schema versions - (void)testGetSchemaVersion { XCTAssertEqual(RLMNotVersioned, [RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:nil]); NSError *error; XCTAssertEqual(RLMNotVersioned, [RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:&error]); RLMValidateRealmError(error, RLMErrorInvalidDatabase, @"Realm at path '%@' has not been initialized.", RLMDefaultRealmURL().path); RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = nil; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } XCTAssertEqual(0U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]); config.schemaVersion = 1; config.migrationBlock = ^(__unused RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(0U, oldSchemaVersion); }; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } XCTAssertEqual(1U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]); } - (void)testSchemaVersionCannotGoDown { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 10; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:config.encryptionKey error:nil]); config.schemaVersion = 5; RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil], @"Provided schema version 5 is less than last set version 10."); } - (void)testDifferentSchemaVersionsAtDifferentPaths { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 10; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:config.encryptionKey error:nil]); RLMRealmConfiguration *config2 = [RLMRealmConfiguration defaultConfiguration]; config2.schemaVersion = 5; config2.fileURL = RLMTestRealmURL(); @autoreleasepool { [RLMRealm realmWithConfiguration:config2 error:nil]; } XCTAssertEqual(5U, [RLMRealm schemaVersionAtURL:config2.fileURL encryptionKey:config.encryptionKey error:nil]); // Should not have been changed XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:config.encryptionKey error:nil]); } #pragma mark - Migration Requirements - (void)testAddingClassDoesNotRequireMigration { RLMRealmConfiguration *config = self.config; config.objectClasses = @[MigrationTestObject.class]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } - (void)testRemovingClassDoesNotRequireMigration { RLMRealmConfiguration *config = self.config; config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } config.objectClasses = @[MigrationTestObject.class]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } - (void)testAddingColumnRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; from.properties = [from.properties subarrayWithRange:{0, 1}]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testRemovingColumnRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; to.properties = [to.properties subarrayWithRange:{0, 1}]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testChangingColumnOrderDoesNotRequireMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; to.properties = @[to.properties[1], to.properties[0]]; [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testAddingIndexDoesNotRequireMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [to.properties[0] setIndexed:YES]; [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testRemovingIndexDoesNotRequireMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [from.properties[0] setIndexed:YES]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testAddingPrimaryKeyRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; to.primaryKeyProperty = to.properties[0]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testRemovingPrimaryKeyRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; from.primaryKeyProperty = from.properties[0]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testChangingPrimaryKeyRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; from.primaryKeyProperty = from.properties[0]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; to.primaryKeyProperty = to.properties[1]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testMakingPropertyOptionalRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [from.properties[0] setOptional:NO]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testMakingPropertyNonOptionalRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; [to.properties[0] setOptional:NO]; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testChangingLinkTargetRequiresMigration { NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class], [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]]; RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; [to.properties[0] setObjectClassName:@"MigrationTwoStringObject"]; [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from] to:[linkTargets arrayByAddingObject:to]]; } - (void)testChangingLinkListTargetRequiresMigration { NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class], [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]]; RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; [to.properties[1] setObjectClassName:@"MigrationTwoStringObject"]; [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from] to:[linkTargets arrayByAddingObject:to]]; } - (void)testChangingPropertyTypesRequiresMigration { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; to.objectClass = RLMObject.class; RLMProperty *prop = to.properties[0]; RLMProperty *strProp = to.properties[1]; prop.type = strProp.type; [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]]; } - (void)testDeleteRealmIfMigrationNeededWithSetCustomSchema { RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; from.properties = [from.properties subarrayWithRange:{0, 1}]; RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]; RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:@[from]]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } config.customSchema = [self schemaWithObjects:@[to]]; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; config.deleteRealmIfMigrationNeeded = YES; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]); } - (void)testDeleteRealmIfMigrationNeeded { for (uint64_t targetSchemaVersion = 1; targetSchemaVersion < 2; targetSchemaVersion++) { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; configuration.customSchema = [self schemaWithObjects:@[objectSchema]]; @autoreleasepool { [[NSFileManager defaultManager] removeItemAtURL:configuration.fileURL error:nil]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm transactionWithBlock:^{ [realm addObject:[MigrationTestObject new]]; }]; } // Change string to int, requiring a migration objectSchema.objectClass = RLMObject.class; RLMProperty *stringCol = objectSchema.properties[1]; stringCol.type = RLMPropertyTypeInt; stringCol.optional = NO; objectSchema.properties = @[stringCol]; configuration.customSchema = [self schemaWithObjects:@[objectSchema]]; @autoreleasepool { XCTAssertThrows([RLMRealm realmWithConfiguration:configuration error:nil]); RLMRealmConfiguration *dynamicConfiguration = [RLMRealmConfiguration defaultConfiguration]; dynamicConfiguration.dynamic = YES; XCTAssertFalse([[RLMRealm realmWithConfiguration:dynamicConfiguration error:nil] isEmpty]); } configuration.schemaVersion = targetSchemaVersion; configuration.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; configuration.deleteRealmIfMigrationNeeded = YES; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); XCTAssertTrue(realm.isEmpty); } } #pragma mark - Allowed schema mismatches - (void)testMismatchedIndexAllowedForReadOnly { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; [objectSchema.properties[0] setIndexed:YES]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *) { }]; // should be able to open readonly with mismatched index schema RLMRealmConfiguration *config = [self config]; config.readOnly = true; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; auto& info = realm->_info[@"StringObject"]; XCTAssertTrue(info.table()->has_search_index(info.tableColumn(objectSchema.properties[0].name))); } - (void)testRearrangeProperties { // create object in default realm [RLMRealm.defaultRealm transactionWithBlock:^{ [CircleObject createInDefaultRealmWithValue:@[@"data", NSNull.null]]; }]; // create realm with the properties reversed RLMSchema *schema = [[RLMSchema sharedSchema] copy]; RLMObjectSchema *objectSchema = schema[@"CircleObject"]; objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]]; RLMRealm *realm = [self realmWithTestPathAndSchema:schema]; [realm beginWriteTransaction]; // -createObject:withValue: takes values in the order the properties appear in the array [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]]; RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]), @"Invalid value 'data' to initialize object of type 'CircleObject'"); [realm commitWriteTransaction]; // accessors should work CircleObject *obj = [[CircleObject allObjectsInRealm:realm] firstObject]; XCTAssertEqualObjects(@"data", obj.data); XCTAssertNil(obj.next); [realm beginWriteTransaction]; XCTAssertNoThrow(obj.data = @"new data"); XCTAssertNoThrow(obj.next = obj); [realm commitWriteTransaction]; // open the default Realm and make sure accessors with alternate ordering work CircleObject *defaultObj = [[CircleObject allObjects] firstObject]; XCTAssertEqualObjects(defaultObj.data, @"data"); RLMAssertRealmSchemaMatchesTable(self, realm); // re-check that things still work for the realm with the swapped order XCTAssertEqualObjects(obj.data, @"new data"); [realm beginWriteTransaction]; [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]]; RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]), @"Invalid value 'data' to initialize object of type 'CircleObject'"); [realm commitWriteTransaction]; } #pragma mark - Migration block invocatios - (void)testMigrationBlockNotCalledForIntialRealmCreation { RLMRealmConfiguration *config = self.config; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } - (void)testMigrationBlockNotCalledWhenSchemaVersionIsUnchanged { RLMRealmConfiguration *config = self.config; config.schemaVersion = 1; @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { XCTFail(@"Migration block should not have been called"); }; @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); } } - (void)testMigrationBlockCalledWhenSchemaVersionHasChanged { RLMRealmConfiguration *config = self.config; config.schemaVersion = 1; @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } __block bool migrationCalled = false; config.schemaVersion = 2; config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { migrationCalled = true; }; @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } XCTAssertTrue(migrationCalled); migrationCalled = false; config.schemaVersion = 3; @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); } XCTAssertTrue(migrationCalled); } #pragma mark - Async Migration - (void)testAsyncMigration { RLMRealmConfiguration *c = self.config; c.schemaVersion = 1; @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:c error:nil]); } XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String)); XCTestExpectation *ex = [self expectationWithDescription:@"async-migration"]; __block bool migrationCalled = false; c.schemaVersion = 2; c.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) { migrationCalled = true; }; [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue() callback:^(RLMRealm *realm, NSError *error) { XCTAssertTrue(migrationCalled); XCTAssertNil(error); XCTAssertNotNil(realm); [ex fulfill]; }]; [self waitForExpectationsWithTimeout:1 handler:nil]; XCTAssertTrue(migrationCalled); XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String)); } #pragma mark - Migration Correctness - (void)testRemovingSubclass { RLMProperty *prop = [[RLMProperty alloc] initWithName:@"id" type:RLMPropertyTypeInt objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO]; RLMObjectSchema *objectSchema = [[RLMObjectSchema alloc] initWithClassName:@"DeletedClass" objectClass:RLMObject.class properties:@[prop]]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:@"DeletedClass" withValue:@[@0]]; }]; // apply migration RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); XCTAssertTrue([migration deleteDataForClassName:@"DeletedClass"]); XCTAssertFalse([migration deleteDataForClassName:@"NoSuchClass"]); XCTAssertFalse([migration deleteDataForClassName:self.nonLiteralNil]); [migration createObject:StringObject.className withValue:@[@"migration"]]; XCTAssertTrue([migration deleteDataForClassName:StringObject.className]); }]; XCTAssertFalse(ObjectStore::table_for_object_type(realm.group, "DeletedClass"), @"The deleted class should not have a table."); XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); } - (void)testAddingPropertyAtEnd { // create schema to migrate from with single string column RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; objectSchema.properties = @[objectSchema.properties[0]]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationTestObject.className withValue:@[@1]]; [realm createObject:MigrationTestObject.className withValue:@[@2]]; }]; // apply migration RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationTestObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertThrows(oldObject[@"stringCol"], @"stringCol should not exist on old object"); NSNumber *intObj; XCTAssertNoThrow(intObj = oldObject[@"intCol"], @"Should be able to access intCol on oldObject"); XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]); NSString *stringObj = [NSString stringWithFormat:@"%@", intObj]; XCTAssertNoThrow(newObject[@"stringCol"] = stringObj, @"Should be able to set stringCol"); }]; }]; // verify migration MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1]; XCTAssertEqual(mig1.intCol, 2, @"Int column should have value 2"); XCTAssertEqualObjects(mig1.stringCol, @"2", @"String column should be populated"); } - (void)testAddingPropertyAtBeginningPreservesData { // create schema to migrate from with the second and third columns from the final data RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:ThreeFieldMigrationTestObject.class]; objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[2]]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:ThreeFieldMigrationTestObject.className withValue:@[@1, @2]]; }]; RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:ThreeFieldMigrationTestObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertThrows(oldObject[@"col1"]); XCTAssertEqualObjects(oldObject[@"col2"], newObject[@"col2"]); XCTAssertEqualObjects(oldObject[@"col3"], newObject[@"col3"]); }]; }]; // verify migration ThreeFieldMigrationTestObject *mig = [ThreeFieldMigrationTestObject allObjectsInRealm:realm][0]; XCTAssertEqual(0, mig.col1); XCTAssertEqual(1, mig.col2); XCTAssertEqual(2, mig.col3); } - (void)testRemoveProperty { // create schema with an extra column RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; RLMProperty *thirdProperty = [[RLMProperty alloc] initWithName:@"deletedCol" type:RLMPropertyTypeBool objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO]; objectSchema.properties = [objectSchema.properties arrayByAddingObject:thirdProperty]; // create realm with old schema and populate [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationTestObject.className withValue:@[@1, @"1", @YES]]; [realm createObject:MigrationTestObject.className withValue:@[@2, @"2", @NO]]; }]; // apply migration RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationTestObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNoThrow(oldObject[@"deletedCol"], @"Deleted column should be accessible on old object."); XCTAssertThrows(newObject[@"deletedCol"], @"Deleted column should not be accessible on new object."); XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]); XCTAssertEqualObjects(newObject[@"stringCol"], oldObject[@"stringCol"]); }]; }]; // verify migration MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1]; XCTAssertThrows(mig1[@"deletedCol"], @"Deleted column should no longer be accessible."); } - (void)testRemoveAndAddProperty { // create schema to migrate from with single string column RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; RLMProperty *oldInt = [[RLMProperty alloc] initWithName:@"oldIntCol" type:RLMPropertyTypeInt objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO]; objectSchema.properties = @[oldInt, objectSchema.properties[1]]; // create realm with old schema and populate [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]]; [realm createObject:MigrationTestObject.className withValue:@[@1, @"2"]]; }]; // object migration object void (^migrateObjectBlock)(RLMObject *, RLMObject *) = ^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNoThrow(oldObject[@"oldIntCol"], @"Deleted column should be accessible on old object."); XCTAssertThrows(oldObject[@"intCol"], @"New column should not be accessible on old object."); XCTAssertEqual([oldObject[@"oldIntCol"] intValue], 1, @"Deleted column value is correct."); XCTAssertNoThrow(newObject[@"intCol"], @"New column is accessible on new object."); XCTAssertThrows(newObject[@"oldIntCol"], @"Old column should not be accessible on old object."); XCTAssertEqual([newObject[@"intCol"] intValue], 0, @"New column value is uninitialized."); }; // apply migration RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationTestObject.className block:migrateObjectBlock]; }]; // verify migration MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1]; XCTAssertThrows(mig1[@"oldIntCol"], @"Deleted column should no longer be accessible."); } - (void)testChangePropertyType { // make string an int RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; objectSchema.objectClass = RLMObject.class; RLMProperty *stringCol = objectSchema.properties[1]; stringCol.type = RLMPropertyTypeInt; stringCol.optional = NO; // create realm with old schema and populate [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationTestObject.className withValue:@[@1, @1]]; [realm createObject:MigrationTestObject.className withValue:@[@2, @2]]; }]; // apply migration RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationTestObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]); NSNumber *intObj = oldObject[@"stringCol"]; XCTAssert([intObj isKindOfClass:NSNumber.class], @"Old stringCol should be int"); newObject[@"stringCol"] = intObj.stringValue; }]; }]; // verify migration MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1]; XCTAssertEqualObjects(mig1[@"stringCol"], @"2", @"stringCol should be string after migration."); } - (void)testChangeObjectLinkType { // create realm with old schema and populate [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) { id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]]; [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj], @[obj]]]; }]; // Make the object link property link to a different class RLMRealmConfiguration *config = self.config; RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; [objectSchema.properties[0] setObjectClassName:MigrationLinkObject.className]; config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]]; // Apply migration config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationLinkObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject[@"object"]); XCTAssertNil(newObject[@"object"]); XCTAssertEqual(1U, [oldObject[@"array"] count]); XCTAssertEqual(1U, [newObject[@"array"] count]); XCTAssertEqual(1U, [oldObject[@"set"] count]); XCTAssertEqual(1U, [newObject[@"set"] count]); }]; }; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); } - (void)testChangeArrayLinkType { // create realm with old schema and populate RLMRealmConfiguration *config = [self config]; [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) { id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]]; [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj]]]; }]; // Make the array linklist property link to a different class RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; [objectSchema.properties[1] setObjectClassName:MigrationLinkObject.className]; config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]]; // Apply migration config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationLinkObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject[@"object"]); XCTAssertNotNil(newObject[@"object"]); XCTAssertEqual(1U, [oldObject[@"array"] count]); XCTAssertEqual(0U, [newObject[@"array"] count]); }]; }; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); } - (void)testChangeSetLinkType { // create realm with old schema and populate RLMRealmConfiguration *config = [self config]; [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) { id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]]; [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj], @[obj]]]; }]; // Make the set linklist property link to a different class RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; [objectSchema.properties[2] setObjectClassName:MigrationLinkObject.className]; config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]]; // Apply migration config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); [migration enumerateObjects:MigrationLinkObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject[@"object"]); XCTAssertNotNil(newObject[@"object"]); XCTAssertEqual(1U, [oldObject[@"set"] count]); XCTAssertEqual(0U, [newObject[@"set"] count]); }]; }; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); } - (void)testMakingPropertyPrimaryPreservesValues { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationStringPrimaryKeyObject.class]; objectSchema.primaryKeyProperty = nil; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"1"]]; [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"2"]]; }]; RLMRealm *realm = [self migrateTestRealmWithBlock:nil]; RLMResults *objects = [MigrationStringPrimaryKeyObject allObjectsInRealm:realm]; XCTAssertEqualObjects(@"1", [objects[0] stringCol]); XCTAssertEqualObjects(@"2", [objects[1] stringCol]); } - (void)testAddingPrimaryKeyShouldRejectDuplicateValues { // make the pk non-primary so that we can add duplicate values RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class]; objectSchema.primaryKeyProperty = nil; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { // populate with values that will be invalid when the property is made primary [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]]; [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]]; }]; // Fails due to duplicate values [self failToMigrateTestRealmWithBlock:nil]; // apply good migration that deletes duplicates RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { NSMutableSet *seen = [NSMutableSet set]; __block bool duplicateDeleted = false; [migration enumerateObjects:@"MigrationPrimaryKeyObject" block:^(__unused RLMObject *oldObject, RLMObject *newObject) { if ([seen containsObject:newObject[@"intCol"]]) { duplicateDeleted = true; [migration deleteObject:newObject]; } else { [seen addObject:newObject[@"intCol"]]; } }]; XCTAssertEqual(true, duplicateDeleted); }]; // make sure deletion occurred XCTAssertEqual(1U, [[MigrationPrimaryKeyObject allObjectsInRealm:realm] count]); } - (void)testIncompleteMigrationIsRolledBack { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class]; objectSchema.primaryKeyProperty = nil; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]]; [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]]; }]; // fail to apply migration [self failToMigrateTestRealmWithBlock:nil]; // should still be able to open with pre-migration schema XCTAssertNoThrow([self realmWithSingleObject:objectSchema]); } - (void)testAddObjectDuringMigration { // initialize realm @autoreleasepool { [self realmWithTestPath]; } RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration * migration, uint64_t) { [migration createObject:StringObject.className withValue:@[@"string"]]; }]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testEnumeratedObjectsDuringMigration { [self createTestRealmWithClasses:@[StringObject.class, ArrayPropertyObject.class, SetPropertyObject.class, IntObject.class] block:^(RLMRealm *realm) { [StringObject createInRealm:realm withValue:@[@"string"]]; [ArrayPropertyObject createInRealm:realm withValue:@[@"array", @[@[@"string"]], @[@[@1]]]]; [SetPropertyObject createInRealm:realm withValue:@[@"set", @[@[@"string"]], @[@[@1]]]]; }]; RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects([oldObject valueForKey:@"stringCol"], oldObject[@"stringCol"]); [newObject setValue:@"otherString" forKey:@"stringCol"]; XCTAssertEqualObjects([oldObject valueForKey:@"realm"], oldObject.realm); XCTAssertThrows([oldObject valueForKey:@"noSuchKey"]); XCTAssertThrows([newObject setValue:@1 forKey:@"noSuchKey"]); }]; [migration enumerateObjects:ArrayPropertyObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqual(RLMDynamicObject.class, newObject.class); XCTAssertEqual(RLMDynamicObject.class, oldObject.class); XCTAssertEqual(RLMDynamicObject.class, [[oldObject[@"array"] firstObject] class]); XCTAssertEqual(RLMDynamicObject.class, [[newObject[@"array"] firstObject] class]); }]; [migration enumerateObjects:SetPropertyObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqual(RLMDynamicObject.class, newObject.class); XCTAssertEqual(RLMDynamicObject.class, oldObject.class); XCTAssertEqual(RLMDynamicObject.class, [[oldObject[@"set"] allObjects][0] class]); XCTAssertEqual(RLMDynamicObject.class, [[newObject[@"set"] allObjects][0] class]); }]; }]; XCTAssertEqualObjects(@"otherString", [[StringObject allObjectsInRealm:realm].firstObject stringCol]); } - (void)testEnumerateObjectsAfterDeleteObjects { [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) { [StringObject createInRealm:realm withValue:@[@"1"]]; [StringObject createInRealm:realm withValue:@[@"2"]]; [StringObject createInRealm:realm withValue:@[@"3"]]; [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [IntObject createInRealm:realm withValue:@[@3]]; [BoolObject createInRealm:realm withValue:@[@YES]]; [BoolObject createInRealm:realm withValue:@[@NO]]; [BoolObject createInRealm:realm withValue:@[@YES]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { __block NSInteger count = 0; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]); if ([oldObject[@"stringCol"] isEqualToString:@"2"]) { [migration deleteObject:newObject]; } }]; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]); count++; }]; XCTAssertEqual(count, 2); count = 0; [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]); if ([oldObject[@"intCol"] isEqualToNumber:@1]) { [migration deleteObject:newObject]; } }]; [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]); count++; }]; XCTAssertEqual(count, 2); [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]); [migration deleteObject:newObject]; }]; [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) { XCTFail(@"This line should not executed since all objects have been deleted."); }]; }]; } - (void)testEnumerateObjectsAfterDeleteInsertObjects { [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) { [StringObject createInRealm:realm withValue:@[@"1"]]; [StringObject createInRealm:realm withValue:@[@"2"]]; [StringObject createInRealm:realm withValue:@[@"3"]]; [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [IntObject createInRealm:realm withValue:@[@3]]; [BoolObject createInRealm:realm withValue:@[@YES]]; [BoolObject createInRealm:realm withValue:@[@NO]]; [BoolObject createInRealm:realm withValue:@[@YES]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { __block NSInteger count = 0; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]); if ([newObject[@"stringCol"] isEqualToString:@"2"]) { [migration deleteObject:newObject]; [migration createObject:StringObject.className withValue:@[@"A"]]; } }]; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]); count++; }]; XCTAssertEqual(count, 2); count = 0; [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]); if ([newObject[@"intCol"] isEqualToNumber:@1]) { [migration deleteObject:newObject]; [migration createObject:IntObject.className withValue:@[@0]]; } }]; [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]); count++; }]; XCTAssertEqual(count, 2); [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]); [migration deleteObject:newObject]; [migration createObject:BoolObject.className withValue:@[@NO]]; }]; [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) { XCTFail(@"This line should not executed since all objects have been deleted."); }]; }]; } - (void)testEnumerateObjectTypeRemovedFromSchema { [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; }]; RLMRealmConfiguration *config = self.config; config.objectClasses = @[StringObject.class]; config.schemaVersion = 1; __block int enumerateCalls = 0; config.migrationBlock = ^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject); XCTAssertNil(newObject); ++enumerateCalls; XCTAssertGreaterThan([oldObject[@"intCol"] intValue], 0); }]; }; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); XCTAssertEqual(enumerateCalls, 2); } - (void)testEnumerateObjectsAfterDeleteData { [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); }]; [migration deleteDataForClassName:IntObject.className]; [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *) { XCTFail(@"should not have enumerated any objects"); }]; }]; } - (RLMResults *)objectsOfType:(::Class)cls { auto config = self.config; config.schemaVersion = 1; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; return [cls allObjectsInRealm:realm]; } - (void)testDeleteSomeObjectsWithinMigration { [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [IntObject createInRealm:realm withValue:@[@3]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) { if ([newObject[@"intCol"] intValue] != 2) { [migration deleteObject:newObject]; } }]; }]; XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2])); } - (void)testDeleteObjectsWithinSeparateEnumerations { [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [IntObject createInRealm:realm withValue:@[@3]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) { if ([newObject[@"intCol"] intValue] == 1) { [migration deleteObject:newObject]; } }]; [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) { if ([newObject[@"intCol"] intValue] == 3) { [migration deleteObject:newObject]; } }]; }]; XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2])); } - (void)testDeleteAndRecreateObjectsWithinMigration { [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [PrimaryIntObject createInRealm:realm withValue:@[@1]]; [PrimaryIntObject createInRealm:realm withValue:@[@2]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) { [migration deleteObject:newObject]; }]; [migration enumerateObjects:PrimaryIntObject.className block:^(RLMObject *, RLMObject *newObject) { [migration deleteObject:newObject]; }]; [migration createObject:IntObject.className withValue:@[@2]]; [migration createObject:IntObject.className withValue:@[@4]]; [migration createObject:PrimaryIntObject.className withValue:@[@2]]; [migration createObject:PrimaryIntObject.className withValue:@[@4]]; }]; XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4])); XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4])); } - (void)testDeleteAllDataAndRecreateObjectsWithinMigration { [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@1]]; [IntObject createInRealm:realm withValue:@[@2]]; [PrimaryIntObject createInRealm:realm withValue:@[@1]]; [PrimaryIntObject createInRealm:realm withValue:@[@2]]; }]; [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration deleteDataForClassName:IntObject.className]; [migration deleteDataForClassName:PrimaryIntObject.className]; [migration createObject:IntObject.className withValue:@[@2]]; [migration createObject:IntObject.className withValue:@[@4]]; [migration createObject:PrimaryIntObject.className withValue:@[@2]]; [migration createObject:PrimaryIntObject.className withValue:@[@4]]; }]; XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4])); XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4])); } - (void)testRequiredToNullableAutoMigration { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class]; [objectSchema.properties setValue:@NO forKey:@"optional"]; // create initial required column [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [AllOptionalTypes createInRealm:realm withValue:@[@1, @1, @1, @1, @"str", [@"data" dataUsingEncoding:NSUTF8StringEncoding], [NSDate dateWithTimeIntervalSince1970:1]]]; [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2", [@"data2" dataUsingEncoding:NSUTF8StringEncoding], [NSDate dateWithTimeIntervalSince1970:2]]]; }]; RLMRealm *realm = [self migrateTestRealmWithBlock:nil]; RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm]; XCTAssertEqual(2U, allObjects.count); AllOptionalTypes *obj = allObjects[0]; XCTAssertEqualObjects(@1, obj.intObj); XCTAssertEqualObjects(@1, obj.floatObj); XCTAssertEqualObjects(@1, obj.doubleObj); XCTAssertEqualObjects(@1, obj.boolObj); XCTAssertEqualObjects(@"str", obj.string); XCTAssertEqualObjects([@"data" dataUsingEncoding:NSUTF8StringEncoding], obj.data); XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:1], obj.date); obj = allObjects[1]; XCTAssertEqualObjects(@2, obj.intObj); XCTAssertEqualObjects(@2, obj.floatObj); XCTAssertEqualObjects(@2, obj.doubleObj); XCTAssertEqualObjects(@0, obj.boolObj); XCTAssertEqualObjects(@"str2", obj.string); XCTAssertEqualObjects([@"data2" dataUsingEncoding:NSUTF8StringEncoding], obj.data); XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:2], obj.date); } - (void)testNullableToRequiredMigration { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class]; // create initial nullable column [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { [AllOptionalTypes createInRealm:realm withValue:@[ [NSNull null], [NSNull null], [NSNull null], [NSNull null], [NSNull null], [NSNull null], [NSNull null]]]; [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2", [@"data2" dataUsingEncoding:NSUTF8StringEncoding], [NSDate dateWithTimeIntervalSince1970:2]]]; }]; objectSchema.objectClass = RLMObject.class; [objectSchema.properties setValue:@NO forKey:@"optional"]; RLMRealm *realm; @autoreleasepool { RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:@[ objectSchema ]]; config.schemaVersion = 1; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); } RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm]; XCTAssertEqual(2U, allObjects.count); AllOptionalTypes *obj = allObjects[0]; XCTAssertEqualObjects(@0, obj[@"intObj"]); XCTAssertEqualObjects(@0, obj[@"floatObj"]); XCTAssertEqualObjects(@0, obj[@"doubleObj"]); XCTAssertEqualObjects(@0, obj[@"boolObj"]); XCTAssertEqualObjects(@"", obj[@"string"]); XCTAssertEqualObjects(NSData.data, obj[@"data"]); XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]); obj = allObjects[1]; XCTAssertEqualObjects(@0, obj[@"intObj"]); XCTAssertEqualObjects(@0, obj[@"floatObj"]); XCTAssertEqualObjects(@0, obj[@"doubleObj"]); XCTAssertEqualObjects(@0, obj[@"boolObj"]); XCTAssertEqualObjects(@"", obj[@"string"]); XCTAssertEqualObjects(NSData.data, obj[@"data"]); XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]); } - (void)testMigrationAfterReorderingProperties { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class]; // Create a table where the order of columns does not match the order the properties are declared in the class. objectSchema.properties = @[ objectSchema.properties[2], objectSchema.properties[0], objectSchema.properties[1] ]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { // We use a dictionary here to ensure that the test reaches the migration case below, even if the non-migration // case doesn't handle the ordering correctly. The non-migration case is tested in testRearrangeProperties. [RequiredPropertiesObject createInRealm:realm withValue:@{ @"stringCol": @"Hello", @"dateCol": [NSDate date], @"binaryCol": [NSData data] }]; }]; objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class]; RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:@[objectSchema]]; config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t) { [migration createObject:RequiredPropertiesObject.className withValue:@[@"World", [NSData data], [NSDate date]]]; }; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMResults *allObjects = [RequiredPropertiesObject allObjectsInRealm:realm]; XCTAssertEqualObjects(@"Hello", [allObjects[0] stringCol]); XCTAssertEqualObjects(@"World", [allObjects[1] stringCol]); } - (void)testModifyPrimaryKeyInMigration { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class]; objectSchema.primaryKeyProperty = objectSchema[@"intCol"]; [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) { for (int i = 0; i < 10; ++i) { [PrimaryStringObject createInRealm:realm withValue:@[@(i).stringValue, @(i + 10)]]; } }]; auto realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:@"PrimaryStringObject" block:^(RLMObject *oldObject, RLMObject *newObject) { newObject[@"stringCol"] = [oldObject[@"intCol"] stringValue]; }]; }]; for (int i = 10; i < 20; ++i) { auto obj = [PrimaryStringObject objectInRealm:realm forPrimaryKey:@(i).stringValue]; XCTAssertNotNil(obj); XCTAssertEqual(obj.intCol, i); } } - (void)testChangeEmptyTableFromTopLevelToEmbedded { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; RLMObjectSchema *toChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; RLMObjectSchema *toParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self assertMigrationRequiredForChangeFrom:@[fromChild, fromParent] to:@[toChild, toParent]]; } - (void)testChangeEmptyTableFromEmbeddedToTopLevel { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; RLMObjectSchema *toChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; toChild.isEmbedded = false; RLMObjectSchema *toParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self assertMigrationRequiredForChangeFrom:@[fromChild, fromParent] to:@[toChild, toParent]]; } - (void)testChangeToEmbeddedRequiresMigration { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; RLMObjectSchema *toChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; RLMObjectSchema *toParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self createTestRealmWithSchema:@[fromChild, fromParent] block:^(RLMRealm *realm) { EmbeddedIntObject *childObject = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@42]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@42, childObject, NSNull.null]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject, NSNull.null]]; }]; [self assertMigrationRequiredForChangeFrom:@[fromChild, fromParent] to:@[toChild, toParent]]; } - (void)testChangeTableToEmbeddedWithOnlyOneLinkPerObject { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self createTestRealmWithSchema:@[fromChild, fromParent] block:^(RLMRealm *realm) { EmbeddedIntObject *childObject1 = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@42]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@42, childObject1, NSNull.null]]; EmbeddedIntObject *childObject2 = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@43]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject2, NSNull.null]]; }]; __block int parentEnumerateCalls = 0; __block int childEnumerateCalls = 0; RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:EmbeddedIntParentObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { parentEnumerateCalls++; XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); NSNumber *newIntProperty = newObject[@"object"][@"intCol"]; XCTAssertEqualObjects(oldObject[@"object"][@"intCol"], newIntProperty); XCTAssert([newIntProperty isEqual: @42] || [newIntProperty isEqual:@43]); }]; [migration enumerateObjects:EmbeddedIntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { childEnumerateCalls++; XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); NSNumber *newIntProperty = newObject[@"intCol"]; XCTAssertEqualObjects(oldObject[@"intCol"], newIntProperty); XCTAssert([newIntProperty isEqual:@42] || [newIntProperty isEqual:@43]); }]; }]; XCTAssertNotNil(realm); XCTAssertEqual(parentEnumerateCalls, 2); XCTAssertEqual(childEnumerateCalls, 2); RLMResults *parentObjects = RLMGetObjects(realm, EmbeddedIntParentObject.className, nil); XCTAssertEqual(parentObjects.count, 2U); EmbeddedIntParentObject *firstParentObject = parentObjects[0]; XCTAssertEqual(firstParentObject.pk, 42); EmbeddedIntObject *firstParentsChild = firstParentObject.object; XCTAssertEqual(firstParentsChild.intCol, 42); EmbeddedIntParentObject *secondParentObject = parentObjects[1]; EmbeddedIntObject *secondParentsChild = secondParentObject.object; XCTAssertEqual(secondParentsChild.intCol, 43); } - (void)testChangeToEmbeddedWithMultipleBacklinksHandledAutomatically { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self createTestRealmWithSchema:@[fromChild, fromParent] block:^(RLMRealm *realm) { EmbeddedIntObject *childObject = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@42]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@42, childObject, NSNull.null]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject, NSNull.null]]; }]; RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *, uint64_t) {}]; RLMResults *parents = [EmbeddedIntParentObject allObjectsInRealm:realm]; XCTAssertEqual(parents.count, 2U); XCTAssertEqual(parents[0].object.intCol, 42); XCTAssertEqual(parents[1].object.intCol, 42); XCTAssertFalse([parents[0].object isEqualToObject:parents[1].object]); } - (void)testConvertToEmbeddedWithMultipleIncomingLinksResolvedInMigrationBlock { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self createTestRealmWithSchema:@[fromChild, fromParent] block:^(RLMRealm *realm) { EmbeddedIntObject *childObject = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@42]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@42, childObject, NSNull.null]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject, NSNull.null]]; }]; __block int parentEnumerateCalls = 0; __block int childEnumerateCalls = 0; RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:EmbeddedIntParentObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { parentEnumerateCalls++; XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); newObject[@"object"] = nil; }]; [migration enumerateObjects:EmbeddedIntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { childEnumerateCalls++; XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); [migration deleteObject:newObject]; }]; }]; XCTAssertEqual(parentEnumerateCalls, 2); XCTAssertEqual(childEnumerateCalls, 1); RLMResults *parentObjects = RLMGetObjects(realm, EmbeddedIntParentObject.className, nil); XCTAssertEqual(parentObjects.count, 2U); EmbeddedIntParentObject *firstParentObject = parentObjects[0]; XCTAssertNil(firstParentObject.object); EmbeddedIntParentObject *secondParentObject = parentObjects[1]; XCTAssertNil(secondParentObject.object); RLMResults *childObjects = RLMGetObjects(realm, EmbeddedIntObject.className, nil); XCTAssertEqual(childObjects.count, 0U); } - (void)testConvertToEmbeddedAddingMoreLinks { RLMObjectSchema *fromChild = [RLMObjectSchema schemaForObjectClass:EmbeddedIntObject.class]; fromChild.isEmbedded = false; RLMObjectSchema *fromParent = [RLMObjectSchema schemaForObjectClass:EmbeddedIntParentObject.class]; [self createTestRealmWithSchema:@[fromChild, fromParent] block:^(RLMRealm *realm) { EmbeddedIntObject *childObject = (EmbeddedIntObject *)[realm createObject:EmbeddedIntObject.className withValue:@[@42]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@42, childObject, NSNull.null]]; [realm createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject, NSNull.null]]; }]; __block int parentEnumerateCalls = 0; [self failToMigrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) { [migration enumerateObjects:EmbeddedIntParentObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { parentEnumerateCalls++; XCTAssertNotNil(oldObject); XCTAssertNotNil(newObject); RLMObject *childObject = newObject[@"object"]; XCTAssertNotNil(childObject); if (childObject == nil) { return; } [migration createObject:EmbeddedIntParentObject.className withValue:@[@43, childObject, NSNull.null]]; }]; }]; XCTAssertEqual(parentEnumerateCalls, 2); } #pragma mark - Property Rename // Successful Property Rename Tests - (void)testMigrationRenameProperty { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllTypesObject.class]; RLMObjectSchema *stringObjectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; RLMObjectSchema *mixedObjectSchema = [RLMObjectSchema schemaForObjectClass:MixedObject.class]; RLMObjectSchema *linkingObjectsSchema = [RLMObjectSchema schemaForObjectClass:LinkToAllTypesObject.class]; NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:objectSchema.properties.count]; for (RLMProperty *property in objectSchema.properties) { [beforeProperties addObject:[property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]]]; } NSArray *afterProperties = objectSchema.properties; objectSchema.properties = beforeProperties; NSDictionary *valueDictionary = [AllTypesObject values:1 stringObject:(id)@[@"a"] mixedObject:(id)@[@"a"]]; NSMutableArray *inputValue = [NSMutableArray arrayWithCapacity:valueDictionary.count]; for (NSString *key in [afterProperties valueForKey:@"name"]) { [inputValue addObject:valueDictionary[key]]; } [self createTestRealmWithSchema:@[objectSchema, stringObjectSchema, mixedObjectSchema, linkingObjectsSchema] block:^(RLMRealm *realm) { [AllTypesObject createInRealm:realm withValue:inputValue]; }]; objectSchema.properties = afterProperties; RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:@[objectSchema, stringObjectSchema, mixedObjectSchema, linkingObjectsSchema] migrationBlock:^(RLMMigration *migration, __unused uint64_t oldSchemaVersion) { [afterProperties enumerateObjectsUsingBlock:^(RLMProperty *property, NSUInteger idx, __unused BOOL *stop) { [migration renamePropertyForClass:AllTypesObject.className oldName:[beforeProperties[idx] name] newName:property.name]; [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertNotNil(oldObject[[beforeProperties[idx] name]]); RLMAssertThrowsWithReasonMatching(newObject[[beforeProperties[idx] name]], @"Invalid property name"); if ([property.objectClassName isEqualToString:@""]) { XCTAssertEqualObjects(oldObject[[beforeProperties[idx] name]], newObject[property.name]); } }]; }]; [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { NSString *(^regexReplace)(NSString *) = ^(NSString *desc) { NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"<0x[0-9a-f]+>" options:NSRegularExpressionCaseInsensitive error:nil]; return [regex stringByReplacingMatchesInString:desc options:0 range:NSMakeRange(0, desc.length) withTemplate:@""]; }; NSString *oldDescription = [oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""]; NSString *newDescription = newObject.description; oldDescription = regexReplace(oldDescription); newDescription = regexReplace(newDescription); XCTAssertEqualObjects(oldDescription, newDescription); }]; }]; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; RLMAssertRealmSchemaMatchesTable(self, realm); RLMResults *allObjects = [AllTypesObject allObjectsInRealm:realm]; XCTAssertEqual(1U, allObjects.count); XCTAssertEqual(1U, [[StringObject allObjectsInRealm:realm] count]); AllTypesObject *obj = allObjects.firstObject; XCTAssertEqualObjects(inputValue[0], @(obj.boolCol)); XCTAssertEqualObjects(inputValue[1], @(obj.intCol)); XCTAssertEqualObjects(inputValue[2], @(obj.floatCol)); XCTAssertEqualObjects(inputValue[3], @(obj.doubleCol)); XCTAssertEqualObjects(inputValue[4], obj.stringCol); XCTAssertEqualObjects(inputValue[5], obj.binaryCol); XCTAssertEqualObjects(inputValue[6], obj.dateCol); XCTAssertEqualObjects(inputValue[7], @(obj.cBoolCol)); XCTAssertEqualObjects(inputValue[8], @(obj.longCol)); XCTAssertEqualObjects(inputValue[9], obj.decimalCol); XCTAssertEqualObjects(inputValue[10], obj.objectIdCol); XCTAssertEqualObjects(inputValue[11], obj.uuidCol); XCTAssertEqualObjects(inputValue[12], @[obj.objectCol.stringCol]); XCTAssertEqualObjects(inputValue[13], @[obj.mixedObjectCol.anyCol]); XCTAssertEqualObjects(inputValue[14], obj.anyCol); } - (void)testMultipleMigrationRenameProperty { RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol0"]]; [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) { [StringObject createInRealm:realm withValue:@[@"0"]]; }]; schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol"]]; __block bool migrationCalled = false; RLMRealmConfiguration *config = self.config; config.customSchema = [self schemaWithObjects:@[schema]]; config.schemaVersion = 2; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldVersion){ migrationCalled = true; __block id oldValue = nil; if (oldVersion < 1) { [migration renamePropertyForClass:StringObject.className oldName:@"stringCol0" newName:@"stringCol1"]; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { oldValue = oldObject[@"stringCol0"]; XCTAssertNotNil(oldValue); RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name"); RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name"); }]; } if (oldVersion < 2) { [migration renamePropertyForClass:StringObject.className oldName:@"stringCol1" newName:@"stringCol"]; [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) { XCTAssertEqualObjects(oldObject[@"stringCol0"], oldValue); XCTAssertEqualObjects(newObject[@"stringCol"], oldValue); RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name"); RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name"); }]; } }; XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); XCTAssertTrue(migrationCalled); XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]); } - (void)testMigrationRenamePropertyPrimaryKeyBoth { [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { schema.primaryKeyProperty = beforeProperty; } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) { schema.primaryKeyProperty = afterProperty; }]; } - (void)testMigrationRenamePropertyUnsetPrimaryKey { [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { schema.primaryKeyProperty = beforeProperty; } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { schema.primaryKeyProperty = nil; }]; } - (void)testMigrationRenamePropertySetPrimaryKey { [self assertPropertyRenameError:nil firstSchemaTransform:nil secondSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *, RLMProperty *afterProperty) { schema.primaryKeyProperty = afterProperty; }]; } - (void)testMigrationRenamePropertyIndexBoth { [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { afterProperty.indexed = YES; beforeProperty.indexed = YES; } secondSchemaTransform:nil]; } - (void)testMigrationRenamePropertyUnsetIndex { [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { beforeProperty.indexed = YES; } secondSchemaTransform:nil]; } - (void)testMigrationRenamePropertySetIndex { [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) { afterProperty.indexed = YES; } secondSchemaTransform:nil]; } - (void)testMigrationRenamePropertySetOptional { [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) { beforeProperty.optional = NO; } secondSchemaTransform:nil]; } // Unsuccessful Property Rename Tests - (void)testMigrationRenamePropertySetRequired { [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from optional to required." firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) { afterProperty.optional = NO; } secondSchemaTransform:nil]; } - (void)testMigrationRenamePropertyTypeMismatch { [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from type 'int' to 'string'." firstSchemaTransform:^(RLMObjectSchema *, RLMProperty *beforeProperty, RLMProperty *) { beforeProperty.type = RLMPropertyTypeInt; } secondSchemaTransform:nil]; } - (void)testMigrationRenamePropertyObjectTypeMismatch { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class]; RLMObjectSchema *migrationObjectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]; NSArray *afterProperties = objectSchema.properties; NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:2]; for (RLMProperty *property in afterProperties) { RLMProperty *beforeProperty = [property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]]; beforeProperty.objectClassName = MigrationLinkObject.className; [beforeProperties addObject:beforeProperty]; } objectSchema.properties = beforeProperties; [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) { // No need to create an object }]; objectSchema.properties = afterProperties; [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_object' to 'object' because it would change from type '' to ''." objectSchemas:@[objectSchema, migrationObjectSchema] className:MigrationLinkObject.className oldName:[beforeProperties[0] name] newName:[afterProperties[0] name]]; [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_array' to 'array' because it would change from type 'array' to 'array'." objectSchemas:@[objectSchema, migrationObjectSchema] className:MigrationLinkObject.className oldName:[beforeProperties[1] name] newName:[afterProperties[1] name]]; } - (void)testMigrationRenameMissingPropertiesAndClasses { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) { // No need to create an object }]; // Missing Old Property [self assertPropertyRenameError:@"Cannot rename property 'StringObject.nonExistentProperty1' because it does not exist." objectSchemas:@[objectSchema] className:StringObject.className oldName:@"nonExistentProperty1" newName:@"nonExistentProperty2"]; // Missing New Property RLMObjectSchema *renamedProperty = [objectSchema copy]; renamedProperty.properties[0].name = @"stringCol2"; [self assertPropertyRenameError:@"Renamed property 'StringObject.nonExistentProperty' does not exist." objectSchemas:@[renamedProperty] className:StringObject.className oldName:@"stringCol" newName:@"nonExistentProperty"]; // Removed Class [self assertPropertyRenameError:@"Cannot rename properties for type 'StringObject' because it has been removed from the Realm." objectSchemas:@[[RLMObjectSchema schemaForObjectClass:IntObject.class]] className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"]; // Without Removing Old Property RLMProperty *secondProperty = [objectSchema.properties.firstObject copyWithNewName:@"stringCol2"]; objectSchema.properties = [objectSchema.properties arrayByAddingObject:secondProperty]; [self assertPropertyRenameError:@"Cannot rename property 'StringObject.stringCol' to 'stringCol2' because the source property still exists." objectSchemas:@[objectSchema] className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"]; } @end ================================================ FILE: Realm/Tests/NotificationTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMRealmConfiguration_Private.h" @interface NotificationTests : RLMTestCase @property (nonatomic, strong) RLMNotificationToken *token; @property (nonatomic) bool called; @end @implementation NotificationTests - (void)setUp { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ for (int i = 0; i < 10; ++i) [IntObject createInDefaultRealmWithValue:@[@(i)]]; }]; } _token = [self.query addNotificationBlock:^(RLMResults *results, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); self.called = true; CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); } - (void)tearDown { [_token invalidate]; [super tearDown]; } - (RLMResults *)query { return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"]; } - (void)runAndWaitForNotification:(void (^)(RLMRealm *))block { _called = false; [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ block(realm); }]; }]; } - (void)expectNotification:(void (^)(RLMRealm *))block { [self runAndWaitForNotification:block]; XCTAssertTrue(_called); } - (void)expectNoNotification:(void (^)(RLMRealm *))block { [self runAndWaitForNotification:block]; XCTAssertFalse(_called); } - (void)testInsertObjectMatchingQuery { [self expectNotification:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@3]]; }]; } - (void)testInsertObjectNotMatchingQuery { [self expectNoNotification:^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@10]]; }]; } - (void)testModifyObjectMatchingQuery { [self expectNotification:^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }]; } - (void)testModifyObjectToNoLongerMatchQuery { [self expectNotification:^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"]; }]; } - (void)testModifyObjectNotMatchingQuery { [self expectNoNotification:^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"]; }]; } - (void)testModifyObjectToMatchQuery { [self expectNotification:^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"]; }]; } - (void)testDeleteObjectMatchingQuery { [self expectNotification:^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }]; } - (void)testDeleteObjectNotMatchingQuery { [self expectNoNotification:^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]]; }]; [self expectNoNotification:^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }]; } - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching { [self expectNotification:^(RLMRealm *realm) { // Make the last object match the query [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3]; // Move the now-matching object over a previously matching object [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }]; } - (void)testSuppressCollectionNotification { _called = false; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransactionWithoutNotifying:@[_token] error:nil]; // Add a new callback that we can wait for, as we can't wait for a // notification to not be delivered RLMNotificationToken *token = [self.query addNotificationBlock:^(__unused RLMResults *results, __unused RLMCollectionChange *change, __unused NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [token invalidate]; XCTAssertFalse(_called); } - (void)testSuppressRealmNotification { RLMRealm *realm = [RLMRealm defaultRealm]; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) { XCTFail(@"should not have been called"); }]; [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransactionWithoutNotifying:@[token] error:nil]; // local realm notifications are called synchronously so no need to wait for anything [token invalidate]; } - (void)testSuppressRealmNotificationInTransactionWithBlock { RLMRealm *realm = [RLMRealm defaultRealm]; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) { XCTFail(@"should not have been called"); }]; [realm transactionWithoutNotifying:@[token] block:^{ [realm deleteAllObjects]; }]; // local realm notifications are called synchronously so no need to wait for anything [token invalidate]; } - (void)testSuppressRealmNotificationForWrongRealm { RLMRealm *otherRealm = [self realmWithTestPath]; RLMNotificationToken *token = [otherRealm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) { XCTFail(@"should not have been called"); }]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; XCTAssertThrows([realm commitWriteTransactionWithoutNotifying:@[token] error:nil]); [realm cancelWriteTransaction]; [token invalidate]; } - (void)testSuppressCollectionNotificationForWrongRealm { // Test with the token's realm not in a write transaction RLMRealm *otherRealm = [self realmWithTestPath]; [otherRealm beginWriteTransaction]; XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]); // and in a write transaction RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]); [realm cancelWriteTransaction]; [otherRealm cancelWriteTransaction]; } @end @interface SortedNotificationTests : NotificationTests @end @implementation SortedNotificationTests - (RLMResults *)query { return [[IntObject objectsWhere:@"intCol > 0 AND intCol < 5"] sortedResultsUsingKeyPath:@"intCol" ascending:NO]; } - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject { [self expectNoNotification:^(RLMRealm *realm) { // Make a matching object be the last row [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]]; // Delete a non-last, non-match row so that a matched row is moved [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }]; } - (void)testMultipleMovesOfSingleRow { [self expectNotification:^(RLMRealm *realm) { [realm deleteObjects:[IntObject allObjectsInRealm:realm]]; [IntObject createInRealm:realm withValue:@[@10]]; [IntObject createInRealm:realm withValue:@[@10]]; [IntObject createInRealm:realm withValue:@[@3]]; }]; [self expectNoNotification:^(RLMRealm *realm) { RLMResults *objects = [IntObject allObjectsInRealm:realm]; [realm deleteObject:objects[1]]; [realm deleteObject:objects[0]]; }]; } @end @protocol ChangesetTestCase - (RLMResults *)query; - (void)prepare; @end @interface NSIndexPath (TableViewHelpers) @property (nonatomic, readonly) NSInteger section; @property (nonatomic, readonly) NSInteger row; @end @implementation NSIndexPath (TableViewHelpers) - (NSInteger)section { return [self indexAtPosition:0]; } - (NSInteger)row { return [self indexAtPosition:1]; } @end static RLMCollectionChange *getChange(RLMTestCase *self, void (^block)(RLMRealm *realm)) { [self prepare]; __block bool first = true; RLMResults *query = [self query]; __block RLMCollectionChange *changes; id token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); changes = c; XCTAssertTrue(first == !changes); first = false; CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ block(realm); }]; }]; [(RLMNotificationToken *)token invalidate]; token = nil; return changes; } static void ExpectChange(id self, NSArray *deletions, NSArray *insertions, NSArray *modifications, void (^block)(RLMRealm *realm)) { RLMCollectionChange *changes = getChange(self, block); XCTAssertNotNil(changes); if (!changes) { return; } XCTAssertEqualObjects(deletions, changes.deletions); XCTAssertEqualObjects(insertions, changes.insertions); XCTAssertEqualObjects(modifications, changes.modifications); NSInteger section = __LINE__; NSArray *deletionPaths = [changes deletionsInSection:section]; NSArray *insertionPaths = [changes insertionsInSection:section + 1]; NSArray *modificationPaths = [changes modificationsInSection:section + 2]; XCTAssert(deletionPaths.count == 0 || [deletionPaths[0] section] == section); XCTAssert(insertionPaths.count == 0 || [insertionPaths[0] section] == section + 1); XCTAssert(modificationPaths.count == 0 || [modificationPaths[0] section] == section + 2); XCTAssertEqualObjects(deletions, [deletionPaths valueForKey:@"row"]); XCTAssertEqualObjects(insertions, [insertionPaths valueForKey:@"row"]); XCTAssertEqualObjects(modifications, [modificationPaths valueForKey:@"row"]); } #define ExpectNoChange(self, block) XCTAssertNil(getChange((self), (block))) @interface ChangesetTests : RLMTestCase @end @implementation ChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; for (int i = 0; i < 10; ++i) { IntObject *io = [IntObject createInDefaultRealmWithValue:@[@(i)]]; [ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], @[io]]]; } }]; } } - (RLMResults *)query { return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"]; } - (void)testDeleteMultiple { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]]; }); ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[[IntObject allObjectsInRealm:realm] valueForKey:@"self"]]; }); } - (void)testDeleteNewlyInsertedRowMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@3]]; [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject]; }); } - (void)testInsertObjectMatchingQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@3]]; }); } - (void)testInsertObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@5]]; }); } - (void)testInsertBothMatchingAndNonMatching { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@5]]; [IntObject createInRealm:realm withValue:@[@3]]; }); } - (void)testInsertMultipleMatching { ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) { [IntObject createInRealm:realm withValue:@[@5]]; [IntObject createInRealm:realm withValue:@[@3]]; [IntObject createInRealm:realm withValue:@[@5]]; [IntObject createInRealm:realm withValue:@[@2]]; }); } - (void)testModifyObjectMatchingQuery { ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }); } - (void)testModifyObjectToNoLongerMatchQuery { ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"]; }); } - (void)testModifyObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"]; }); } - (void)testModifyObjectToMatchQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"]; }); } - (void)testModifyObjectShiftedByDeletion { ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }); } - (void)testDeleteObjectMatchingQuery { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]]; }); ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); } - (void)testDeleteNonMatchingBeforeMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testDeleteNonMatchingAfterMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]]; }); } #if 0 // maybe relevant to queries on backlinks? - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject { ExpectChange(self, @[@3], @[@0], @[], ^(RLMRealm *realm) { // Make a matching object be the last row [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]]; // Delete a non-last, non-match row so that a matched row is moved [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching { ExpectChange(self, @[@1], @[@1], @[], ^(RLMRealm *realm) { // Make the last object match the query [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3]; // Move the now-matching object over a previously matching object [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); } #endif - (void)testExcludingChangesFromSkippedTransaction { [self prepare]; __block bool first = true; RLMResults *query = [self query]; __block RLMCollectionChange *changes; RLMNotificationToken *token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); changes = c; XCTAssertTrue(first || changes); first = false; CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [query.realm beginWriteTransaction]; [IntObject createInRealm:query.realm withValue:@[@3]]; [query.realm commitWriteTransactionWithoutNotifying:@[token] error:nil]; [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@3]]; }]; }]; [token invalidate]; token = nil; XCTAssertNotNil(changes); // Should only have the row inserted in the background transaction XCTAssertEqualObjects(@[@5], changes.insertions); } - (void)testMultipleWriteTransactionsWithinNotification { [self prepare]; RLMResults *query1 = [self query]; __block int calls1 = 0; RLMNotificationToken *token1 = [query1 addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); if (calls1++ == 0) { XCTAssertNil(c); return; } XCTAssertEqualObjects(c.deletions, @[@(5 - calls1)]); }]; RLMResults *query2 = [self query]; __block int calls2 = 0; RLMNotificationToken *token2 = [query2 addNotificationBlock:^(RLMResults *results, __unused RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); ++calls2; RLMRealm *realm = results.realm; if (realm.inWriteTransaction) { return; } while (results.count) { [realm beginWriteTransaction]; [realm deleteObject:[results lastObject]]; [realm commitWriteTransaction]; } }]; id ex = [self expectationWithDescription:@"last query gets final notification"]; RLMResults *query3 = [self query]; __block int calls3 = 0; RLMNotificationToken *token3 = [query3 addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); if (++calls3 == 1) { XCTAssertNil(c); } else { XCTAssertEqualObjects(c.deletions, @[@(5 - calls3)]); } if (results.count == 0) { [ex fulfill]; } }]; [self waitForExpectations:@[ex] timeout:2.0]; XCTAssertEqual(calls1, 5); XCTAssertEqual(calls2, 5); XCTAssertEqual(calls3, 5); [token1 invalidate]; [token2 invalidate]; [token3 invalidate]; } @end @interface LinkViewArrayChangesetTests : RLMTestCase @end @implementation LinkViewArrayChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; for (int i = 0; i < 10; ++i) { [IntObject createInDefaultRealmWithValue:@[@(i)]]; } [ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], [IntObject allObjectsInRealm:realm]]]; }]; } } - (RLMResults *)query { return [[[ArrayPropertyObject.allObjects firstObject] intArray] objectsWhere:@"intCol > 0 AND intCol < 5"]; } - (void)testDeleteMultiple { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]]; }); } - (void)testModifyObjectMatchingQuery { ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }); } - (void)testModifyObjectToNoLongerMatchQuery { ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"]; }); } - (void)testModifyObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"]; }); } - (void)testModifyObjectToMatchQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"]; }); } - (void)testDeleteObjectMatchingQuery { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]]; }); ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); } - (void)testDeleteNonMatchingBeforeMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testDeleteNonMatchingAfterMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]]; }); } - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject { ExpectNoChange(self, ^(RLMRealm *realm) { // Make a matching object be the last row [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]]; // Delete a non-last, non-match row so that a matched row is moved [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching { ExpectChange(self, @[@1], @[@3], @[], ^(RLMRealm *realm) { // Make the last object match the query [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3]; // Move the now-matching object over a previously matching object [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); } - (void)testDeleteNewlyInsertedRowMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array addObject:[IntObject createInRealm:realm withValue:@[@3]]]; [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject]; }); } - (void)testInsertObjectMatchingQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array addObject:[IntObject createInRealm:realm withValue:@[@3]]]; }); } - (void)testInsertObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array addObject:[IntObject createInRealm:realm withValue:@[@5]]]; }); } - (void)testInsertBothMatchingAndNonMatching { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [array addObject:[IntObject createInRealm:realm withValue:@[@3]]]; }); } - (void)testInsertMultipleMatching { ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [array addObject:[IntObject createInRealm:realm withValue:@[@3]]]; [array addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [array addObject:[IntObject createInRealm:realm withValue:@[@2]]]; }); } - (void)testInsertAtIndex { ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; IntObject *io = [IntObject createInRealm:realm withValue:@[@3]]; [array insertObject:io atIndex:0]; }); ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; IntObject *io = [IntObject createInRealm:realm withValue:@[@3]]; [array insertObject:io atIndex:1]; }); ExpectChange(self, @[], @[@1], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; IntObject *io = [IntObject createInRealm:realm withValue:@[@3]]; [array insertObject:io atIndex:2]; }); ExpectNoChange(self, ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; IntObject *io = [IntObject createInRealm:realm withValue:@[@5]]; [array insertObject:io atIndex:2]; }); } - (void)testExchangeObjects { // adjacent swap: one move, since second is redundant // ExpectChange(self, @[@1, @0], @[], @[], ^(RLMRealm *realm) { // RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; // [array exchangeObjectAtIndex:1 withObjectAtIndex:2]; // }); // non-adjacent: two moves needed // ExpectChange(self, @[@0, @2], ^(RLMRealm *realm) { // RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; // [array exchangeObjectAtIndex:1 withObjectAtIndex:3]; // }); } - (void)testRemoveFromArray { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array removeObjectAtIndex:1]; }); ExpectNoChange(self, ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array removeObjectAtIndex:0]; }); } - (void)testClearArray { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array removeAllObjects]; }); } - (void)testDeleteArray { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[ArrayPropertyObject allObjectsInRealm:realm]]; }); } - (void)testDeleteAlreadyEmptyArray { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [ArrayPropertyObject createInRealm:realm withValue:@[]]; }]; RLMArray *array = [[ArrayPropertyObject allObjectsInRealm:realm].firstObject intArray]; __block RLMCollectionChange *changes; __block int calls = 0; id token = [array addNotificationBlock:^(RLMArray *results, RLMCollectionChange *c, NSError *error) { XCTAssertNotNil(results); XCTAssertNil(error); changes = c; ++calls; CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteObjects:[ArrayPropertyObject allObjectsInRealm:realm]]; }]; }]; [(RLMNotificationToken *)token invalidate]; XCTAssertEqual(calls, 1); } - (void)testModifyObjectShiftedByInsertsAndDeletions { ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }); ExpectChange(self, @[], @[@0], @[@3], ^(RLMRealm *realm) { RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray]; [array insertObject:[IntObject createInRealm:realm withValue:@[@3]] atIndex:0]; [[IntObject objectsInRealm:realm where:@"intCol = 4"] setValue:@3 forKey:@"intCol"]; }); } @end @interface LinkViewSetChangesetTests : RLMTestCase @end @implementation LinkViewSetChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; for (int i = 0; i < 10; ++i) { [IntObject createInDefaultRealmWithValue:@[@(i)]]; } [SetPropertyObject createInDefaultRealmWithValue:@[@"", @[], [IntObject allObjectsInRealm:realm]]]; }]; } } - (RLMResults *)query { return [[[SetPropertyObject.allObjects firstObject] intSet] objectsWhere:@"intCol > 0 AND intCol < 5"]; } - (void)testDeleteMultiple { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]]; [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]]; }); } - (void)testModifyObjectMatchingQuery { ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"]; }); } - (void)testModifyObjectToNoLongerMatchQuery { ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"]; }); } - (void)testModifyObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"]; }); } - (void)testModifyObjectToMatchQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"]; }); } - (void)testDeleteObjectMatchingQuery { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]]; }); ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]]; }); } - (void)testDeleteNonMatchingBeforeMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testDeleteNonMatchingAfterMatches { ExpectNoChange(self, ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]]; }); } - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject { ExpectNoChange(self, ^(RLMRealm *realm) { // Make a matching object be the last row [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]]; // Delete a non-last, non-match row so that a matched row is moved [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]]; }); } - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching { ExpectChange(self, @[@1], @[@3], @[], ^(RLMRealm *realm) { // Make the last object match the query [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3]; // Move the now-matching object over a previously matching object [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; }); } - (void)testDeleteNewlyInsertedRowMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set addObject:[IntObject createInRealm:realm withValue:@[@3]]]; [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject]; }); } - (void)testInsertObjectMatchingQuery { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set addObject:[IntObject createInRealm:realm withValue:@[@3]]]; }); } - (void)testInsertObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set addObject:[IntObject createInRealm:realm withValue:@[@5]]]; }); } - (void)testInsertBothMatchingAndNonMatching { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [set addObject:[IntObject createInRealm:realm withValue:@[@3]]]; }); } - (void)testInsertMultipleMatching { ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [set addObject:[IntObject createInRealm:realm withValue:@[@3]]]; [set addObject:[IntObject createInRealm:realm withValue:@[@5]]]; [set addObject:[IntObject createInRealm:realm withValue:@[@2]]]; }); } - (void)testRemoveFromSet { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set removeObject:set.allObjects[1]]; }); ExpectNoChange(self, ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set removeObject:set.allObjects[0]]; }); } - (void)testClearSet { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { RLMSet *set = [[[SetPropertyObject allObjectsInRealm:realm] firstObject] intSet]; [set removeAllObjects]; }); } - (void)testDeleteSet { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[SetPropertyObject allObjectsInRealm:realm]]; }); } - (void)testModifyObjectShiftedByInsertsAndDeletions { ExpectChange(self, @[@0, @1], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]]; [[IntObject objectsInRealm:realm where:@"intCol = 1"] setValue:@10 forKey:@"intCol"]; }); ExpectNoChange(self, ^(RLMRealm *realm) { [[IntObject objectsInRealm:realm where:@"intCol = 9"] setValue:@11 forKey:@"intCol"]; }); } @end @interface DictionaryChangesetTests : RLMTestCase @end @implementation DictionaryChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ DictionaryPropertyObject *dictObj = [DictionaryPropertyObject new]; [realm deleteAllObjects]; for (int i = 0; i < 10; ++i) { IntObject *intObject = [IntObject createInDefaultRealmWithValue:@[@(i)]]; NSString *key = [NSString stringWithFormat:@"key%d", i]; [dictObj.intObjDictionary setObject:intObject forKey:key]; } [realm addObject:dictObj]; }]; } } - (RLMResults *)query { return [[[DictionaryPropertyObject.allObjects firstObject] intObjDictionary] objectsWhere:@"intCol > 0 AND intCol < 5"]; } - (void)testDeleteNewlyInsertedRowMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; dictionary[@"anotherKey"] = [IntObject createInRealm:realm withValue:@[@3]]; [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject]; }); } - (void)testInsertObjectMatchingQuery { ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; dictionary[@"key"] = [IntObject createInRealm:realm withValue:@[@3]]; }); } - (void)testInsertObjectNotMatchingQuery { ExpectNoChange(self, ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; dictionary[@"key"] = [IntObject createInRealm:realm withValue:@[@5]]; }); } - (void)testInsertBothMatchingAndNonMatching { ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; dictionary[@"keyA"] = [IntObject createInRealm:realm withValue:@[@5]]; dictionary[@"keyB"] = [IntObject createInRealm:realm withValue:@[@3]]; }); } - (void)testInsertMultipleMatching { ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; dictionary[@"keyA"] = [IntObject createInRealm:realm withValue:@[@5]]; dictionary[@"keyB"] = [IntObject createInRealm:realm withValue:@[@3]]; dictionary[@"keyC"] = [IntObject createInRealm:realm withValue:@[@5]]; dictionary[@"keyD"] = [IntObject createInRealm:realm withValue:@[@2]]; }); } - (void)testRemoveFromDictionary { ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; [dictionary removeObjectForKey:@"key1"]; }); ExpectNoChange(self, ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; [dictionary removeObjectForKey:@"key0"]; }); } - (void)testClearDictionary { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { RLMDictionary *dictionary = [[[DictionaryPropertyObject allObjectsInRealm:realm] firstObject] intObjDictionary]; [dictionary removeAllObjects]; }); } - (void)testDeleteDictionary { ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[DictionaryPropertyObject allObjectsInRealm:realm]]; }); } @end @interface LinkedObjectChangesetTests : RLMTestCase @end @implementation LinkedObjectChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; for (int i = 0; i < 5; ++i) { [LinkStringObject createInRealm:realm withValue:@[[StringObject createInRealm:realm withValue:@[@""]]]]; } }]; } } - (RLMResults *)query { return LinkStringObject.allObjects; } - (void)testDeleteLinkedObject { ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) { [realm deleteObject:[StringObject allObjectsInRealm:realm][3]]; }); } - (void)testModifyLinkedObject { ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) { [[StringObject allObjectsInRealm:realm][3] setStringCol:@"a"]; }); } - (void)testInsertUnlinkedObject { ExpectNoChange(self, ^(RLMRealm *realm) { [StringObject createInRealm:realm withValue:@[@""]]; }); } - (void)testTableClearFollowedByInsertsAndDeletes { ExpectChange(self, @[], @[], @[@0, @1, @2, @3, @4], ^(RLMRealm *realm) { [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; [StringObject createInRealm:realm withValue:@[@""]]; [realm deleteObject:[StringObject createInRealm:realm withValue:@[@""]]]; }); } @end @interface LinkingObjectsChangesetTests : RLMTestCase @end @implementation LinkingObjectsChangesetTests - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; PersonObject *child = [PersonObject createInDefaultRealmWithValue:@[ @"Child", @0 ]]; for (int i = 0; i < 10; ++i) { // It takes a village to raise a child… NSString *name = [NSString stringWithFormat:@"Parent %d", i]; [PersonObject createInDefaultRealmWithValue:@[ name, @(25 + i), @[ child ]]]; } }]; } } - (RLMResults *)query { return [[PersonObject.allObjects firstObject] parents]; } - (void)testDeleteOneLinkingObject { ExpectChange(self, @[@5, @9], @[@5], @[], ^(RLMRealm *realm) { [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 30"]]; }); } - (void)testDeleteSomeLinkingObjects { ExpectChange(self, @[@2, @7, @8, @9], @[@2], @[], ^(RLMRealm *realm) { [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 32"]]; [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 27"]]; }); } - (void)testDeleteAllLinkingObjects { ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 20"]]; }); } - (void)testDeleteAll { ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) { [realm deleteObjects:[PersonObject allObjectsInRealm:realm]]; }); } - (void)testUnlinkOne { ExpectChange(self, @[@4, @9], @[@4], @[], ^(RLMRealm *realm) { PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 29"] firstObject]; [parent.children removeAllObjects]; }); } - (void)testUnlinkAll { ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) { for (PersonObject *parent in [PersonObject objectsInRealm:realm where:@"age > 20"]) [parent.children removeAllObjects]; }); } - (void)testAddNewParent { ExpectChange(self, @[], @[@10], @[], ^(RLMRealm *realm) { PersonObject *child = [[PersonObject objectsInRealm:realm where:@"children.@count == 0"] firstObject]; [PersonObject createInDefaultRealmWithValue:@[ @"New parent", @40, @[ child ]]]; }); } - (void)testAddDuplicateParent { ExpectChange(self, @[], @[@10], @[@7], ^(RLMRealm *realm) { PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 32"] firstObject]; [parent.children addObject:[parent.children firstObject]]; }); } - (void)testModifyParent { ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) { PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 28"] firstObject]; parent.age = parent.age + 1; }); } @end @interface AllTypesWithPrimaryKey : RLMObject @property BOOL boolCol; @property int intCol; @property float floatCol; @property double doubleCol; @property NSString *stringCol; @property NSData *binaryCol; @property NSDate *dateCol; @property bool cBoolCol; @property int64_t longCol; @property RLMObjectId *objectIdCol; @property RLMDecimal128 *decimalCol; @property StringObject *objectCol; @property NSUUID *uuidCol; @property id anyCol; @property MixedObject *mixedObjectCol; @property (nonatomic) int pk; @end @implementation AllTypesWithPrimaryKey + (NSString *)primaryKey { return @"pk"; } @end // clang thinks the tests below have retain cycles because `_obj` could retain // the block passed to addNotificationBlock (but it doesn't) #pragma clang diagnostic ignored "-Warc-retain-cycles" @interface ObjectNotifierTests : RLMTestCase @end @implementation ObjectNotifierTests { NSDictionary *_initialValues; NSDictionary *_values; NSArray *_propertyNames; AllTypesObject *_obj; } - (void)setUp { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"string"; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = @"string"; _initialValues = [AllTypesObject values:1 stringObject:nil mixedObject:nil]; _values = [AllTypesObject values:2 stringObject:so mixedObject:mo]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; _obj = [AllTypesObject createInRealm:realm withValue:_initialValues]; [realm commitWriteTransaction]; _propertyNames = [_obj.objectSchema.properties valueForKey:@"name"]; } - (void)tearDown { _values = nil; _initialValues = nil; _obj = nil; [super tearDown]; } - (void)testObserveUnmanagedObject { AllTypesObject *unmanagedObj = [[AllTypesObject alloc] init]; XCTAssertThrows([unmanagedObj addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) {}]); XCTAssertThrows([unmanagedObj addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) {} keyPaths:@[@"boolCol"]]); } - (void)testDeleteObservedObject { XCTestExpectation *expectation0 = [self expectationWithDescription:@"delete observed object"]; XCTestExpectation *expectation1 = [self expectationWithDescription:@"delete observed object"]; RLMNotificationToken *token0 = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertTrue(deleted); XCTAssertNil(error); XCTAssertNil(changes); [expectation0 fulfill]; }]; RLMNotificationToken *token1 = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertTrue(deleted); XCTAssertNil(error); XCTAssertNil(changes); [expectation1 fulfill]; } keyPaths:@[@"boolCol"]]; RLMRealm *realm = _obj.realm; [realm beginWriteTransaction]; [realm deleteObject:_obj]; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token0 invalidate]; [token1 invalidate]; } - (void)testChangeAllPropertyTypes { __block NSString *property; __block XCTestExpectation *expectation = nil; RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); RLMPropertyChange *prop = changes[0]; XCTAssertEqualObjects(prop.name, property); XCTAssertNil(prop.previousValue); if ([prop.name isEqualToString:@"objectCol"]) { XCTAssertTrue([prop.value isEqualToObject:_values[property]], @"%@: %@ %@", property, prop.value, _values[property]); } else if ([prop.name isEqualToString:@"mixedObjectCol"]) { XCTAssertEqualObjects(((MixedObject *)prop.value).anyCol, ((MixedObject *)_values[property]).anyCol); } else { XCTAssertEqualObjects(prop.value, _values[property]); } [expectation fulfill]; }]; for (property in _propertyNames) { expectation = [self expectationWithDescription:@""]; [_obj.realm beginWriteTransaction]; _obj[property] = _values[property]; [_obj.realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } [token invalidate]; } - (void)testChangeAllPropertyTypesFromBackground { __block NSString *propertyName; __block RLMThreadSafeReference *mixedObject; RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); RLMPropertyChange *prop = changes[0]; XCTAssertEqualObjects(prop.name, propertyName); if ([prop.name isEqualToString:@"objectCol"]) { XCTAssertNil(prop.previousValue); XCTAssertNotNil(prop.value); } else if ([prop.name isEqualToString:@"mixedObjectCol"]) { XCTAssertNil(prop.previousValue); RLMRealm *realm = [RLMRealm defaultRealm]; MixedObject *mo = [realm resolveThreadSafeReference:mixedObject]; XCTAssertEqualObjects(((MixedObject *)prop.value).anyCol, mo.anyCol); } else { XCTAssertEqualObjects(prop.previousValue, _initialValues[prop.name]); XCTAssertEqualObjects(prop.value, _values[prop.name]); } }]; for (propertyName in _propertyNames) { [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; obj[propertyName] = _values[propertyName]; if ([propertyName isEqualToString:@"mixedObjectCol"]) { mixedObject = [RLMThreadSafeReference referenceWithThreadConfined:_values[@"mixedObjectCol"]]; } [realm commitWriteTransaction]; }]; [_obj.realm refresh]; } [token invalidate]; } - (void)testChangeAllPropertyTypesInSingleTransaction { XCTestExpectation *expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, _values.count); NSUInteger i = 0; for (RLMPropertyChange *prop in changes) { XCTAssertEqualObjects(prop.name, _propertyNames[i]); if ([prop.name isEqualToString:@"objectCol"]) { XCTAssertTrue([prop.value isEqualToObject:_values[prop.name]]); } else if ([prop.name isEqualToString:@"mixedObjectCol"]) { XCTAssertEqualObjects(((MixedObject *)prop.value).anyCol, ((MixedObject *)_values[prop.name]).anyCol); } else { XCTAssertEqualObjects(prop.value, _values[prop.name]); } ++i; } [expectation fulfill]; }]; [_obj.realm beginWriteTransaction]; for (NSString *propertyName in _propertyNames) { _obj[propertyName] = _values[propertyName]; } [_obj.realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testMultipleObjectNotifiers { [_obj.realm beginWriteTransaction]; AllTypesObject *obj2 = [AllTypesObject createInRealm:_obj.realm withValue:_obj]; [_obj.realm commitWriteTransaction]; XCTestExpectation *expectation = [self expectationWithDescription:@""]; __block NSUInteger calls = 0; id block = ^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); XCTAssertEqualObjects(changes[0].name, @"intCol"); XCTAssertEqualObjects(changes[0].previousValue, @1); XCTAssertEqualObjects(changes[0].value, @2); if (++calls == 2) { [expectation fulfill]; } }; RLMNotificationToken *token1 = [_obj addNotificationBlock:block]; RLMNotificationToken *token2 = [_obj addNotificationBlock:block]; RLMNotificationToken *token3 = [obj2 addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) { XCTFail(@"notification block for wrong object called"); }]; // Ensure initial notification is processed so that the change can report previousValue [_obj.realm transactionWithBlock:^{}]; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealm]; AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; obj.intCol = 2; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token1 invalidate]; [token2 invalidate]; [token3 invalidate]; } - (void)testArrayPropertiesMerelyReportModification { [_obj.realm beginWriteTransaction]; ArrayOfAllTypesObject *array = [ArrayOfAllTypesObject createInRealm:_obj.realm withValue:@[@[]]]; [_obj.realm commitWriteTransaction]; XCTestExpectation *expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [array addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); XCTAssertEqualObjects(changes[0].name, @"array"); XCTAssertNil(changes[0].previousValue); XCTAssertNil(changes[0].value); [expectation fulfill]; }]; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealm]; ArrayOfAllTypesObject *obj = [[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; [obj.array addObject:[[AllTypesObject allObjectsInRealm:realm] firstObject]]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testDiffedUpdatesOnlyNotifyForPropertiesWhichActuallyChanged { NSMutableDictionary *values = [_initialValues mutableCopy]; values[@"pk"] = @1; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:_values[@"objectCol"]]; AllTypesWithPrimaryKey *obj = [AllTypesWithPrimaryKey createInRealm:realm withValue:values]; [realm commitWriteTransaction]; __block NSString *propertyName; __block XCTestExpectation *expectation = nil; RLMNotificationToken *token = [obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); RLMPropertyChange *prop = changes[0]; XCTAssertEqualObjects(prop.name, propertyName); XCTAssertNil(prop.previousValue); if ([prop.name isEqualToString:@"objectCol"]) { XCTAssertTrue([prop.value isEqualToObject:_values[prop.name]], @"%@: %@ %@", prop.name, prop.value, _values[prop.name]); } else if ([prop.name isEqualToString:@"mixedObjectCol"]) { XCTAssertEqualObjects(((MixedObject *)prop.value).anyCol, ((MixedObject *)_values[prop.name]).anyCol); } else { XCTAssertEqualObjects(prop.value, _values[prop.name]); } [expectation fulfill]; }]; for (propertyName in _propertyNames) { expectation = [self expectationWithDescription:propertyName]; [realm beginWriteTransaction]; values[propertyName] = _values[propertyName]; [AllTypesWithPrimaryKey createOrUpdateModifiedInRealm:realm withValue:values]; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } [token invalidate]; } #pragma mark - Object Notification Key Path Filtering - (void)testModifyObservedKeyPathLocally { XCTestExpectation *ex = [self expectationWithDescription:@"change notification"]; RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); RLMPropertyChange *prop = changes[0]; XCTAssertEqualObjects(prop.name, @"boolCol"); [ex fulfill]; } keyPaths:@[@"boolCol"]]; [_obj.realm beginWriteTransaction]; XCTAssertNotEqual(_obj.boolCol, [_values[@"boolCol"] boolValue]); _obj.boolCol = [_values[@"boolCol"] boolValue]; [_obj.realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } - (void)testModifyUnobservedKeyPathLocally { XCTestExpectation *ex = [self expectationWithDescription:@"no change notification"]; ex.inverted = true; RLMNotificationToken *token = [_obj addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) { [ex fulfill]; } keyPaths:@[@"boolCol"]]; [_obj.realm beginWriteTransaction]; _obj.intCol = _obj.intCol + _obj.intCol; [_obj.realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } - (void)testModifyObservedKeyPathRemotely { XCTestExpectation *ex = [self expectationWithDescription:@"change notification"]; RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); RLMPropertyChange *prop = changes[0]; XCTAssertEqualObjects(prop.name, @"boolCol"); [ex fulfill]; } keyPaths:@[@"boolCol"]]; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealm]; AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; XCTAssertNotEqual(obj.boolCol, [_values[@"boolCol"] boolValue]); obj.boolCol = [_values[@"boolCol"] boolValue]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } - (void)testModifyUnobservedKeyPathRemotely { XCTestExpectation *ex = [self expectationWithDescription:@"no change notification"]; ex.inverted = true; RLMNotificationToken *token = [_obj addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) { [ex fulfill]; } keyPaths:@[@"boolCol"]]; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealm]; AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; obj.intCol = obj.intCol + obj.intCol; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } - (void)testModifyObservedKeyPathArrayProperty { XCTestExpectation *ex = [self expectationWithDescription:@"change notification"]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInRealm:realm withValue:@{}]; EmployeeObject *employee = [EmployeeObject createInRealm:realm withValue:@{@"age": @30, @"hired": @NO}]; [company.employees addObject:employee]; [realm commitWriteTransaction]; RLMNotificationToken *token = [company addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1U); for (RLMPropertyChange *prop in changes) { XCTAssertEqualObjects(prop.name, @"employees"); XCTAssertFalse(prop.previousValue); XCTAssertFalse(prop.value); // Observing an array will lead to nil here. } [ex fulfill]; } keyPaths:@[@"employees.hired"]]; [realm beginWriteTransaction]; employee.hired = true; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } - (void)testModifyUnobservedKeyPathArrayProperty { XCTestExpectation *ex = [self expectationWithDescription:@"no change notification"]; ex.inverted = true; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInRealm:realm withValue:@{}]; EmployeeObject *employee = [EmployeeObject createInRealm:realm withValue:@{@"age": @30, @"hired": @NO}]; [company.employees addObject:employee]; [realm commitWriteTransaction]; RLMNotificationToken *token = [company addNotificationBlock:^(__unused BOOL deletd, __unused NSArray *changes, __unused NSError *error) { [ex fulfill]; } keyPaths:@[@"employees.hired"]]; [realm beginWriteTransaction]; employee.age = 42; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:0.1 handler:nil]; [token invalidate]; } @end static void ExpectObjectChange(RLMTestCase *self, void (^block)(RLMRealm *realm)) { [self prepare]; RLMResults *query = [self query]; __block XCTestExpectation *expectation = nil; RLMNotificationToken *token = [[query firstObject] addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1); [expectation fulfill]; }]; RLMRealm *realm = [RLMRealm defaultRealm]; expectation = [self expectationWithDescription:@"collections_mixed_notifications"]; [realm beginWriteTransaction]; block(realm); [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [token invalidate]; } static void ExpectMixedDictionaryChange(RLMTestCase *self, NSArray *deletions, NSArray *insertions, NSArray *modifications, void (^block)(RLMRealm *realm)) { [self prepare]; MixedObject *mixedObject = [[self query] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; __block XCTestExpectation *expectation = nil; RLMNotificationToken *token = [dictionary addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *changes, NSError *error) { XCTAssertNil(error); XCTAssertNotNil(dictionary); if (!changes) { return; } XCTAssertEqualObjects(deletions, changes.deletions); XCTAssertEqualObjects(insertions, changes.insertions); XCTAssertEqualObjects(modifications, changes.modifications); [expectation fulfill]; }]; RLMRealm *realm = [RLMRealm defaultRealm]; expectation = [self expectationWithDescription:@"collections_mixed_notifications"]; [realm beginWriteTransaction]; block(realm); [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [token invalidate]; } @interface MixedDictionaryCollectionChangesetTests : RLMTestCase @end @implementation MixedDictionaryCollectionChangesetTests - (NSDictionary *)testDictionary { return @{ @"key2" : @"hello2", @"key3" : @YES, @"key4" : @123, @"key5" : @456.789 }; } - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; MixedObject *mixedObject = [MixedObject new]; mixedObject.anyCol = [self testDictionary]; [realm addObject:mixedObject]; }]; } } - (RLMResults *)query { return MixedObject.allObjects; } - (void)testAddToMixedObservationWithKeypath { [self prepare]; __block XCTestExpectation *expectation = nil; RLMRealm *realm = [RLMRealm defaultRealm]; MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMNotificationToken *token = [mixedObject addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1); [expectation fulfill]; } keyPaths:@[ @"anyCol" ]]; expectation = [self expectationWithDescription:@"collections_mixed_notifications"]; [realm beginWriteTransaction]; mixedObject.anyCol = @{ @"key2": @987.321 }; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [token invalidate]; } - (void)testMixedDictionaryChangesInResults { ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = [self testDictionary]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = @{ @"key": @987.321 }; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dict = (RLMDictionary *)mixedObject.anyCol; dict[@"key"] = @NO; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dict = (RLMDictionary *)mixedObject.anyCol; dict[@"key1"] = @"newvalue"; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dict = (RLMDictionary *)mixedObject.anyCol; [dict removeAllObjects]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dict = (RLMDictionary *)mixedObject.anyCol; [dict removeObjectForKey:@"key5"]; }); } - (void)testMixedDictionaryObjectNotifications { ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = [self testDictionary]; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; dictionary[@"key2"] = @NO; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; dictionary[@"key4"] = [NSNull null]; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; [dictionary removeAllObjects]; }); } - (void)testMixedDictionaryCollectionChanges { ExpectMixedDictionaryChange(self, @[@"key2", @"key3", @"key4", @"key5"], @[@"key1", @"key3"], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = @{ @"key1": @YES, @"key3": @987.321 }; }); ExpectMixedDictionaryChange(self, @[], @[], @[@"key3"], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; dictionary[@"key3"] = @NO; }); ExpectMixedDictionaryChange(self, @[@"key4"], @[], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; dictionary[@"key4"] = nil; }); ExpectMixedDictionaryChange(self, @[], @[@"key1"], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; dictionary[@"key1"] = @345; }); ExpectMixedDictionaryChange(self, @[@"key2"], @[], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; [dictionary removeObjectForKey:@"key2"]; }); ExpectMixedDictionaryChange(self, @[@"key2", @"key3", @"key4", @"key5"], @[], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)mixedObject.anyCol; [dictionary removeAllObjects]; }); } @end static void ExpectMixedArrayChange(RLMTestCase *self, NSArray *deletions, NSArray *insertions, NSArray *modifications, void (^block)(RLMRealm *realm)) { [self prepare]; MixedObject *mixedObject = [[self query] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; __block XCTestExpectation *expectation = nil; RLMNotificationToken *token = [array addNotificationBlock:^(RLMArray *array, RLMCollectionChange *changes, NSError *error) { XCTAssertNil(error); XCTAssertNotNil(array); if (!changes) { return; } XCTAssertEqualObjects(deletions, changes.deletions); XCTAssertEqualObjects(insertions, changes.insertions); XCTAssertEqualObjects(modifications, changes.modifications); [expectation fulfill]; }]; RLMRealm *realm = [RLMRealm defaultRealm]; expectation = [self expectationWithDescription:@"collections_mixed_notifications"]; [realm beginWriteTransaction]; block(realm); [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [token invalidate]; } @interface MixedArrayCollectionChangesetTests : RLMTestCase @end @implementation MixedArrayCollectionChangesetTests - (NSArray *)testArray { return @[ @"hello2", @YES, @123, @456.789 ]; } - (void)prepare { @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; MixedObject *mixedObject = [MixedObject new]; mixedObject.anyCol = [self testArray]; [realm addObject:mixedObject]; }]; } } - (RLMResults *)query { return MixedObject.allObjects; } - (void)testAddToMixedObservationWithKeypath { [self prepare]; __block XCTestExpectation *expectation = nil; RLMRealm *realm = [RLMRealm defaultRealm]; MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMNotificationToken *token = [mixedObject addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) { XCTAssertFalse(deleted); XCTAssertNil(error); XCTAssertEqual(changes.count, 1); [expectation fulfill]; } keyPaths:@[ @"anyCol" ]]; expectation = [self expectationWithDescription:@"collections_mixed_notifications"]; [realm beginWriteTransaction]; mixedObject.anyCol = @[ @987.321 ]; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [token invalidate]; } - (void)testMixedArrayChangesInResults { ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = [self testArray]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = @[ @987.321 ]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; array[0] = @NO; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array addObject:@"newvalue"]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array insertObject:@765 atIndex:1]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeLastObject]; }); ExpectChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeAllObjects]; }); } - (void)testMixedArrayObjectNotifications { ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = [self testArray]; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array addObject:@NO]; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; array[3] = @"hey"; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeObjectAtIndex:2]; }); ExpectObjectChange(self, ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeAllObjects]; }); } - (void)testMixedArrayCollectionChanges { ExpectMixedArrayChange(self, @[@0, @1, @2, @3], @[@0, @1], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; mixedObject.anyCol = @[ @YES, @987.321 ]; }); ExpectMixedArrayChange(self, @[], @[@4], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array addObject: @NO]; }); ExpectMixedArrayChange(self, @[], @[@1], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array insertObject:@"hey" atIndex:1]; }); ExpectMixedArrayChange(self, @[], @[], @[@0], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; array[0] = [RLMObjectId objectId]; }); ExpectMixedArrayChange(self, @[@3], @[], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeLastObject]; }); ExpectMixedArrayChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) { MixedObject *mixedObject = [[MixedObject allObjectsInRealm:realm] firstObject]; RLMArray *array = (RLMArray *)mixedObject.anyCol; [array removeAllObjects]; }); } @end ================================================ FILE: Realm/Tests/ObjectCreationTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMTestCase.h" #pragma mark - Test Objects @interface DogExtraObject : RLMObject @property NSString *dogName; @property int age; @property NSString *breed; @end @implementation DogExtraObject @end @interface BizzaroDog : RLMObject @property int dogName; @property NSString *age; @end @implementation BizzaroDog @end @interface PrimaryKeyWithDefault : RLMObject @property NSString *stringCol; @property int intCol; @end @implementation PrimaryKeyWithDefault + (NSString *)primaryKey { return @"stringCol"; } + (NSDictionary *)defaultPropertyValues { return @{@"intCol": @10}; } @end @interface AllLinks : RLMObject @property StringObject *string; @property PrimaryStringObject *primaryString; @property RLM_GENERIC_ARRAY(IntObject) *intArray; @property RLM_GENERIC_ARRAY(PrimaryIntObject) *primaryIntArray; @property RLM_GENERIC_SET(IntObject) *intSet; @property RLM_GENERIC_SET(PrimaryIntObject) *primaryIntSet; @end @implementation AllLinks @end @interface AllLinksWithPrimary : RLMObject @property NSString *pk; @property StringObject *string; @property PrimaryStringObject *primaryString; @property RLM_GENERIC_ARRAY(IntObject) *intArray; @property RLM_GENERIC_ARRAY(PrimaryIntObject) *primaryIntArray; @property RLM_GENERIC_SET(IntObject) *intSet; @property RLM_GENERIC_SET(PrimaryIntObject) *primaryIntSet; @end @implementation AllLinksWithPrimary + (NSString *)primaryKey { return @"pk"; } @end @interface PrimaryKeyAndRequiredString : RLMObject @property int pk; @property NSString *value; @end @implementation PrimaryKeyAndRequiredString + (NSString *)primaryKey { return @"pk"; } + (NSArray *)requiredProperties { return @[@"value"]; } @end #pragma mark - Tests @interface ObjectCreationTests : RLMTestCase @end @implementation ObjectCreationTests #pragma mark - Init With Value - (void)testInitWithInvalidThings { RLMAssertThrowsWithReasonMatching([[DogObject alloc] initWithValue:self.nonLiteralNil], @"Must provide a non-nil value"); RLMAssertThrowsWithReasonMatching([[DogObject alloc] initWithValue:NSNull.null], @"Must provide a non-nil value"); RLMAssertThrowsWithReasonMatching([[DogObject alloc] initWithValue:@"name"], @"Invalid value 'name' to initialize object of type 'DogObject'"); } - (void)testInitWithArray { auto co = [[CompanyObject alloc] initWithValue:@[]]; XCTAssertNil(co.name); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company"]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company", NSNull.null]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company", @[]]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[@[@"name", @2, @YES]]]]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); EmployeeObject *eo = co.employees.firstObject; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[eo]]]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); eo = co.employees.firstObject; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); } - (void)testInitWithSet { auto co = [[CompanyObject alloc] initWithValue:@[]]; XCTAssertNil(co.name); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company"]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company", NSNull.null]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"empty company", @[]]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[@[@"name", @2, @YES]], @[@[@"name", @2, @YES]]]]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employeeSet.count, 1U); EmployeeObject *eo = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[eo], @[eo]]]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employeeSet.count, 1U); eo = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); } - (void)testWithNonArrayEnumerableForRLMArrayProperty { auto employees = @[@[@"name", @2, @YES], @[@"name 2", @3, @NO]]; auto co = [[CompanyObject alloc] initWithValue:@[@"one employee", employees.reverseObjectEnumerator]]; XCTAssertEqual(2U, co.employees.count); XCTAssertEqualObjects(@"name 2", co.employees[0].name); XCTAssertEqualObjects(@"name", co.employees[1].name); } - (void)testWithNonSetEnumerableForRLMSetProperty { auto employees = @[@[@"name", @2, @YES], @[@"name 2", @3, @NO]]; auto co = [[CompanyObject alloc] initWithValue:@[@"one employee", employees.reverseObjectEnumerator, employees.reverseObjectEnumerator]]; XCTAssertEqual(2U, co.employeeSet.count); XCTAssertTrue([[co.employeeSet valueForKey:@"name"] containsObject:@"name"]); XCTAssertTrue([[co.employeeSet valueForKey:@"name"] containsObject:@"name 2"]); } - (void)testInitWithArrayUsesDefaultValuesForMissingFields { auto obj = [[NumberDefaultsObject alloc] initWithValue:@[]]; XCTAssertEqualObjects(obj.intObj, @1); XCTAssertEqualObjects(obj.floatObj, @2.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); obj = [[NumberDefaultsObject alloc] initWithValue:@[@10, @22.2f]]; XCTAssertEqualObjects(obj.intObj, @10); XCTAssertEqualObjects(obj.floatObj, @22.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); } - (void)testInitWithInvalidArray { RLMAssertThrowsWithReason(([[DogObject alloc] initWithValue:@[@"name", @"age"]]), @"Invalid value 'age' of type '__NSCFConstantString' for 'int' property 'DogObject.age'."); RLMAssertThrowsWithReason(([[DogObject alloc] initWithValue:@[@"name", NSNull.null]]), @"Invalid value '(null)' of type '(null)' for 'int' property 'DogObject.age'."); RLMAssertThrowsWithReason(([[DogObject alloc] initWithValue:@[@"name", @5, @"too many values"]]), @"Invalid array input: more values (3) than properties (2)."); } - (void)testInitWithDictionary { auto co = [[CompanyObject alloc] initWithValue:@{}]; XCTAssertNil(co.name); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@{@"name": NSNull.null}]; XCTAssertNil(co.name); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@{@"name": @"empty company"}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); co = [[CompanyObject alloc] initWithValue:@{@"name": @"empty company", @"employees": NSNull.null, @"employeeSet": NSNull.null}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@{@"name": @"empty company", @"employees": @[], @"employeeSet": @[]}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [[CompanyObject alloc] initWithValue:@{@"name": @"one employee", @"employees": @[@[@"name", @2, @YES]], @"employeeSet": @[@[@"name", @2, @YES]]}]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); XCTAssertEqual(co.employeeSet.count, 1U); EmployeeObject *eo = co.employees.firstObject; EmployeeObject *eo2 = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqualObjects(eo2.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); XCTAssertEqual(eo2.age, 2); XCTAssertEqual(eo2.hired, YES); co = [[CompanyObject alloc] initWithValue:@{@"name": @"one employee", @"employees": @[@{@"name": @"name", @"age": @2, @"hired": @YES}], @"employeeSet": @[@{@"name": @"name", @"age": @2, @"hired": @YES}]}]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); XCTAssertEqual(co.employeeSet.count, 1U); eo = co.employees.firstObject; eo2 = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); XCTAssertEqualObjects(eo2.name, @"name"); XCTAssertEqual(eo2.age, 2); XCTAssertEqual(eo2.hired, YES); co = [[CompanyObject alloc] initWithValue:@{@"name": @"no employees", @"extra fields": @"are okay"}]; XCTAssertEqualObjects(co.name, @"no employees"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); } - (void)testInitWithInvalidDictionary { RLMAssertThrowsWithReason(([[DogObject alloc] initWithValue:@{@"name": @"a", @"age": NSNull.null}]), @"Invalid value '(null)' of type '(null)' for 'int' property 'DogObject.age'"); RLMAssertThrowsWithReasonMatching(([[DogObject alloc] initWithValue:@{@"name": @"a", @"age": NSDate.date}]), @"Invalid value '20.*' of type '.*Date' for 'int' property 'DogObject.age'"); } - (void)testInitWithDictionaryUsesDefaultValuesForMissingFields { auto obj = [[NumberDefaultsObject alloc] initWithValue:@{}]; XCTAssertEqualObjects(obj.intObj, @1); XCTAssertEqualObjects(obj.floatObj, @2.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); obj = [[NumberDefaultsObject alloc] initWithValue:@{@"intObj": @10}]; XCTAssertEqualObjects(obj.intObj, @10); XCTAssertEqualObjects(obj.floatObj, @2.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); } - (void)testInitWithObject { auto eo = [[EmployeeObject alloc] init]; eo.name = @"employee name"; eo.age = 1; eo.hired = NO; auto co = [[CompanyObject alloc] init]; co.name = @"name"; [co.employees addObject:eo]; auto co2 = [[CompanyObject alloc] initWithValue:co]; XCTAssertEqualObjects(co.name, co2.name); XCTAssertEqual(co.employees[0], co2.employees[0]); // not EqualObjects as it's a shallow copy auto dogExt = [[DogExtraObject alloc] initWithValue:@[@"Fido", @12, @"Poodle"]]; auto dog = [[DogObject alloc] initWithValue:dogExt]; XCTAssertEqualObjects(dog.dogName, @"Fido"); XCTAssertEqual(dog.age, 12); auto owner = [[OwnerObject alloc] initWithValue:@[@"Alex", dogExt]]; XCTAssertEqualObjects(owner.dog.dogName, @"Fido"); auto array1 = [[AllPrimitiveArrays alloc] init]; [array1.intObj addObject:@2]; auto array2 = [[AllPrimitiveArrays alloc] initWithValue:array1]; XCTAssertEqual(array2.intObj.count, 1U); XCTAssertEqualObjects(array2.intObj.firstObject, @2); auto set1 = [[AllPrimitiveSets alloc] init]; [set1.intObj addObject:@2]; auto set2 = [[AllPrimitiveSets alloc] initWithValue:set1]; XCTAssertEqual(set2.intObj.count, 1U); XCTAssertEqualObjects(set2.intObj.allObjects[0], @2); } - (void)testInitWithInvalidObject { // No overlap in properties auto so = [[StringObject alloc] initWithValue:@[@"str"]]; RLMAssertThrowsWithReason([[IntObject alloc] initWithValue:so], @"missing key 'intCol'"); // Dog has some but not all of DogExtra's properties auto dog = [[DogObject alloc] initWithValue:@[@"Fido", @10]]; RLMAssertThrowsWithReason([[DogExtraObject alloc] initWithValue:dog], @"missing key 'breed'"); // Same property names, but different types RLMAssertThrowsWithReason([[BizzaroDog alloc] initWithValue:dog], @"Invalid value 'Fido' of type '__NSCFConstantString' for 'int' property 'BizzaroDog.dogName'"); } - (void)testInitPrimitiveArraysWithInvalidValues { RLMAssertThrowsWithReason([[AllPrimitiveArrays alloc] initWithValue:@{@"intObj": @[NSNull.null]}], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveArrays alloc] initWithValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveArrays alloc] initWithValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveArrays alloc] initWithValue:@{@"intObj": @1}], @"Invalid value (1) for 'int' array property 'AllPrimitiveArrays.intObj': value is not enumerable."); } - (void)testInitPrimitiveSetsWithInvalidValues { RLMAssertThrowsWithReason([[AllPrimitiveSets alloc] initWithValue:@{@"intObj": @[NSNull.null]}], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveSets alloc] initWithValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveSets alloc] initWithValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '" RLMConstantString "' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([[AllPrimitiveSets alloc] initWithValue:@{@"intObj": @1}], @"Invalid value (1) for 'int' set property 'AllPrimitiveSets.intObj': value is not enumerable."); } - (void)testInitWithCustomAccessors { // Create with array auto ca = [[CustomAccessorsObject alloc] initWithValue:@[@"a", @1]]; XCTAssertEqualObjects(ca.name, @"a"); XCTAssertEqual(ca.age, 1); // Create with dictionary ca = [[CustomAccessorsObject alloc] initWithValue:@{@"name": @"b", @"age": @2}]; XCTAssertEqualObjects(ca.name, @"b"); XCTAssertEqual(ca.age, 2); // Create with KVC-compatible object ca = [[CustomAccessorsObject alloc] initWithValue:ca]; XCTAssertEqualObjects(ca.name, @"b"); XCTAssertEqual(ca.age, 2); } - (void)testInitWithRenamedColumns { // Create with array auto obj = [[RenamedProperties1 alloc] initWithValue:@[@1, @"a"]]; XCTAssertEqual(obj.propA, 1); XCTAssertEqualObjects(obj.propB, @"a"); // Create with dictionary obj = [[RenamedProperties1 alloc] initWithValue:@{@"propB": @"b", @"propA": @2}]; XCTAssertEqual(obj.propA, 2); XCTAssertEqualObjects(obj.propB, @"b"); // Create with KVC-compatible object obj = [[RenamedProperties1 alloc] initWithValue:obj]; XCTAssertEqual(obj.propA, 2); XCTAssertEqualObjects(obj.propB, @"b"); } - (void)testInitAllPropertyTypes { auto now = [NSDate dateWithTimeIntervalSince1970:1]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto so = [[StringObject alloc] init]; so.stringCol = @"string"; auto ao = [[AllTypesObject alloc] initWithValue:[AllTypesObject values:1 stringObject:so]]; XCTAssertEqual(ao.boolCol, YES); XCTAssertEqual(ao.intCol, 1); XCTAssertEqual(ao.floatCol, 1.1f); XCTAssertEqual(ao.doubleCol, 1.11); XCTAssertEqualObjects(ao.stringCol, @"a"); XCTAssertEqualObjects(ao.binaryCol, bytes); XCTAssertEqualObjects(ao.decimalCol, [[RLMDecimal128 alloc] initWithNumber:@(1)]); XCTAssertEqualObjects(ao.dateCol, now); XCTAssertEqual(ao.cBoolCol, true); XCTAssertEqual(ao.longCol, INT_MAX + 1LL); XCTAssertEqual(ao.objectCol, so); auto opt = [[AllOptionalTypes alloc] initWithValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; XCTAssertNil(opt.intObj); XCTAssertNil(opt.boolObj); XCTAssertNil(opt.floatObj); XCTAssertNil(opt.doubleObj); XCTAssertNil(opt.date); XCTAssertNil(opt.data); XCTAssertNil(opt.string); opt = [[AllOptionalTypes alloc] initWithValue:@[@1, @2.2f, @3.3, @YES, @"str", bytes, now]]; XCTAssertEqualObjects(opt.intObj, @1); XCTAssertEqualObjects(opt.boolObj, @YES); XCTAssertEqualObjects(opt.floatObj, @2.2f); XCTAssertEqualObjects(opt.doubleObj, @3.3); XCTAssertEqualObjects(opt.date, now); XCTAssertEqualObjects(opt.data, bytes); XCTAssertEqualObjects(opt.string, @"str"); auto arrays = [[AllPrimitiveArrays alloc] initWithValue:@{@"intObj": @[@1, @2, @3], @"boolObj": @[@YES, @NO], @"floatObj": @[@1.1f, @2.2f], @"doubleObj": @[@3.3, @4.4], @"stringObj": @[@"a", @"b"], @"dateObj": @[now], @"dataObj": @[bytes]}]; XCTAssertEqual(3U, arrays.intObj.count); XCTAssertEqual(2U, arrays.boolObj.count); XCTAssertEqual(2U, arrays.floatObj.count); XCTAssertEqual(2U, arrays.doubleObj.count); XCTAssertEqual(2U, arrays.stringObj.count); XCTAssertEqual(1U, arrays.dateObj.count); XCTAssertEqual(1U, arrays.dataObj.count); XCTAssertEqualObjects([arrays.intObj valueForKey:@"self"], (@[@1, @2, @3])); XCTAssertEqualObjects([arrays.boolObj valueForKey:@"self"], (@[@YES, @NO])); XCTAssertEqualObjects([arrays.floatObj valueForKey:@"self"], (@[@1.1f, @2.2f])); XCTAssertEqualObjects([arrays.doubleObj valueForKey:@"self"], (@[@3.3, @4.4])); XCTAssertEqualObjects([arrays.stringObj valueForKey:@"self"], (@[@"a", @"b"])); XCTAssertEqualObjects([arrays.dateObj valueForKey:@"self"], (@[now])); XCTAssertEqualObjects([arrays.dataObj valueForKey:@"self"], (@[bytes])); auto sets = [[AllPrimitiveSets alloc] initWithValue:@{@"intObj": @[@1, @2, @3], @"boolObj": @[@YES, @NO], @"floatObj": @[@1.1f, @2.2f], @"doubleObj": @[@3.3, @4.4], @"stringObj": @[@"a", @"b"], @"dateObj": @[now], @"dataObj": @[bytes]}]; XCTAssertEqual(3U, sets.intObj.count); XCTAssertEqual(2U, sets.boolObj.count); XCTAssertEqual(2U, sets.floatObj.count); XCTAssertEqual(2U, sets.doubleObj.count); XCTAssertEqual(2U, sets.stringObj.count); XCTAssertEqual(1U, sets.dateObj.count); XCTAssertEqual(1U, sets.dataObj.count); XCTAssertTrue([[NSSet setWithArray:[[sets.intObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[@1, @2, @3])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.boolObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[@YES, @NO])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.floatObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[@1.1f, @2.2f])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.doubleObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[@3.3, @4.4])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.stringObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[@"a", @"b"])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.dateObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[now])]]); XCTAssertTrue([[NSSet setWithArray:[[sets.dataObj valueForKey:@"self"] allObjects]] isEqualToSet:[NSSet setWithArray:(@[bytes])]]); } - (void)testInitValidatesNumberTypes { XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{}])); RLMAssertThrowsWithReason(([[NumberObject alloc] initWithValue:@{@"intObj": @1.1}]), @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int?' property 'NumberObject.intObj'."); RLMAssertThrowsWithReason(([[NumberObject alloc] initWithValue:@{@"intObj": @1.1f}]), @"Invalid value '1.1' of type '" RLMConstantFloat "' for 'int?' property 'NumberObject.intObj'."); XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"boolObj": @YES}])); XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"boolObj": @1}])); XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"boolObj": @0}])); // This error is kinda bad.... RLMAssertThrowsWithReason(([[NumberObject alloc] initWithValue:@{@"boolObj": @1.0}]), @"Invalid value '1' of type '" RLMConstantDouble "' for 'bool?' property 'NumberObject.boolObj'."); RLMAssertThrowsWithReason(([[NumberObject alloc] initWithValue:@{@"boolObj": @1.0f}]), @"Invalid value '1' of type '" RLMConstantFloat "' for 'bool?' property 'NumberObject.boolObj'."); RLMAssertThrowsWithReason(([[NumberObject alloc] initWithValue:@{@"boolObj": @2}]), @"Invalid value '2' of type '" RLMConstantInt "' for 'bool?' property 'NumberObject.boolObj'."); XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"floatObj": @1.1}])); RLMAssertThrowsWithReasonMatching(([[NumberObject alloc] initWithValue:@{@"floatObj": @DBL_MAX}]), @"Invalid value '.*' of type '" RLMConstantDouble "' for 'float\\?' property 'NumberObject.floatObj'"); XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"doubleObj": @DBL_MAX}])); } #pragma mark - Create - (void)testCreateWithArray { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto co = [CompanyObject createInRealm:realm withValue:@[@"empty company", NSNull.null, NSNull.null]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [CompanyObject createInRealm:realm withValue:@[@"empty company", @[], @[]]]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [CompanyObject createInRealm:realm withValue:@[@"one employee", @[@[@"name", @2, @YES]], @[@[@"name", @2, @YES]]]]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); XCTAssertEqual(co.employeeSet.count, 1U); EmployeeObject *eo = co.employees.firstObject; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); eo = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); [realm cancelWriteTransaction]; } - (void)testCreateWithInvalidArray { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReason(([DogObject createInRealm:realm withValue:@[@"name", @"age"]]), @"Invalid value 'age' of type '__NSCFConstantString' for 'int' property 'DogObject.age'"); RLMAssertThrowsWithReason(([DogObject createInRealm:realm withValue:@[@"name", NSNull.null]]), @"Invalid value '' of type 'NSNull' for 'int' property 'DogObject.age'"); RLMAssertThrowsWithReason(([DogObject createInRealm:realm withValue:@[@"name", @5, @"too many values"]]), @"Invalid array input: more values (3) than properties (2)."); RLMAssertThrowsWithReason(([PrimaryStringObject createInRealm:realm withValue:@[]]), @"Missing value for property 'PrimaryStringObject.stringCol'"); [realm cancelWriteTransaction]; } - (void)testCreateWithDictionary { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto co = [CompanyObject createInRealm:realm withValue:@{}]; XCTAssertNil(co.name); XCTAssertEqual(co.employees.count, 0U); co = [CompanyObject createInRealm:realm withValue:@{@"name": NSNull.null}]; XCTAssertNil(co.name); XCTAssertEqual(co.employees.count, 0U); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"empty company"}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"empty company", @"employees": NSNull.null, @"employeeSet": NSNull.null}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"empty company", @"employees": @[], @"employeeSet": @[]}]; XCTAssertEqualObjects(co.name, @"empty company"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"one employee", @"employees": @[@[@"name", @2, @YES]], @"employeeSet": @[@[@"name", @2, @YES]]}]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); XCTAssertEqual(co.employeeSet.count, 1U); EmployeeObject *eo = co.employees.firstObject; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); eo = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"one employee", @"employees": @[@{@"name": @"name", @"age": @2, @"hired": @YES}], @"employeeSet": @[@{@"name": @"name", @"age": @2, @"hired": @YES}]}]; XCTAssertEqualObjects(co.name, @"one employee"); XCTAssertEqual(co.employees.count, 1U); XCTAssertEqual(co.employeeSet.count, 1U); eo = co.employees.firstObject; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); eo = co.employeeSet.allObjects[0]; XCTAssertEqualObjects(eo.name, @"name"); XCTAssertEqual(eo.age, 2); XCTAssertEqual(eo.hired, YES); co = [CompanyObject createInRealm:realm withValue:@{@"name": @"no employees", @"extra fields": @"are okay"}]; XCTAssertEqualObjects(co.name, @"no employees"); XCTAssertEqual(co.employees.count, 0U); XCTAssertEqual(co.employeeSet.count, 0U); [realm cancelWriteTransaction]; } - (void)testCreateWithInvalidDictionary { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReason(([DogObject createInRealm:realm withValue:@{@"name": @"a", @"age": NSNull.null}]), @"Invalid value '' of type 'NSNull' for 'int' property 'DogObject.age'"); RLMAssertThrowsWithReasonMatching(([DogObject createInRealm:realm withValue:@{@"name": @"a", @"age": NSDate.date}]), @"Invalid value '20.*' for 'int' property 'DogObject.age'"); [realm cancelWriteTransaction]; } - (void)testCreateWithObject { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto eo = [[EmployeeObject alloc] init]; eo.name = @"employee name"; eo.age = 1; eo.hired = NO; auto co = [[CompanyObject alloc] init]; co.name = @"name"; [co.employees addObject:eo]; [co.employeeSet addObject:eo]; auto co2 = [CompanyObject createInRealm:realm withValue:co]; XCTAssertEqualObjects(co.name, co2.name); // Deep copy, so it's a different object XCTAssertFalse([co.employees[0] isEqualToObject:co2.employees[0]]); XCTAssertEqualObjects(co.employees[0].name, co2.employees[0].name); XCTAssertFalse([co.employeeSet.allObjects[0] isEqualToObject:co2.employeeSet.allObjects[0]]); XCTAssertEqualObjects(co.employeeSet.allObjects[0].name, co2.employeeSet.allObjects[0].name); auto dogExt = [DogExtraObject createInRealm:realm withValue:@[@"Fido", @12, @"Poodle"]]; auto dog = [DogObject createInRealm:realm withValue:dogExt]; XCTAssertEqualObjects(dog.dogName, @"Fido"); XCTAssertEqual(dog.age, 12); auto owner = [OwnerObject createInRealm:realm withValue:@[@"Alex", dogExt]]; XCTAssertEqualObjects(owner.dog.dogName, @"Fido"); [realm cancelWriteTransaction]; } - (void)testCreateWithInvalidObject { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReasonMatching([DogObject createInRealm:realm withValue:self.nonLiteralNil], @"Must provide a non-nil value"); RLMAssertThrowsWithReasonMatching([DogObject createInRealm:realm withValue:NSNull.null], @"Must provide a non-nil value"); RLMAssertThrowsWithReasonMatching([DogObject createInRealm:realm withValue:@""], @"Invalid value '' to initialize object of type 'DogObject'"); // No overlap in properties auto so = [StringObject createInRealm:realm withValue:@[@"str"]]; RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:so], @"missing key 'intCol'"); // Dog has some but not all of DogExtra's properties auto dog = [DogObject createInRealm:realm withValue:@[@"Fido", @10]]; RLMAssertThrowsWithReasonMatching([DogExtraObject createInRealm:realm withValue:dog], @"missing key 'breed'"); // Same property names, but different types RLMAssertThrowsWithReasonMatching([BizzaroDog createInRealm:realm withValue:dog], @"Invalid value 'Fido' of type '.*' for 'int' property 'BizzaroDog.dogName'"); [realm cancelWriteTransaction]; } - (void)testCreateAllPropertyTypes { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate dateWithTimeIntervalSince1970:1]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto so = [[StringObject alloc] init]; so.stringCol = @"string"; auto ao = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; XCTAssertEqual(ao.boolCol, YES); XCTAssertEqual(ao.intCol, 1); XCTAssertEqual(ao.floatCol, 1.1f); XCTAssertEqual(ao.doubleCol, 1.11); XCTAssertEqualObjects(ao.stringCol, @"a"); XCTAssertEqualObjects(ao.binaryCol, bytes); XCTAssertEqualObjects(ao.dateCol, now); XCTAssertEqual(ao.cBoolCol, true); XCTAssertEqual(ao.longCol, INT_MAX + 1LL); XCTAssertNotEqual(ao.objectCol, so); XCTAssertEqualObjects(ao.objectCol.stringCol, @"string"); auto opt = [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; XCTAssertNil(opt.intObj); XCTAssertNil(opt.boolObj); XCTAssertNil(opt.floatObj); XCTAssertNil(opt.doubleObj); XCTAssertNil(opt.date); XCTAssertNil(opt.data); XCTAssertNil(opt.string); opt = [AllOptionalTypes createInRealm:realm withValue:@[@1, @2.2f, @3.3, @YES, @"str", bytes, now]]; XCTAssertEqualObjects(opt.intObj, @1); XCTAssertEqualObjects(opt.boolObj, @YES); XCTAssertEqualObjects(opt.floatObj, @2.2f); XCTAssertEqualObjects(opt.doubleObj, @3.3); XCTAssertEqualObjects(opt.date, now); XCTAssertEqualObjects(opt.data, bytes); XCTAssertEqualObjects(opt.string, @"str"); [realm cancelWriteTransaction]; } - (void)testCreateRequiredPrimitiveArrays { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto req = [AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1, @2, @3], @"boolObj": @[@YES, @NO], @"floatObj": @[@1.1f, @2.2f], @"doubleObj": @[@3.3, @4.4], @"stringObj": @[@"a", @"b"], @"dateObj": @[now], @"dataObj": @[bytes]}]; XCTAssertEqual(3U, req.intObj.count); XCTAssertEqual(2U, req.boolObj.count); XCTAssertEqual(2U, req.floatObj.count); XCTAssertEqual(2U, req.doubleObj.count); XCTAssertEqual(2U, req.stringObj.count); XCTAssertEqual(1U, req.dateObj.count); XCTAssertEqual(1U, req.dataObj.count); XCTAssertEqualObjects([req.intObj valueForKey:@"self"], (@[@1, @2, @3])); XCTAssertEqualObjects([req.boolObj valueForKey:@"self"], (@[@YES, @NO])); XCTAssertEqualObjects([req.floatObj valueForKey:@"self"], (@[@1.1f, @2.2f])); XCTAssertEqualObjects([req.doubleObj valueForKey:@"self"], (@[@3.3, @4.4])); XCTAssertEqualObjects([req.stringObj valueForKey:@"self"], (@[@"a", @"b"])); XCTAssertEqualObjects([req.dateObj valueForKey:@"self"], (@[now])); XCTAssertEqualObjects([req.dataObj valueForKey:@"self"], (@[bytes])); [realm cancelWriteTransaction]; } - (void)testCreateRequiredPrimitiveSets { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto req = [AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@1, @2, @3], @"boolObj": @[@YES, @NO], @"floatObj": @[@1.1f, @2.2f], @"doubleObj": @[@3.3, @4.4], @"stringObj": @[@"a", @"b"], @"dateObj": @[now], @"dataObj": @[bytes]}]; XCTAssertEqual(3U, req.intObj.count); XCTAssertEqual(2U, req.boolObj.count); XCTAssertEqual(2U, req.floatObj.count); XCTAssertEqual(2U, req.doubleObj.count); XCTAssertEqual(2U, req.stringObj.count); XCTAssertEqual(1U, req.dateObj.count); XCTAssertEqual(1U, req.dataObj.count); XCTAssertEqualObjects([req.intObj valueForKey:@"self"], ([NSSet setWithArray:@[@1, @2, @3]])); XCTAssertEqualObjects([req.boolObj valueForKey:@"self"], ([NSSet setWithArray:@[@NO, @YES]])); XCTAssertEqualObjects([req.floatObj valueForKey:@"self"], ([NSSet setWithArray:@[@1.1f, @2.2f]])); XCTAssertEqualObjects([req.doubleObj valueForKey:@"self"], ([NSSet setWithArray:@[@3.3, @4.4]])); XCTAssertEqualObjects([req.stringObj valueForKey:@"self"], ([NSSet setWithArray:@[@"a", @"b"]])); XCTAssertEqualObjects([req.dateObj valueForKey:@"self"], ([NSSet setWithArray:@[now]])); XCTAssertEqualObjects([req.dataObj valueForKey:@"self"], ([NSSet setWithArray:@[bytes]])); [realm cancelWriteTransaction]; } #if 0 - (void)testCreateRequiredPrimitiveArraysWithNonNSArrayEnumerable { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto req = [AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1, @2, @3].reverseObjectEnumerator, @"boolObj": @[@YES, @NO].reverseObjectEnumerator, @"floatObj": @[@1.1f, @2.2f].reverseObjectEnumerator, @"doubleObj": @[@3.3, @4.4].reverseObjectEnumerator, @"stringObj": @[@"a", @"b"].reverseObjectEnumerator, @"dateObj": @[now].reverseObjectEnumerator, @"dataObj": @[bytes].reverseObjectEnumerator}]; XCTAssertEqual(3U, req.intObj.count); XCTAssertEqual(2U, req.boolObj.count); XCTAssertEqual(2U, req.floatObj.count); XCTAssertEqual(2U, req.doubleObj.count); XCTAssertEqual(2U, req.stringObj.count); XCTAssertEqual(1U, req.dateObj.count); XCTAssertEqual(1U, req.dataObj.count); XCTAssertEqualObjects([req.intObj valueForKey:@"self"], (@[@3, @2, @1])); XCTAssertEqualObjects([req.boolObj valueForKey:@"self"], (@[@NO, @YES])); XCTAssertEqualObjects([req.floatObj valueForKey:@"self"], (@[@2.2f, @1.1f])); XCTAssertEqualObjects([req.doubleObj valueForKey:@"self"], (@[@4.4, @3.3])); XCTAssertEqualObjects([req.stringObj valueForKey:@"self"], (@[@"b", @"a"])); XCTAssertEqualObjects([req.dateObj valueForKey:@"self"], (@[now])); XCTAssertEqualObjects([req.dataObj valueForKey:@"self"], (@[bytes])); [realm cancelWriteTransaction]; } #endif - (void)testCreatePrimitiveArraysWithNSNull { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto req = [AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": NSNull.null, @"boolObj": NSNull.null, @"floatObj": NSNull.null, @"doubleObj": NSNull.null, @"stringObj": NSNull.null, @"dateObj": NSNull.null, @"dataObj": NSNull.null}]; XCTAssertEqual(0U, req.intObj.count); XCTAssertEqual(0U, req.boolObj.count); XCTAssertEqual(0U, req.floatObj.count); XCTAssertEqual(0U, req.doubleObj.count); XCTAssertEqual(0U, req.stringObj.count); XCTAssertEqual(0U, req.dateObj.count); XCTAssertEqual(0U, req.dataObj.count); } - (void)testCreatePrimitiveSetsWithNSNull { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto req = [AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": NSNull.null, @"boolObj": NSNull.null, @"floatObj": NSNull.null, @"doubleObj": NSNull.null, @"stringObj": NSNull.null, @"dateObj": NSNull.null, @"dataObj": NSNull.null}]; XCTAssertEqual(0U, req.intObj.count); XCTAssertEqual(0U, req.boolObj.count); XCTAssertEqual(0U, req.floatObj.count); XCTAssertEqual(0U, req.doubleObj.count); XCTAssertEqual(0U, req.stringObj.count); XCTAssertEqual(0U, req.dateObj.count); XCTAssertEqual(0U, req.dataObj.count); } - (void)testCreatePrimitiveArraysWithMissingKeys { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto req = [AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1, @2, @2], @"dataObj": NSNull.null}]; XCTAssertEqual(3U, req.intObj.count); XCTAssertEqual(0U, req.boolObj.count); XCTAssertEqual(0U, req.floatObj.count); XCTAssertEqual(0U, req.doubleObj.count); XCTAssertEqual(0U, req.stringObj.count); XCTAssertEqual(0U, req.dateObj.count); XCTAssertEqual(0U, req.dataObj.count); } - (void)testCreatePrimitiveSetsWithMissingKeys { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto req = [AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@1, @2, @2], @"dataObj": NSNull.null}]; XCTAssertEqual(2U, req.intObj.count); XCTAssertEqual(0U, req.boolObj.count); XCTAssertEqual(0U, req.floatObj.count); XCTAssertEqual(0U, req.doubleObj.count); XCTAssertEqual(0U, req.stringObj.count); XCTAssertEqual(0U, req.dateObj.count); XCTAssertEqual(0U, req.dataObj.count); } - (void)testCreateRequiredPrimitiveArraysWithInvalidValues { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[NSNull.null]}], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @1}], @"Invalid value (1) for 'int' array property 'AllPrimitiveArrays.intObj': value is not enumerable."); [realm cancelWriteTransaction]; } - (void)testCreateRequiredPrimitiveSetsWithInvalidValues { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[NSNull.null]}], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '" RLMConstantString "' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([AllPrimitiveSets createInRealm:realm withValue:@{@"intObj": @1}], @"Invalid value (1) for 'int' set property 'AllPrimitiveSets.intObj': value is not enumerable."); [realm cancelWriteTransaction]; } - (void)testCreateOptionalPrimitiveArrays { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto req = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1, @2, @3, NSNull.null], @"boolObj": @[@YES, @NO, NSNull.null], @"floatObj": @[@1.1f, @2.2f, NSNull.null], @"doubleObj": @[@3.3, @4.4, NSNull.null], @"stringObj": @[@"a", @"b", NSNull.null], @"dateObj": @[now, NSNull.null], @"dataObj": @[bytes, NSNull.null], @"uuidObj": @[[[NSUUID alloc] initWithUUIDString:@"137decc8-b300-4954-a233-f89909f4fd89"], [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]]}]; XCTAssertEqual(4U, req.intObj.count); XCTAssertEqual(3U, req.boolObj.count); XCTAssertEqual(3U, req.floatObj.count); XCTAssertEqual(3U, req.doubleObj.count); XCTAssertEqual(3U, req.stringObj.count); XCTAssertEqual(2U, req.dateObj.count); XCTAssertEqual(2U, req.dataObj.count); XCTAssertEqualObjects([req.intObj valueForKey:@"self"], (@[@1, @2, @3, NSNull.null])); XCTAssertEqualObjects([req.boolObj valueForKey:@"self"], (@[@YES, @NO, NSNull.null])); XCTAssertEqualObjects([req.floatObj valueForKey:@"self"], (@[@1.1f, @2.2f, NSNull.null])); XCTAssertEqualObjects([req.doubleObj valueForKey:@"self"], (@[@3.3, @4.4, NSNull.null])); XCTAssertEqualObjects([req.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); XCTAssertEqualObjects([req.dateObj valueForKey:@"self"], (@[now, NSNull.null])); XCTAssertEqualObjects([req.dataObj valueForKey:@"self"], (@[bytes, NSNull.null])); [realm cancelWriteTransaction]; } - (void)testCreateOptionalPrimitiveSets { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto req = [AllOptionalPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@1, @2, @3, NSNull.null], @"boolObj": @[@YES, @NO, NSNull.null], @"floatObj": @[@1.1f, @2.2f, NSNull.null], @"doubleObj": @[@3.3, @4.4, NSNull.null], @"stringObj": @[@"a", @"b", NSNull.null], @"dateObj": @[now, NSNull.null], @"dataObj": @[bytes, NSNull.null]}]; XCTAssertEqual(4U, req.intObj.count); XCTAssertEqual(3U, req.boolObj.count); XCTAssertEqual(3U, req.floatObj.count); XCTAssertEqual(3U, req.doubleObj.count); XCTAssertEqual(3U, req.stringObj.count); XCTAssertEqual(2U, req.dateObj.count); XCTAssertEqual(2U, req.dataObj.count); XCTAssertEqualObjects([req.intObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, @1, @2, @3]])); XCTAssertEqualObjects([req.boolObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); XCTAssertEqualObjects([req.floatObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, @1.1f, @2.2f]])); XCTAssertEqualObjects([req.doubleObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, @3.3, @4.4]])); XCTAssertEqualObjects([req.stringObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, @"a", @"b"]])); XCTAssertEqualObjects([req.dateObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, now]])); XCTAssertEqualObjects([req.dataObj valueForKey:@"self"], ([NSSet setWithArray:@[NSNull.null, bytes]])); [realm cancelWriteTransaction]; } - (void)testCreateOptionalPrimitiveArraysWithInvalidValues { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReasonMatching([AllOptionalPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '.*NS.*Number' for 'int\\?' array property 'AllOptionalPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([AllOptionalPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '__NSCFConstantString' for 'int?' array property 'AllOptionalPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason([AllOptionalPrimitiveArrays createInRealm:realm withValue:@{@"intObj": @1}], @"Invalid value (1) for 'int?' array property 'AllOptionalPrimitiveArrays.intObj': value is not enumerable."); } - (void)testCreateOptionalPrimitiveSetsWithInvalidValues { auto realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([AllOptionalPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@1.1]}], @"Invalid value '1.1' of type '" RLMConstantDouble "' for 'int?' set property 'AllOptionalPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([AllOptionalPrimitiveSets createInRealm:realm withValue:@{@"intObj": @[@"0"]}], @"Invalid value '0' of type '" RLMConstantString "' for 'int?' set property 'AllOptionalPrimitiveSets.intObj'."); RLMAssertThrowsWithReason([AllOptionalPrimitiveSets createInRealm:realm withValue:@{@"intObj": @1}], @"Invalid value (1) for 'int?' set property 'AllOptionalPrimitiveSets.intObj': value is not enumerable."); } - (void)testCreateUsesDefaultValuesForMissingDictionaryKeys { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [NumberDefaultsObject createInRealm:realm withValue:@{}]; XCTAssertEqualObjects(obj.intObj, @1); XCTAssertEqualObjects(obj.floatObj, @2.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); obj = [NumberDefaultsObject createInRealm:realm withValue:@{@"intObj": @10}]; XCTAssertEqualObjects(obj.intObj, @10); XCTAssertEqualObjects(obj.floatObj, @2.2f); XCTAssertEqualObjects(obj.doubleObj, @3.3); XCTAssertEqualObjects(obj.boolObj, @NO); [realm cancelWriteTransaction]; } - (void)testCreateOnManagedObjectInSameRealmShallowCopies { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [StringObject createInRealm:realm withValue:@[@"str"]]; auto pso = [PrimaryStringObject createInRealm:realm withValue:@[@"pk", @1]]; auto io = [IntObject createInRealm:realm withValue:@[@2]]; auto pio = [PrimaryIntObject createInRealm:realm withValue:@[@3]]; auto links = [AllLinks createInRealm:realm withValue:@[so, pso, @[io, io], @[pio, pio]]]; auto copy = [AllLinks createInRealm:realm withValue:links]; XCTAssertEqual(2U, [AllLinks allObjectsInRealm:realm].count); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); XCTAssertEqual(1U, [PrimaryIntObject allObjectsInRealm:realm].count); XCTAssertTrue([links.string isEqualToObject:so]); XCTAssertTrue([copy.string isEqualToObject:so]); XCTAssertTrue([links.primaryString isEqualToObject:pso]); XCTAssertTrue([copy.primaryString isEqualToObject:pso]); [realm cancelWriteTransaction]; } - (void)testCreateOnManagedObjectInDifferentRealmDeepCopies { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto realm2 = [self realmWithTestPath]; [realm2 beginWriteTransaction]; auto so = [StringObject createInRealm:realm withValue:@[@"str"]]; auto pso = [PrimaryStringObject createInRealm:realm withValue:@[@"pk", @1]]; auto io = [IntObject createInRealm:realm withValue:@[@2]]; auto pio = [PrimaryIntObject createInRealm:realm withValue:@[@3]]; auto links = [AllLinks createInRealm:realm withValue:@[so, pso, @[io, io], @[pio]]]; [AllLinks createInRealm:realm2 withValue:links]; XCTAssertEqual(1U, [AllLinks allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm2].count); XCTAssertEqual(2U, [IntObject allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [PrimaryIntObject allObjectsInRealm:realm2].count); [realm cancelWriteTransaction]; [realm2 cancelWriteTransaction]; } - (void)testCreateOnManagedObjectInDifferentRealmDoesntReallyWorkUsefullyWithLinkedPKs { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto realm2 = [self realmWithTestPath]; [realm2 beginWriteTransaction]; auto pio = [PrimaryIntObject createInRealm:realm withValue:@[@3]]; auto links = [AllLinks createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, @[pio, pio]]]; RLMAssertThrowsWithReason([AllLinks createInRealm:realm2 withValue:links], @"existing primary key value '3'"); [realm cancelWriteTransaction]; [realm2 cancelWriteTransaction]; } - (void)testCreateWithInvalidatedObject { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj1 = [IntObject createInRealm:realm withValue:@[@0]]; auto obj2 = [IntObject createInRealm:realm withValue:@[@1]]; id obj1alias = [IntObject allObjectsInRealm:realm].firstObject; [realm deleteObject:obj1]; RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:obj1], @"Object has been deleted or invalidated."); RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:obj1alias], @"Object has been deleted or invalidated."); [realm commitWriteTransaction]; [realm invalidate]; [realm beginWriteTransaction]; RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:obj2], @"Object has been deleted or invalidated."); [realm cancelWriteTransaction]; } - (void)testCreateOutsideWriteTransaction { auto realm = [RLMRealm defaultRealm]; RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:@[@0]], @"call beginWriteTransaction"); } - (void)testCreateInNilRealm { RLMAssertThrowsWithReasonMatching(([IntObject createInRealm:self.nonLiteralNil withValue:@[@0]]), @"Realm must not be nil"); } - (void)testCreatingObjectWithoutAnyPropertiesWorks { @autoreleasepool { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [AbstractObject createInRealm:realm withValue:@[]]; [realm commitWriteTransaction]; } auto realm = [RLMRealm defaultRealm]; XCTAssertEqual(1U, [AbstractObject allObjectsInRealm:realm].count); } - (void)testCreateWithNonEnumerableValueForArrayProperty { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason(([CompanyObject createInRealm:realm withValue:@[@"one employee", @1]]), @"Invalid value (1) for 'EmployeeObject' array property 'CompanyObject.employees': value is not enumerable."); RLMAssertThrowsWithReason(([CompanyObject createInRealm:realm withValue:@[@"one employee", @[], @1]]), @"Invalid value (1) for 'EmployeeObject' set property 'CompanyObject.employeeSet': value is not enumerable."); [realm cancelWriteTransaction]; } - (void)testCreateWithNonArrayEnumerableValueForArrayProperty { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto employees = @[@[@"name", @2, @YES], @[@"name 2", @3, @NO]]; auto co = [CompanyObject createInRealm:realm withValue:@[@"one employee", employees.reverseObjectEnumerator, employees.reverseObjectEnumerator]]; XCTAssertEqual(2U, co.employees.count); XCTAssertEqualObjects(@"name 2", co.employees[0].name); XCTAssertEqualObjects(@"name", co.employees[1].name); XCTAssertEqual(2U, co.employeeSet.count); XCTAssertTrue([[co.employeeSet valueForKey:@"name"] containsObject:@"name 2"]); XCTAssertTrue([[co.employeeSet valueForKey:@"name"] containsObject:@"name"]); [realm cancelWriteTransaction]; } - (void)testCreateWithCustomAccessors { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // Create with array auto ca = [CustomAccessorsObject createInRealm:realm withValue:@[@"a", @1]]; XCTAssertEqualObjects(ca.name, @"a"); XCTAssertEqual(ca.age, 1); // Create with dictionary ca = [CustomAccessorsObject createInRealm:realm withValue:@{@"name": @"b", @"age": @2}]; XCTAssertEqualObjects(ca.name, @"b"); XCTAssertEqual(ca.age, 2); // Create with KVC-compatible object auto ca2 = [CustomAccessorsObject createInRealm:realm withValue:ca]; XCTAssertEqualObjects(ca2.name, @"b"); XCTAssertEqual(ca2.age, 2); [realm cancelWriteTransaction]; } - (void)testCreateWithRenamedColumns { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // Create with array auto obj = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]]; XCTAssertEqual(obj.propA, 1); XCTAssertEqualObjects(obj.propB, @"a"); // Create with dictionary obj = [RenamedProperties1 createInRealm:realm withValue:@{@"propB": @"b", @"propA": @2}]; XCTAssertEqual(obj.propA, 2); XCTAssertEqualObjects(obj.propB, @"b"); // Create with KVC-compatible object obj = [RenamedProperties1 createInRealm:realm withValue:obj]; XCTAssertEqual(obj.propA, 2); XCTAssertEqualObjects(obj.propB, @"b"); // Verify that they're all readable via the other class RLMResults *results = [RenamedProperties2 allObjectsInRealm:realm]; XCTAssertEqual(results[0].propC, 1); XCTAssertEqualObjects(results[0].propD, @"a"); XCTAssertEqual(results[1].propC, 2); XCTAssertEqualObjects(results[1].propD, @"b"); XCTAssertEqual(results[2].propC, 2); XCTAssertEqualObjects(results[2].propD, @"b"); [realm cancelWriteTransaction]; } #pragma mark - Create Or Update - (void)testCreateOrUpdateWithoutPKThrows { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([DogObject createOrUpdateInRealm:realm withValue:@[]], @"'DogObject' does not have a primary key"); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateUpdatesExistingItemWithSamePK { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"pk", @2]]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(so.intCol, 2); auto so2 = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"pk", @3]]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(so.intCol, 3); XCTAssertEqualObjects(so, so2); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateWithNullPrimaryKey { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @5}]; [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @7}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 7); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": NSNull.null, @"intCol": @11}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 11); [PrimaryNullableIntObject createOrUpdateInRealm:realm withValue:@{@"value": @5}]; [PrimaryNullableIntObject createOrUpdateInRealm:realm withValue:@{@"value": @7}]; XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:NSNull.null].value, 7); [PrimaryNullableIntObject createOrUpdateInRealm:realm withValue:@{@"optIntCol": NSNull.null, @"value": @11}]; XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:nil].value, 11); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateDoesNotModifyKeysNotPresent { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"pk", @2]]; auto so2 = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"pk"}]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(so.intCol, 2); XCTAssertEqual(so2.intCol, 2); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateDoesNotReplaceExistingValuesWithDefaults { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [PrimaryKeyWithDefault createInRealm:realm withValue:@[@"pk", @2]]; [PrimaryKeyWithDefault createOrUpdateInRealm:realm withValue:@{@"stringCol": @"pk"}]; XCTAssertEqual(so.intCol, 2); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateReplacesExistingArrayPropertiesAndDoesNotMergeThem { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [AllLinksWithPrimary createInRealm:realm withValue:@[@"pk", @[@"str"], @[@"str pk", @5], @[@[@1], @[@2], @[@3]]]]; [AllLinksWithPrimary createOrUpdateInRealm:realm withValue:@[@"pk", @[@"str"], @[@"str pk", @6], @[@[@4]]]]; XCTAssertEqual(1U, obj.intArray.count); XCTAssertEqual(4, obj.intArray[0].intCol); XCTAssertEqual(6, obj.primaryString.intCol); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateReusesExistingLinkedObjectsWithPrimaryKeys { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [AllLinksWithPrimary createInRealm:realm withValue:@[@"pk", NSNull.null, @[@"str pk", @5]]]; [AllLinksWithPrimary createOrUpdateInRealm:realm withValue:@[@"pk", NSNull.null, @{@"stringCol": @"str pk"}]]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateCreatesNewLinkedObjectsWithoutPrimaryKeys { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [AllLinksWithPrimary createInRealm:realm withValue:@[@"pk", @[@"str"]]]; [AllLinksWithPrimary createOrUpdateInRealm:realm withValue:@[@"pk", @[@"str"]]]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateWithMissingValuesAndNoExistingObject { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"pk"}], @"Missing value for property 'PrimaryStringObject.intCol'"); RLMAssertThrowsWithReason(([PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"pk", @"intCol": NSNull.null}]), @"Invalid value '' of type 'NSNull' for 'int' property 'PrimaryStringObject.intCol'"); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateOnManagedObjectInSameRealmReturnsExistingObjectInstance { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"pk", @2]]; auto so2 = [PrimaryStringObject createOrUpdateInRealm:realm withValue:so]; XCTAssertEqual(so, so2); XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateOnManagedObjectInDifferentRealmDeepCopies { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto realm2 = [self realmWithTestPath]; [realm2 beginWriteTransaction]; auto so = [StringObject createInRealm:realm withValue:@[@"str"]]; auto pso = [PrimaryStringObject createInRealm:realm withValue:@[@"pk", @1]]; auto io = [IntObject createInRealm:realm withValue:@[@2]]; auto pio = [PrimaryIntObject createInRealm:realm withValue:@[@3]]; auto links = [AllLinksWithPrimary createInRealm:realm withValue:@[@"pk", so, pso, @[io, io], @[pio, pio]]]; auto copy = [AllLinksWithPrimary createOrUpdateInRealm:realm2 withValue:links]; XCTAssertEqual(1U, [AllLinksWithPrimary allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm2].count); XCTAssertEqual(2U, [IntObject allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [PrimaryIntObject allObjectsInRealm:realm2].count); XCTAssertEqualObjects(so.stringCol, copy.string.stringCol); XCTAssertEqualObjects(pso.stringCol, copy.primaryString.stringCol); XCTAssertEqual(pso.intCol, copy.primaryString.intCol); XCTAssertEqual(2U, copy.intArray.count); XCTAssertEqual(2U, copy.primaryIntArray.count); [realm cancelWriteTransaction]; [realm2 cancelWriteTransaction]; } - (void)testCreateOrUpdateWithNilValues { auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto nonnull = [[AllOptionalTypesPK alloc] initWithValue:@[@0, @1, @2.2f, @3.3, @YES, @"a", bytes, now]]; auto null = [[AllOptionalTypesPK alloc] initWithValue:@[@0]]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [AllOptionalTypesPK createInRealm:realm withValue:nonnull]; [AllOptionalTypesPK createOrUpdateInRealm:realm withValue:null]; XCTAssertNil(obj.intObj); XCTAssertNil(obj.floatObj); XCTAssertNil(obj.doubleObj); XCTAssertNil(obj.boolObj); XCTAssertNil(obj.string); XCTAssertNil(obj.data); XCTAssertNil(obj.date); [AllOptionalTypesPK createOrUpdateInRealm:realm withValue:nonnull]; [AllOptionalTypesPK createOrUpdateInRealm:realm withValue:@[@0]]; // No values specified, so old values should remain XCTAssertNotNil(obj.intObj); XCTAssertNotNil(obj.floatObj); XCTAssertNotNil(obj.doubleObj); XCTAssertNotNil(obj.boolObj); XCTAssertNotNil(obj.string); XCTAssertNotNil(obj.data); XCTAssertNotNil(obj.date); [AllOptionalTypesPK createOrUpdateInRealm:realm withValue:@{@"pk": @0}]; XCTAssertNotNil(obj.intObj); XCTAssertNotNil(obj.floatObj); XCTAssertNotNil(obj.doubleObj); XCTAssertNotNil(obj.boolObj); XCTAssertNotNil(obj.string); XCTAssertNotNil(obj.data); XCTAssertNotNil(obj.date); [AllOptionalTypesPK createOrUpdateInRealm:realm withValue:@{@"pk": @0, @"intObj": NSNull.null, @"floatObj": NSNull.null, @"doubleObj": NSNull.null, @"boolObj": NSNull.null, @"string": NSNull.null, @"data": NSNull.null, @"date": NSNull.null, }]; XCTAssertNil(obj.intObj); XCTAssertNil(obj.floatObj); XCTAssertNil(obj.doubleObj); XCTAssertNil(obj.boolObj); XCTAssertNil(obj.string); XCTAssertNil(obj.data); XCTAssertNil(obj.date); [realm cancelWriteTransaction]; } - (void)testCreateOrUpdateWithRenamedPrimaryKey { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [RenamedPrimaryKey createInRealm:realm withValue:@[@1, @2]]; [RenamedPrimaryKey createOrUpdateInRealm:realm withValue:@[@1, @3]]; XCTAssertEqual(obj.pk, 1); XCTAssertEqual(obj.value, 3); [RenamedPrimaryKey createOrUpdateInRealm:realm withValue:@{@"pk": @1, @"value": @4}]; XCTAssertEqual(obj.value, 4); [realm cancelWriteTransaction]; } #pragma mark - Add - (void)testAddInvalidated { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; id dog = [DogObject createInRealm:realm withValue:@[@"name", @1]]; id dog2 = [DogObject allObjectsInRealm:realm].firstObject; [realm deleteObject:dog]; RLMAssertThrowsWithReason([realm addObject:dog], @"Adding a deleted or invalidated"); RLMAssertThrowsWithReason([realm addObject:dog2], @"Adding a deleted or invalidated"); [realm cancelWriteTransaction]; } - (void)testAddDuplicatePrimaryKey { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:[[PrimaryStringObject alloc] initWithValue:@[@"pk", @1]]]; RLMAssertThrowsWithReason(([realm addObject:[[PrimaryStringObject alloc] initWithValue:@[@"pk", @1]]]), @"existing primary key value 'pk'"); [realm cancelWriteTransaction]; } - (void)testAddNested { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[@[@"name", @2, @YES]]]]; [realm addObject:co]; XCTAssertEqual(co.realm, realm); XCTAssertEqualObjects(co.name, @"one employee"); auto eo = co.employees[0]; XCTAssertEqual(eo.realm, realm); XCTAssertEqualObjects(eo.name, @"name"); eo = [[EmployeeObject alloc] initWithValue:@[@"name 2", @3, @NO]]; co = [[CompanyObject alloc] initWithValue:@[@"one employee", @[eo]]]; [realm addObject:co]; XCTAssertEqual(co.realm, realm); XCTAssertEqual(eo.realm, realm); [realm cancelWriteTransaction]; } - (void)testAddingObjectWithoutAnyPropertiesWorks { @autoreleasepool { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:[[AbstractObject alloc] initWithValue:@[]]]; [realm commitWriteTransaction]; } auto realm = [RLMRealm defaultRealm]; XCTAssertEqual(1U, [AbstractObject allObjectsInRealm:realm].count); } - (void)testAddWithCustomAccessors { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto ca = [[CustomAccessorsObject alloc] initWithValue:@[@"a", @1]]; [realm addObject:ca]; XCTAssertEqualObjects(ca.name, @"a"); XCTAssertEqual(ca.age, 1); [realm cancelWriteTransaction]; } - (void)testAddWithRenamedColumns { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [[RenamedProperties1 alloc] initWithValue:@[@1, @"a"]]; [realm addObject:obj]; XCTAssertEqual(obj.propA, 1); XCTAssertEqualObjects(obj.propB, @"a"); RLMResults *results = [RenamedProperties2 allObjectsInRealm:realm]; XCTAssertEqual(results[0].propC, 1); XCTAssertEqualObjects(results[0].propD, @"a"); [realm cancelWriteTransaction]; } - (void)testAddToCurrentRealmIsNoOp { DogObject *dog = [[DogObject alloc] init]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:dog]; XCTAssertEqual(dog.realm, realm); XCTAssertEqual(1U, [DogObject allObjectsInRealm:realm].count); XCTAssertNoThrow([realm addObject:dog]); XCTAssertEqual(dog.realm, realm); XCTAssertEqual(1U, [DogObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testAddToDifferentRealmThrows { auto eo = [[EmployeeObject alloc] init]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:eo]; auto realm2 = [self realmWithTestPath]; [realm2 beginWriteTransaction]; RLMAssertThrowsWithReason([realm2 addObject:eo], @"Object is already managed by another Realm. Use create instead to copy it into this Realm."); XCTAssertEqual(eo.realm, realm); auto co = [CompanyObject new]; [co.employees addObject:eo]; RLMAssertThrowsWithReason([realm2 addObject:co], @"Object is already managed by another Realm. Use create instead to copy it into this Realm."); XCTAssertEqual(co.realm, realm2); [realm cancelWriteTransaction]; [realm2 cancelWriteTransaction]; } - (void)testAddToCurrentRealmChecksForWrite { DogObject *dog = [[DogObject alloc] init]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:dog]; [realm commitWriteTransaction]; RLMAssertThrowsWithReason([realm addObject:dog], @"call beginWriteTransaction"); } - (void)testAddObjectWithObserver { DogObject *dog = [[DogObject alloc] init]; [dog addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptions)0 context:0]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([realm addObject:dog], @"Cannot add an object with observers to a Realm"); [realm cancelWriteTransaction]; [dog removeObserver:self forKeyPath:@"name"]; } - (void)testAddObjectWithNilValueForRequiredProperty { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([realm addObject:[[RequiredPropertiesObject alloc] init]], @"Invalid value '' of type 'NSNull' for 'string' property 'RequiredPropertiesObject.stringCol'."); [realm cancelWriteTransaction]; } #pragma mark - Add Or Update - (void)testAddOrUpdateWithoutPKThrows { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMAssertThrowsWithReason([realm addOrUpdateObject:[DogObject new]], @"'DogObject' does not have a primary key"); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateUpdatesExistingItemWithSamePK { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so1 = [[PrimaryStringObject alloc] initWithValue:@[@"pk", @2]]; auto so2 = [[PrimaryStringObject alloc] initWithValue:@[@"pk", @3]]; [realm addOrUpdateObject:so1]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(so1.intCol, 2); [realm addOrUpdateObject:so2]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); XCTAssertEqual(so1.intCol, 3); XCTAssertEqualObjects(so1, so2); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateWithNullPrimaryKey { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so1 = [[PrimaryNullableStringObject alloc] initWithValue:@{@"intCol": @5}]; auto so2 = [[PrimaryNullableStringObject alloc] initWithValue:@{@"intCol": @7}]; XCTAssertNil([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null]); XCTAssertNil([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil]); [realm addOrUpdateObject:so1]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 5); XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 5); [realm addOrUpdateObject:so2]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 7); XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 7); auto io1 = [[PrimaryNullableIntObject alloc] initWithValue:@{@"value": @5}]; auto io2 = [[PrimaryNullableIntObject alloc] initWithValue:@{@"value": @7}]; XCTAssertNil([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:NSNull.null]); XCTAssertNil([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:nil]); [realm addOrUpdateObject:io1]; XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:NSNull.null].value, 5); XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:nil].value, 5); [realm addOrUpdateObject:io2]; XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:NSNull.null].value, 7); XCTAssertEqual([PrimaryNullableIntObject objectInRealm:realm forPrimaryKey:nil].value, 7); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateDoesNotHaveAnyConceptOfKeysNotPresentThatShouldBeLeftAlone { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj1 = [[PrimaryKeyWithDefault alloc] initWithValue:@[@"pk", @2]]; auto obj2 = [[PrimaryKeyWithDefault alloc] initWithValue:@[@"pk"]]; [realm addOrUpdateObject:obj1]; [realm addOrUpdateObject:obj2]; XCTAssertEqual(obj1.intCol, 10); [realm cancelWriteTransaction]; } - (void)testAddObjectWithNilValueForRequiredPropertyDoesNotUseExistingValue { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [PrimaryKeyAndRequiredString createInRealm:realm withValue:@[@0, @"value"]]; RLMAssertThrowsWithReason([realm addOrUpdateObject:[[PrimaryKeyAndRequiredString alloc] init]], @"Invalid value '' of type 'NSNull' for 'string' property 'PrimaryKeyAndRequiredString.value'"); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateWithDuplicateConflictingValuesForPrimaryKeyInArrayProperty { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto company = [[PrimaryCompanyObject alloc] init]; [company.employees addObject:[[PrimaryEmployeeObject alloc] initWithValue:@[@"a", @1, @NO]]]; [company.employees addObject:[[PrimaryEmployeeObject alloc] initWithValue:@[@"a", @2, @NO]]]; [company.employeeSet addObject:[[PrimaryEmployeeObject alloc] initWithValue:@[@"a", @1, @NO]]]; [company.employeeSet addObject:[[PrimaryEmployeeObject alloc] initWithValue:@[@"a", @2, @NO]]]; [realm addOrUpdateObject:company]; XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count); XCTAssertEqual(2, company.employees[0].age); XCTAssertEqual(2, company.employees[1].age); XCTAssertEqualObjects(company.employees[0], company.employees[1]); XCTAssertEqual(2, company.employeeSet.allObjects[0].age); XCTAssertEqualObjects(company.employeeSet.allObjects[0], company.employeeSet.allObjects.lastObject); } - (void)testAddOrUpdateReplacesExistingArrayPropertiesAndDoesNotMergeThem { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj1 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", @[@"str"], @[@"str pk", @5], @[@[@1], @[@2], @[@3]], @[], @[@[@1], @[@2], @[@3]]]]; auto obj2 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", @[@"str"], @[@"str pk", @6], @[@[@4]], @[], @[@[@4]]]]; [realm addOrUpdateObject:obj1]; [realm addOrUpdateObject:obj2]; XCTAssertEqual(1U, obj1.intArray.count); XCTAssertEqual(4, obj1.intArray[0].intCol); XCTAssertEqual(1U, obj1.intSet.count); XCTAssertEqual(4, obj1.intSet.allObjects[0].intCol); XCTAssertEqual(6, obj1.primaryString.intCol); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateReusesExistingLinkedObjectsWithPrimaryKeys { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj1 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", NSNull.null, @[@"str pk", @5]]]; auto obj2 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", NSNull.null, @{@"stringCol": @"str pk"}]]; [realm addOrUpdateObject:obj1]; [realm addOrUpdateObject:obj2]; XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateAddsNewLinkedObjectsWithoutPrimaryKeys { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj1 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", @[@"str"]]]; auto obj2 = [[AllLinksWithPrimary alloc] initWithValue:@[@"pk", @[@"str"]]]; [realm addOrUpdateObject:obj1]; [realm addOrUpdateObject:obj2]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); XCTAssertFalse([obj1.string isEqualToObject:[StringObject allObjectsInRealm:realm][0]]); XCTAssertTrue([obj1.string isEqualToObject:[StringObject allObjectsInRealm:realm][1]]); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateOnManagedObjectInSameRealmIsNoOp { auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto so = [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"pk", @2]]; XCTAssertNoThrow([realm addOrUpdateObject:so]); XCTAssertEqual(1U, [PrimaryStringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; } - (void)testAddOrUpdateOnManagedObjectInDifferentRealmThrows { auto eo = [[PrimaryEmployeeObject alloc] init]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:eo]; auto realm2 = [self realmWithTestPath]; [realm2 beginWriteTransaction]; RLMAssertThrowsWithReason([realm2 addOrUpdateObject:eo], @"Object is already managed by another Realm. Use create instead to copy it into this Realm."); XCTAssertEqual(eo.realm, realm); auto co = [PrimaryCompanyObject new]; [co.employees addObject:eo]; RLMAssertThrowsWithReason([realm2 addOrUpdateObject:co], @"Object is already managed by another Realm. Use create instead to copy it into this Realm."); XCTAssertEqual(co.realm, realm2); [realm cancelWriteTransaction]; [realm2 cancelWriteTransaction]; } - (void)testAddOrUpdateWithNilValues { auto now = [NSDate date]; auto bytes = [NSData dataWithBytes:"a" length:1]; auto nonnull = [[AllOptionalTypesPK alloc] initWithValue:@[@0, @1, @2.2f, @3.3, @YES, @"a", bytes, now]]; auto null = [[AllOptionalTypesPK alloc] initWithValue:@[@0]]; auto realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; auto obj = [AllOptionalTypesPK createInRealm:realm withValue:nonnull]; auto nullobj = [[AllOptionalTypesPK alloc] initWithValue:null]; [realm addOrUpdateObject:nullobj]; XCTAssertNil(obj.intObj); XCTAssertNil(obj.floatObj); XCTAssertNil(obj.doubleObj); XCTAssertNil(obj.boolObj); XCTAssertNil(obj.string); XCTAssertNil(obj.data); XCTAssertNil(obj.date); [realm cancelWriteTransaction]; } @end ================================================ FILE: Realm/Tests/ObjectIdTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" #import @interface ObjectIdTests : RLMTestCase @end @implementation ObjectIdTests #pragma mark - Initialization - (void)testObjectIdInitialization { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; XCTAssertTrue([objectId.stringValue isEqualToString:strValue]); XCTAssertTrue([strValue isEqualToString:objectId.stringValue]); NSDate *now = [NSDate date]; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithTimestamp:now machineIdentifier:10 processIdentifier:20]; XCTAssertEqual((int)now.timeIntervalSince1970, objectId2.timestamp.timeIntervalSince1970); } - (void)testObjectIdComparision { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; NSString *strValue2 = @"000123450000ffbeef91906d"; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithString:strValue2 error:nil]; NSString *strValue3 = @"000123450000ffbeef91906c"; RLMObjectId *objectId3 = [[RLMObjectId alloc] initWithString:strValue3 error:nil]; XCTAssertFalse([objectId isEqual:objectId2]); XCTAssertTrue([objectId isEqual:objectId3]); } - (void)testObjectIdGreaterThan { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; NSString *strValue2 = @"000154850000ffbaaf20906d"; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithString:strValue2 error:nil]; NSString *strValue3 = @"000123450000ffbeef91906c"; RLMObjectId *objectId3 = [[RLMObjectId alloc] initWithString:strValue3 error:nil]; XCTAssertTrue([objectId2 isGreaterThan:objectId]); XCTAssertFalse([objectId isGreaterThan:objectId3]); } - (void)testObjectIdGreaterThanOrEqualTo { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; NSString *strValue2 = @"000154850000ffbaaf20906d"; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithString:strValue2 error:nil]; NSString *strValue3 = @"000123450000ffbeef91906c"; RLMObjectId *objectId3 = [[RLMObjectId alloc] initWithString:strValue3 error:nil]; XCTAssertTrue([objectId2 isGreaterThanOrEqualTo:objectId]); XCTAssertTrue([objectId isGreaterThanOrEqualTo:objectId3]); } - (void)testObjectIdLessThan { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; NSString *strValue2 = @"000154850000ffbaaf20906d"; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithString:strValue2 error:nil]; NSString *strValue3 = @"000123450000ffbeef91906c"; RLMObjectId *objectId3 = [[RLMObjectId alloc] initWithString:strValue3 error:nil]; XCTAssertTrue([objectId isLessThan:objectId2]); XCTAssertFalse([objectId isLessThan:objectId3]); } - (void)testObjectIdLessThanOrEqualTo { NSString *strValue = @"000123450000ffbeef91906c"; RLMObjectId *objectId = [[RLMObjectId alloc] initWithString:strValue error:nil]; NSString *strValue2 = @"000154850000ffbaaf20906d"; RLMObjectId *objectId2 = [[RLMObjectId alloc] initWithString:strValue2 error:nil]; NSString *strValue3 = @"000123450000ffbeef91906c"; RLMObjectId *objectId3 = [[RLMObjectId alloc] initWithString:strValue3 error:nil]; XCTAssertTrue([objectId isLessThanOrEqualTo:objectId2]); XCTAssertTrue([objectId isLessThanOrEqualTo:objectId3]); } @end ================================================ FILE: Realm/Tests/ObjectInterfaceTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #pragma mark - Test Objects @interface PrimaryKeyWithLinkObject : RLMObject @property NSString *primaryKey; @property StringObject *string; @end @implementation PrimaryKeyWithLinkObject + (NSString *)primaryKey { return @"primaryKey"; } @end #pragma mark - Tests @interface ObjectInterfaceTests : RLMTestCase @end @implementation ObjectInterfaceTests - (void)testCustomAccessorsObject { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CustomAccessorsObject *ca = [CustomAccessorsObject createInRealm:realm withValue:@[@"name", @2]]; XCTAssertEqualObjects(ca.name, @"name"); XCTAssertEqualObjects([ca getThatName], @"name"); XCTAssertEqual(ca.age, 2); XCTAssertEqual([ca age], 2); [ca setTheInt:99]; XCTAssertEqual(ca.age, 99); XCTAssertEqual([ca age], 99); [realm cancelWriteTransaction]; } - (void)testClassExtension { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; BaseClassStringObject *bObject = [[BaseClassStringObject alloc ] init]; bObject.intCol = 1; bObject.stringCol = @"stringVal"; [realm addObject:bObject]; [realm commitWriteTransaction]; BaseClassStringObject *objectFromRealm = [BaseClassStringObject allObjects][0]; XCTAssertEqual(1, objectFromRealm.intCol, @"Should be 1"); XCTAssertEqualObjects(@"stringVal", objectFromRealm.stringCol, @"Should be stringVal"); } - (void)testNSNumberProperties { NumberObject *obj = [NumberObject new]; obj.intObj = @20; obj.floatObj = @0.7f; obj.doubleObj = @33.3; obj.boolObj = @YES; XCTAssertEqualObjects(@20, obj.intObj); XCTAssertEqualObjects(@0.7f, obj.floatObj); XCTAssertEqualObjects(@33.3, obj.doubleObj); XCTAssertEqualObjects(@YES, obj.boolObj); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqualObjects(@20, obj.intObj); XCTAssertEqualObjects(@0.7f, obj.floatObj); XCTAssertEqualObjects(@33.3, obj.doubleObj); XCTAssertEqualObjects(@YES, obj.boolObj); } - (void)testOptionalStringProperties { RLMRealm *realm = [RLMRealm defaultRealm]; StringObject *so = [[StringObject alloc] init]; XCTAssertNil(so.stringCol); XCTAssertNil([so valueForKey:@"stringCol"]); XCTAssertNil(so[@"stringCol"]); so.stringCol = @"a"; XCTAssertEqualObjects(so.stringCol, @"a"); XCTAssertEqualObjects([so valueForKey:@"stringCol"], @"a"); XCTAssertEqualObjects(so[@"stringCol"], @"a"); [so setValue:nil forKey:@"stringCol"]; XCTAssertNil(so.stringCol); XCTAssertNil([so valueForKey:@"stringCol"]); XCTAssertNil(so[@"stringCol"]); [realm transactionWithBlock:^{ [realm addObject:so]; XCTAssertNil(so.stringCol); XCTAssertNil([so valueForKey:@"stringCol"]); XCTAssertNil(so[@"stringCol"]); }]; so = [StringObject allObjectsInRealm:realm].firstObject; XCTAssertNil(so.stringCol); XCTAssertNil([so valueForKey:@"stringCol"]); XCTAssertNil(so[@"stringCol"]); [realm transactionWithBlock:^{ so.stringCol = @"b"; }]; XCTAssertEqualObjects(so.stringCol, @"b"); XCTAssertEqualObjects([so valueForKey:@"stringCol"], @"b"); XCTAssertEqualObjects(so[@"stringCol"], @"b"); [realm transactionWithBlock:^{ so.stringCol = @""; }]; XCTAssertEqualObjects(so.stringCol, @""); XCTAssertEqualObjects([so valueForKey:@"stringCol"], @""); XCTAssertEqualObjects(so[@"stringCol"], @""); } - (void)testOptionalBinaryProperties { RLMRealm *realm = [RLMRealm defaultRealm]; BinaryObject *bo = [[BinaryObject alloc] init]; XCTAssertNil(bo.binaryCol); XCTAssertNil([bo valueForKey:@"binaryCol"]); XCTAssertNil(bo[@"binaryCol"]); NSData *aData = [@"a" dataUsingEncoding:NSUTF8StringEncoding]; bo.binaryCol = aData; XCTAssertEqualObjects(bo.binaryCol, aData); XCTAssertEqualObjects([bo valueForKey:@"binaryCol"], aData); XCTAssertEqualObjects(bo[@"binaryCol"], aData); [bo setValue:nil forKey:@"binaryCol"]; XCTAssertNil(bo.binaryCol); XCTAssertNil([bo valueForKey:@"binaryCol"]); XCTAssertNil(bo[@"binaryCol"]); [realm transactionWithBlock:^{ [realm addObject:bo]; XCTAssertNil(bo.binaryCol); XCTAssertNil([bo valueForKey:@"binaryCol"]); XCTAssertNil(bo[@"binaryCol"]); }]; bo = [BinaryObject allObjectsInRealm:realm].firstObject; XCTAssertNil(bo.binaryCol); XCTAssertNil([bo valueForKey:@"binaryCol"]); XCTAssertNil(bo[@"binaryCol"]); NSData *bData = [@"b" dataUsingEncoding:NSUTF8StringEncoding]; [realm transactionWithBlock:^{ bo.binaryCol = bData; }]; XCTAssertEqualObjects(bo.binaryCol, bData); XCTAssertEqualObjects([bo valueForKey:@"binaryCol"], bData); XCTAssertEqualObjects(bo[@"binaryCol"], bData); NSData *emptyData = [NSData data]; [realm transactionWithBlock:^{ bo.binaryCol = emptyData; }]; XCTAssertEqualObjects(bo.binaryCol, emptyData); XCTAssertEqualObjects([bo valueForKey:@"binaryCol"], emptyData); XCTAssertEqualObjects(bo[@"binaryCol"], emptyData); } - (void)testOptionalNumberProperties { void (^assertNullProperties)(NumberObject *) = ^(NumberObject *no){ XCTAssertNil(no.intObj); XCTAssertNil(no.doubleObj); XCTAssertNil(no.floatObj); XCTAssertNil(no.boolObj); XCTAssertNil([no valueForKey:@"intObj"]); XCTAssertNil([no valueForKey:@"doubleObj"]); XCTAssertNil([no valueForKey:@"floatObj"]); XCTAssertNil([no valueForKey:@"boolObj"]); XCTAssertNil(no[@"intObj"]); XCTAssertNil(no[@"doubleObj"]); XCTAssertNil(no[@"floatObj"]); XCTAssertNil(no[@"boolObj"]); }; void (^assertNonNullProperties)(NumberObject *) = ^(NumberObject *no){ XCTAssertEqualObjects(no.intObj, @1); XCTAssertEqualObjects(no.doubleObj, @1.1); XCTAssertEqualObjects(no.floatObj, @2.2f); XCTAssertEqualObjects(no.boolObj, @YES); XCTAssertEqualObjects([no valueForKey:@"intObj"], @1); XCTAssertEqualObjects([no valueForKey:@"doubleObj"], @1.1); XCTAssertEqualObjects([no valueForKey:@"floatObj"], @2.2f); XCTAssertEqualObjects([no valueForKey:@"boolObj"], @YES); XCTAssertEqualObjects(no[@"intObj"], @1); XCTAssertEqualObjects(no[@"doubleObj"], @1.1); XCTAssertEqualObjects(no[@"floatObj"], @2.2f); XCTAssertEqualObjects(no[@"boolObj"], @YES); }; RLMRealm *realm = [RLMRealm defaultRealm]; NumberObject *no = [[NumberObject alloc] init]; assertNullProperties(no); no.intObj = @1; no.doubleObj = @1.1; no.floatObj = @2.2f; no.boolObj = @YES; assertNonNullProperties(no); no.intObj = nil; no.doubleObj = nil; no.floatObj = nil; no.boolObj = nil; assertNullProperties(no); no[@"intObj"] = @1; no[@"doubleObj"] = @1.1; no[@"floatObj"] = @2.2f; no[@"boolObj"] = @YES; assertNonNullProperties(no); no.intObj = nil; no.doubleObj = nil; no.floatObj = nil; no.boolObj = nil; [realm transactionWithBlock:^{ [realm addObject:no]; assertNullProperties(no); }]; no = [NumberObject allObjectsInRealm:realm].firstObject; assertNullProperties(no); [realm transactionWithBlock:^{ no.intObj = @1; no.doubleObj = @1.1; no.floatObj = @2.2f; no.boolObj = @YES; }]; assertNonNullProperties(no); } - (void)testRequiredNumberProperties { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RequiredNumberObject *obj = [RequiredNumberObject createInRealm:realm withValue:@[@0, @0, @0, @0]]; RLMAssertThrowsWithReason(obj.intObj = nil, @"Invalid null value for non-nullable property 'RequiredNumberObject.intObj'"); RLMAssertThrowsWithReason(obj.floatObj = nil, @"Invalid null value for non-nullable property 'RequiredNumberObject.floatObj'"); RLMAssertThrowsWithReason(obj.doubleObj = nil, @"Invalid null value for non-nullable property 'RequiredNumberObject.doubleObj'"); RLMAssertThrowsWithReason(obj.boolObj = nil, @"Invalid null value for non-nullable property 'RequiredNumberObject.boolObj'"); obj.intObj = @1; XCTAssertEqualObjects(obj.intObj, @1); obj.floatObj = @2.2f; XCTAssertEqualObjects(obj.floatObj, @2.2f); obj.doubleObj = @3.3; XCTAssertEqualObjects(obj.doubleObj, @3.3); obj.boolObj = @YES; XCTAssertEqualObjects(obj.boolObj, @YES); [realm cancelWriteTransaction]; } - (void)testSettingNonOptionalPropertiesToNil { RequiredPropertiesObject *ro = [[RequiredPropertiesObject alloc] init]; ro.stringCol = nil; ro.binaryCol = nil; XCTAssertNil(ro.stringCol); XCTAssertNil(ro.binaryCol); ro.stringCol = @"a"; ro.binaryCol = [@"a" dataUsingEncoding:NSUTF8StringEncoding]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:ro]; RLMAssertThrowsWithReasonMatching(ro.stringCol = nil, @"Invalid null value for non-nullable property 'RequiredPropertiesObject.stringCol'."); RLMAssertThrowsWithReasonMatching(ro.binaryCol = nil, @"Invalid null value for non-nullable property 'RequiredPropertiesObject.binaryCol'."); [realm cancelWriteTransaction]; } - (void)testIntSizes { RLMRealm *realm = [self realmWithTestPath]; int16_t v16 = 1 << 12; int32_t v32 = 1 << 30; int64_t v64 = 1LL << 40; AllIntSizesObject *obj = [AllIntSizesObject new]; // Test unmanaged obj[@"int16"] = @(v16); XCTAssertEqual([obj[@"int16"] shortValue], v16); obj[@"int16"] = @(v32); XCTAssertNotEqual([obj[@"int16"] intValue], v32, @"should truncate"); obj.int16 = 0; obj.int16 = v16; XCTAssertEqual(obj.int16, v16); obj[@"int32"] = @(v32); XCTAssertEqual([obj[@"int32"] intValue], v32); obj[@"int32"] = @(v64); XCTAssertNotEqual([obj[@"int32"] longLongValue], v64, @"should truncate"); obj.int32 = 0; obj.int32 = v32; XCTAssertEqual(obj.int32, v32); obj[@"int64"] = @(v64); XCTAssertEqual([obj[@"int64"] longLongValue], v64); obj.int64 = 0; obj.int64 = v64; XCTAssertEqual(obj.int64, v64); // Test in realm [realm beginWriteTransaction]; [realm addObject:obj]; XCTAssertEqual(obj.int16, v16); XCTAssertEqual(obj.int32, v32); XCTAssertEqual(obj.int64, v64); obj.int16 = 0; obj.int32 = 0; obj.int64 = 0; obj[@"int16"] = @(v16); XCTAssertEqual([obj[@"int16"] shortValue], v16); obj.int16 = 0; obj.int16 = v16; XCTAssertEqual(obj.int16, v16); obj[@"int32"] = @(v32); XCTAssertEqual([obj[@"int32"] intValue], v32); obj.int32 = 0; obj.int32 = v32; XCTAssertEqual(obj.int32, v32); obj[@"int64"] = @(v64); XCTAssertEqual([obj[@"int64"] longLongValue], v64); obj.int64 = 0; obj.int64 = v64; XCTAssertEqual(obj.int64, v64); [realm commitWriteTransaction]; } - (void)testRenamedProperties { RenamedProperties1 *obj1 = [[RenamedProperties1 alloc] initWithValue:@{@"propA": @5, @"propB": @"a"}]; XCTAssertEqual(obj1.propA, 5); XCTAssertEqualObjects(obj1.propB, @"a"); XCTAssertEqualObjects(obj1[@"propA"], @5); XCTAssertEqualObjects(obj1[@"propB"], @"a"); XCTAssertEqualObjects([obj1 valueForKey:@"propA"], @5); XCTAssertEqualObjects([obj1 valueForKey:@"propB"], @"a"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj1]; XCTAssertEqual(obj1.propA, 5); XCTAssertEqualObjects(obj1.propB, @"a"); XCTAssertEqualObjects(obj1[@"propA"], @5); XCTAssertEqualObjects(obj1[@"propB"], @"a"); XCTAssertEqualObjects([obj1 valueForKey:@"propA"], @5); XCTAssertEqualObjects([obj1 valueForKey:@"propB"], @"a"); RenamedProperties2 *obj2 = [RenamedProperties2 createInRealm:realm withValue:@{@"propC": @6, @"propD": @"b"}]; XCTAssertEqual(obj2.propC, 6); XCTAssertEqualObjects(obj2.propD, @"b"); XCTAssertEqualObjects(obj2[@"propC"], @6); XCTAssertEqualObjects(obj2[@"propD"], @"b"); XCTAssertEqualObjects([obj2 valueForKey:@"propC"], @6); XCTAssertEqualObjects([obj2 valueForKey:@"propD"], @"b"); RLMResults *results1 = [RenamedProperties1 allObjectsInRealm:realm]; RLMResults *results2 = [RenamedProperties2 allObjectsInRealm:realm]; XCTAssertTrue([results1[0] isEqualToObject:results2[0]]); XCTAssertTrue([results1[1] isEqualToObject:results2[1]]); LinkToRenamedProperties1 *link1 = [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, obj2, @[obj1, results1[1]]]]; LinkToRenamedProperties2 *link2 = [LinkToRenamedProperties2 createInRealm:realm withValue:@[obj2, obj1, @[obj2, results2[0]]]]; XCTAssertTrue([link1.linkA isKindOfClass:[RenamedProperties1 class]]); XCTAssertTrue([link1.linkB isKindOfClass:[RenamedProperties2 class]]); XCTAssertTrue([link1.array[0] isKindOfClass:[RenamedProperties1 class]]); XCTAssertTrue([link1.array[1] isKindOfClass:[RenamedProperties1 class]]); XCTAssertTrue([link2.linkC isKindOfClass:[RenamedProperties2 class]]); XCTAssertTrue([link2.linkD isKindOfClass:[RenamedProperties1 class]]); XCTAssertTrue([link2.array[0] isKindOfClass:[RenamedProperties2 class]]); XCTAssertTrue([link2.array[1] isKindOfClass:[RenamedProperties2 class]]); XCTAssertTrue([link1.linkA isEqualToObject:results1[0]]); XCTAssertTrue([link1.linkB isEqualToObject:results1[1]]); XCTAssertTrue([link1.linkA isEqualToObject:results2[0]]); XCTAssertTrue([link1.linkB isEqualToObject:results2[1]]); XCTAssertTrue([link2.linkC isEqualToObject:results1[1]]); XCTAssertTrue([link2.linkD isEqualToObject:results1[0]]); XCTAssertTrue([link2.linkC isEqualToObject:results2[1]]); XCTAssertTrue([link2.linkD isEqualToObject:results2[0]]); XCTAssertEqualObjects([link1.array valueForKey:@"propB"], (@[@"a", @"b"])); XCTAssertEqualObjects([link2.array valueForKey:@"propD"], (@[@"b", @"a"])); XCTAssertTrue([obj1.linking1[0] isEqualToObject:link1]); XCTAssertTrue([obj1.linking2[0] isEqualToObject:link2]); XCTAssertTrue([obj2.linking1[0] isEqualToObject:link2]); XCTAssertTrue([obj2.linking2[0] isEqualToObject:link1]); [realm cancelWriteTransaction]; } - (void)testAllMethodsCheckThread { RLMRealm *realm = [RLMRealm defaultRealm]; __block AllTypesObject *obj; __block StringObject *stringObj; NSDictionary *values = [AllTypesObject values:1 stringObject:nil]; [realm transactionWithBlock:^{ obj = [AllTypesObject createInRealm:realm withValue:values]; stringObj = [StringObject createInRealm:realm withValue:@[@""]]; }]; [realm beginWriteTransaction]; NSArray *propertyNames = [obj.objectSchema.properties valueForKey:@"name"]; [self dispatchAsyncAndWait:^{ // Getters for (NSString *prop in propertyNames) { RLMAssertThrowsWithReasonMatching(obj[prop], @"thread"); RLMAssertThrowsWithReasonMatching([obj valueForKey:prop], @"thread"); } RLMAssertThrowsWithReasonMatching(obj.boolCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.intCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.floatCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.doubleCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.stringCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.binaryCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.dateCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.cBoolCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.longCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectIdCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.decimalCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectCol, @"thread"); RLMAssertThrowsWithReasonMatching(obj.linkingObjectsCol, @"thread"); // Setters for (NSString *prop in propertyNames) { RLMAssertThrowsWithReasonMatching(obj[prop] = values[prop], @"thread"); RLMAssertThrowsWithReasonMatching([obj setValue:values[prop] forKey:prop], @"thread"); } RLMAssertThrowsWithReasonMatching(obj.boolCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.intCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.floatCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.doubleCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.stringCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.binaryCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.dateCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.cBoolCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.longCol = 0, @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectIdCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.decimalCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectCol = nil, @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectCol = [StringObject new], @"thread"); RLMAssertThrowsWithReasonMatching(obj.objectCol = stringObj, @"thread"); }]; [realm cancelWriteTransaction]; } - (void)testAllMethodsCheckForInvalidation { RLMRealm *realm = [RLMRealm defaultRealm]; __block StringObject *stringObj; NSDictionary *values = [AllTypesObject values:1 stringObject:nil]; [realm transactionWithBlock:^{ [AllTypesObject createInRealm:realm withValue:values]; stringObj = [StringObject createInRealm:realm withValue:@[@""]]; }]; for (int i = 0; i < 2; ++i) { AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; // Deleting the object directly and indirectly leave the managed // accessor in different states, so test both if (i == 0) { [realm deleteObject:obj]; } else { [realm deleteObjects:[AllTypesObject allObjectsInRealm:realm]]; } NSArray *propertyNames = [obj.objectSchema.properties valueForKey:@"name"]; // Getters for (NSString *prop in propertyNames) { RLMAssertThrowsWithReasonMatching(obj[prop], @"invalidated"); RLMAssertThrowsWithReasonMatching([obj valueForKey:prop], @"invalidated"); } RLMAssertThrowsWithReasonMatching(obj.boolCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.intCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.floatCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.doubleCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.stringCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.binaryCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.dateCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.cBoolCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.longCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectIdCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.decimalCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectCol, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.linkingObjectsCol, @"invalidated"); // Setters for (NSString *prop in propertyNames) { RLMAssertThrowsWithReasonMatching(obj[prop] = values[prop], @"invalidated"); RLMAssertThrowsWithReasonMatching([obj setValue:values[prop] forKey:prop], @"invalidated"); } RLMAssertThrowsWithReasonMatching(obj.boolCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.intCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.floatCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.doubleCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.stringCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.binaryCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.dateCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.cBoolCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.longCol = 0, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectIdCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.decimalCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectCol = nil, @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectCol = [StringObject new], @"invalidated"); RLMAssertThrowsWithReasonMatching(obj.objectCol = stringObj, @"invalidated"); [realm cancelWriteTransaction]; } } @end ================================================ FILE: Realm/Tests/ObjectSchemaTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #pragma mark - Test Objects @interface IndexedObject : RLMObject @property NSString *stringCol; @property NSInteger integerCol; @property int intCol; @property long longCol; @property long long longlongCol; @property BOOL boolCol; @property NSDate *dateCol; @property NSNumber *optionalIntCol; @property NSNumber *optionalBoolCol; @property float floatCol; @property double doubleCol; @property NSData *dataCol; @property NSNumber *optionalFloatCol; @property NSNumber *optionalDoubleCol; @end @implementation IndexedObject + (NSArray *)indexedProperties { return @[@"stringCol", @"integerCol", @"intCol", @"longCol", @"longlongCol", @"boolCol", @"dateCol", @"optionalIntCol", @"optionalBoolCol"]; } @end #pragma mark - Tests @interface ObjectSchemaTests : RLMTestCase @end @implementation ObjectSchemaTests - (void)testDescription { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[PrimaryStringObject class]]; XCTAssertEqualObjects(objectSchema.description, @"PrimaryStringObject {\n" @"\tstringCol {\n" @"\t\ttype = string;\n" @"\t\tcolumnName = stringCol;\n" @"\t\tindexed = YES;\n" @"\t\tisPrimary = YES;\n" @"\t\tarray = NO;\n" @"\t\tset = NO;\n" @"\t\tdictionary = NO;\n" @"\t\toptional = NO;\n" @"\t}\n" @"\tintCol {\n" @"\t\ttype = int;\n" @"\t\tcolumnName = intCol;\n" @"\t\tindexed = NO;\n" @"\t\tisPrimary = NO;\n" @"\t\tarray = NO;\n" @"\t\tset = NO;\n" @"\t\tdictionary = NO;\n" @"\t\toptional = NO;\n" @"\t}\n" @"}"); objectSchema = [RLMObjectSchema schemaForObjectClass:[EmbeddedIntObject class]]; XCTAssertEqualObjects(objectSchema.description, @"EmbeddedIntObject (embedded) {\n" @"\tintCol {\n" @"\t\ttype = int;\n" @"\t\tcolumnName = intCol;\n" @"\t\tindexed = NO;\n" @"\t\tisPrimary = NO;\n" @"\t\tarray = NO;\n" @"\t\tset = NO;\n" @"\t\tdictionary = NO;\n" @"\t\toptional = NO;\n" @"\t}\n" @"}"); objectSchema = [RLMObjectSchema schemaForObjectClass:[RenamedProperties class]]; XCTAssertEqualObjects(objectSchema.description, @"RenamedProperties {\n" @"\tintCol {\n" @"\t\ttype = int;\n" @"\t\tcolumnName = custom_intCol;\n" @"\t\tindexed = NO;\n" @"\t\tisPrimary = NO;\n" @"\t\tarray = NO;\n" @"\t\tset = NO;\n" @"\t\tdictionary = NO;\n" @"\t\toptional = NO;\n" @"\t}\n" @"\tstringCol {\n" @"\t\ttype = string;\n" @"\t\tcolumnName = custom_stringCol;\n" @"\t\tindexed = NO;\n" @"\t\tisPrimary = NO;\n" @"\t\tarray = NO;\n" @"\t\tset = NO;\n" @"\t\tdictionary = NO;\n" @"\t\toptional = YES;\n" @"\t}\n" @"}"); } - (void)testObjectForKeyedSubscript { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[PrimaryStringObject class]]; XCTAssertEqualObjects(objectSchema[@"stringCol"].name, @"stringCol"); XCTAssertEqualObjects(objectSchema[@"intCol"].name, @"intCol"); XCTAssertNil(objectSchema[@"missing"]); } - (void)testPrimaryKeyProperty { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[PrimaryStringObject class]]; XCTAssertEqualObjects(objectSchema.primaryKeyProperty.name, @"stringCol"); objectSchema = [RLMObjectSchema schemaForObjectClass:[StringObject class]]; XCTAssertNil(objectSchema.primaryKeyProperty); } #pragma mark - Schema Discovery - (void)testIgnoredUnsupportedProperty { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[IgnoredURLObject class]]; XCTAssertEqual(1U, objectSchema.properties.count); XCTAssertEqualObjects(objectSchema.properties[0].name, @"name"); } - (void)testReadOnlyPropertiesImplicitlyIgnored { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[ReadOnlyPropertyObject class]]; XCTAssertEqual(1U, objectSchema.properties.count); XCTAssertEqualObjects(objectSchema.properties[0].name, @"readOnlyPropertyMadeReadWriteInClassExtension"); } - (void)testIndex { RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:[IndexedObject class]]; XCTAssertTrue(schema[@"stringCol"].indexed); XCTAssertTrue(schema[@"integerCol"].indexed); XCTAssertTrue(schema[@"intCol"].indexed); XCTAssertTrue(schema[@"longCol"].indexed); XCTAssertTrue(schema[@"longlongCol"].indexed); XCTAssertTrue(schema[@"boolCol"].indexed); XCTAssertTrue(schema[@"dateCol"].indexed); XCTAssertTrue(schema[@"optionalIntCol"].indexed); XCTAssertTrue(schema[@"optionalBoolCol"].indexed); XCTAssertFalse(schema[@"floatCol"].indexed); XCTAssertFalse(schema[@"doubleCol"].indexed); XCTAssertFalse(schema[@"dataCol"].indexed); XCTAssertFalse(schema[@"optionalFloatCol"].indexed); XCTAssertFalse(schema[@"optionalDoubleCol"].indexed); } @end ================================================ FILE: Realm/Tests/ObjectTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #import "RLMSchema_Private.h" #import #import #import #import #pragma mark - Test Objects @interface DefaultObject : RLMObject @property int intCol; @property float floatCol; @property double doubleCol; @property BOOL boolCol; @property NSDate *dateCol; @property NSString *stringCol; @property NSData *binaryCol; @end @implementation DefaultObject + (NSDictionary *)defaultPropertyValues { NSString *binaryString = @"binary"; NSData *binaryData = [binaryString dataUsingEncoding:NSUTF8StringEncoding]; return @{@"intCol": @12, @"floatCol": @88.9f, @"doubleCol": @1002.892, @"boolCol": @YES, @"dateCol": [NSDate dateWithTimeIntervalSince1970:999999], @"stringCol": @"potato", @"binaryCol": binaryData}; } @end @interface DynamicDefaultObject : RLMObject @property int intCol; @property float floatCol; @property double doubleCol; @property NSDate *dateCol; @property NSString *stringCol; @property NSData *binaryCol; @end @implementation DynamicDefaultObject + (BOOL)shouldIncludeInDefaultSchema { return NO; } + (NSDictionary *)defaultPropertyValues { static NSInteger dynamicDefaultSeed = 0; dynamicDefaultSeed++; return @{@"intCol": @(dynamicDefaultSeed), @"floatCol": @((float)dynamicDefaultSeed), @"doubleCol": @((double)dynamicDefaultSeed), @"dateCol": [NSDate dateWithTimeIntervalSince1970:dynamicDefaultSeed], @"stringCol": [[NSUUID UUID] UUIDString], @"binaryCol": [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding]}; } + (NSString *)primaryKey { return @"intCol"; } @end @class CycleObject; RLM_COLLECTION_TYPE(CycleObject) @interface CycleObject : RLMObject @property RLM_GENERIC_ARRAY(CycleObject) *objects; @property RLM_GENERIC_SET(CycleObject) *objectSet; @end @implementation CycleObject @end @interface PrimaryStringObjectWrapper : RLMObject @property PrimaryStringObject *primaryStringObject; @end @implementation PrimaryStringObjectWrapper @end @interface PrimaryNestedObject : RLMObject @property int primaryCol; @property PrimaryStringObject *primaryStringObject; @property PrimaryStringObjectWrapper *primaryStringObjectWrapper; @property StringObject *stringObject; @property RLM_GENERIC_ARRAY(PrimaryIntObject) *primaryIntArray; @property RLM_GENERIC_SET(PrimaryIntObject) *primaryIntSet; @property NSString *stringCol; @end @implementation PrimaryNestedObject + (NSString *)primaryKey { return @"primaryCol"; } + (NSDictionary *)defaultPropertyValues { return @{@"stringCol": @"default"}; } @end @interface StringSubclassObject : StringObject @property NSString *stringCol2; @end @implementation StringSubclassObject @end @interface StringObjectNoThrow : StringObject @end @implementation StringObjectNoThrow - (id)valueForUndefinedKey:(__unused NSString *)key { return nil; } @end @interface StringSubclassObjectWithDefaults : StringObjectNoThrow @property NSString *stringCol2; @end @implementation StringSubclassObjectWithDefaults +(NSDictionary *)defaultPropertyValues { return @{@"stringCol2": @"default"}; } @end @interface StringLinkObject : RLMObject @property StringObject *stringObjectCol; @property RLM_GENERIC_ARRAY(StringObject) *stringObjectArrayCol; @end @implementation StringLinkObject @end @interface ReadOnlyPropertyObject () @property (readwrite) int readOnlyPropertyMadeReadWriteInClassExtension; @end @interface DataObject : RLMObject @property NSData *data1; @property NSData *data2; @end @implementation DataObject @end @interface DateObjectNoThrow : DateObject @property NSDate *date2; @end @implementation DateObjectNoThrow - (id)valueForUndefinedKey:(__unused NSString *)key { return nil; } @end @interface DateSubclassObject : DateObjectNoThrow @property NSDate *date3; @end @implementation DateSubclassObject @end @interface DateDefaultsObject : DateObjectNoThrow @property NSDate *date3; @end @implementation DateDefaultsObject + (NSDictionary *)defaultPropertyValues { return @{@"date3": [NSDate date]}; } @end @interface SubclassDateObject : NSObject @property NSDate *dateCol; @property (getter=customGetter) NSDate *date2; @property (setter=customSetter:) NSDate *date3; @end @implementation SubclassDateObject @end #pragma mark - Tests @interface ObjectTests : RLMTestCase @end @implementation ObjectTests - (void)testKeyedSubscripting { EmployeeObject *objs = [[EmployeeObject alloc] initWithValue:@{@"name": @"Test0", @"age": @23, @"hired": @NO}]; XCTAssertEqualObjects(objs[@"name"], @"Test0", @"Name should be Test0"); XCTAssertEqualObjects(objs[@"age"], @23, @"age should be 23"); XCTAssertEqualObjects(objs[@"hired"], @NO, @"hired should be NO"); objs[@"name"] = @"Test1"; XCTAssertEqualObjects(objs.name, @"Test1", @"Name should be Test1"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *obj0 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test1", @"age": @24, @"hired": @NO}]; EmployeeObject *obj1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test2", @"age": @25, @"hired": @YES}]; [realm commitWriteTransaction]; XCTAssertEqualObjects(obj0[@"name"], @"Test1", @"Name should be Test1"); XCTAssertEqualObjects(obj1[@"name"], @"Test2", @"Name should be Test1"); [realm beginWriteTransaction]; obj0[@"name"] = @"newName"; [realm commitWriteTransaction]; XCTAssertEqualObjects(obj0[@"name"], @"newName", @"Name should be newName"); [realm beginWriteTransaction]; obj0[@"name"] = nil; [realm commitWriteTransaction]; XCTAssertNil(obj0[@"name"]); } - (void)testCannotUpdatePrimaryKey { PrimaryIntObject *intObj = [[PrimaryIntObject alloc] init]; intObj.intCol = 1; XCTAssertNoThrow(intObj.intCol = 0); PrimaryStringObject *stringObj = [[PrimaryStringObject alloc] init]; stringObj.stringCol = @"a"; XCTAssertNoThrow(stringObj.stringCol = @"b"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:intObj]; RLMAssertThrowsWithReason(intObj.intCol = 1, @"Primary key can't be changed"); RLMAssertThrowsWithReason(intObj[@"intCol"] = @1, @"Primary key can't be changed"); RLMAssertThrowsWithReason([intObj setValue:@1 forKey:@"intCol"], @"Primary key can't be changed"); [realm addObject:stringObj]; RLMAssertThrowsWithReason(stringObj.stringCol = @"a", @"Primary key can't be changed"); RLMAssertThrowsWithReason(stringObj[@"stringCol"] = @"a", @"Primary key can't be changed"); RLMAssertThrowsWithReason([stringObj setValue:@"a" forKey:@"stringCol"], @"Primary key can't be changed"); [realm cancelWriteTransaction]; } - (void)testDataTypes { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; const char bin[4] = { 0, 1, 2, 3 }; NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2]; NSData *bin2 = [[NSData alloc] initWithBytes:bin length:sizeof bin]; NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000]; NSDate *timeZero = [NSDate dateWithTimeIntervalSince1970:0]; RLMObjectId *objectId1 = [RLMObjectId objectId]; RLMObjectId *objectId2 = [RLMObjectId objectId]; AllTypesObject *c = [[AllTypesObject alloc] init]; c.boolCol = NO; c.intCol = 54; c.floatCol = 0.7f; c.doubleCol = 0.8; c.stringCol = @"foo"; c.binaryCol = bin1; c.dateCol = timeZero; c.cBoolCol = false; c.longCol = 99; c.decimalCol = [RLMDecimal128 decimalWithNumber:@1]; c.objectIdCol = objectId1; c.objectCol = [[StringObject alloc] init]; c.objectCol.stringCol = @"c"; c.uuidCol = [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]; [realm addObject:c]; [AllTypesObject createInRealm:realm withValue:@[@YES, @506, @7.7f, @8.8, @"banach", bin2, timeNow, @YES, @(-20), [RLMDecimal128 decimalWithNumber:@2], objectId2, [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"], NSNull.null]]; [realm commitWriteTransaction]; AllTypesObject *row1 = [AllTypesObject allObjects][0]; AllTypesObject *row2 = [AllTypesObject allObjects][1]; XCTAssertEqual(row1.boolCol, NO, @"row1.BoolCol"); XCTAssertEqual(row2.boolCol, YES, @"row2.BoolCol"); XCTAssertEqual(row1.intCol, 54, @"row1.IntCol"); XCTAssertEqual(row2.intCol, 506, @"row2.IntCol"); XCTAssertEqual(row1.floatCol, 0.7f, @"row1.FloatCol"); XCTAssertEqual(row2.floatCol, 7.7f, @"row2.FloatCol"); XCTAssertEqual(row1.doubleCol, 0.8, @"row1.DoubleCol"); XCTAssertEqual(row2.doubleCol, 8.8, @"row2.DoubleCol"); XCTAssertTrue([row1.stringCol isEqual:@"foo"], @"row1.StringCol"); XCTAssertTrue([row2.stringCol isEqual:@"banach"], @"row2.StringCol"); XCTAssertTrue([row1.binaryCol isEqual:bin1], @"row1.BinaryCol"); XCTAssertTrue([row2.binaryCol isEqual:bin2], @"row2.BinaryCol"); XCTAssertTrue(([row1.dateCol isEqual:timeZero]), @"row1.DateCol"); XCTAssertTrue(([row2.dateCol isEqual:timeNow]), @"row2.DateCol"); XCTAssertEqual(row1.cBoolCol, false, @"row1.cBoolCol"); XCTAssertEqual(row2.cBoolCol, true, @"row2.cBoolCol"); XCTAssertEqual(row1.longCol, 99L, @"row1.IntCol"); XCTAssertEqual(row2.longCol, -20L, @"row2.IntCol"); XCTAssertEqualObjects(row1.decimalCol, [RLMDecimal128 decimalWithNumber:@1]); XCTAssertEqualObjects(row2.decimalCol, [RLMDecimal128 decimalWithNumber:@2]); XCTAssertEqualObjects(row1.objectIdCol, objectId1); XCTAssertEqualObjects(row2.objectIdCol, objectId2); XCTAssertTrue([row1.objectCol.stringCol isEqual:@"c"], @"row1.objectCol"); XCTAssertNil(row2.objectCol, @"row2.objectCol"); XCTAssertTrue([row1.uuidCol.UUIDString isEqualToString: @"137DECC8-B300-4954-A233-F89909F4FD89"], @"row1.uuidCol"); XCTAssertTrue([row2.uuidCol.UUIDString isEqualToString: @"137DECC8-B300-4954-A233-F89909F4FD89"], @"row2.uuidCol"); [realm transactionWithBlock:^{ row1.boolCol = NO; row1.cBoolCol = false; row1.boolCol = (BOOL)6; row1.cBoolCol = (BOOL)6; }]; XCTAssertEqual(row1.boolCol, true); XCTAssertEqual(row1.cBoolCol, true); AllTypesObject *o = [[AllTypesObject alloc] initWithValue:row1]; o.floatCol = NAN; o.doubleCol = NAN; o.decimalCol = [RLMDecimal128 decimalWithNumber:[NSDecimalNumber notANumber]]; [realm transactionWithBlock:^{ [realm addObject:o]; }]; XCTAssertTrue(isnan(o.floatCol)); XCTAssertTrue(isnan(o.doubleCol)); XCTAssertTrue(o.decimalCol.isNaN); } - (void)testObjectSubclass { // test className methods XCTAssertEqualObjects(@"StringObject", [StringObject className]); XCTAssertEqualObjects(@"StringSubclassObject", [StringSubclassObject className]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [StringObject createInDefaultRealmWithValue:@[@"string"]]; StringSubclassObject *obj = [StringSubclassObject createInDefaultRealmWithValue:@[@"string", @"string2"]]; // ensure property ordering XCTAssertEqualObjects([obj.objectSchema.properties[0] name], @"stringCol"); XCTAssertEqualObjects([obj.objectSchema.properties[1] name], @"stringCol2"); [realm commitWriteTransaction]; // ensure creation in proper table RLMResults *results = StringSubclassObject.allObjects; XCTAssertEqual(1U, results.count); XCTAssertEqual(1U, StringObject.allObjects.count); // ensure exceptions on when using polymorphism [realm beginWriteTransaction]; StringLinkObject *linkObject = [StringLinkObject createInDefaultRealmWithValue:@[NSNull.null, @[]]]; RLMAssertThrowsWithReasonMatching(linkObject.stringObjectCol = obj, @"Can't .*StringSubclassObject.*StringObject"); RLMAssertThrowsWithReasonMatching([linkObject.stringObjectArrayCol addObject:obj], @"Object of type .*StringSubclassObject.*does not match.*StringObject.*"); [realm commitWriteTransaction]; } - (void)testDateDistantFuture { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantFuture]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(NSDate.distantFuture, dateObject.dateCol); } - (void)testDateDistantPast { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantPast]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(NSDate.distantPast, dateObject.dateCol); } - (void)testDate50kYears { NSCalendarUnit units = (NSCalendarUnit)(NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitDay); NSDateComponents *components = [[NSCalendar currentCalendar] components:units fromDate:NSDate.date]; components.calendar = [NSCalendar currentCalendar]; components.year += 50000; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[components.date]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(components.date, dateObject.dateCol); } static void testDatesInRange(NSTimeInterval from, NSTimeInterval to, void (^check)(NSDate *, NSDate *)) { NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:from]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]]; while (from < to) @autoreleasepool { check(dateObject.dateCol, date); from = nextafter(from, DBL_MAX); date = [NSDate dateWithTimeIntervalSinceReferenceDate:from]; dateObject.dateCol = date; } [realm commitWriteTransaction]; } - (void)testExactRepresentationOfDatesAroundNow { NSDate *date = [NSDate date]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundDistantFuture { NSDate *date = [NSDate distantFuture]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundEpoch { NSDate *date = [NSDate dateWithTimeIntervalSince1970:0]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundReferenceDate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; NSDate *zero = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[zero]]; XCTAssertEqualObjects(dateObject.dateCol, zero); // Just shy of 1ns should still be zero dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(1e-9, -DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); // Very slightly over 1ns (since 1e-9 can't be exactly represented by a double) dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1e-9]; XCTAssertNotEqualObjects(dateObject.dateCol, zero); // Round toward zero, so -1ns + epsilon is zero dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(0, -DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(-1e-9, DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1e-9]; XCTAssertNotEqualObjects(dateObject.dateCol, zero); [realm commitWriteTransaction]; } - (void)testDatesOutsideOfTimestampRange { NSDate *date = [NSDate date]; NSDate *maxDate = [NSDate dateWithTimeIntervalSince1970:(double)(1ULL << 63) + .999999999]; NSDate *minDate = [NSDate dateWithTimeIntervalSince1970:-(double)(1ULL << 63) - .999999999]; NSDate *justOverMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, DBL_MAX)]; NSDate *justUnderMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, -DBL_MAX)]; NSDate *justOverMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, DBL_MAX)]; NSDate *justUnderMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, -DBL_MAX)]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]]; dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, [NSDate dateWithTimeIntervalSince1970:0]); dateObject.dateCol = maxDate; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = justOverMaxDate; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:DBL_MAX]; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = minDate; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = justUnderMinDate; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-DBL_MAX]; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = justUnderMaxDate; XCTAssertEqualObjects(dateObject.dateCol, justUnderMaxDate); dateObject.dateCol = justOverMinDate; XCTAssertEqualObjects(dateObject.dateCol, justOverMinDate); [realm commitWriteTransaction]; } - (void)testDataSizeLimits { RLMRealm *realm = [RLMRealm defaultRealm]; // Allocation must be < 16 MB, with an 8-byte header and the allocation size // 8-byte aligned static const int maxSize = 0xFFFFFF - 15; // Multiple 16 MB blobs should be fine void *buffer = malloc(maxSize); strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello"); DataObject *obj = [[DataObject alloc] init]; obj.data1 = obj.data2 = [NSData dataWithBytesNoCopy:buffer length:maxSize freeWhenDone:YES]; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqual(maxSize, obj.data1.length); XCTAssertEqual(maxSize, obj.data2.length); XCTAssertTrue(strcmp((const char *)obj.data1.bytes + obj.data1.length - sizeof("hello") - 1, "hello") == 0); XCTAssertTrue(strcmp((const char *)obj.data2.bytes + obj.data2.length - sizeof("hello") - 1, "hello") == 0); // A blob over 16 MB should throw (and not crash) [realm beginWriteTransaction]; RLMAssertThrowsWithReason(obj.data1 = [NSData dataWithBytesNoCopy:malloc(maxSize + 1) length:maxSize + 1 freeWhenDone:YES], @"Binary too big"); [realm commitWriteTransaction]; } - (void)testStringSizeLimits { RLMRealm *realm = [RLMRealm defaultRealm]; // Allocation must be < 16 MB, with an 8-byte header, trailing NUL, and the // allocation size 8-byte aligned static const int maxSize = 0xFFFFFF - 16; void *buffer = calloc(maxSize, 1); strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello"); NSString *str = [[NSString alloc] initWithBytesNoCopy:buffer length:maxSize encoding:NSUTF8StringEncoding freeWhenDone:YES]; StringObject *obj = [[StringObject alloc] init]; obj.stringCol = str; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqualObjects(str, obj.stringCol); // A blob over 16 MB should throw (and not crash) [realm beginWriteTransaction]; XCTAssertThrows(obj.stringCol = [[NSString alloc] initWithBytesNoCopy:calloc(maxSize + 1, 1) length:maxSize + 1 encoding:NSUTF8StringEncoding freeWhenDone:YES]); [realm commitWriteTransaction]; } - (void)testAddingObjectNotInSchemaThrows { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[StringObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; RLMAssertThrowsWithReasonMatching([realm addObject:[[IntObject alloc] initWithValue:@[@1]]], @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`"); RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:@[@1]], @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`"); XCTAssertNoThrow([realm addObject:[[StringObject alloc] initWithValue:@[@"A"]]]); XCTAssertNoThrow([StringObject createInRealm:realm withValue:@[@"A"]]); [realm cancelWriteTransaction]; } static void addProperty(Class cls, const char *name, const char *type, size_t size, size_t align, id getter) { objc_property_attribute_t objectColAttrs[] = { {"T", type}, {"V", name}, }; class_addIvar(cls, name, size, align, type); class_addProperty(cls, name, objectColAttrs, sizeof(objectColAttrs) / sizeof(objc_property_attribute_t)); char encoding[4] = " @:"; encoding[0] = *type; class_addMethod(cls, sel_registerName(name), imp_implementationWithBlock(getter), encoding); } - (void)testObjectSubclassAddedAtRuntime { Class objectClass = objc_allocateClassPair(RLMObject.class, "RuntimeGeneratedObject", 0); addProperty(objectClass, "objectCol", "@\"RuntimeGeneratedObject\"", sizeof(id), alignof(id), ^(__unused id obj) { return nil; }); addProperty(objectClass, "intCol", "i", sizeof(int), alignof(int), ^int(__unused id obj) { return 0; }); objc_registerClassPair(objectClass); XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject"); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[objectClass]; XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject"); RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; id object = [objectClass createInRealm:realm withValue:@{@"objectCol": [[objectClass alloc] init], @"intCol": @17}]; RLMObjectSchema *schema = [object objectSchema]; XCTAssertNotNil(schema[@"objectCol"]); XCTAssertNotNil(schema[@"intCol"]); XCTAssert([[object objectCol] isKindOfClass:objectClass]); XCTAssertEqual([object intCol], 17); [realm commitWriteTransaction]; } #pragma mark - Default Property Values - (NSDictionary *)defaultValuesDictionary { return @{@"intCol" : @98, @"floatCol" : @231.0f, @"doubleCol": @123732.9231, @"boolCol" : @NO, @"dateCol" : [NSDate dateWithTimeIntervalSince1970:454321], @"stringCol": @"Westeros", @"binaryCol": [@"inputData" dataUsingEncoding:NSUTF8StringEncoding]}; } - (void)testDefaultValuesFromNoValuePresent { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; NSDictionary *inputValues = [self defaultValuesDictionary]; NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable for (NSString *key in keys) { NSMutableDictionary *dict = [inputValues mutableCopy]; [dict removeObjectForKey:key]; [DefaultObject createInRealm:realm withValue:dict]; } [realm commitWriteTransaction]; // Test allObject for DefaultObject NSDictionary *defaultValues = [DefaultObject defaultPropertyValues]; RLMResults *allObjects = [DefaultObject allObjectsInRealm:realm]; for (NSUInteger i = 0; i < keys.count; ++i) { DefaultObject *object = allObjects[i]; for (NSUInteger j = 0; j < keys.count; ++j) { NSString *key = keys[j]; if (i == j) { XCTAssertEqualObjects(object[key], defaultValues[key]); } else { XCTAssertEqualObjects(object[key], inputValues[key]); } } } } - (void)testDefaultValuesFromNSNull { RLMRealm *realm = [RLMRealm defaultRealm]; NSDictionary *defaultValues = [DefaultObject defaultPropertyValues]; NSDictionary *inputValues = [self defaultValuesDictionary]; NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable for (NSString *key in keys) { NSMutableDictionary *dict = [inputValues mutableCopy]; dict[key] = NSNull.null; RLMProperty *prop = realm.schema[@"DefaultObject"][key]; if (prop.optional) { [realm beginWriteTransaction]; [DefaultObject createInRealm:realm withValue:dict]; [realm commitWriteTransaction]; DefaultObject *object = DefaultObject.allObjects.lastObject; for (NSUInteger j = 0; j < keys.count; ++j) { NSString *key2 = keys[j]; if ([key isEqualToString:key2]) { XCTAssertEqualObjects(object[key2], prop.optional ? nil : defaultValues[key2]); } else { XCTAssertEqualObjects(object[key2], inputValues[key2]); } } } else { [realm beginWriteTransaction]; RLMAssertThrowsWithReason([DefaultObject createInRealm:realm withValue:dict], @"Invalid value '' of type 'NSNull' for "); [realm commitWriteTransaction]; } } } - (void)testDefaultNSNumberPropertyValues { void (^assertDefaults)(NumberObject *) = ^(NumberObject *no) { XCTAssertEqualObjects(no.intObj, @1); XCTAssertEqualObjects(no.floatObj, @2.2f); XCTAssertEqualObjects(no.doubleObj, @3.3); XCTAssertEqualObjects(no.boolObj, @NO); }; assertDefaults([[NumberDefaultsObject alloc] init]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; assertDefaults([NumberDefaultsObject createInRealm:realm withValue:@{}]); [realm cancelWriteTransaction]; } - (void)testDynamicDefaultPropertyValues { void (^assertDifferentPropertyValues)(DynamicDefaultObject *, DynamicDefaultObject *) = ^(DynamicDefaultObject *obj1, DynamicDefaultObject *obj2) { XCTAssertNotEqual(obj1.intCol, obj2.intCol); XCTAssertNotEqual(obj1.floatCol, obj2.floatCol); XCTAssertNotEqual(obj1.doubleCol, obj2.doubleCol); XCTAssertNotEqualWithAccuracy(obj1.dateCol.timeIntervalSinceReferenceDate, obj2.dateCol.timeIntervalSinceReferenceDate, 0.01f); XCTAssertNotEqualObjects(obj1.stringCol, obj2.stringCol); XCTAssertNotEqualObjects(obj1.binaryCol, obj2.binaryCol); }; assertDifferentPropertyValues([[DynamicDefaultObject alloc] init], [[DynamicDefaultObject alloc] init]); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[[DynamicDefaultObject class]]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; assertDifferentPropertyValues([DynamicDefaultObject createInRealm:realm withValue:@{}], [DynamicDefaultObject createInRealm:realm withValue:@{}]); [realm cancelWriteTransaction]; } #pragma mark - Ignored Properties - (void)testCanUseIgnoredProperty { NSURL *url = [NSURL URLWithString:@"http://realm.io"]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; IgnoredURLObject *obj = [IgnoredURLObject new]; obj.name = @"Realm"; obj.url = url; [realm addObject:obj]; XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable inside a write block"); [realm commitWriteTransaction]; XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable outside a write block"); IgnoredURLObject *obj2 = [[IgnoredURLObject objectsWithPredicate:nil] firstObject]; XCTAssertNotNil(obj2, @"object with ignored property should still be stored and accessible through the realm"); XCTAssertEqualObjects(obj2.name, obj.name, @"managed property should be the same"); XCTAssertNil(obj2.url, @"ignored property should be nil when getting from realm"); } #pragma mark - Create - (void)testCreateInRealmValidationForDictionary { RLMRealm *realm = [RLMRealm defaultRealm]; NSDictionary *dictValidAllTypes = [AllTypesObject values:0 stringObject:nil mixedObject: nil]; [realm beginWriteTransaction]; XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:dictValidAllTypes]), @"Creating object with valid value types should not throw exception"); for (NSString *keyToInvalidate in dictValidAllTypes) { NSMutableDictionary *invalidInput = [dictValidAllTypes mutableCopy]; id obj = @"invalid"; if ([keyToInvalidate isEqualToString:@"stringCol"]) { obj = @1; } if ([keyToInvalidate isEqualToString:@"anyCol"]) { obj = self; } invalidInput[keyToInvalidate] = obj; RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput], @"Invalid value '.*'"); } [realm commitWriteTransaction]; } - (void)testCreateInRealmValidationForArray { RLMRealm *realm = [RLMRealm defaultRealm]; // add test/link object to realm [realm beginWriteTransaction]; StringObject *to = [StringObject createInRealm:realm withValue:@[@"c"]]; [realm commitWriteTransaction]; const char bin[4] = { 0, 1, 2, 3 }; NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2]; NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000]; NSArray *const arrayValidAllTypes = @[@NO, @54, @0.7f, @0.8, @"foo", bin1, timeNow, @NO, @(99), [RLMDecimal128 decimalWithNumber:@2], [RLMObjectId objectId], [[NSUUID alloc] init], to]; [realm beginWriteTransaction]; // Test NSArray XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:arrayValidAllTypes]), @"Creating object with valid value types should not throw exception"); const NSInteger stringColIndex = 4; for (NSUInteger i = 0; i < arrayValidAllTypes.count; i++) { NSMutableArray *invalidInput = [arrayValidAllTypes mutableCopy]; id obj = @"invalid"; if (i == stringColIndex) { obj = @1; } invalidInput[i] = obj; RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput], @"Invalid value '.*'"); } [realm commitWriteTransaction]; } - (void)testCreateInRealmReusesExistingObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]]; OwnerObject *owner = [OwnerObject createInDefaultRealmWithValue:@[@"name", dog]]; XCTAssertTrue([owner.dog isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); DogArrayObject *dogArray = [DogArrayObject createInDefaultRealmWithValue:@[@[dog]]]; XCTAssertTrue([dogArray.dogs[0] isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); [realm commitWriteTransaction]; } - (void)testCreateInRealmReusesExistingNestedObjectsByPrimaryKey { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; PrimaryEmployeeObject *eo = [PrimaryEmployeeObject createInRealm:realm withValue:@[@"Samuel", @19, @NO]]; PrimaryCompanyObject *co = [PrimaryCompanyObject createInRealm:realm withValue:@[@"Realm", @[eo], @[eo], @{ @"key": eo }, eo, @[eo]]]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{ @"name": @"Realm", @"intern": @{@"name":@"Samuel", @"hired":@YES}, }]; [realm commitWriteTransaction]; XCTAssertEqual(1U, co.employees.count); XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count); XCTAssertEqualObjects(@"Samuel", eo.name); XCTAssertEqual(YES, eo.hired); XCTAssertEqual(19, eo.age); [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{ @"name": @"Realm", @"employees": @[@{@"name":@"Samuel", @"hired":@NO}], @"employeeSet": @[@{@"name":@"Samuel", @"hired":@NO}], @"intern": @{@"name":@"Samuel", @"age":@20}, }]; [realm commitWriteTransaction]; XCTAssertEqual(1U, co.employees.count); XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count); XCTAssertEqualObjects(@"Samuel", eo.name); XCTAssertEqual(NO, eo.hired); XCTAssertEqual(20, eo.age); [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{@"name": @"Realm", @"wrappedIntern": @[eo]}]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [[PrimaryEmployeeObject allObjectsInRealm:realm] count]); } - (void)testCreateInRealmCopiesFromOtherRealm { RLMRealm *realm1 = [RLMRealm defaultRealm]; RLMRealm *realm2 = [self realmWithTestPath]; [realm1 beginWriteTransaction]; [realm2 beginWriteTransaction]; DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]]; OwnerObject *owner = [OwnerObject createInRealm:realm2 withValue:@[@"name", dog]]; XCTAssertFalse([owner.dog isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); XCTAssertEqual(1U, [DogObject allObjectsInRealm:realm2].count); DogArrayObject *dogArray = [DogArrayObject createInRealm:realm2 withValue:@[@[dog]]]; XCTAssertFalse([dogArray.dogs[0] isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); XCTAssertEqual(2U, [DogObject allObjectsInRealm:realm2].count); [realm1 commitWriteTransaction]; [realm2 commitWriteTransaction]; } - (void)testCreateInRealmWithOtherObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObjectNoThrow *object = [DateObjectNoThrow createInDefaultRealmWithValue:@[NSDate.date, NSDate.date]]; // create subclass with instance of base class with/without default objects XCTAssertNoThrow([DateSubclassObject createInDefaultRealmWithValue:object]); XCTAssertNoThrow([DateObjectNoThrow createInDefaultRealmWithValue:object]); // create using non-realm object with custom getter SubclassDateObject *obj = [SubclassDateObject new]; obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1000]; obj.date2 = [NSDate dateWithTimeIntervalSinceReferenceDate:2000]; obj.date3 = [NSDate dateWithTimeIntervalSinceReferenceDate:3000]; [DateDefaultsObject createInDefaultRealmWithValue:obj]; XCTAssertEqual(2U, DateObjectNoThrow.allObjects.count); [realm commitWriteTransaction]; } #pragma mark - Description - (void)testObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // Init object before adding to realm EmployeeObject *soInit = [[EmployeeObject alloc] init]; soInit.name = @"Peter"; soInit.age = 30; soInit.hired = YES; [realm addObject:soInit]; // description asserts block void (^descriptionAsserts)(NSString *) = ^(NSString *description) { XCTAssertTrue([description rangeOfString:@"name"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"Peter"].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"age"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:[@30 description]].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"hired"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:[@YES description]].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); }; // Test description in write block descriptionAsserts(soInit.description); [realm commitWriteTransaction]; // Test description in read block NSString *objDescription = [[[EmployeeObject objectsWithPredicate:nil] firstObject] description]; descriptionAsserts(objDescription); soInit = [[EmployeeObject alloc] init]; soInit.age = 20; XCTAssert([soInit.description rangeOfString:@"(null)"].location != NSNotFound); } - (void)testObjectCycleDescription { CycleObject *obj = [[CycleObject alloc] init]; [RLMRealm.defaultRealm transactionWithBlock:^{ [RLMRealm.defaultRealm addObject:obj]; [obj.objects addObject:obj]; [obj.objectSet addObject:obj]; }]; XCTAssertNoThrow(obj.description); } - (void)testDataObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; char longData[200]; [DataObject createInRealm:realm withValue:@[[NSData dataWithBytes:&longData length:200], [NSData dataWithBytes:&longData length:2]]]; [realm commitWriteTransaction]; DataObject *obj = [DataObject allObjectsInRealm:realm].firstObject; XCTAssertTrue([obj.description rangeOfString:@"200 total bytes"].location != NSNotFound); XCTAssertTrue([obj.description rangeOfString:@"2 total bytes"].location != NSNotFound); } - (void)testDeletedObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *obj = [EmployeeObject createInRealm:realm withValue:@[@"Peter", @30, @YES]]; [realm deleteObject:obj]; [realm commitWriteTransaction]; XCTAssertNoThrow(obj.description); } - (void)testManagedObjectUnknownKey { IntObject *obj = [[IntObject alloc] init]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; RLMAssertThrowsWithReason([obj objectForKeyedSubscript:@""], @"Invalid property name '' for class 'IntObject'"); RLMAssertThrowsWithReason([obj setObject:@0 forKeyedSubscript:@""], @"Invalid property name '' for class 'IntObject'"); } - (void)testUnmanagedRealmObjectUnknownKey { IntObject *obj = [[IntObject alloc] init]; XCTAssertThrows([obj objectForKeyedSubscript:@""]); XCTAssertThrows([obj setObject:@0 forKeyedSubscript:@""]); } - (void)testEquality { IntObject *obj = [[IntObject alloc] init]; IntObject *otherObj = [[IntObject alloc] init]; RLMRealm *realm = [RLMRealm defaultRealm]; RLMRealm *otherRealm = [self realmWithTestPath]; XCTAssertFalse([obj isEqual:[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false."); XCTAssertFalse([obj isEqualToObject:(RLMObject *)[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false."); XCTAssertTrue([obj isEqual:obj], @"Same instance."); XCTAssertTrue([obj isEqualToObject:obj], @"Same instance."); XCTAssertFalse([obj isEqualToObject:otherObj], @"Comparison outside of realm."); [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:otherObj], @"One in realm, the other is not."); XCTAssertTrue([obj isEqualToObject:[IntObject allObjects][0]], @"Same table and index."); [otherRealm beginWriteTransaction]; [otherRealm addObject:otherObj]; [otherRealm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:otherObj], @"Different realms."); [realm beginWriteTransaction]; [realm addObject:[[IntObject alloc] init]]; [realm addObject:[[BoolObject alloc] init]]; [realm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:[IntObject allObjects][1]], @"Same table, different index."); XCTAssertFalse([obj isEqualToObject:[BoolObject allObjects][0]], @"Different tables."); } - (void)testCrossThreadAccess { IntObject *obj = [[IntObject alloc] init]; // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(obj.intCol = 5); }]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason(obj.intCol, @"incorrect thread"); }]; } - (void)testIsDeleted { StringObject *obj1 = [[StringObject alloc] initWithValue:@[@"a"]]; XCTAssertEqual(obj1.invalidated, NO); RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [realm addObject:obj1]; StringObject *obj2 = [StringObject createInRealm:realm withValue:@[@"b"]]; XCTAssertEqual([obj1 isInvalidated], NO); XCTAssertEqual(obj2.invalidated, NO); [realm commitWriteTransaction]; // delete [realm beginWriteTransaction]; // Delete directly [realm deleteObject:obj1]; // Delete as result of query since then obj2's realm could point to a different instance [realm deleteObject:[[StringObject allObjectsInRealm:realm] firstObject]]; XCTAssertEqual(obj1.invalidated, YES); XCTAssertEqual(obj2.invalidated, YES); RLMAssertThrowsWithReason([realm addObject:obj1], @"deleted or invalidated"); NSArray *propObject = @[@"", @[obj2], @[]]; RLMAssertThrowsWithReason([ArrayPropertyObject createInRealm:realm withValue:propObject], @"deleted or invalidated"); [realm commitWriteTransaction]; XCTAssertEqual(obj1.invalidated, YES); XCTAssertNil(obj1.realm, @"Realm should be nil after deletion"); } #pragma mark - Invalidated - (void)testIsInvalidated { RLMRealm *realm = [RLMRealm defaultRealm]; StringObject *obj1 = [[StringObject alloc] initWithValue:@[@"a"]]; XCTAssertEqual(obj1.isInvalidated, NO); [realm transactionWithBlock:^{ [realm addObject:obj1]; }]; XCTAssertEqual(obj1.isInvalidated, NO); [realm transactionWithBlock:^{ [realm deleteObject:obj1]; }]; XCTAssertEqual(obj1.isInvalidated, YES); } - (void)testInvalidatedWithCustomObjectClasses { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[[StringObject class]]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; StringObject *obj1 = [[StringObject alloc] initWithValue:@[@"a"]]; XCTAssertEqual(obj1.isInvalidated, NO); [realm transactionWithBlock:^{ [realm addObject:obj1]; }]; XCTAssertEqual(obj1.isInvalidated, NO); [realm transactionWithBlock:^{ [realm deleteObject:obj1]; }]; XCTAssertEqual(obj1.isInvalidated, YES); } #pragma mark - Primary Keys - (void)testPrimaryKey { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; RLMAssertThrowsWithReason([PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])], @"existing primary key value"); [PrimaryIntObject createInDefaultRealmWithValue:(@[@1])]; [PrimaryIntObject createInDefaultRealmWithValue:(@{@"intCol": @2})]; RLMAssertThrowsWithReason([PrimaryIntObject createInDefaultRealmWithValue:(@[@1])], @"existing primary key value"); [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])]; [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 41)])]; RLMAssertThrowsWithReason([PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])], @"existing primary key value"); [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@1]]; [PrimaryNullableIntObject createInDefaultRealmWithValue:(@{@"optIntCol": @2, @"value": @0})]; [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]]; RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[@1, @0])], @"existing primary key value"); RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[NSNull.null, @0])], @"existing primary key value"); [realm commitWriteTransaction]; } - (void)testCreateOrUpdate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; PrimaryNullableStringObject *obj1 = [PrimaryNullableStringObject createOrUpdateInDefaultRealmWithValue:@[@"string", @1]]; RLMResults *objects = [PrimaryNullableStringObject allObjects]; XCTAssertEqual([objects count], 1U, @"Should have 1 object"); XCTAssertEqual(obj1.intCol, 1, @"Value should be 1"); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"string2", @"intCol": @2}]; XCTAssertEqual([objects count], 2U, @"Should have 2 objects"); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @5}]; [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @7}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 7); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": NSNull.null, @"intCol": @11}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 11); // upsert with new secondary property [PrimaryNullableStringObject createOrUpdateInDefaultRealmWithValue:@[@"string", @3]]; XCTAssertEqual([objects count], 3U, @"Should have 3 objects"); XCTAssertEqual(obj1.intCol, 3, @"Value should be 3"); [realm commitWriteTransaction]; } - (void)testCreateOrUpdateNestedObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@0, @[@"string", @1], @[@[@"string", @1]], @[@"string"], @[@[@1]], @[@[@1]], @""]]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryIntObject allObjects] count], 1U, @"Should have 1 object"); // update parent and nested object [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0, @"primaryStringObject": @[@"string", @2], @"primaryStringObjectWrapper": @[@[@"string", @2]], @"stringObject": @[@"string2"]}]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([PrimaryStringObject.allObjects.lastObject intCol], 2, @"intCol should be 2"); XCTAssertEqualObjects([PrimaryNestedObject.allObjects.lastObject stringCol], @"", @"stringCol should not have been updated"); XCTAssertEqual(1U, [PrimaryNestedObject.allObjects.lastObject primaryIntArray].count, @"intArray should not have been overwritten"); XCTAssertEqual(1U, [PrimaryNestedObject.allObjects.lastObject primaryIntSet].count, @"intSet should not have been overwritten"); XCTAssertEqual([[StringObject allObjects] count], 2U, @"Should have 2 objects"); // test partial update nulling out object/array properties [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0, @"stringCol": @"updated", @"stringObject": NSNull.null, @"primaryIntArray": NSNull.null, @"primaryIntSet": NSNull.null}]; PrimaryNestedObject *obj = PrimaryNestedObject.allObjects.lastObject; XCTAssertEqual(2, obj.primaryStringObject.intCol, @"primaryStringObject should not have changed"); XCTAssertEqualObjects(obj.stringCol, @"updated", @"stringCol should have been updated"); XCTAssertEqual(0U, obj.primaryIntArray.count, @"intArray should not have been emptied"); XCTAssertEqual(0U, obj.primaryIntSet.count, @"intSet should not have been emptied"); XCTAssertNil(obj.stringObject, @"stringObject should be nil"); // inserting new object should update nested obj = [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@1, @[@"string", @3], @[@[@"string", @3]], @[@"string"], @[], @[], @""]]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 3, @"intCol should be 3"); // test addOrUpdateObject obj.primaryStringObject = [PrimaryStringObject createInDefaultRealmWithValue:@[@"string2", @1]]; PrimaryNestedObject *obj1 = [[PrimaryNestedObject alloc] initWithValue:@[@1, @[@"string2", @4], @[@[@"string2", @4]], @[@"string"], @[@[@1], @[@2]], @[@[@1], @[@2]], @""]]; [realm addOrUpdateObject:obj1]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryIntObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 4, @"intCol should be 4"); [realm commitWriteTransaction]; } - (void)testCreateOrUpdateWithReorderedColumns { @autoreleasepool { // Create a Realm file with the properties in reverse order RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class]; objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]]; RLMSchema *schema = [RLMSchema new]; schema.objectSchema = @[objectSchema]; RLMRealm *realm = [self realmWithTestPathAndSchema:schema]; [realm beginWriteTransaction]; [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@5, @"a"]]; [realm commitWriteTransaction]; } RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 5); // Values in array are used in property declaration order, not table column order [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"a", @6]]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 6); [PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"a", @"intCol": @7}]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 7); [realm commitWriteTransaction]; } - (void)testObjectInSet { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; // set object with primary and non primary keys as they both override isEqual and hash PrimaryStringObject *obj = [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; StringObject *strObj = [StringObject createInDefaultRealmWithValue:@[@"string"]]; NSMutableSet *dict = [NSMutableSet set]; [dict addObject:obj]; [dict addObject:strObj]; // primary key objects should match even with duplicate instances of the same object XCTAssertTrue([dict containsObject:obj]); XCTAssertTrue([dict containsObject:[[PrimaryStringObject allObjects] firstObject]]); // non-primary key objects should only match when comparing identical instances XCTAssertTrue([dict containsObject:strObj]); XCTAssertFalse([dict containsObject:[[StringObject allObjects] firstObject]]); [realm commitWriteTransaction]; } - (void)testObjectForKey { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; PrimaryStringObject *strObj = [PrimaryStringObject createInDefaultRealmWithValue:@[@"key", @0]]; PrimaryNullableStringObject *nullStrObj = [PrimaryNullableStringObject createInDefaultRealmWithValue:@[NSNull.null, @0]]; PrimaryIntObject *intObj = [PrimaryIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nonNullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]]; [realm commitWriteTransaction]; // no PK RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:@""], @"does not have a primary key"); RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:@0], @"does not have a primary key"); RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:NSNull.null], @"does not have a primary key"); RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:nil], @"does not have a primary key"); RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:nil], @"does not have a primary key"); // wrong PK type RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@0], @"Invalid value '0' of type '.*Number.*' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@[]], @"of type '.*Array.*' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReasonMatching([PrimaryIntObject objectForPrimaryKey:@""], @"Invalid value '' of type '.*String.*' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:nil], @"Invalid value '(null)' of type '(null)' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:NSNull.null], @"Invalid value '' of type 'NSNull' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:nil], @"Invalid value '(null)' of type '(null)' for 'string' property 'PrimaryStringObject.stringCol'."); // no object with key XCTAssertNil([PrimaryStringObject objectForPrimaryKey:@"bad key"]); XCTAssertNil([PrimaryIntObject objectForPrimaryKey:@1]); // object with key exists XCTAssertEqualObjects(strObj, [PrimaryStringObject objectForPrimaryKey:@"key"]); XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:NSNull.null]); XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:nil]); XCTAssertEqualObjects(intObj, [PrimaryIntObject objectForPrimaryKey:@0]); XCTAssertEqualObjects(nonNullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:@0]); XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:NSNull.null]); XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:nil]); // nil realm throws RLMAssertThrowsWithReason([PrimaryIntObject objectInRealm:self.nonLiteralNil forPrimaryKey:@0], @"Realm must not be nil"); } - (void)testClassExtension { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; BaseClassStringObject *bObject = [[BaseClassStringObject alloc ] init]; bObject.intCol = 1; bObject.stringCol = @"stringVal"; [realm addObject:bObject]; [realm commitWriteTransaction]; BaseClassStringObject *objectFromRealm = [BaseClassStringObject allObjects][0]; XCTAssertEqual(1, objectFromRealm.intCol); XCTAssertEqualObjects(@"stringVal", objectFromRealm.stringCol); } #pragma mark - Frozen Objects static IntObject *managedObject(void) { IntObject *obj = [[IntObject alloc] init]; RLMRealm *realm = RLMRealm.defaultRealm; [realm transactionWithBlock:^{ [realm addObject:obj]; }]; return obj; } - (void)testIsFrozen { IntObject *standalone = [[IntObject alloc] init]; IntObject *managed = managedObject(); IntObject *frozen = [managed freeze]; XCTAssertFalse(standalone.isFrozen); XCTAssertFalse(managed.isFrozen); XCTAssertTrue(frozen.isFrozen); } - (void)testFreezeUnmanagedObject { RLMAssertThrowsWithReason([[[IntObject alloc] init] freeze], @"Unmanaged objects cannot be frozen."); } - (void)testFreezingFrozenObjectReturnsSelf { IntObject *obj = managedObject(); IntObject *frozen = obj.freeze; XCTAssertNotEqual(obj, frozen); XCTAssertNotEqual(obj.freeze, frozen); XCTAssertEqual(frozen, frozen.freeze); } - (void)testFreezingDeletedObject { IntObject *obj = managedObject(); [obj.realm transactionWithBlock:^{ [obj.realm deleteObject:obj]; }]; RLMAssertThrowsWithReason([obj freeze], @"Object has been deleted or invalidated."); } - (void)testFreezeFromWrongThread { IntObject *obj = managedObject(); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([obj freeze], @"Realm accessed from incorrect thread"); }]; } - (void)testAccessFrozenObjectFromDifferentThread { IntObject *obj = managedObject(); IntObject *frozen = [obj freeze]; [self dispatchAsyncAndWait:^{ XCTAssertEqual(frozen.intCol, 0); }]; } - (void)testMutateFrozenObject { IntObject *obj = managedObject(); IntObject *frozen = obj.freeze; RLMRealm *realm = frozen.realm; RLMAssertThrowsWithReason([realm beginWriteTransaction], @"Can't perform transactions on a frozen Realm"); XCTAssertThrows(frozen.intCol = 1); } - (void)testObserveFrozenObject { IntObject *frozen = [managedObject() freeze]; id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {}; RLMAssertThrowsWithReason([frozen addNotificationBlock:block], @"Frozen Realms do not change and do not have change notifications."); } - (void)testFrozenObjectEquality { IntObject *liveObj = [[IntObject alloc] init]; RLMRealm *realm = RLMRealm.defaultRealm; [realm transactionWithBlock:^{ [realm addObject:liveObj]; }]; IntObject *frozen1 = [liveObj freeze]; IntObject *frozen2 = [liveObj freeze]; XCTAssertNotEqual(frozen1, frozen2); XCTAssertEqualObjects(frozen1, frozen2); [realm transactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"a"]]; }]; IntObject *frozen3 = [liveObj freeze]; XCTAssertEqualObjects(frozen1, frozen2); XCTAssertNotEqualObjects(frozen1, frozen3); XCTAssertNotEqualObjects(frozen2, frozen3); } - (void)testFrozenObjectHashing { RLMRealm *realm = RLMRealm.defaultRealm; [realm transactionWithBlock:^{ // NSSet does a linear search on an array for very small sets, so make // enough objects to ensure it actually does hash lookups for (int i = 0; i < 200; ++i) { [IntObject createInRealm:realm withValue:@[@(i)]]; } }]; NSMutableSet *frozenSet = [NSMutableSet new]; NSMutableSet *thawedSet = [NSMutableSet new]; RLMResults *allObjects = [IntObject allObjectsInRealm:realm]; for (int i = 0; i < 100; ++i) { [thawedSet addObject:allObjects[i]]; [frozenSet addObject:allObjects[i].freeze]; } for (IntObject *obj in allObjects) { XCTAssertFalse([thawedSet containsObject:obj]); XCTAssertFalse([frozenSet containsObject:obj]); XCTAssertEqual([frozenSet containsObject:obj.freeze], obj.intCol < 100); } } - (void)testFreezeInsideWriteTransaction { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; IntObject *obj = [IntObject createInRealm:realm withValue:@[@1]]; RLMAssertThrowsWithReason([obj freeze], @"Cannot freeze an object in the same write transaction as it was created in."); [realm commitWriteTransaction]; [realm beginWriteTransaction]; obj.intCol = 2; // Frozen objects have the value of the object at the start of the transaction XCTAssertEqual(obj.freeze.intCol, 1); [realm cancelWriteTransaction]; } - (void)testThaw { IntObject *frozen = [managedObject() freeze]; XCTAssertTrue([frozen isFrozen]); IntObject *live = [frozen thaw]; XCTAssertFalse([live isFrozen]); RLMRealm *liveRealm = live.realm; [liveRealm beginWriteTransaction]; live.intCol = 1; [liveRealm commitWriteTransaction]; XCTAssertNotEqual(live.intCol, frozen.intCol); } - (void)testThawDeleted { IntObject *obj = managedObject(); IntObject *frozen = [obj freeze]; XCTAssertTrue([frozen isFrozen]); RLMRealm *realm = obj.realm; [realm beginWriteTransaction]; [realm deleteObject:obj]; [realm commitWriteTransaction]; IntObject *thawed = [frozen thaw]; XCTAssertNil(thawed, @"Thaw should return nil when object was deleted"); } - (void)testThawPreviousVersion { IntObject *obj = managedObject(); IntObject *frozen = [obj freeze]; XCTAssertTrue([frozen isFrozen]); XCTAssertEqual(obj.intCol, frozen.intCol); RLMRealm *realm = obj.realm; [realm beginWriteTransaction]; obj.intCol = 1; [realm commitWriteTransaction]; XCTAssertNotEqual(obj.intCol, frozen.intCol, @"Frozen object shouldn't mutate"); IntObject *thawed = [frozen thaw]; XCTAssertFalse(thawed.frozen); XCTAssertEqual(thawed.intCol, obj.intCol, @"Thawed object should reflect transactions since the original reference was frozen."); } - (void)testThawUpdatedOnDifferentThread { IntObject *obj = managedObject(); RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:obj]; __block IntObject *frozen; [self dispatchAsyncAndWait:^{ IntObject *obj = [RLMRealm.defaultRealm resolveThreadSafeReference:tsr]; [RLMRealm.defaultRealm beginWriteTransaction]; obj.intCol = 1; [RLMRealm.defaultRealm commitWriteTransaction]; frozen = [obj freeze]; }]; IntObject* thawed = [frozen thaw]; XCTAssertEqual(thawed.intCol, 0, @"Thaw shouldn't reflect background transactions until main thread realm is refreshed"); [RLMRealm.defaultRealm refresh]; XCTAssertEqual(thawed.intCol, 1); } - (void)testThawCreatedOnDifferentThread { __attribute((objc_precise_lifetime)) RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqual([[IntObject allObjects] count], 0); __block IntObject *frozen; [self dispatchAsyncAndWait:^{ IntObject *obj = managedObject(); frozen = [obj freeze]; }]; XCTAssertNil([frozen thaw]); XCTAssertEqual([[IntObject allObjects] count], 0); [RLMRealm.defaultRealm refresh]; XCTAssertEqual([[IntObject allObjects] count], 1); } @end ================================================ FILE: Realm/Tests/PerformanceTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.h" #if !DEBUG && TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR @interface PerformanceTests : RLMTestCase @property (nonatomic) dispatch_queue_t queue; @property (nonatomic) dispatch_semaphore_t sema; @end static RLMRealm *s_smallRealm, *s_mediumRealm, *s_largeRealm; @implementation PerformanceTests + (void)setUp { [super setUp]; s_smallRealm = [self createStringObjects:1]; s_mediumRealm = [self createStringObjects:5]; s_largeRealm = [self createStringObjects:50]; } + (void)tearDown { s_smallRealm = s_mediumRealm = s_largeRealm = nil; [RLMRealm resetRealmState]; [super tearDown]; } - (void)resetRealmState { // Do nothing, as we need to keep our in-memory realms around between tests } - (void)measureBlock:(__attribute__((noescape)) void (^)(void))block { [super measureBlock:^{ @autoreleasepool { block(); } }]; } - (void)measureMetrics:(NSArray *)metrics automaticallyStartMeasuring:(BOOL)automaticallyStartMeasuring forBlock:(__attribute__((noescape)) void (^)(void))block { [super measureMetrics:metrics automaticallyStartMeasuring:automaticallyStartMeasuring forBlock:^{ @autoreleasepool { block(); } }]; } + (RLMRealm *)createStringObjects:(int)factor { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.inMemoryIdentifier = @(factor).stringValue; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm beginWriteTransaction]; for (int i = 0; i < 10000 * factor; ++i) { [StringObject createInRealm:realm withValue:@[@"a"]]; [StringObject createInRealm:realm withValue:@[@"b"]]; } [realm commitWriteTransaction]; return realm; } - (RLMRealm *)testRealm { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.inMemoryIdentifier = @"test"; return [RLMRealm realmWithConfiguration:config error:nil]; } - (void)testInsertMultiple { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = self.realmWithTestPath; [self startMeasuring]; [realm beginWriteTransaction]; for (int i = 0; i < 500000; ++i) { StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; [realm addObject:obj]; } [realm commitWriteTransaction]; [self stopMeasuring]; [self tearDown]; }]; } - (void)testInsertSingleLiteral { [self measureBlock:^{ RLMRealm *realm = self.realmWithTestPath; for (int i = 0; i < 5000; ++i) { [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; } [self tearDown]; }]; } - (void)testInsertMultipleLiteral { [self measureBlock:^{ RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; for (int i = 0; i < 500000; ++i) { [StringObject createInRealm:realm withValue:@[@"a"]]; } [realm commitWriteTransaction]; [self tearDown]; }]; } - (RLMRealm *)getStringObjects:(int)factor { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.inMemoryIdentifier = @(factor).stringValue; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [NSFileManager.defaultManager removeItemAtURL:RLMTestRealmURL() error:nil]; [realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:nil]; return [self realmWithTestPath]; } - (void)testCountWhereQuery { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ for (int i = 0; i < 50; ++i) { RLMResults *array = [StringObject objectsInRealm:realm where:@"stringCol = 'a'"]; [array count]; } }]; } - (void)testCountWhereTableView { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ for (int i = 0; i < 100; ++i) { RLMResults *array = [StringObject objectsInRealm:realm where:@"stringCol = 'a'"]; [array firstObject]; // Force materialization of backing table view [array count]; } }]; } - (void)testEnumerateAndAccessQuery { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ for (StringObject *so in [StringObject objectsInRealm:realm where:@"stringCol = 'a'"]) { (void)[so stringCol]; } }]; } - (void)testEnumerateAndAccessAll { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ for (StringObject *so in [StringObject allObjectsInRealm:realm]) { (void)[so stringCol]; } }]; } - (void)testEnumerateAndAccessAllTV { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { (void)[so stringCol]; } [realm cancelWriteTransaction]; }]; } - (void)testEnumerateAndAccessAllSlow { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ RLMResults *all = [StringObject allObjectsInRealm:realm]; for (NSUInteger i = 0; i < all.count; ++i) { (void)[all[i] stringCol]; } }]; } - (void)testEnumerateAndAccessArrayProperty { RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; [self measureBlock:^{ for (StringObject *so in apo.array) { (void)[so stringCol]; } }]; } - (void)testEnumerateAndAccessArrayPropertySlow { RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; [self measureBlock:^{ RLMArray *array = apo.array; for (NSUInteger i = 0; i < array.count; ++i) { (void)[array[i] stringCol]; } }]; } - (void)testEnumerateAndAccessSetProperty { RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *spo = [SetPropertyObject createInRealm:realm withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; [self measureBlock:^{ for (StringObject *so in spo.set) { (void)[so stringCol]; } }]; } - (void)testEnumerateAndAccessSetPropertySlow { RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *spo = [SetPropertyObject createInRealm:realm withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; [self measureBlock:^{ RLMSet *set = spo.set; for (NSUInteger i = 0; i < set.count; ++i) { (void)set.count; } }]; } - (void)testEnumerateAndAccessDictionaryPropertySlow { RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; DictionaryPropertyObject *dpo = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { NSString *key = [[NSUUID UUID] UUIDString]; dpo.stringDictionary[key] = so; } [realm commitWriteTransaction]; [self measureBlock:^{ RLMDictionary *d = dpo.stringDictionary; for (NSUInteger i = 0; i < d.count; ++i) { (void)d.count; } }]; } - (void)testEnumerateAndMutateAll { RLMRealm *realm = [self getStringObjects:50]; [self measureBlock:^{ [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { so.stringCol = @"c"; } [realm commitWriteTransaction]; }]; } - (void)testEnumerateAndMutateQuery { RLMRealm *realm = [self getStringObjects:5]; [self measureBlock:^{ [realm beginWriteTransaction]; for (StringObject *so in [StringObject objectsInRealm:realm where:@"stringCol != 'b'"]) { so.stringCol = @"c"; } [realm commitWriteTransaction]; }]; } - (void)testQueryConstruction { RLMRealm *realm = self.realmWithTestPath; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"boolCol = false and (intCol = 5 or floatCol = 1.0) and objectCol = nil and longCol != 7 and stringCol IN {'a', 'b', 'c'}"]; [self measureBlock:^{ for (int i = 0; i < 5000; ++i) { [AllTypesObject objectsInRealm:realm withPredicate:predicate]; } }]; } - (void)testDeleteAll { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [self startMeasuring]; [realm beginWriteTransaction]; [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; [realm commitWriteTransaction]; [self stopMeasuring]; }]; } - (void)testQueryDeletion { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:5]; [self startMeasuring]; [realm beginWriteTransaction]; [realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a' OR stringCol = 'b'"]]; [realm commitWriteTransaction]; [self stopMeasuring]; }]; } - (void)testManualDeletion { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:5]; NSMutableArray *objects = [NSMutableArray arrayWithCapacity:10000]; for (StringObject *obj in [StringObject allObjectsInRealm:realm]) { [objects addObject:obj]; } [self startMeasuring]; [realm beginWriteTransaction]; [realm deleteObjects:objects]; [realm commitWriteTransaction]; [self stopMeasuring]; }]; } - (void)testUnIndexedStringLookup { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; for (int i = 0; i < 10000; ++i) { [StringObject createInRealm:realm withValue:@[@(i).stringValue]]; } [realm commitWriteTransaction]; [self measureBlock:^{ for (int i = 0; i < 10000; ++i) { [[StringObject objectsInRealm:realm where:@"stringCol = %@", @(i).stringValue] firstObject]; } }]; } - (void)testIndexedStringLookup { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; for (int i = 0; i < 10000; ++i) { [IndexedStringObject createInRealm:realm withValue:@[@(i).stringValue]]; } [realm commitWriteTransaction]; [self measureBlock:^{ for (int i = 0; i < 10000; ++i) { [[IndexedStringObject objectsInRealm:realm where:@"stringCol = %@", @(i).stringValue] firstObject]; } }]; } - (void)testLargeINQuery { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; NSMutableArray *ids = [NSMutableArray arrayWithCapacity:300000]; for (int i = 0; i < 300000; ++i) { [IntObject createInRealm:realm withValue:@[@(i)]]; if (i % 2) { [ids addObject:@(i)]; } } [realm commitWriteTransaction]; [self measureBlock:^{ (void)[[IntObject objectsInRealm:realm where:@"intCol IN %@", ids] firstObject]; }]; } - (void)testSortingAllObjects { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; for (int i = 0; i < 300000; ++i) { [IntObject createInRealm:realm withValue:@[@(arc4random())]]; } [realm commitWriteTransaction]; [self measureBlock:^{ (void)[[IntObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"intCol" ascending:YES].lastObject; }]; } - (void)testRealmCreationCached { __block RLMRealm *realm; [self dispatchAsyncAndWait:^{ realm = [self realmWithTestPath]; // ensure a cached realm for the path }]; [self measureBlock:^{ for (int i = 0; i < 2500; ++i) { @autoreleasepool { [self realmWithTestPath]; } } }]; [realm configuration]; } - (void)testRealmCreationUncached { [self measureBlock:^{ for (int i = 0; i < 500; ++i) { @autoreleasepool { [self realmWithTestPath]; } } }]; } - (void)testRealmFileCreation { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; __block int measurement = 0; const int iterations = 100; [self measureBlock:^{ for (int i = 0; i < iterations; ++i) { @autoreleasepool { config.inMemoryIdentifier = @(measurement * iterations + i).stringValue; [RLMRealm realmWithConfiguration:config error:nil]; } } ++measurement; }]; } - (void)testInvalidateRefresh { RLMRealm *realm = [self testRealm]; [self measureBlock:^{ for (int i = 0; i < 500000; ++i) { @autoreleasepool { [realm invalidate]; [realm refresh]; } } }]; } - (void)testCommitWriteTransaction { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = self.testRealm; [realm beginWriteTransaction]; IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; [self startMeasuring]; while (obj.intCol < 1000) { [realm transactionWithBlock:^{ obj.intCol++; }]; } [self stopMeasuring]; }]; } - (void)testCommitWriteTransactionWithLocalNotification { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = self.testRealm; [realm beginWriteTransaction]; IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { }]; [self startMeasuring]; while (obj.intCol < 5000) { [realm transactionWithBlock:^{ obj.intCol++; }]; } [self stopMeasuring]; [token invalidate]; }]; } - (void)testCommitWriteTransactionWithCrossThreadNotification { const int stopValue = 5000; [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = self.testRealm; [realm beginWriteTransaction]; IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [self dispatchAsync:^{ RLMRealm *realm = self.testRealm; IntObject *obj = [[IntObject allObjectsInRealm:realm] firstObject]; __block RLMNotificationToken *token; CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { if (obj.intCol == stopValue) { CFRunLoopStop(CFRunLoopGetCurrent()); } }]; dispatch_semaphore_signal(sema); }); CFRunLoopRun(); [token invalidate]; }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [self startMeasuring]; while (obj.intCol < stopValue) { [realm transactionWithBlock:^{ obj.intCol++; }]; } [self dispatchAsyncAndWait:^{}]; [self stopMeasuring]; }]; } - (void)testCommitWriteTransactionWithResultsNotification { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:5]; RLMResults *results = [StringObject allObjectsInRealm:realm]; RLMNotificationToken *token = [results addNotificationBlock:^(__unused RLMResults *results, __unused RLMCollectionChange *change, __unused NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [realm beginWriteTransaction]; [realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a'"]]; [realm commitWriteTransaction]; [self startMeasuring]; CFRunLoopRun(); [token invalidate]; }]; } - (void)testCommitWriteTransactionWithListNotification { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:5]; [realm beginWriteTransaction]; ArrayPropertyObject *arrayObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; RLMNotificationToken *token = [arrayObj.array addNotificationBlock:^(__unused RLMArray *results, __unused RLMCollectionChange *change, __unused NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [realm beginWriteTransaction]; [realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a'"]]; [realm commitWriteTransaction]; [self startMeasuring]; CFRunLoopRun(); [token invalidate]; }]; } - (void)testCommitWriteTransactionWithObjectNotifications { RLMRealm *realm = [self getStringObjects:5]; NSMutableArray *tokens = [NSMutableArray new]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { [tokens addObject:[so addNotificationBlock:^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); }]]; } // Object notifiers don't have an initial notification, so trigger a // small unmeasured notification we can wait for [realm beginWriteTransaction]; [[StringObject allObjectsInRealm:realm].firstObject setStringCol:@"a"]; [realm commitWriteTransaction]; CFRunLoopRun(); [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { so.stringCol = @"a"; } [realm commitWriteTransaction]; [self startMeasuring]; CFRunLoopRun(); }]; for (RLMNotificationToken *token in tokens) { [token invalidate]; } } - (void)testRegisterObjectNotififers { [self measureBlock:^{ RLMRealm *realm = [self getStringObjects:1]; NSMutableArray *tokens = [NSMutableArray new]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { [tokens addObject:[so addNotificationBlock:^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) { CFRunLoopStop(CFRunLoopGetCurrent()); }]]; } // Object notifiers don't have an initial notification, so trigger a // small unmeasured notification we can wait for [realm beginWriteTransaction]; [[StringObject allObjectsInRealm:realm].firstObject setStringCol:@"a"]; [realm commitWriteTransaction]; CFRunLoopRun(); for (RLMNotificationToken *token in tokens) { [token invalidate]; } // Destroying the Realm waits for the notifiers to tear down }]; } - (void)testCrossThreadSyncLatency { const int stopValue = 5000; [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = self.testRealm; [realm beginWriteTransaction]; [realm deleteAllObjects]; IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [self dispatchAsync:^{ RLMRealm *realm = self.testRealm; IntObject *obj = [[IntObject allObjectsInRealm:realm] firstObject]; __block RLMNotificationToken *token; CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { if (obj.intCol == stopValue) { CFRunLoopStop(CFRunLoopGetCurrent()); } else if (obj.intCol % 2 == 0) { [realm transactionWithBlock:^{ obj.intCol++; }]; } }]; dispatch_semaphore_signal(sema); }); CFRunLoopRun(); [token invalidate]; }]; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { if (obj.intCol % 2 == 1 && obj.intCol < stopValue) { [realm transactionWithBlock:^{ obj.intCol++; }]; } }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [self startMeasuring]; [realm transactionWithBlock:^{ obj.intCol++; }]; while (obj.intCol < stopValue) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } [self dispatchAsyncAndWait:^{}]; [self stopMeasuring]; [token invalidate]; }]; } - (void)testArrayKVOIndexHandlingRemoveForward { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; const NSUInteger initial = obj.array.count; [self observeObject:obj keyPath:@"array" until:^(ArrayPropertyObject *obj) { return obj.array.count < initial; }]; [self startMeasuring]; [realm beginWriteTransaction]; for (NSUInteger i = 0; i < obj.array.count; i += 10) { [obj.array removeObjectAtIndex:i]; } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testArrayKVOIndexHandlingRemoveBackwards { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; const NSUInteger initial = obj.array.count; [self observeObject:obj keyPath:@"array" until:^(ArrayPropertyObject *o) { return o.array.count < initial; }]; [self startMeasuring]; [realm beginWriteTransaction]; for (NSUInteger i = obj.array.count; i > 0; i -= i > 10 ? 10 : i) { [obj.array removeObjectAtIndex:i - 1]; } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testArrayKVOIndexHandlingInsertCompact { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8; const NSUInteger factor = count / 10; [self observeObject:obj keyPath:@"array" until:^(ArrayPropertyObject *obj) { return obj.array.count >= count; }]; RLMArray *array = obj.array; [self startMeasuring]; [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { [array addObject:so]; if (array.count % factor == 0) { [realm commitWriteTransaction]; dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER); [realm beginWriteTransaction]; } if (array.count > count) { break; } } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testArrayKVOIndexHandlingInsertSparse { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8; const NSUInteger factor = count / 10; [self observeObject:obj keyPath:@"array" until:^(ArrayPropertyObject *obj) { return obj.array.count >= count; }]; RLMArray *array = obj.array; [self startMeasuring]; [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { NSUInteger index = array.count; if (array.count > factor) { index = index * 3 % factor; } [array insertObject:so atIndex:index]; if (array.count % factor == 0) { [realm commitWriteTransaction]; dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER); [realm beginWriteTransaction]; } if (array.count > count) { break; } } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testSetKVOIndexHandlingRemoveForward { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *obj = [SetPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; const NSUInteger initial = obj.set.count; [self observeObject:obj keyPath:@"set" until:^(SetPropertyObject *obj) { return obj.set.count < initial; }]; [self startMeasuring]; [realm beginWriteTransaction]; [obj.set removeAllObjects]; [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testSetKVOIndexHandlingRemoveBackwards { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *obj = [SetPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]]; [realm commitWriteTransaction]; const NSUInteger initial = obj.set.count; [self observeObject:obj keyPath:@"set" until:^(SetPropertyObject *obj) { return obj.set.count < initial; }]; [self startMeasuring]; [realm beginWriteTransaction]; [obj.set removeAllObjects]; [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testSetKVOIndexHandlingInsertCompact { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *obj = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8; const NSUInteger factor = count / 10; [self observeObject:obj keyPath:@"set" until:^(SetPropertyObject *obj) { return obj.set.count >= count; }]; RLMSet *set = obj.set; [self startMeasuring]; [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { [set addObject:so]; if (set.count % factor == 0) { [realm commitWriteTransaction]; dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER); [realm beginWriteTransaction]; } if (set.count > count) { break; } } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)testSetKVOIndexHandlingInsertSparse { [self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{ RLMRealm *realm = [self getStringObjects:50]; [realm beginWriteTransaction]; SetPropertyObject *obj = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8; [self observeObject:obj keyPath:@"set" until:^(SetPropertyObject *o) { return [o set].count >= count; }]; RLMSet *set = obj.set; [self startMeasuring]; [realm beginWriteTransaction]; for (StringObject *so in [StringObject allObjectsInRealm:realm]) { [set addObject:so]; if (set.count > count) { break; } } [realm commitWriteTransaction]; dispatch_sync(_queue, ^{}); }]; } - (void)observeObject:(RLMObject *)object keyPath:(NSString *)keyPath until:(int (^)(id))block { self.sema = dispatch_semaphore_create(0); self.queue = dispatch_queue_create("bg", 0); RLMRealmConfiguration *config = object.realm.configuration; NSString *className = [object.class className]; dispatch_async(_queue, ^{ RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; id obj = [[realm allObjects:className] firstObject]; [obj addObserver:self forKeyPath:keyPath options:(NSKeyValueObservingOptions)0 context:(__bridge void *)_sema]; dispatch_semaphore_signal(_sema); while (!block(obj)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } [obj removeObserver:self forKeyPath:keyPath context:(__bridge void *)_sema]; }); dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER); } - (void)observeValueForKeyPath:(__unused NSString *)keyPath ofObject:(__unused id)object change:(__unused NSDictionary *)change context:(void *)context { dispatch_semaphore_signal((__bridge dispatch_semaphore_t)context); } @end #endif ================================================ FILE: Realm/Tests/PredicateUtilTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMTestCase.h" #import "RLMPredicateUtil.hpp" @interface PredicateUtilTests : RLMTestCase @end @implementation PredicateUtilTests - (void)testVisitingAllExpressionTypes { auto testPredicate = [&](NSPredicate *predicate, size_t expectedExpressionCount) { size_t visitCount = 0; auto visitExpression = [&](NSExpression *expression) { visitCount++; return expression; }; NSPredicate *transformedPredicate = transformPredicate(predicate, visitExpression); XCTAssertEqualObjects(predicate, transformedPredicate); XCTAssertEqual(visitCount, expectedExpressionCount); }; auto testPredicateString = [=](NSString *predicateString, size_t expectedExpressionCount) { return testPredicate([NSPredicate predicateWithFormat:predicateString], expectedExpressionCount); }; testPredicateString(@"TRUEPREDICATE", 0); testPredicateString(@"A == B", 2); testPredicateString(@"A == B AND C == 1", 4); testPredicateString(@"A.@count == 2", 2); testPredicateString(@"SUBQUERY(collection, $variable, $variable.property == 1).@count > 2", 9); testPredicateString(@"A IN {1, 2, 3}", 5); testPredicateString(@"A IN B UNION C", 4); testPredicateString(@"A IN B INTERSECT C", 4); testPredicateString(@"A IN B MINUS C", 4); if ([NSExpression respondsToSelector:@selector(expressionForConditional:trueExpression:falseExpression:)]) { // Only test conditional predicates on platforms that support them (i.e. iOS 9 and later). testPredicateString(@"TERNARY(TRUEPREDICATE, A, B) == 1", 4); } testPredicateString(@"ANYKEY == 1", 2); testPredicateString(@"SELF == 1", 2); testPredicate([NSPredicate predicateWithBlock:^(id, NSDictionary*) { return NO; }], 0); auto block = ^(id, NSArray *, NSMutableDictionary *) { return @"Hello"; }; testPredicate([NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForBlock:block arguments:nil] rightExpression:[NSExpression expressionForConstantValue:@"hello"] modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0], 2); } @end ================================================ FILE: Realm/Tests/PrimitiveArrayPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveArrays : RLMObject @property (nonatomic) AllPrimitiveArrays *link; @end @implementation LinkToAllPrimitiveArrays @end @interface LinkToAllOptionalPrimitiveArrays : RLMObject @property (nonatomic) AllOptionalPrimitiveArrays *link; @end @implementation LinkToAllOptionalPrimitiveArrays @end @interface PrimitiveArrayPropertyTests : RLMTestCase @end @implementation PrimitiveArrayPropertyTests { AllPrimitiveArrays *unmanaged; AllPrimitiveArrays *managed; AllOptionalPrimitiveArrays *optUnmanaged; AllOptionalPrimitiveArrays *optManaged; RLMRealm *realm; NSArray *allArrays; } - (void)setUp { unmanaged = [[AllPrimitiveArrays alloc] init]; optUnmanaged = [[AllOptionalPrimitiveArrays alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveArrays createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@[]]; allArrays = @[ unmanaged.boolObj, unmanaged.intObj, unmanaged.floatObj, unmanaged.doubleObj, unmanaged.stringObj, unmanaged.dataObj, unmanaged.dateObj, unmanaged.decimalObj, unmanaged.objectIdObj, unmanaged.uuidObj, unmanaged.anyBoolObj, unmanaged.anyIntObj, unmanaged.anyFloatObj, unmanaged.anyDoubleObj, unmanaged.anyStringObj, unmanaged.anyDataObj, unmanaged.anyDateObj, unmanaged.anyDecimalObj, unmanaged.anyObjectIdObj, unmanaged.anyUUIDObj, optUnmanaged.boolObj, optUnmanaged.intObj, optUnmanaged.floatObj, optUnmanaged.doubleObj, optUnmanaged.stringObj, optUnmanaged.dataObj, optUnmanaged.dateObj, optUnmanaged.decimalObj, optUnmanaged.objectIdObj, optUnmanaged.uuidObj, managed.boolObj, managed.intObj, managed.floatObj, managed.doubleObj, managed.stringObj, managed.dataObj, managed.dateObj, managed.decimalObj, managed.objectIdObj, managed.uuidObj, managed.anyBoolObj, managed.anyIntObj, managed.anyFloatObj, managed.anyDoubleObj, managed.anyStringObj, managed.anyDataObj, managed.anyDateObj, managed.anyDecimalObj, managed.anyObjectIdObj, managed.anyUUIDObj, optManaged.boolObj, optManaged.intObj, optManaged.floatObj, optManaged.doubleObj, optManaged.stringObj, optManaged.dataObj, optManaged.dateObj, optManaged.decimalObj, optManaged.objectIdObj, optManaged.uuidObj, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"b"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(2), decimal128(3)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.boolObj addObjects:@[@NO, @YES, NSNull.null]]; [optUnmanaged.intObj addObjects:@[@2, @3, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[@2.2f, @3.3f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[@2.2, @3.3, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[@"a", @"b", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[data(1), data(2), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[date(1), date(2), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[objectId(1), objectId(2), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"b"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(3)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(2), decimal128(3)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[@NO, @YES, NSNull.null]]; [optManaged.intObj addObjects:@[@2, @3, NSNull.null]]; [optManaged.floatObj addObjects:@[@2.2f, @3.3f, NSNull.null]]; [optManaged.doubleObj addObjects:@[@2.2, @3.3, NSNull.null]]; [optManaged.stringObj addObjects:@[@"a", @"b", NSNull.null]]; [optManaged.dataObj addObjects:@[data(1), data(2), NSNull.null]]; [optManaged.dateObj addObjects:@[date(1), date(2), NSNull.null]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(2), NSNull.null]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); [unmanaged.intObj addObject:@1]; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(unmanaged.anyBoolObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyIntObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyFloatObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDoubleObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyStringObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDataObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDateObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDecimalObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyObjectIdObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyUUIDObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertFalse(unmanaged.anyBoolObj.optional); uncheckedAssertFalse(unmanaged.anyIntObj.optional); uncheckedAssertFalse(unmanaged.anyFloatObj.optional); uncheckedAssertFalse(unmanaged.anyDoubleObj.optional); uncheckedAssertFalse(unmanaged.anyStringObj.optional); uncheckedAssertFalse(unmanaged.anyDataObj.optional); uncheckedAssertFalse(unmanaged.anyDateObj.optional); uncheckedAssertFalse(unmanaged.anyDecimalObj.optional); uncheckedAssertFalse(unmanaged.anyObjectIdObj.optional); uncheckedAssertFalse(unmanaged.anyUUIDObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(unmanaged.anyBoolObj.objectClassName); uncheckedAssertNil(unmanaged.anyIntObj.objectClassName); uncheckedAssertNil(unmanaged.anyFloatObj.objectClassName); uncheckedAssertNil(unmanaged.anyDoubleObj.objectClassName); uncheckedAssertNil(unmanaged.anyStringObj.objectClassName); uncheckedAssertNil(unmanaged.anyDataObj.objectClassName); uncheckedAssertNil(unmanaged.anyDateObj.objectClassName); uncheckedAssertNil(unmanaged.anyDecimalObj.objectClassName); uncheckedAssertNil(unmanaged.anyObjectIdObj.objectClassName); uncheckedAssertNil(unmanaged.anyUUIDObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(unmanaged.anyBoolObj.realm); uncheckedAssertNil(unmanaged.anyIntObj.realm); uncheckedAssertNil(unmanaged.anyFloatObj.realm); uncheckedAssertNil(unmanaged.anyDoubleObj.realm); uncheckedAssertNil(unmanaged.anyStringObj.realm); uncheckedAssertNil(unmanaged.anyDataObj.realm); uncheckedAssertNil(unmanaged.anyDateObj.realm); uncheckedAssertNil(unmanaged.anyDecimalObj.realm); uncheckedAssertNil(unmanaged.anyObjectIdObj.realm); uncheckedAssertNil(unmanaged.anyUUIDObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMArray *array; @autoreleasepool { AllPrimitiveArrays *obj = [[AllPrimitiveArrays alloc] init]; array = obj.intObj; uncheckedAssertFalse(array.invalidated); } uncheckedAssertFalse(array.invalidated); } - (void)testDeleteObjectsInRealm { for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([realm deleteObjects:array], @"Cannot delete objects from RLMArray"); } } - (void)testObjectAtIndex { RLMAssertThrowsWithReason([unmanaged.intObj objectAtIndex:0], @"Index 0 is out of bounds (must be less than 0)."); [unmanaged.intObj addObject:@1]; uncheckedAssertEqualObjects([unmanaged.intObj objectAtIndex:0], @1); } - (void)testObjectsAtIndexes { NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; [indexSet addIndex:0]; [indexSet addIndex:2]; XCTAssertNil([unmanaged.intObj objectsAtIndexes:indexSet]); XCTAssertNil([managed.intObj objectsAtIndexes:indexSet]); [unmanaged.intObj addObject:@1]; [unmanaged.intObj addObject:@2]; [unmanaged.intObj addObject:@3]; uncheckedAssertEqualObjects([unmanaged.intObj objectsAtIndexes:indexSet], (@[@1, @3])); [managed.intObj addObject:@1]; [managed.intObj addObject:@2]; [managed.intObj addObject:@3]; uncheckedAssertEqualObjects([managed.intObj objectsAtIndexes:indexSet], (@[@1, @3])); [indexSet addIndex:3]; XCTAssertNil([unmanaged.intObj objectsAtIndexes:indexSet]); XCTAssertNil([managed.intObj objectsAtIndexes:indexSet]); } - (void)testFirstObject { for (RLMArray *array in allArrays) { uncheckedAssertNil(array.firstObject); } [self addObjects]; uncheckedAssertEqualObjects(unmanaged.boolObj.firstObject, @NO); uncheckedAssertEqualObjects(unmanaged.intObj.firstObject, @2); uncheckedAssertEqualObjects(unmanaged.floatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj.firstObject, @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj.firstObject, data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj.firstObject, date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj.firstObject, @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj.firstObject, @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj.firstObject, @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj.firstObject, data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj.firstObject, date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj.firstObject, @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj.firstObject, @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj.firstObject, @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj.firstObject, data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj.firstObject, date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj.firstObject, @NO); uncheckedAssertEqualObjects(managed.intObj.firstObject, @2); uncheckedAssertEqualObjects(managed.floatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(managed.doubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(managed.stringObj.firstObject, @"a"); uncheckedAssertEqualObjects(managed.dataObj.firstObject, data(1)); uncheckedAssertEqualObjects(managed.dateObj.firstObject, date(1)); uncheckedAssertEqualObjects(managed.decimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj.firstObject, @NO); uncheckedAssertEqualObjects(managed.anyIntObj.firstObject, @2); uncheckedAssertEqualObjects(managed.anyFloatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(managed.anyStringObj.firstObject, @"a"); uncheckedAssertEqualObjects(managed.anyDataObj.firstObject, data(1)); uncheckedAssertEqualObjects(managed.anyDateObj.firstObject, date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj.firstObject, @NO); uncheckedAssertEqualObjects(optManaged.intObj.firstObject, @2); uncheckedAssertEqualObjects(optManaged.floatObj.firstObject, @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj.firstObject, @2.2); uncheckedAssertEqualObjects(optManaged.stringObj.firstObject, @"a"); uncheckedAssertEqualObjects(optManaged.dataObj.firstObject, data(1)); uncheckedAssertEqualObjects(optManaged.dateObj.firstObject, date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj.firstObject, decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj.firstObject, objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj.firstObject, uuid(@"00000000-0000-0000-0000-000000000000")); for (RLMArray *array in allArrays) { [array removeAllObjects]; } [optUnmanaged.boolObj addObject:NSNull.null]; [optUnmanaged.intObj addObject:NSNull.null]; [optUnmanaged.floatObj addObject:NSNull.null]; [optUnmanaged.doubleObj addObject:NSNull.null]; [optUnmanaged.stringObj addObject:NSNull.null]; [optUnmanaged.dataObj addObject:NSNull.null]; [optUnmanaged.dateObj addObject:NSNull.null]; [optUnmanaged.decimalObj addObject:NSNull.null]; [optUnmanaged.objectIdObj addObject:NSNull.null]; [optUnmanaged.uuidObj addObject:NSNull.null]; [optManaged.boolObj addObject:NSNull.null]; [optManaged.intObj addObject:NSNull.null]; [optManaged.floatObj addObject:NSNull.null]; [optManaged.doubleObj addObject:NSNull.null]; [optManaged.stringObj addObject:NSNull.null]; [optManaged.dataObj addObject:NSNull.null]; [optManaged.dateObj addObject:NSNull.null]; [optManaged.decimalObj addObject:NSNull.null]; [optManaged.objectIdObj addObject:NSNull.null]; [optManaged.uuidObj addObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.boolObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj.firstObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj.firstObject, NSNull.null); } - (void)testLastObject { for (RLMArray *array in allArrays) { uncheckedAssertNil(array.lastObject); } [self addObjects]; uncheckedAssertEqualObjects(unmanaged.boolObj.lastObject, @YES); uncheckedAssertEqualObjects(unmanaged.intObj.lastObject, @3); uncheckedAssertEqualObjects(unmanaged.floatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(unmanaged.doubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(unmanaged.stringObj.lastObject, @"b"); uncheckedAssertEqualObjects(unmanaged.dataObj.lastObject, data(2)); uncheckedAssertEqualObjects(unmanaged.dateObj.lastObject, date(2)); uncheckedAssertEqualObjects(unmanaged.decimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(unmanaged.objectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(unmanaged.uuidObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj.lastObject, @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj.lastObject, @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj.lastObject, @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj.lastObject, data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj.lastObject, date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(managed.boolObj.lastObject, @YES); uncheckedAssertEqualObjects(managed.intObj.lastObject, @3); uncheckedAssertEqualObjects(managed.floatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(managed.doubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(managed.stringObj.lastObject, @"b"); uncheckedAssertEqualObjects(managed.dataObj.lastObject, data(2)); uncheckedAssertEqualObjects(managed.dateObj.lastObject, date(2)); uncheckedAssertEqualObjects(managed.decimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(managed.objectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(managed.uuidObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj.lastObject, @YES); uncheckedAssertEqualObjects(managed.anyIntObj.lastObject, @3); uncheckedAssertEqualObjects(managed.anyFloatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(managed.anyStringObj.lastObject, @"b"); uncheckedAssertEqualObjects(managed.anyDataObj.lastObject, data(2)); uncheckedAssertEqualObjects(managed.anyDateObj.lastObject, date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj.lastObject, NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj.lastObject, NSNull.null); for (RLMArray *array in allArrays) { [array removeLastObject]; } uncheckedAssertEqualObjects(optUnmanaged.boolObj.lastObject, @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj.lastObject, @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj.lastObject, @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj.lastObject, data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj.lastObject, date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj.lastObject, @YES); uncheckedAssertEqualObjects(optManaged.intObj.lastObject, @3); uncheckedAssertEqualObjects(optManaged.floatObj.lastObject, @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj.lastObject, @3.3); uncheckedAssertEqualObjects(optManaged.stringObj.lastObject, @"b"); uncheckedAssertEqualObjects(optManaged.dataObj.lastObject, data(2)); uncheckedAssertEqualObjects(optManaged.dateObj.lastObject, date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj.lastObject, decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj.lastObject, objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj.lastObject, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); } - (void)testAddObject { RLMAssertThrowsWithReason([unmanaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [unmanaged.boolObj addObject:@NO]; [unmanaged.intObj addObject:@2]; [unmanaged.floatObj addObject:@2.2f]; [unmanaged.doubleObj addObject:@2.2]; [unmanaged.stringObj addObject:@"a"]; [unmanaged.dataObj addObject:data(1)]; [unmanaged.dateObj addObject:date(1)]; [unmanaged.decimalObj addObject:decimal128(2)]; [unmanaged.objectIdObj addObject:objectId(1)]; [unmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [unmanaged.anyBoolObj addObject:@NO]; [unmanaged.anyIntObj addObject:@2]; [unmanaged.anyFloatObj addObject:@2.2f]; [unmanaged.anyDoubleObj addObject:@2.2]; [unmanaged.anyStringObj addObject:@"a"]; [unmanaged.anyDataObj addObject:data(1)]; [unmanaged.anyDateObj addObject:date(1)]; [unmanaged.anyDecimalObj addObject:decimal128(2)]; [unmanaged.anyObjectIdObj addObject:objectId(1)]; [unmanaged.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optUnmanaged.boolObj addObject:@NO]; [optUnmanaged.intObj addObject:@2]; [optUnmanaged.floatObj addObject:@2.2f]; [optUnmanaged.doubleObj addObject:@2.2]; [optUnmanaged.stringObj addObject:@"a"]; [optUnmanaged.dataObj addObject:data(1)]; [optUnmanaged.dateObj addObject:date(1)]; [optUnmanaged.decimalObj addObject:decimal128(2)]; [optUnmanaged.objectIdObj addObject:objectId(1)]; [optUnmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [managed.boolObj addObject:@NO]; [managed.intObj addObject:@2]; [managed.floatObj addObject:@2.2f]; [managed.doubleObj addObject:@2.2]; [managed.stringObj addObject:@"a"]; [managed.dataObj addObject:data(1)]; [managed.dateObj addObject:date(1)]; [managed.decimalObj addObject:decimal128(2)]; [managed.objectIdObj addObject:objectId(1)]; [managed.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [managed.anyBoolObj addObject:@NO]; [managed.anyIntObj addObject:@2]; [managed.anyFloatObj addObject:@2.2f]; [managed.anyDoubleObj addObject:@2.2]; [managed.anyStringObj addObject:@"a"]; [managed.anyDataObj addObject:data(1)]; [managed.anyDateObj addObject:date(1)]; [managed.anyDecimalObj addObject:decimal128(2)]; [managed.anyObjectIdObj addObject:objectId(1)]; [managed.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optManaged.boolObj addObject:@NO]; [optManaged.intObj addObject:@2]; [optManaged.floatObj addObject:@2.2f]; [optManaged.doubleObj addObject:@2.2]; [optManaged.stringObj addObject:@"a"]; [optManaged.dataObj addObject:data(1)]; [optManaged.dateObj addObject:date(1)]; [optManaged.decimalObj addObject:decimal128(2)]; [optManaged.objectIdObj addObject:objectId(1)]; [optManaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[0], @NO); uncheckedAssertEqualObjects(managed.intObj[0], @2); uncheckedAssertEqualObjects(managed.floatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optManaged.intObj[0], @2); uncheckedAssertEqualObjects(optManaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); [optUnmanaged.boolObj addObject:NSNull.null]; [optUnmanaged.intObj addObject:NSNull.null]; [optUnmanaged.floatObj addObject:NSNull.null]; [optUnmanaged.doubleObj addObject:NSNull.null]; [optUnmanaged.stringObj addObject:NSNull.null]; [optUnmanaged.dataObj addObject:NSNull.null]; [optUnmanaged.dateObj addObject:NSNull.null]; [optUnmanaged.decimalObj addObject:NSNull.null]; [optUnmanaged.objectIdObj addObject:NSNull.null]; [optUnmanaged.uuidObj addObject:NSNull.null]; [optManaged.boolObj addObject:NSNull.null]; [optManaged.intObj addObject:NSNull.null]; [optManaged.floatObj addObject:NSNull.null]; [optManaged.doubleObj addObject:NSNull.null]; [optManaged.stringObj addObject:NSNull.null]; [optManaged.dataObj addObject:NSNull.null]; [optManaged.dateObj addObject:NSNull.null]; [optManaged.decimalObj addObject:NSNull.null]; [optManaged.objectIdObj addObject:NSNull.null]; [optManaged.uuidObj addObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[1], NSNull.null); } - (void)testAddObjects { RLMAssertThrowsWithReason([unmanaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[0], @NO); uncheckedAssertEqualObjects(managed.intObj[0], @2); uncheckedAssertEqualObjects(managed.floatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optManaged.intObj[0], @2); uncheckedAssertEqualObjects(optManaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.boolObj[1], @YES); uncheckedAssertEqualObjects(unmanaged.intObj[1], @3); uncheckedAssertEqualObjects(unmanaged.floatObj[1], @3.3f); uncheckedAssertEqualObjects(unmanaged.doubleObj[1], @3.3); uncheckedAssertEqualObjects(unmanaged.stringObj[1], @"b"); uncheckedAssertEqualObjects(unmanaged.dataObj[1], data(2)); uncheckedAssertEqualObjects(unmanaged.dateObj[1], date(2)); uncheckedAssertEqualObjects(unmanaged.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(unmanaged.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[1], @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj[1], @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[1], @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[1], @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj[1], @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[1], data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[1], date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.boolObj[1], @YES); uncheckedAssertEqualObjects(managed.intObj[1], @3); uncheckedAssertEqualObjects(managed.floatObj[1], @3.3f); uncheckedAssertEqualObjects(managed.doubleObj[1], @3.3); uncheckedAssertEqualObjects(managed.stringObj[1], @"b"); uncheckedAssertEqualObjects(managed.dataObj[1], data(2)); uncheckedAssertEqualObjects(managed.dateObj[1], date(2)); uncheckedAssertEqualObjects(managed.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(managed.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(managed.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj[1], @YES); uncheckedAssertEqualObjects(managed.anyIntObj[1], @3); uncheckedAssertEqualObjects(managed.anyFloatObj[1], @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj[1], @3.3); uncheckedAssertEqualObjects(managed.anyStringObj[1], @"b"); uncheckedAssertEqualObjects(managed.anyDataObj[1], data(2)); uncheckedAssertEqualObjects(managed.anyDateObj[1], date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj[1], @YES); uncheckedAssertEqualObjects(optManaged.intObj[1], @3); uncheckedAssertEqualObjects(optManaged.floatObj[1], @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj[1], @3.3); uncheckedAssertEqualObjects(optManaged.stringObj[1], @"b"); uncheckedAssertEqualObjects(optManaged.dataObj[1], data(2)); uncheckedAssertEqualObjects(optManaged.dateObj[1], date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[2], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[2], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[2], NSNull.null); } - (void)testInsertObject { RLMAssertThrowsWithReason([unmanaged.boolObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj insertObject:@2 atIndex:0], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj insertObject:@2 atIndex:0], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj insertObject:@2 atIndex:0], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj insertObject:@2 atIndex:0], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj insertObject:@"a" atIndex:0], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj insertObject:NSNull.null atIndex:0], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([unmanaged.boolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.intObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.floatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.doubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.stringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.dataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.dateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.decimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.objectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyIntObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyStringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyDataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyDateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.boolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.intObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.floatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.stringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.dataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.dateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.boolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.intObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.floatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.doubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.stringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.dataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.dateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.decimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.objectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyBoolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyIntObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyFloatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyDoubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyStringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyDataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyDateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyDecimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyObjectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([managed.anyUUIDObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.boolObj insertObject:@NO atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.intObj insertObject:@2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.floatObj insertObject:@2.2f atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.doubleObj insertObject:@2.2 atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.stringObj insertObject:@"a" atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.dataObj insertObject:data(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.dateObj insertObject:date(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.decimalObj insertObject:decimal128(2) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.objectIdObj insertObject:objectId(1) atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); RLMAssertThrowsWithReason([optManaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:1], @"Index 1 is out of bounds (must be less than 1)."); [unmanaged.boolObj insertObject:@NO atIndex:0]; [unmanaged.intObj insertObject:@2 atIndex:0]; [unmanaged.floatObj insertObject:@2.2f atIndex:0]; [unmanaged.doubleObj insertObject:@2.2 atIndex:0]; [unmanaged.stringObj insertObject:@"a" atIndex:0]; [unmanaged.dataObj insertObject:data(1) atIndex:0]; [unmanaged.dateObj insertObject:date(1) atIndex:0]; [unmanaged.decimalObj insertObject:decimal128(2) atIndex:0]; [unmanaged.objectIdObj insertObject:objectId(1) atIndex:0]; [unmanaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; [unmanaged.anyBoolObj insertObject:@NO atIndex:0]; [unmanaged.anyIntObj insertObject:@2 atIndex:0]; [unmanaged.anyFloatObj insertObject:@2.2f atIndex:0]; [unmanaged.anyDoubleObj insertObject:@2.2 atIndex:0]; [unmanaged.anyStringObj insertObject:@"a" atIndex:0]; [unmanaged.anyDataObj insertObject:data(1) atIndex:0]; [unmanaged.anyDateObj insertObject:date(1) atIndex:0]; [unmanaged.anyDecimalObj insertObject:decimal128(2) atIndex:0]; [unmanaged.anyObjectIdObj insertObject:objectId(1) atIndex:0]; [unmanaged.anyUUIDObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; [optUnmanaged.boolObj insertObject:@NO atIndex:0]; [optUnmanaged.intObj insertObject:@2 atIndex:0]; [optUnmanaged.floatObj insertObject:@2.2f atIndex:0]; [optUnmanaged.doubleObj insertObject:@2.2 atIndex:0]; [optUnmanaged.stringObj insertObject:@"a" atIndex:0]; [optUnmanaged.dataObj insertObject:data(1) atIndex:0]; [optUnmanaged.dateObj insertObject:date(1) atIndex:0]; [optUnmanaged.decimalObj insertObject:decimal128(2) atIndex:0]; [optUnmanaged.objectIdObj insertObject:objectId(1) atIndex:0]; [optUnmanaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; [managed.boolObj insertObject:@NO atIndex:0]; [managed.intObj insertObject:@2 atIndex:0]; [managed.floatObj insertObject:@2.2f atIndex:0]; [managed.doubleObj insertObject:@2.2 atIndex:0]; [managed.stringObj insertObject:@"a" atIndex:0]; [managed.dataObj insertObject:data(1) atIndex:0]; [managed.dateObj insertObject:date(1) atIndex:0]; [managed.decimalObj insertObject:decimal128(2) atIndex:0]; [managed.objectIdObj insertObject:objectId(1) atIndex:0]; [managed.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; [managed.anyBoolObj insertObject:@NO atIndex:0]; [managed.anyIntObj insertObject:@2 atIndex:0]; [managed.anyFloatObj insertObject:@2.2f atIndex:0]; [managed.anyDoubleObj insertObject:@2.2 atIndex:0]; [managed.anyStringObj insertObject:@"a" atIndex:0]; [managed.anyDataObj insertObject:data(1) atIndex:0]; [managed.anyDateObj insertObject:date(1) atIndex:0]; [managed.anyDecimalObj insertObject:decimal128(2) atIndex:0]; [managed.anyObjectIdObj insertObject:objectId(1) atIndex:0]; [managed.anyUUIDObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; [optManaged.boolObj insertObject:@NO atIndex:0]; [optManaged.intObj insertObject:@2 atIndex:0]; [optManaged.floatObj insertObject:@2.2f atIndex:0]; [optManaged.doubleObj insertObject:@2.2 atIndex:0]; [optManaged.stringObj insertObject:@"a" atIndex:0]; [optManaged.dataObj insertObject:data(1) atIndex:0]; [optManaged.dateObj insertObject:date(1) atIndex:0]; [optManaged.decimalObj insertObject:decimal128(2) atIndex:0]; [optManaged.objectIdObj insertObject:objectId(1) atIndex:0]; [optManaged.uuidObj insertObject:uuid(@"00000000-0000-0000-0000-000000000000") atIndex:0]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[0], @NO); uncheckedAssertEqualObjects(managed.intObj[0], @2); uncheckedAssertEqualObjects(managed.floatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optManaged.intObj[0], @2); uncheckedAssertEqualObjects(optManaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); [unmanaged.boolObj insertObject:@YES atIndex:0]; [unmanaged.intObj insertObject:@3 atIndex:0]; [unmanaged.floatObj insertObject:@3.3f atIndex:0]; [unmanaged.doubleObj insertObject:@3.3 atIndex:0]; [unmanaged.stringObj insertObject:@"b" atIndex:0]; [unmanaged.dataObj insertObject:data(2) atIndex:0]; [unmanaged.dateObj insertObject:date(2) atIndex:0]; [unmanaged.decimalObj insertObject:decimal128(3) atIndex:0]; [unmanaged.objectIdObj insertObject:objectId(2) atIndex:0]; [unmanaged.uuidObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; [unmanaged.anyBoolObj insertObject:@YES atIndex:0]; [unmanaged.anyIntObj insertObject:@3 atIndex:0]; [unmanaged.anyFloatObj insertObject:@3.3f atIndex:0]; [unmanaged.anyDoubleObj insertObject:@3.3 atIndex:0]; [unmanaged.anyStringObj insertObject:@"b" atIndex:0]; [unmanaged.anyDataObj insertObject:data(2) atIndex:0]; [unmanaged.anyDateObj insertObject:date(2) atIndex:0]; [unmanaged.anyDecimalObj insertObject:decimal128(3) atIndex:0]; [unmanaged.anyObjectIdObj insertObject:objectId(2) atIndex:0]; [unmanaged.anyUUIDObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; [optUnmanaged.boolObj insertObject:@YES atIndex:0]; [optUnmanaged.intObj insertObject:@3 atIndex:0]; [optUnmanaged.floatObj insertObject:@3.3f atIndex:0]; [optUnmanaged.doubleObj insertObject:@3.3 atIndex:0]; [optUnmanaged.stringObj insertObject:@"b" atIndex:0]; [optUnmanaged.dataObj insertObject:data(2) atIndex:0]; [optUnmanaged.dateObj insertObject:date(2) atIndex:0]; [optUnmanaged.decimalObj insertObject:decimal128(3) atIndex:0]; [optUnmanaged.objectIdObj insertObject:objectId(2) atIndex:0]; [optUnmanaged.uuidObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; [managed.boolObj insertObject:@YES atIndex:0]; [managed.intObj insertObject:@3 atIndex:0]; [managed.floatObj insertObject:@3.3f atIndex:0]; [managed.doubleObj insertObject:@3.3 atIndex:0]; [managed.stringObj insertObject:@"b" atIndex:0]; [managed.dataObj insertObject:data(2) atIndex:0]; [managed.dateObj insertObject:date(2) atIndex:0]; [managed.decimalObj insertObject:decimal128(3) atIndex:0]; [managed.objectIdObj insertObject:objectId(2) atIndex:0]; [managed.uuidObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; [managed.anyBoolObj insertObject:@YES atIndex:0]; [managed.anyIntObj insertObject:@3 atIndex:0]; [managed.anyFloatObj insertObject:@3.3f atIndex:0]; [managed.anyDoubleObj insertObject:@3.3 atIndex:0]; [managed.anyStringObj insertObject:@"b" atIndex:0]; [managed.anyDataObj insertObject:data(2) atIndex:0]; [managed.anyDateObj insertObject:date(2) atIndex:0]; [managed.anyDecimalObj insertObject:decimal128(3) atIndex:0]; [managed.anyObjectIdObj insertObject:objectId(2) atIndex:0]; [managed.anyUUIDObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; [optManaged.boolObj insertObject:@YES atIndex:0]; [optManaged.intObj insertObject:@3 atIndex:0]; [optManaged.floatObj insertObject:@3.3f atIndex:0]; [optManaged.doubleObj insertObject:@3.3 atIndex:0]; [optManaged.stringObj insertObject:@"b" atIndex:0]; [optManaged.dataObj insertObject:data(2) atIndex:0]; [optManaged.dateObj insertObject:date(2) atIndex:0]; [optManaged.decimalObj insertObject:decimal128(3) atIndex:0]; [optManaged.objectIdObj insertObject:objectId(2) atIndex:0]; [optManaged.uuidObj insertObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") atIndex:0]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @YES); uncheckedAssertEqualObjects(unmanaged.intObj[0], @3); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.boolObj[0], @YES); uncheckedAssertEqualObjects(managed.intObj[0], @3); uncheckedAssertEqualObjects(managed.floatObj[0], @3.3f); uncheckedAssertEqualObjects(managed.doubleObj[0], @3.3); uncheckedAssertEqualObjects(managed.stringObj[0], @"b"); uncheckedAssertEqualObjects(managed.dataObj[0], data(2)); uncheckedAssertEqualObjects(managed.dateObj[0], date(2)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @YES); uncheckedAssertEqualObjects(managed.anyIntObj[0], @3); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @3.3); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"b"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(2)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optManaged.intObj[0], @3); uncheckedAssertEqualObjects(optManaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(unmanaged.boolObj[1], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[1], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[1], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[1], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[1], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[1], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[1], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[1], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[1], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[1], @NO); uncheckedAssertEqualObjects(managed.intObj[1], @2); uncheckedAssertEqualObjects(managed.floatObj[1], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[1], @2.2); uncheckedAssertEqualObjects(managed.stringObj[1], @"a"); uncheckedAssertEqualObjects(managed.dataObj[1], data(1)); uncheckedAssertEqualObjects(managed.dateObj[1], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[1], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[1], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[1], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[1], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[1], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[1], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[1], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[1], @NO); uncheckedAssertEqualObjects(optManaged.intObj[1], @2); uncheckedAssertEqualObjects(optManaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); [optUnmanaged.boolObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.intObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.floatObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.doubleObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.stringObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.dataObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.dateObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.decimalObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.objectIdObj insertObject:NSNull.null atIndex:1]; [optUnmanaged.uuidObj insertObject:NSNull.null atIndex:1]; [optManaged.boolObj insertObject:NSNull.null atIndex:1]; [optManaged.intObj insertObject:NSNull.null atIndex:1]; [optManaged.floatObj insertObject:NSNull.null atIndex:1]; [optManaged.doubleObj insertObject:NSNull.null atIndex:1]; [optManaged.stringObj insertObject:NSNull.null atIndex:1]; [optManaged.dataObj insertObject:NSNull.null atIndex:1]; [optManaged.dateObj insertObject:NSNull.null atIndex:1]; [optManaged.decimalObj insertObject:NSNull.null atIndex:1]; [optManaged.objectIdObj insertObject:NSNull.null atIndex:1]; [optManaged.uuidObj insertObject:NSNull.null atIndex:1]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optManaged.intObj[0], @3); uncheckedAssertEqualObjects(optManaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.boolObj[2], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[2], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[2], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[2], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[2], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[2], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[2], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[2], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[2], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[2], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[2], @NO); uncheckedAssertEqualObjects(optManaged.intObj[2], @2); uncheckedAssertEqualObjects(optManaged.floatObj[2], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[2], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[2], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[2], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[2], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[2], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[2], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[2], uuid(@"00000000-0000-0000-0000-000000000000")); } - (void)testRemoveObject { for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"Index 0 is out of bounds (must be less than 0)."); } [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 3U); uncheckedAssertEqual(optUnmanaged.intObj.count, 3U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 3U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 3U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 3U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 3U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 3U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 3U); uncheckedAssertEqual(optManaged.boolObj.count, 3U); uncheckedAssertEqual(optManaged.intObj.count, 3U); uncheckedAssertEqual(optManaged.floatObj.count, 3U); uncheckedAssertEqual(optManaged.doubleObj.count, 3U); uncheckedAssertEqual(optManaged.stringObj.count, 3U); uncheckedAssertEqual(optManaged.dataObj.count, 3U); uncheckedAssertEqual(optManaged.dateObj.count, 3U); uncheckedAssertEqual(optManaged.decimalObj.count, 3U); uncheckedAssertEqual(optManaged.objectIdObj.count, 3U); uncheckedAssertEqual(optManaged.uuidObj.count, 3U); RLMAssertThrowsWithReason([unmanaged.boolObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.intObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.floatObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.doubleObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.stringObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.dataObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.dateObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.decimalObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.objectIdObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([unmanaged.uuidObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.boolObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.intObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.floatObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.doubleObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.stringObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.dataObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.dateObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.decimalObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.objectIdObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([managed.uuidObj removeObjectAtIndex:2], @"Index 2 is out of bounds (must be less than 2)."); RLMAssertThrowsWithReason([optUnmanaged.boolObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.intObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.floatObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.stringObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.dataObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.dateObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.boolObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.intObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.floatObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.doubleObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.stringObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.dataObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.dateObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.decimalObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.objectIdObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); RLMAssertThrowsWithReason([optManaged.uuidObj removeObjectAtIndex:3], @"Index 3 is out of bounds (must be less than 3)."); for (RLMArray *array in allArrays) { [array removeObjectAtIndex:0]; } uncheckedAssertEqual(unmanaged.boolObj.count, 1U); uncheckedAssertEqual(unmanaged.intObj.count, 1U); uncheckedAssertEqual(unmanaged.floatObj.count, 1U); uncheckedAssertEqual(unmanaged.doubleObj.count, 1U); uncheckedAssertEqual(unmanaged.stringObj.count, 1U); uncheckedAssertEqual(unmanaged.dataObj.count, 1U); uncheckedAssertEqual(unmanaged.dateObj.count, 1U); uncheckedAssertEqual(unmanaged.decimalObj.count, 1U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.uuidObj.count, 1U); uncheckedAssertEqual(managed.boolObj.count, 1U); uncheckedAssertEqual(managed.intObj.count, 1U); uncheckedAssertEqual(managed.floatObj.count, 1U); uncheckedAssertEqual(managed.doubleObj.count, 1U); uncheckedAssertEqual(managed.stringObj.count, 1U); uncheckedAssertEqual(managed.dataObj.count, 1U); uncheckedAssertEqual(managed.dateObj.count, 1U); uncheckedAssertEqual(managed.decimalObj.count, 1U); uncheckedAssertEqual(managed.objectIdObj.count, 1U); uncheckedAssertEqual(managed.uuidObj.count, 1U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj[0], @YES); uncheckedAssertEqualObjects(unmanaged.intObj[0], @3); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.boolObj[0], @YES); uncheckedAssertEqualObjects(managed.intObj[0], @3); uncheckedAssertEqualObjects(managed.floatObj[0], @3.3f); uncheckedAssertEqualObjects(managed.doubleObj[0], @3.3); uncheckedAssertEqualObjects(managed.stringObj[0], @"b"); uncheckedAssertEqualObjects(managed.dataObj[0], data(2)); uncheckedAssertEqualObjects(managed.dateObj[0], date(2)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @YES); uncheckedAssertEqualObjects(managed.anyIntObj[0], @3); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @3.3); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"b"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(2)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @YES); uncheckedAssertEqualObjects(optManaged.intObj[0], @3); uncheckedAssertEqualObjects(optManaged.floatObj[0], @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @3.3); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"b"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(2)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[1], NSNull.null); } - (void)testRemoveLastObject { for (RLMArray *array in allArrays) { XCTAssertNoThrow([array removeLastObject]); } [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 3U); uncheckedAssertEqual(optUnmanaged.intObj.count, 3U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 3U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 3U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 3U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 3U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 3U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 3U); uncheckedAssertEqual(optManaged.boolObj.count, 3U); uncheckedAssertEqual(optManaged.intObj.count, 3U); uncheckedAssertEqual(optManaged.floatObj.count, 3U); uncheckedAssertEqual(optManaged.doubleObj.count, 3U); uncheckedAssertEqual(optManaged.stringObj.count, 3U); uncheckedAssertEqual(optManaged.dataObj.count, 3U); uncheckedAssertEqual(optManaged.dateObj.count, 3U); uncheckedAssertEqual(optManaged.decimalObj.count, 3U); uncheckedAssertEqual(optManaged.objectIdObj.count, 3U); uncheckedAssertEqual(optManaged.uuidObj.count, 3U); for (RLMArray *array in allArrays) { [array removeLastObject]; } uncheckedAssertEqual(unmanaged.boolObj.count, 1U); uncheckedAssertEqual(unmanaged.intObj.count, 1U); uncheckedAssertEqual(unmanaged.floatObj.count, 1U); uncheckedAssertEqual(unmanaged.doubleObj.count, 1U); uncheckedAssertEqual(unmanaged.stringObj.count, 1U); uncheckedAssertEqual(unmanaged.dataObj.count, 1U); uncheckedAssertEqual(unmanaged.dateObj.count, 1U); uncheckedAssertEqual(unmanaged.decimalObj.count, 1U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.uuidObj.count, 1U); uncheckedAssertEqual(managed.boolObj.count, 1U); uncheckedAssertEqual(managed.intObj.count, 1U); uncheckedAssertEqual(managed.floatObj.count, 1U); uncheckedAssertEqual(managed.doubleObj.count, 1U); uncheckedAssertEqual(managed.stringObj.count, 1U); uncheckedAssertEqual(managed.dataObj.count, 1U); uncheckedAssertEqual(managed.dateObj.count, 1U); uncheckedAssertEqual(managed.decimalObj.count, 1U); uncheckedAssertEqual(managed.objectIdObj.count, 1U); uncheckedAssertEqual(managed.uuidObj.count, 1U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[0], @NO); uncheckedAssertEqualObjects(managed.intObj[0], @2); uncheckedAssertEqualObjects(managed.floatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optManaged.intObj[0], @2); uncheckedAssertEqualObjects(optManaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], @YES); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], @3); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], @3.3); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], @"b"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], date(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.boolObj[1], @YES); uncheckedAssertEqualObjects(optManaged.intObj[1], @3); uncheckedAssertEqualObjects(optManaged.floatObj[1], @3.3f); uncheckedAssertEqualObjects(optManaged.doubleObj[1], @3.3); uncheckedAssertEqualObjects(optManaged.stringObj[1], @"b"); uncheckedAssertEqualObjects(optManaged.dataObj[1], data(2)); uncheckedAssertEqualObjects(optManaged.dateObj[1], date(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[1], decimal128(3)); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], objectId(2)); uncheckedAssertEqualObjects(optManaged.uuidObj[1], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); } - (void)testReplace { RLMAssertThrowsWithReason([unmanaged.boolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.intObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.floatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.doubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.stringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.dataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.dateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyIntObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyStringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyDataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyDateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.boolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.intObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.floatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.stringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.dataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.dateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.boolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.intObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.floatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.doubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.stringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.dataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.dateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.decimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.objectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyBoolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyIntObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyFloatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyDoubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyStringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyDataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyDateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyDecimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyObjectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([managed.anyUUIDObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.boolObj replaceObjectAtIndex:0 withObject:@NO], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.intObj replaceObjectAtIndex:0 withObject:@2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.floatObj replaceObjectAtIndex:0 withObject:@2.2f], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.doubleObj replaceObjectAtIndex:0 withObject:@2.2], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.stringObj replaceObjectAtIndex:0 withObject:@"a"], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.dataObj replaceObjectAtIndex:0 withObject:data(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.dateObj replaceObjectAtIndex:0 withObject:date(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(2)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(1)], @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([optManaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"00000000-0000-0000-0000-000000000000")], @"Index 0 is out of bounds (must be less than 0)."); [unmanaged.boolObj addObject:@NO]; [unmanaged.boolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @YES); [unmanaged.intObj addObject:@2]; [unmanaged.intObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(unmanaged.intObj[0], @3); [unmanaged.floatObj addObject:@2.2f]; [unmanaged.floatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(unmanaged.floatObj[0], @3.3f); [unmanaged.doubleObj addObject:@2.2]; [unmanaged.doubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @3.3); [unmanaged.stringObj addObject:@"a"]; [unmanaged.stringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"b"); [unmanaged.dataObj addObject:data(1)]; [unmanaged.dataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(2)); [unmanaged.dateObj addObject:date(1)]; [unmanaged.dateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(2)); [unmanaged.decimalObj addObject:decimal128(2)]; [unmanaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(3)); [unmanaged.objectIdObj addObject:objectId(1)]; [unmanaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(2)); [unmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [unmanaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [unmanaged.anyBoolObj addObject:@NO]; [unmanaged.anyBoolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @YES); [unmanaged.anyIntObj addObject:@2]; [unmanaged.anyIntObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @3); [unmanaged.anyFloatObj addObject:@2.2f]; [unmanaged.anyFloatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @3.3f); [unmanaged.anyDoubleObj addObject:@2.2]; [unmanaged.anyDoubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @3.3); [unmanaged.anyStringObj addObject:@"a"]; [unmanaged.anyStringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"b"); [unmanaged.anyDataObj addObject:data(1)]; [unmanaged.anyDataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(2)); [unmanaged.anyDateObj addObject:date(1)]; [unmanaged.anyDateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(2)); [unmanaged.anyDecimalObj addObject:decimal128(2)]; [unmanaged.anyDecimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(3)); [unmanaged.anyObjectIdObj addObject:objectId(1)]; [unmanaged.anyObjectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(2)); [unmanaged.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [unmanaged.anyUUIDObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [optUnmanaged.boolObj addObject:@NO]; [optUnmanaged.boolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @YES); [optUnmanaged.intObj addObject:@2]; [optUnmanaged.intObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @3); [optUnmanaged.floatObj addObject:@2.2f]; [optUnmanaged.floatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @3.3f); [optUnmanaged.doubleObj addObject:@2.2]; [optUnmanaged.doubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @3.3); [optUnmanaged.stringObj addObject:@"a"]; [optUnmanaged.stringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"b"); [optUnmanaged.dataObj addObject:data(1)]; [optUnmanaged.dataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(2)); [optUnmanaged.dateObj addObject:date(1)]; [optUnmanaged.dateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(2)); [optUnmanaged.decimalObj addObject:decimal128(2)]; [optUnmanaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(3)); [optUnmanaged.objectIdObj addObject:objectId(1)]; [optUnmanaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(2)); [optUnmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optUnmanaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [managed.boolObj addObject:@NO]; [managed.boolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(managed.boolObj[0], @YES); [managed.intObj addObject:@2]; [managed.intObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(managed.intObj[0], @3); [managed.floatObj addObject:@2.2f]; [managed.floatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(managed.floatObj[0], @3.3f); [managed.doubleObj addObject:@2.2]; [managed.doubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(managed.doubleObj[0], @3.3); [managed.stringObj addObject:@"a"]; [managed.stringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(managed.stringObj[0], @"b"); [managed.dataObj addObject:data(1)]; [managed.dataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(managed.dataObj[0], data(2)); [managed.dateObj addObject:date(1)]; [managed.dateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(managed.dateObj[0], date(2)); [managed.decimalObj addObject:decimal128(2)]; [managed.decimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(3)); [managed.objectIdObj addObject:objectId(1)]; [managed.objectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(2)); [managed.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [managed.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [managed.anyBoolObj addObject:@NO]; [managed.anyBoolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(managed.anyBoolObj[0], @YES); [managed.anyIntObj addObject:@2]; [managed.anyIntObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(managed.anyIntObj[0], @3); [managed.anyFloatObj addObject:@2.2f]; [managed.anyFloatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(managed.anyFloatObj[0], @3.3f); [managed.anyDoubleObj addObject:@2.2]; [managed.anyDoubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @3.3); [managed.anyStringObj addObject:@"a"]; [managed.anyStringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(managed.anyStringObj[0], @"b"); [managed.anyDataObj addObject:data(1)]; [managed.anyDataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(managed.anyDataObj[0], data(2)); [managed.anyDateObj addObject:date(1)]; [managed.anyDateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(managed.anyDateObj[0], date(2)); [managed.anyDecimalObj addObject:decimal128(2)]; [managed.anyDecimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(3)); [managed.anyObjectIdObj addObject:objectId(1)]; [managed.anyObjectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(2)); [managed.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [managed.anyUUIDObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [optManaged.boolObj addObject:@NO]; [optManaged.boolObj replaceObjectAtIndex:0 withObject:@YES]; uncheckedAssertEqualObjects(optManaged.boolObj[0], @YES); [optManaged.intObj addObject:@2]; [optManaged.intObj replaceObjectAtIndex:0 withObject:@3]; uncheckedAssertEqualObjects(optManaged.intObj[0], @3); [optManaged.floatObj addObject:@2.2f]; [optManaged.floatObj replaceObjectAtIndex:0 withObject:@3.3f]; uncheckedAssertEqualObjects(optManaged.floatObj[0], @3.3f); [optManaged.doubleObj addObject:@2.2]; [optManaged.doubleObj replaceObjectAtIndex:0 withObject:@3.3]; uncheckedAssertEqualObjects(optManaged.doubleObj[0], @3.3); [optManaged.stringObj addObject:@"a"]; [optManaged.stringObj replaceObjectAtIndex:0 withObject:@"b"]; uncheckedAssertEqualObjects(optManaged.stringObj[0], @"b"); [optManaged.dataObj addObject:data(1)]; [optManaged.dataObj replaceObjectAtIndex:0 withObject:data(2)]; uncheckedAssertEqualObjects(optManaged.dataObj[0], data(2)); [optManaged.dateObj addObject:date(1)]; [optManaged.dateObj replaceObjectAtIndex:0 withObject:date(2)]; uncheckedAssertEqualObjects(optManaged.dateObj[0], date(2)); [optManaged.decimalObj addObject:decimal128(2)]; [optManaged.decimalObj replaceObjectAtIndex:0 withObject:decimal128(3)]; uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(3)); [optManaged.objectIdObj addObject:objectId(1)]; [optManaged.objectIdObj replaceObjectAtIndex:0 withObject:objectId(2)]; uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(2)); [optManaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optManaged.uuidObj replaceObjectAtIndex:0 withObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [optUnmanaged.boolObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], NSNull.null); [optUnmanaged.intObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.intObj[0], NSNull.null); [optUnmanaged.floatObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], NSNull.null); [optUnmanaged.doubleObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], NSNull.null); [optUnmanaged.stringObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], NSNull.null); [optUnmanaged.dataObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], NSNull.null); [optUnmanaged.dateObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], NSNull.null); [optUnmanaged.decimalObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], NSNull.null); [optUnmanaged.objectIdObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], NSNull.null); [optUnmanaged.uuidObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], NSNull.null); [optManaged.boolObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.boolObj[0], NSNull.null); [optManaged.intObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.intObj[0], NSNull.null); [optManaged.floatObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.floatObj[0], NSNull.null); [optManaged.doubleObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.doubleObj[0], NSNull.null); [optManaged.stringObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.stringObj[0], NSNull.null); [optManaged.dataObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.dataObj[0], NSNull.null); [optManaged.dateObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.dateObj[0], NSNull.null); [optManaged.decimalObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.decimalObj[0], NSNull.null); [optManaged.objectIdObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.objectIdObj[0], NSNull.null); [optManaged.uuidObj replaceObjectAtIndex:0 withObject:NSNull.null]; uncheckedAssertEqualObjects(optManaged.uuidObj[0], NSNull.null); RLMAssertThrowsWithReason([unmanaged.boolObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj replaceObjectAtIndex:0 withObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj replaceObjectAtIndex:0 withObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj replaceObjectAtIndex:0 withObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj replaceObjectAtIndex:0 withObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj replaceObjectAtIndex:0 withObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj replaceObjectAtIndex:0 withObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); } - (void)testMove { for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"Index 0 is out of bounds (must be less than 0)."); } for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array moveObjectAtIndex:1 toIndex:0], @"Index 1 is out of bounds (must be less than 0)."); } [unmanaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3, @2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [unmanaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3, @2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [optUnmanaged.intObj addObjects:@[@2, @3, @2, @3]]; [optUnmanaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [optUnmanaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [optUnmanaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [optUnmanaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [optUnmanaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [optUnmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [optUnmanaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [optUnmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.intObj addObjects:@[@2, @3, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3, @2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [optManaged.intObj addObjects:@[@2, @3, @2, @3]]; [optManaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [optManaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [optManaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [optManaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [optManaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; for (RLMArray *array in allArrays) { [array moveObjectAtIndex:2 toIndex:0]; } uncheckedAssertEqualObjects([unmanaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([unmanaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([managed.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([managed.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([managed.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([managed.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([managed.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([managed.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([managed.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.anyBoolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([managed.anyIntObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([managed.anyDataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([managed.anyDateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optManaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([optManaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([optManaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([optManaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([optManaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([optManaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); } - (void)testExchange { for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"Index 0 is out of bounds (must be less than 0)."); } for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array exchangeObjectAtIndex:1 withObjectAtIndex:0], @"Index 1 is out of bounds (must be less than 0)."); } [unmanaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3, @2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [unmanaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3, @2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [optUnmanaged.intObj addObjects:@[@2, @3, @2, @3]]; [optUnmanaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [optUnmanaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [optUnmanaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [optUnmanaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [optUnmanaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [optUnmanaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [optUnmanaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [optUnmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.intObj addObjects:@[@2, @3, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3, @2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [optManaged.intObj addObjects:@[@2, @3, @2, @3]]; [optManaged.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [optManaged.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [optManaged.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [optManaged.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [optManaged.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; for (RLMArray *array in allArrays) { [array exchangeObjectAtIndex:2 withObjectAtIndex:1]; } uncheckedAssertEqualObjects([unmanaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([unmanaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([managed.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([managed.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([managed.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([managed.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([managed.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([managed.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([managed.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.anyBoolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([managed.anyIntObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([managed.anyDataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([managed.anyDateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optManaged.boolObj valueForKey:@"self"], (@[@NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([optManaged.intObj valueForKey:@"self"], (@[@2, @2, @3, @3])); uncheckedAssertEqualObjects([optManaged.floatObj valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKey:@"self"], (@[@2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"self"], (@[@"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([optManaged.dataObj valueForKey:@"self"], (@[data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([optManaged.dateObj valueForKey:@"self"], (@[date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([optManaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); } - (void)testIndexOfObject { uncheckedAssertEqual(NSNotFound, [unmanaged.boolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [unmanaged.intObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [unmanaged.floatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [unmanaged.doubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [unmanaged.stringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [unmanaged.dataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.dateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.decimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [unmanaged.objectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.uuidObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyBoolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyIntObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyFloatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDoubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyStringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDecimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyObjectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyUUIDObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(NSNotFound, [managed.boolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [managed.intObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [managed.floatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [managed.doubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [managed.stringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [managed.dataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [managed.dateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [managed.decimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [managed.objectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [managed.uuidObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(NSNotFound, [managed.anyBoolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [managed.anyIntObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [managed.anyFloatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [managed.anyDoubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [managed.anyStringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [managed.anyDataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [managed.anyDateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [managed.anyDecimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [managed.anyObjectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [managed.anyUUIDObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(NSNotFound, [optManaged.boolObj indexOfObject:@NO]); uncheckedAssertEqual(NSNotFound, [optManaged.intObj indexOfObject:@2]); uncheckedAssertEqual(NSNotFound, [optManaged.floatObj indexOfObject:@2.2f]); uncheckedAssertEqual(NSNotFound, [optManaged.doubleObj indexOfObject:@2.2]); uncheckedAssertEqual(NSNotFound, [optManaged.stringObj indexOfObject:@"a"]); uncheckedAssertEqual(NSNotFound, [optManaged.dataObj indexOfObject:data(1)]); uncheckedAssertEqual(NSNotFound, [optManaged.dateObj indexOfObject:date(1)]); uncheckedAssertEqual(NSNotFound, [optManaged.decimalObj indexOfObject:decimal128(2)]); uncheckedAssertEqual(NSNotFound, [optManaged.objectIdObj indexOfObject:objectId(1)]); uncheckedAssertEqual(NSNotFound, [optManaged.uuidObj indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); RLMAssertThrowsWithReason([unmanaged.boolObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj indexOfObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj indexOfObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj indexOfObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj indexOfObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj indexOfObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj indexOfObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.boolObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.intObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.floatObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.doubleObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.stringObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.dataObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.dateObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.decimalObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.objectIdObj indexOfObject:NSNull.null]); uncheckedAssertEqual(NSNotFound, [optManaged.uuidObj indexOfObject:NSNull.null]); [self addObjects]; uncheckedAssertEqual(1U, [unmanaged.boolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [unmanaged.intObj indexOfObject:@3]); uncheckedAssertEqual(1U, [unmanaged.floatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [unmanaged.doubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [unmanaged.stringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [unmanaged.dataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [unmanaged.dateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [unmanaged.decimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [unmanaged.objectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [unmanaged.uuidObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [unmanaged.anyBoolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [unmanaged.anyIntObj indexOfObject:@3]); uncheckedAssertEqual(1U, [unmanaged.anyFloatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [unmanaged.anyDoubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [unmanaged.anyStringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [unmanaged.anyDataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [unmanaged.anyDateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [unmanaged.anyDecimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [unmanaged.anyObjectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [unmanaged.anyUUIDObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [optUnmanaged.boolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [optUnmanaged.intObj indexOfObject:@3]); uncheckedAssertEqual(1U, [optUnmanaged.floatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [optUnmanaged.doubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [optUnmanaged.stringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [optUnmanaged.dataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [optUnmanaged.dateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [optUnmanaged.decimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [optUnmanaged.objectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [optUnmanaged.uuidObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [managed.boolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [managed.intObj indexOfObject:@3]); uncheckedAssertEqual(1U, [managed.floatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [managed.doubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [managed.stringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [managed.dataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [managed.dateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [managed.decimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [managed.objectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [managed.uuidObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [managed.anyBoolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [managed.anyIntObj indexOfObject:@3]); uncheckedAssertEqual(1U, [managed.anyFloatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [managed.anyDoubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [managed.anyStringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [managed.anyDataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [managed.anyDateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [managed.anyDecimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [managed.anyObjectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [managed.anyUUIDObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [optManaged.boolObj indexOfObject:@YES]); uncheckedAssertEqual(1U, [optManaged.intObj indexOfObject:@3]); uncheckedAssertEqual(1U, [optManaged.floatObj indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [optManaged.doubleObj indexOfObject:@3.3]); uncheckedAssertEqual(1U, [optManaged.stringObj indexOfObject:@"b"]); uncheckedAssertEqual(1U, [optManaged.dataObj indexOfObject:data(2)]); uncheckedAssertEqual(1U, [optManaged.dateObj indexOfObject:date(2)]); uncheckedAssertEqual(1U, [optManaged.decimalObj indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [optManaged.objectIdObj indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [optManaged.uuidObj indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); } - (void)testIndexOfObjectSorted { [managed.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.intObj addObjects:@[@2, @3, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[@NO, @YES, NSNull.null, @YES, @NO]]; [optManaged.intObj addObjects:@[@2, @3, NSNull.null, @3, @2]]; [optManaged.floatObj addObjects:@[@2.2f, @3.3f, NSNull.null, @3.3f, @2.2f]]; [optManaged.doubleObj addObjects:@[@2.2, @3.3, NSNull.null, @3.3, @2.2]]; [optManaged.stringObj addObjects:@[@"a", @"b", NSNull.null, @"b", @"a"]]; [optManaged.dataObj addObjects:@[data(1), data(2), NSNull.null, data(2), data(1)]]; [optManaged.dateObj addObjects:@[date(1), date(2), NSNull.null, date(2), date(1)]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(3), NSNull.null, decimal128(3), decimal128(2)]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(2), NSNull.null, objectId(2), objectId(1)]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; uncheckedAssertEqual(0U, [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES]); uncheckedAssertEqual(0U, [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3]); uncheckedAssertEqual(0U, [[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f]); uncheckedAssertEqual(0U, [[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3]); uncheckedAssertEqual(0U, [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"b"]); uncheckedAssertEqual(0U, [[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)]); uncheckedAssertEqual(0U, [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)]); uncheckedAssertEqual(0U, [[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(3)]); uncheckedAssertEqual(0U, [[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)]); uncheckedAssertEqual(0U, [[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(2U, [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO]); uncheckedAssertEqual(2U, [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2]); uncheckedAssertEqual(2U, [[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f]); uncheckedAssertEqual(2U, [[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2]); uncheckedAssertEqual(2U, [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"]); uncheckedAssertEqual(2U, [[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)]); uncheckedAssertEqual(2U, [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)]); uncheckedAssertEqual(2U, [[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)]); uncheckedAssertEqual(2U, [[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)]); uncheckedAssertEqual(2U, [[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(0U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES]); uncheckedAssertEqual(0U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3]); uncheckedAssertEqual(0U, [[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f]); uncheckedAssertEqual(0U, [[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3]); uncheckedAssertEqual(0U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"b"]); uncheckedAssertEqual(0U, [[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)]); uncheckedAssertEqual(0U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)]); uncheckedAssertEqual(0U, [[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(3)]); uncheckedAssertEqual(0U, [[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)]); uncheckedAssertEqual(0U, [[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(2U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO]); uncheckedAssertEqual(2U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2]); uncheckedAssertEqual(2U, [[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f]); uncheckedAssertEqual(2U, [[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2]); uncheckedAssertEqual(2U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"]); uncheckedAssertEqual(2U, [[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)]); uncheckedAssertEqual(2U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)]); uncheckedAssertEqual(2U, [[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)]); uncheckedAssertEqual(2U, [[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)]); uncheckedAssertEqual(2U, [[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(4U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); uncheckedAssertEqual(4U, [[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); } - (void)testIndexOfObjectDistinct { [managed.boolObj addObjects:@[@NO, @NO, @YES]]; [managed.intObj addObjects:@[@2, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"a", @"b"]]; [managed.dataObj addObjects:@[data(1), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(2), decimal128(3)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[@NO, @NO, NSNull.null, @YES, @NO]]; [optManaged.intObj addObjects:@[@2, @2, NSNull.null, @3, @2]]; [optManaged.floatObj addObjects:@[@2.2f, @2.2f, NSNull.null, @3.3f, @2.2f]]; [optManaged.doubleObj addObjects:@[@2.2, @2.2, NSNull.null, @3.3, @2.2]]; [optManaged.stringObj addObjects:@[@"a", @"a", NSNull.null, @"b", @"a"]]; [optManaged.dataObj addObjects:@[data(1), data(1), NSNull.null, data(2), data(1)]]; [optManaged.dateObj addObjects:@[date(1), date(1), NSNull.null, date(2), date(1)]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(2), NSNull.null, decimal128(3), decimal128(2)]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(1), NSNull.null, objectId(2), objectId(1)]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; uncheckedAssertEqual(0U, [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2]); uncheckedAssertEqual(0U, [[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f]); uncheckedAssertEqual(0U, [[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2]); uncheckedAssertEqual(0U, [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"]); uncheckedAssertEqual(0U, [[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)]); uncheckedAssertEqual(0U, [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)]); uncheckedAssertEqual(0U, [[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)]); uncheckedAssertEqual(0U, [[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)]); uncheckedAssertEqual(0U, [[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(1U, [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES]); uncheckedAssertEqual(1U, [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3]); uncheckedAssertEqual(1U, [[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3]); uncheckedAssertEqual(1U, [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"b"]); uncheckedAssertEqual(1U, [[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)]); uncheckedAssertEqual(1U, [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)]); uncheckedAssertEqual(1U, [[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)]); uncheckedAssertEqual(1U, [[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(0U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2]); uncheckedAssertEqual(0U, [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f]); uncheckedAssertEqual(0U, [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2]); uncheckedAssertEqual(0U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"]); uncheckedAssertEqual(0U, [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)]); uncheckedAssertEqual(0U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)]); uncheckedAssertEqual(0U, [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)]); uncheckedAssertEqual(0U, [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)]); uncheckedAssertEqual(0U, [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(2U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES]); uncheckedAssertEqual(2U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3]); uncheckedAssertEqual(2U, [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f]); uncheckedAssertEqual(2U, [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3]); uncheckedAssertEqual(2U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"b"]); uncheckedAssertEqual(2U, [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)]); uncheckedAssertEqual(2U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)]); uncheckedAssertEqual(2U, [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(3)]); uncheckedAssertEqual(2U, [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)]); uncheckedAssertEqual(2U, [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); } - (void)testIndexOfObjectWhere { RLMAssertThrowsWithReason([managed.boolObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.intObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); uncheckedAssertEqual(NSNotFound, [unmanaged.boolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.intObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.floatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.stringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.dataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.dateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyBoolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyIntObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyFloatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDoubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyStringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDecimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyObjectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyUUIDObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"]); [self addObjects]; uncheckedAssertEqual(0U, [unmanaged.boolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.intObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.floatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.stringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.dataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.dateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyBoolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyIntObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyFloatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyDoubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyStringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyDataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyDateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyDecimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyObjectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [unmanaged.anyUUIDObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.boolObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.intObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.floatObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.doubleObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.stringObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.dataObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.dateObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.decimalObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.objectIdObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(0U, [optUnmanaged.uuidObj indexOfObjectWhere:@"TRUEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.boolObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.intObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.floatObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.doubleObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.stringObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.dataObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.dateObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.decimalObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.objectIdObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.uuidObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyBoolObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyIntObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyFloatObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDoubleObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyStringObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDataObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDateObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDecimalObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyObjectIdObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyUUIDObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObjectWhere:@"FALSEPREDICATE"]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObjectWhere:@"FALSEPREDICATE"]); } - (void)testIndexOfObjectWithPredicate { RLMAssertThrowsWithReason([managed.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); uncheckedAssertEqual(NSNotFound, [unmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyBoolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyIntObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyFloatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDoubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyStringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDecimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyObjectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyUUIDObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); [self addObjects]; uncheckedAssertEqual(0U, [unmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyBoolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyIntObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyFloatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyDoubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyStringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyDataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyDateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyDecimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyObjectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [unmanaged.anyUUIDObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(0U, [optUnmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); uncheckedAssertEqual(NSNotFound, [unmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyBoolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyIntObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyFloatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDoubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyStringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyDecimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyObjectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [unmanaged.anyUUIDObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.boolObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.intObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.floatObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.doubleObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.stringObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dataObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.dateObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.decimalObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.objectIdObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); uncheckedAssertEqual(NSNotFound, [optUnmanaged.uuidObj indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); } - (void)testSort { RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.floatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.doubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.dataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.decimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.objectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.uuidObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyBoolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyIntObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyFloatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDoubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyStringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDecimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyObjectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyUUIDObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.floatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.doubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.dataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.decimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.objectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.uuidObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); [managed.boolObj addObjects:@[@NO, @YES, @NO]]; [managed.intObj addObjects:@[@2, @3, @2]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2]]; [managed.stringObj addObjects:@[@"a", @"b", @"a"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1)]]; [managed.decimalObj addObjects:@[decimal128(2), decimal128(3), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1)]]; [managed.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [optManaged.boolObj addObjects:@[@NO, @YES, NSNull.null, @YES, @NO]]; [optManaged.intObj addObjects:@[@2, @3, NSNull.null, @3, @2]]; [optManaged.floatObj addObjects:@[@2.2f, @3.3f, NSNull.null, @3.3f, @2.2f]]; [optManaged.doubleObj addObjects:@[@2.2, @3.3, NSNull.null, @3.3, @2.2]]; [optManaged.stringObj addObjects:@[@"a", @"b", NSNull.null, @"b", @"a"]]; [optManaged.dataObj addObjects:@[data(1), data(2), NSNull.null, data(2), data(1)]]; [optManaged.dateObj addObjects:@[date(1), date(2), NSNull.null, date(2), date(1)]]; [optManaged.decimalObj addObjects:@[decimal128(2), decimal128(3), NSNull.null, decimal128(3), decimal128(2)]]; [optManaged.objectIdObj addObjects:@[objectId(1), objectId(2), NSNull.null, objectId(2), objectId(1)]]; [optManaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@NO, @YES, @NO])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2, @3, @2])); uncheckedAssertEqualObjects([[managed.floatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2f, @3.3f, @2.2f])); uncheckedAssertEqualObjects([[managed.doubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2, @3.3, @2.2])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@"a", @"b", @"a"])); uncheckedAssertEqualObjects([[managed.dataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[data(1), data(2), data(1)])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[date(1), date(2), date(1)])); uncheckedAssertEqualObjects([[managed.decimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[decimal128(2), decimal128(3), decimal128(2)])); uncheckedAssertEqualObjects([[managed.objectIdObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[objectId(1), objectId(2), objectId(1)])); uncheckedAssertEqualObjects([[managed.uuidObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@NO, @YES, NSNull.null, @YES, @NO])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2, @3, NSNull.null, @3, @2])); uncheckedAssertEqualObjects([[optManaged.floatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null, @3.3f, @2.2f])); uncheckedAssertEqualObjects([[optManaged.doubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null, @3.3, @2.2])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@"a", @"b", NSNull.null, @"b", @"a"])); uncheckedAssertEqualObjects([[optManaged.dataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[data(1), data(2), NSNull.null, data(2), data(1)])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[date(1), date(2), NSNull.null, date(2), date(1)])); uncheckedAssertEqualObjects([[optManaged.decimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null, decimal128(3), decimal128(2)])); uncheckedAssertEqualObjects([[optManaged.objectIdObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null, objectId(2), objectId(1)])); uncheckedAssertEqualObjects([[optManaged.uuidObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")])); uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@YES, @NO, @NO])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3, @2, @2])); uncheckedAssertEqualObjects([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3f, @2.2f, @2.2f])); uncheckedAssertEqualObjects([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3, @2.2, @2.2])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@"b", @"a", @"a"])); uncheckedAssertEqualObjects([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[data(2), data(1), data(1)])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[date(2), date(1), date(1)])); uncheckedAssertEqualObjects([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[decimal128(3), decimal128(2), decimal128(2)])); uncheckedAssertEqualObjects([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[objectId(2), objectId(1), objectId(1)])); uncheckedAssertEqualObjects([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000")])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@YES, @YES, @NO, @NO, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3, @3, @2, @2, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3f, @3.3f, @2.2f, @2.2f, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3, @3.3, @2.2, @2.2, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@"b", @"b", @"a", @"a", NSNull.null])); uncheckedAssertEqualObjects([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[data(2), data(2), data(1), data(1), NSNull.null])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[date(2), date(2), date(1), date(1), NSNull.null])); uncheckedAssertEqualObjects([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[decimal128(3), decimal128(3), decimal128(2), decimal128(2), NSNull.null])); uncheckedAssertEqualObjects([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[objectId(2), objectId(2), objectId(1), objectId(1), NSNull.null])); uncheckedAssertEqualObjects([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), NSNull.null])); uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@NO, @NO, @YES])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2, @2, @3])); uncheckedAssertEqualObjects([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2.2f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2.2, @2.2, @3.3])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@"a", @"a", @"b"])); uncheckedAssertEqualObjects([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[data(1), data(1), data(2)])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[date(1), date(1), date(2)])); uncheckedAssertEqualObjects([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[decimal128(2), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[objectId(1), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @NO, @NO, @YES, @YES])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @2, @2, @3, @3])); uncheckedAssertEqualObjects([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @2.2f, @2.2f, @3.3f, @3.3f])); uncheckedAssertEqualObjects([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @2.2, @2.2, @3.3, @3.3])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @"a", @"a", @"b", @"b"])); uncheckedAssertEqualObjects([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, data(1), data(1), data(2), data(2)])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, date(1), date(1), date(2), date(2)])); uncheckedAssertEqualObjects([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, decimal128(2), decimal128(2), decimal128(3), decimal128(3)])); uncheckedAssertEqualObjects([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, objectId(1), objectId(1), objectId(2), objectId(2)])); uncheckedAssertEqualObjects([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); } - (void)testFilter { RLMAssertThrowsWithReason([unmanaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { RLMAssertThrowsWithReason([unmanaged.boolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyIntObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyStringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.boolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); } - (void)testMin { RLMAssertThrowsWithReason([unmanaged.boolObj minOfProperty:@"self"], @"minOfProperty: is not supported for bool array"); RLMAssertThrowsWithReason([unmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string array"); RLMAssertThrowsWithReason([unmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data array"); RLMAssertThrowsWithReason([unmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id array"); RLMAssertThrowsWithReason([unmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid array"); RLMAssertThrowsWithReason([optUnmanaged.boolObj minOfProperty:@"self"], @"minOfProperty: is not supported for bool? array"); RLMAssertThrowsWithReason([optUnmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string? array"); RLMAssertThrowsWithReason([optUnmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data? array"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id? array"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid? array"); RLMAssertThrowsWithReason([managed.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool list 'AllPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string list 'AllPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj minOfProperty:@"self"], @"Operation 'min' not supported for data list 'AllPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([managed.objectIdObj minOfProperty:@"self"], @"Operation 'min' not supported for object id list 'AllPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj minOfProperty:@"self"], @"Operation 'min' not supported for uuid list 'AllPrimitiveArrays.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool? list 'AllOptionalPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string? list 'AllOptionalPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj minOfProperty:@"self"], @"Operation 'min' not supported for data? list 'AllOptionalPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj minOfProperty:@"self"], @"Operation 'min' not supported for object id? list 'AllOptionalPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj minOfProperty:@"self"], @"Operation 'min' not supported for uuid? list 'AllOptionalPrimitiveArrays.uuidObj'"); uncheckedAssertNil([unmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.intObj minOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj minOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([unmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optUnmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optUnmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([managed.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.anyIntObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([managed.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optManaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optManaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optManaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optManaged.decimalObj minOfProperty:@"self"], decimal128(2)); } - (void)testMax { RLMAssertThrowsWithReason([unmanaged.boolObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for bool array"); RLMAssertThrowsWithReason([unmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string array"); RLMAssertThrowsWithReason([unmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data array"); RLMAssertThrowsWithReason([unmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id array"); RLMAssertThrowsWithReason([unmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid array"); RLMAssertThrowsWithReason([optUnmanaged.boolObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for bool? array"); RLMAssertThrowsWithReason([optUnmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string? array"); RLMAssertThrowsWithReason([optUnmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data? array"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id? array"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid? array"); RLMAssertThrowsWithReason([managed.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool list 'AllPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string list 'AllPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj maxOfProperty:@"self"], @"Operation 'max' not supported for data list 'AllPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([managed.objectIdObj maxOfProperty:@"self"], @"Operation 'max' not supported for object id list 'AllPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj maxOfProperty:@"self"], @"Operation 'max' not supported for uuid list 'AllPrimitiveArrays.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool? list 'AllOptionalPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string? list 'AllOptionalPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj maxOfProperty:@"self"], @"Operation 'max' not supported for data? list 'AllOptionalPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj maxOfProperty:@"self"], @"Operation 'max' not supported for object id? list 'AllOptionalPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj maxOfProperty:@"self"], @"Operation 'max' not supported for uuid? list 'AllOptionalPrimitiveArrays.uuidObj'"); uncheckedAssertNil([unmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.intObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj maxOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([unmanaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.decimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([optUnmanaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([optUnmanaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([optUnmanaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([managed.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([managed.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.decimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([managed.anyIntObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([managed.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.anyDecimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([optManaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([optManaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([optManaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([optManaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([optManaged.decimalObj maxOfProperty:@"self"], decimal128(3)); } - (void)testSum { RLMAssertThrowsWithReason([unmanaged.boolObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for bool array"); RLMAssertThrowsWithReason([unmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string array"); RLMAssertThrowsWithReason([unmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data array"); RLMAssertThrowsWithReason([unmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date array"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id array"); RLMAssertThrowsWithReason([unmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid array"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for bool? array"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string? array"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data? array"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date? array"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id? array"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid? array"); RLMAssertThrowsWithReason([managed.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool list 'AllPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string list 'AllPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj sumOfProperty:@"self"], @"Operation 'sum' not supported for data list 'AllPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([managed.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date list 'AllPrimitiveArrays.dateObj'"); RLMAssertThrowsWithReason([managed.objectIdObj sumOfProperty:@"self"], @"Operation 'sum' not supported for object id list 'AllPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj sumOfProperty:@"self"], @"Operation 'sum' not supported for uuid list 'AllPrimitiveArrays.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool? list 'AllOptionalPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string? list 'AllOptionalPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj sumOfProperty:@"self"], @"Operation 'sum' not supported for data? list 'AllOptionalPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([optManaged.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date? list 'AllOptionalPrimitiveArrays.dateObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj sumOfProperty:@"self"], @"Operation 'sum' not supported for object id? list 'AllOptionalPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj sumOfProperty:@"self"], @"Operation 'sum' not supported for uuid? list 'AllOptionalPrimitiveArrays.uuidObj'"); uncheckedAssertEqualObjects([unmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDecimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj sumOfProperty:@"self"], @0); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3), NSNull.null]), .001); XCTAssertEqualWithAccuracy([managed.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([optManaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(2), decimal128(3), NSNull.null]), .001); } - (void)testAverage { RLMAssertThrowsWithReason([unmanaged.boolObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for bool array"); RLMAssertThrowsWithReason([unmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string array"); RLMAssertThrowsWithReason([unmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data array"); RLMAssertThrowsWithReason([unmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date array"); RLMAssertThrowsWithReason([unmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id array"); RLMAssertThrowsWithReason([unmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid array"); RLMAssertThrowsWithReason([optUnmanaged.boolObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for bool? array"); RLMAssertThrowsWithReason([optUnmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string? array"); RLMAssertThrowsWithReason([optUnmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data? array"); RLMAssertThrowsWithReason([optUnmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date? array"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id? array"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid? array"); RLMAssertThrowsWithReason([managed.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool list 'AllPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string list 'AllPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj averageOfProperty:@"self"], @"Operation 'average' not supported for data list 'AllPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([managed.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date list 'AllPrimitiveArrays.dateObj'"); RLMAssertThrowsWithReason([managed.objectIdObj averageOfProperty:@"self"], @"Operation 'average' not supported for object id list 'AllPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj averageOfProperty:@"self"], @"Operation 'average' not supported for uuid list 'AllPrimitiveArrays.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool? list 'AllOptionalPrimitiveArrays.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string? list 'AllOptionalPrimitiveArrays.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj averageOfProperty:@"self"], @"Operation 'average' not supported for data? list 'AllOptionalPrimitiveArrays.dataObj'"); RLMAssertThrowsWithReason([optManaged.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date? list 'AllOptionalPrimitiveArrays.dateObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj averageOfProperty:@"self"], @"Operation 'average' not supported for object id? list 'AllOptionalPrimitiveArrays.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj averageOfProperty:@"self"], @"Operation 'average' not supported for uuid? list 'AllOptionalPrimitiveArrays.uuidObj'"); uncheckedAssertNil([unmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.intObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj averageOfProperty:@"self"]); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3), NSNull.null]), .001); XCTAssertEqualWithAccuracy([managed.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([optManaged.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(2), decimal128(3), NSNull.null]), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES]; for (id value in unmanaged.boolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.boolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3]; for (id value in unmanaged.intObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.intObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f]; for (id value in unmanaged.floatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.floatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3]; for (id value in unmanaged.doubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.doubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b"]; for (id value in unmanaged.stringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.stringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2)]; for (id value in unmanaged.dataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.dataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2)]; for (id value in unmanaged.dateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.dateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3)]; for (id value in unmanaged.decimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.decimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2)]; for (id value in unmanaged.objectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.objectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in unmanaged.uuidObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.uuidObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES]; for (id value in unmanaged.anyBoolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyBoolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3]; for (id value in unmanaged.anyIntObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyIntObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f]; for (id value in unmanaged.anyFloatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyFloatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3]; for (id value in unmanaged.anyDoubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyDoubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b"]; for (id value in unmanaged.anyStringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyStringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2)]; for (id value in unmanaged.anyDataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyDataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2)]; for (id value in unmanaged.anyDateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyDateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3)]; for (id value in unmanaged.anyDecimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyDecimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2)]; for (id value in unmanaged.anyObjectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyObjectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in unmanaged.anyUUIDObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, unmanaged.anyUUIDObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES, NSNull.null]; for (id value in optUnmanaged.boolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.boolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3, NSNull.null]; for (id value in optUnmanaged.intObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.intObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f, NSNull.null]; for (id value in optUnmanaged.floatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.floatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3, NSNull.null]; for (id value in optUnmanaged.doubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.doubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b", NSNull.null]; for (id value in optUnmanaged.stringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.stringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2), NSNull.null]; for (id value in optUnmanaged.dataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.dataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2), NSNull.null]; for (id value in optUnmanaged.dateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.dateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3), NSNull.null]; for (id value in optUnmanaged.decimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.decimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2), NSNull.null]; for (id value in optUnmanaged.objectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.objectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; for (id value in optUnmanaged.uuidObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optUnmanaged.uuidObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES]; for (id value in managed.boolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.boolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3]; for (id value in managed.intObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.intObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f]; for (id value in managed.floatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.floatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3]; for (id value in managed.doubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.doubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b"]; for (id value in managed.stringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.stringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2)]; for (id value in managed.dataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.dataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2)]; for (id value in managed.dateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.dateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3)]; for (id value in managed.decimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.decimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2)]; for (id value in managed.objectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.objectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in managed.uuidObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.uuidObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES]; for (id value in managed.anyBoolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyBoolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3]; for (id value in managed.anyIntObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyIntObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f]; for (id value in managed.anyFloatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyFloatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3]; for (id value in managed.anyDoubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyDoubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b"]; for (id value in managed.anyStringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyStringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2)]; for (id value in managed.anyDataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyDataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2)]; for (id value in managed.anyDateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyDateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3)]; for (id value in managed.anyDecimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyDecimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2)]; for (id value in managed.anyObjectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyObjectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in managed.anyUUIDObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, managed.anyUUIDObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@NO, @YES, NSNull.null]; for (id value in optManaged.boolObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.boolObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2, @3, NSNull.null]; for (id value in optManaged.intObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.intObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2f, @3.3f, NSNull.null]; for (id value in optManaged.floatObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.floatObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@2.2, @3.3, NSNull.null]; for (id value in optManaged.doubleObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.doubleObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[@"a", @"b", NSNull.null]; for (id value in optManaged.stringObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.stringObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[data(1), data(2), NSNull.null]; for (id value in optManaged.dataObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.dataObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[date(1), date(2), NSNull.null]; for (id value in optManaged.dateObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.dateObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[decimal128(2), decimal128(3), NSNull.null]; for (id value in optManaged.decimalObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.decimalObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[objectId(1), objectId(2), NSNull.null]; for (id value in optManaged.objectIdObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.objectIdObj.count); }(); ^{ NSUInteger i = 0; NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; for (id value in optManaged.uuidObj) { uncheckedAssertEqualObjects(values[i++ % values.count], value); } uncheckedAssertEqual(i, optManaged.uuidObj.count); }(); } - (void)testValueForKeySelf { for (RLMArray *array in allArrays) { uncheckedAssertEqualObjects([array valueForKey:@"self"], @[]); } [self addObjects]; uncheckedAssertEqualObjects([unmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([unmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects([managed.boolObj valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([managed.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([managed.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([managed.dataObj valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([managed.dateObj valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([managed.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([managed.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects([managed.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([managed.anyIntObj valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([managed.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([managed.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optManaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); uncheckedAssertEqualObjects([optManaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); uncheckedAssertEqualObjects([optManaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); uncheckedAssertEqualObjects([optManaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); } - (void)testValueForKeyNumericAggregates { uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@avg.self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(2), decimal128(3), NSNull.null]), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(2), decimal128(3), NSNull.null]), .001); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(2), decimal128(3), NSNull.null]), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(2), decimal128(3)]), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3, NSNull.null]), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(2), decimal128(3), NSNull.null]), .001); } - (void)testValueForKeyLength { for (RLMArray *array in allArrays) { uncheckedAssertEqualObjects([array valueForKey:@"length"], @[]); } [self addObjects]; uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"length"], ([@[@"a", @"b"] valueForKey:@"length"])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"length"], ([@[@"a", @"b"] valueForKey:@"length"])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"length"], ([@[@"a", @"b", NSNull.null] valueForKey:@"length"])); uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"length"], ([@[@"a", @"b"] valueForKey:@"length"])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"length"], ([@[@"a", @"b"] valueForKey:@"length"])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"length"], ([@[@"a", @"b", NSNull.null] valueForKey:@"length"])); } // Sort the distinct results to match the order used in values, as it // doesn't preserve the order naturally static NSArray *sortedDistinctUnion(id array, NSString *type, NSString *prop) { return [[array valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOf%@.%@", type, prop]] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { bool aIsNull = a == NSNull.null; bool bIsNull = b == NSNull.null; if (aIsNull && bIsNull) { return 0; } if (aIsNull) { return 1; } if (bIsNull) { return -1; } if ([a isKindOfClass:[NSData class]]) { if ([a length] != [b length]) { return [a length] < [b length] ? -1 : 1; } int result = memcmp([a bytes], [b bytes], [a length]); if (!result) { return 0; } return result < 0 ? -1 : 1; } if ([a isKindOfClass:[RLMObjectId class]]) { int64_t idx1 = [objectIds indexOfObject:a]; int64_t idx2 = [objectIds indexOfObject:b]; return idx1 - idx2; } if ([a respondsToSelector:@selector(objCType)] && [b respondsToSelector:@selector(objCType)]) { return [a compare:b]; } else { if ([a isKindOfClass:[RLMDecimal128 class]]) { a = [NSNumber numberWithDouble:[(RLMDecimal128 *)a doubleValue]]; } if ([b isKindOfClass:[RLMDecimal128 class]]) { b = [NSNumber numberWithDouble:[(RLMDecimal128 *)b doubleValue]]; } return [a compare:b]; } }]; } - (void)testUnionOfObjects { for (RLMArray *array in allArrays) { uncheckedAssertEqualObjects([array valueForKeyPath:@"@unionOfObjects.self"], @[]); } for (RLMArray *array in allArrays) { uncheckedAssertEqualObjects([array valueForKeyPath:@"@distinctUnionOfObjects.self"], @[]); } [self addObjects]; [self addObjects]; uncheckedAssertEqualObjects([unmanaged.boolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([unmanaged.stringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([unmanaged.dataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, NSNull.null, @NO, @YES, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, NSNull.null, @2, @3, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, NSNull.null, @2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, NSNull.null, @2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", NSNull.null, @"a", @"b", NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), NSNull.null, data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), NSNull.null, date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), NSNull.null, decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), NSNull.null, objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects([managed.boolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([managed.stringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([managed.dataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([managed.objectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([managed.uuidObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([managed.anyBoolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([managed.anyDataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([optManaged.boolObj valueForKeyPath:@"@unionOfObjects.self"], (@[@NO, @YES, NSNull.null, @NO, @YES, NSNull.null])); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2, @3, NSNull.null, @2, @3, NSNull.null])); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2f, @3.3f, NSNull.null, @2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@unionOfObjects.self"], (@[@2.2, @3.3, NSNull.null, @2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKeyPath:@"@unionOfObjects.self"], (@[@"a", @"b", NSNull.null, @"a", @"b", NSNull.null])); uncheckedAssertEqualObjects([optManaged.dataObj valueForKeyPath:@"@unionOfObjects.self"], (@[data(1), data(2), NSNull.null, data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@unionOfObjects.self"], (@[date(1), date(2), NSNull.null, date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@unionOfObjects.self"], (@[decimal128(2), decimal128(3), NSNull.null, decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKeyPath:@"@unionOfObjects.self"], (@[objectId(1), objectId(2), NSNull.null, objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects([optManaged.uuidObj valueForKeyPath:@"@unionOfObjects.self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.boolObj, @"Objects", @"self"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.intObj, @"Objects", @"self"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.floatObj, @"Objects", @"self"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.doubleObj, @"Objects", @"self"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.stringObj, @"Objects", @"self"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.dataObj, @"Objects", @"self"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.dateObj, @"Objects", @"self"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.decimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.objectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.uuidObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyBoolObj, @"Objects", @"self"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyIntObj, @"Objects", @"self"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyFloatObj, @"Objects", @"self"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyDoubleObj, @"Objects", @"self"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyStringObj, @"Objects", @"self"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyDataObj, @"Objects", @"self"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyDateObj, @"Objects", @"self"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyDecimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyObjectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(unmanaged.anyUUIDObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.boolObj, @"Objects", @"self"), (@[@NO, @YES, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.intObj, @"Objects", @"self"), (@[@2, @3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.floatObj, @"Objects", @"self"), (@[@2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.doubleObj, @"Objects", @"self"), (@[@2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.stringObj, @"Objects", @"self"), (@[@"a", @"b", NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.dataObj, @"Objects", @"self"), (@[data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.dateObj, @"Objects", @"self"), (@[date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.decimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.objectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optUnmanaged.uuidObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.boolObj, @"Objects", @"self"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.intObj, @"Objects", @"self"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.floatObj, @"Objects", @"self"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.doubleObj, @"Objects", @"self"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.stringObj, @"Objects", @"self"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.dataObj, @"Objects", @"self"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.dateObj, @"Objects", @"self"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.decimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.objectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.uuidObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyBoolObj, @"Objects", @"self"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyIntObj, @"Objects", @"self"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyFloatObj, @"Objects", @"self"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyDoubleObj, @"Objects", @"self"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyStringObj, @"Objects", @"self"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyDataObj, @"Objects", @"self"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyDateObj, @"Objects", @"self"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyDecimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyObjectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(managed.anyUUIDObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.boolObj, @"Objects", @"self"), (@[@NO, @YES, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.intObj, @"Objects", @"self"), (@[@2, @3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.floatObj, @"Objects", @"self"), (@[@2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.doubleObj, @"Objects", @"self"), (@[@2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.stringObj, @"Objects", @"self"), (@[@"a", @"b", NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.dataObj, @"Objects", @"self"), (@[data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.dateObj, @"Objects", @"self"), (@[date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.decimalObj, @"Objects", @"self"), (@[decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.objectIdObj, @"Objects", @"self"), (@[objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(optManaged.uuidObj, @"Objects", @"self"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); } - (void)testUnionOfArrays { RLMResults *allRequired = [AllPrimitiveArrays allObjectsInRealm:realm]; RLMResults *allOptional = [AllOptionalPrimitiveArrays allObjectsInRealm:realm]; uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.boolObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.intObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.floatObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.doubleObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.stringObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.dataObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.dateObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.decimalObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.objectIdObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.uuidObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.boolObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.intObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.floatObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.doubleObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.stringObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.dataObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.dateObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.decimalObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.objectIdObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.uuidObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.boolObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.intObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.floatObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.doubleObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.stringObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.dataObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.dateObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.decimalObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.objectIdObj"], @[]); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.uuidObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.boolObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.intObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.floatObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.doubleObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.stringObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.dataObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.dateObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.decimalObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.objectIdObj"], @[]); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.uuidObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyBoolObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyIntObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyFloatObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDoubleObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyStringObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDataObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDateObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDecimalObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyObjectIdObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyUUIDObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyBoolObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyIntObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyFloatObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyDoubleObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyStringObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyDataObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyDateObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyDecimalObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyObjectIdObj"], @[]); XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.anyUUIDObj"], @[]); [self addObjects]; [AllPrimitiveArrays createInRealm:realm withValue:managed]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:optManaged]; uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.boolObj"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.intObj"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.floatObj"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.doubleObj"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.stringObj"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.dataObj"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.dateObj"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.decimalObj"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.objectIdObj"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.uuidObj"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.boolObj"], (@[@NO, @YES, NSNull.null, @NO, @YES, NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.intObj"], (@[@2, @3, NSNull.null, @2, @3, NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.floatObj"], (@[@2.2f, @3.3f, NSNull.null, @2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.doubleObj"], (@[@2.2, @3.3, NSNull.null, @2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.stringObj"], (@[@"a", @"b", NSNull.null, @"a", @"b", NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.dataObj"], (@[data(1), data(2), NSNull.null, data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.dateObj"], (@[date(1), date(2), NSNull.null, date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.decimalObj"], (@[decimal128(2), decimal128(3), NSNull.null, decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.objectIdObj"], (@[objectId(1), objectId(2), NSNull.null, objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.uuidObj"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"boolObj"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"intObj"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"floatObj"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"doubleObj"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"stringObj"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"dataObj"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"dateObj"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"decimalObj"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"objectIdObj"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"uuidObj"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"boolObj"), (@[@NO, @YES, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"intObj"), (@[@2, @3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"floatObj"), (@[@2.2f, @3.3f, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"doubleObj"), (@[@2.2, @3.3, NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"stringObj"), (@[@"a", @"b", NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"dataObj"), (@[data(1), data(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"dateObj"), (@[date(1), date(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"decimalObj"), (@[decimal128(2), decimal128(3), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"objectIdObj"), (@[objectId(1), objectId(2), NSNull.null])); uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"uuidObj"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyBoolObj"], (@[@NO, @YES, @NO, @YES])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyIntObj"], (@[@2, @3, @2, @3])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyFloatObj"], (@[@2.2f, @3.3f, @2.2f, @3.3f])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDoubleObj"], (@[@2.2, @3.3, @2.2, @3.3])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyStringObj"], (@[@"a", @"b", @"a", @"b"])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDataObj"], (@[data(1), data(2), data(1), data(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDateObj"], (@[date(1), date(2), date(1), date(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyDecimalObj"], (@[decimal128(2), decimal128(3), decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyObjectIdObj"], (@[objectId(1), objectId(2), objectId(1), objectId(2)])); uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.anyUUIDObj"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyBoolObj"), (@[@NO, @YES])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyIntObj"), (@[@2, @3])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyFloatObj"), (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyDoubleObj"), (@[@2.2, @3.3])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyStringObj"), (@[@"a", @"b"])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyDataObj"), (@[data(1), data(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyDateObj"), (@[date(1), date(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyDecimalObj"), (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyObjectIdObj"), (@[objectId(1), objectId(2)])); uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"anyUUIDObj"), (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); } - (void)testSetValueForKey { for (RLMArray *array in allArrays) { RLMAssertThrowsWithReason([array setValue:@0 forKey:@"not self"], @"this class is not key value coding-compliant for the key not self."); } RLMAssertThrowsWithReason([unmanaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; [unmanaged.boolObj setValue:@NO forKey:@"self"]; [unmanaged.intObj setValue:@2 forKey:@"self"]; [unmanaged.floatObj setValue:@2.2f forKey:@"self"]; [unmanaged.doubleObj setValue:@2.2 forKey:@"self"]; [unmanaged.stringObj setValue:@"a" forKey:@"self"]; [unmanaged.dataObj setValue:data(1) forKey:@"self"]; [unmanaged.dateObj setValue:date(1) forKey:@"self"]; [unmanaged.decimalObj setValue:decimal128(2) forKey:@"self"]; [unmanaged.objectIdObj setValue:objectId(1) forKey:@"self"]; [unmanaged.uuidObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [unmanaged.anyBoolObj setValue:@NO forKey:@"self"]; [unmanaged.anyIntObj setValue:@2 forKey:@"self"]; [unmanaged.anyFloatObj setValue:@2.2f forKey:@"self"]; [unmanaged.anyDoubleObj setValue:@2.2 forKey:@"self"]; [unmanaged.anyStringObj setValue:@"a" forKey:@"self"]; [unmanaged.anyDataObj setValue:data(1) forKey:@"self"]; [unmanaged.anyDateObj setValue:date(1) forKey:@"self"]; [unmanaged.anyDecimalObj setValue:decimal128(2) forKey:@"self"]; [unmanaged.anyObjectIdObj setValue:objectId(1) forKey:@"self"]; [unmanaged.anyUUIDObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [optUnmanaged.boolObj setValue:@NO forKey:@"self"]; [optUnmanaged.intObj setValue:@2 forKey:@"self"]; [optUnmanaged.floatObj setValue:@2.2f forKey:@"self"]; [optUnmanaged.doubleObj setValue:@2.2 forKey:@"self"]; [optUnmanaged.stringObj setValue:@"a" forKey:@"self"]; [optUnmanaged.dataObj setValue:data(1) forKey:@"self"]; [optUnmanaged.dateObj setValue:date(1) forKey:@"self"]; [optUnmanaged.decimalObj setValue:decimal128(2) forKey:@"self"]; [optUnmanaged.objectIdObj setValue:objectId(1) forKey:@"self"]; [optUnmanaged.uuidObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [managed.boolObj setValue:@NO forKey:@"self"]; [managed.intObj setValue:@2 forKey:@"self"]; [managed.floatObj setValue:@2.2f forKey:@"self"]; [managed.doubleObj setValue:@2.2 forKey:@"self"]; [managed.stringObj setValue:@"a" forKey:@"self"]; [managed.dataObj setValue:data(1) forKey:@"self"]; [managed.dateObj setValue:date(1) forKey:@"self"]; [managed.decimalObj setValue:decimal128(2) forKey:@"self"]; [managed.objectIdObj setValue:objectId(1) forKey:@"self"]; [managed.uuidObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [managed.anyBoolObj setValue:@NO forKey:@"self"]; [managed.anyIntObj setValue:@2 forKey:@"self"]; [managed.anyFloatObj setValue:@2.2f forKey:@"self"]; [managed.anyDoubleObj setValue:@2.2 forKey:@"self"]; [managed.anyStringObj setValue:@"a" forKey:@"self"]; [managed.anyDataObj setValue:data(1) forKey:@"self"]; [managed.anyDateObj setValue:date(1) forKey:@"self"]; [managed.anyDecimalObj setValue:decimal128(2) forKey:@"self"]; [managed.anyObjectIdObj setValue:objectId(1) forKey:@"self"]; [managed.anyUUIDObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [optManaged.boolObj setValue:@NO forKey:@"self"]; [optManaged.intObj setValue:@2 forKey:@"self"]; [optManaged.floatObj setValue:@2.2f forKey:@"self"]; [optManaged.doubleObj setValue:@2.2 forKey:@"self"]; [optManaged.stringObj setValue:@"a" forKey:@"self"]; [optManaged.dataObj setValue:data(1) forKey:@"self"]; [optManaged.dateObj setValue:date(1) forKey:@"self"]; [optManaged.decimalObj setValue:decimal128(2) forKey:@"self"]; [optManaged.objectIdObj setValue:objectId(1) forKey:@"self"]; [optManaged.uuidObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[0], @NO); uncheckedAssertEqualObjects(managed.intObj[0], @2); uncheckedAssertEqualObjects(managed.floatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[0], @NO); uncheckedAssertEqualObjects(optManaged.intObj[0], @2); uncheckedAssertEqualObjects(optManaged.floatObj[0], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[0], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[0], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[0], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[0], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.boolObj[1], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[1], @2); uncheckedAssertEqualObjects(unmanaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[1], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[1], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[1], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[1], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[1], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[1], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[1], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[1], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[1], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[1], @NO); uncheckedAssertEqualObjects(managed.intObj[1], @2); uncheckedAssertEqualObjects(managed.floatObj[1], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj[1], @2.2); uncheckedAssertEqualObjects(managed.stringObj[1], @"a"); uncheckedAssertEqualObjects(managed.dataObj[1], data(1)); uncheckedAssertEqualObjects(managed.dateObj[1], date(1)); uncheckedAssertEqualObjects(managed.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(managed.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[1], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[1], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[1], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[1], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[1], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[1], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[1], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[1], @NO); uncheckedAssertEqualObjects(optManaged.intObj[1], @2); uncheckedAssertEqualObjects(optManaged.floatObj[1], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[1], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[1], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[1], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[1], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[1], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[1], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[1], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[2], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[2], @2); uncheckedAssertEqualObjects(optUnmanaged.floatObj[2], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[2], @2.2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[2], @"a"); uncheckedAssertEqualObjects(optUnmanaged.dataObj[2], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[2], date(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[2], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[2], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[2], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj[2], @NO); uncheckedAssertEqualObjects(optManaged.intObj[2], @2); uncheckedAssertEqualObjects(optManaged.floatObj[2], @2.2f); uncheckedAssertEqualObjects(optManaged.doubleObj[2], @2.2); uncheckedAssertEqualObjects(optManaged.stringObj[2], @"a"); uncheckedAssertEqualObjects(optManaged.dataObj[2], data(1)); uncheckedAssertEqualObjects(optManaged.dateObj[2], date(1)); uncheckedAssertEqualObjects(optManaged.decimalObj[2], decimal128(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[2], objectId(1)); uncheckedAssertEqualObjects(optManaged.uuidObj[2], uuid(@"00000000-0000-0000-0000-000000000000")); [optUnmanaged.boolObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.intObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.floatObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.stringObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dataObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dateObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.uuidObj setValue:NSNull.null forKey:@"self"]; [optManaged.boolObj setValue:NSNull.null forKey:@"self"]; [optManaged.intObj setValue:NSNull.null forKey:@"self"]; [optManaged.floatObj setValue:NSNull.null forKey:@"self"]; [optManaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optManaged.stringObj setValue:NSNull.null forKey:@"self"]; [optManaged.dataObj setValue:NSNull.null forKey:@"self"]; [optManaged.dateObj setValue:NSNull.null forKey:@"self"]; [optManaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optManaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optManaged.uuidObj setValue:NSNull.null forKey:@"self"]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[0], NSNull.null); } - (void)testAssignment { unmanaged.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged.boolObj[0], @YES); unmanaged.intObj = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged.intObj[0], @3); unmanaged.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged.floatObj[0], @3.3f); unmanaged.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged.doubleObj[0], @3.3); unmanaged.stringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(unmanaged.stringObj[0], @"b"); unmanaged.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged.dataObj[0], data(2)); unmanaged.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged.dateObj[0], date(2)); unmanaged.decimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(unmanaged.decimalObj[0], decimal128(3)); unmanaged.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged.objectIdObj[0], objectId(2)); unmanaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); unmanaged.anyBoolObj = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged.anyBoolObj[0], @YES); unmanaged.anyIntObj = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged.anyIntObj[0], @3); unmanaged.anyFloatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged.anyFloatObj[0], @3.3f); unmanaged.anyDoubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[0], @3.3); unmanaged.anyStringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(unmanaged.anyStringObj[0], @"b"); unmanaged.anyDataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged.anyDataObj[0], data(2)); unmanaged.anyDateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged.anyDateObj[0], date(2)); unmanaged.anyDecimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[0], decimal128(3)); unmanaged.anyObjectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[0], objectId(2)); unmanaged.anyUUIDObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optUnmanaged.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[0], @YES); optUnmanaged.intObj = (id)@[@3]; uncheckedAssertEqualObjects(optUnmanaged.intObj[0], @3); optUnmanaged.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(optUnmanaged.floatObj[0], @3.3f); optUnmanaged.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(optUnmanaged.doubleObj[0], @3.3); optUnmanaged.stringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(optUnmanaged.stringObj[0], @"b"); optUnmanaged.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(optUnmanaged.dataObj[0], data(2)); optUnmanaged.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(optUnmanaged.dateObj[0], date(2)); optUnmanaged.decimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(optUnmanaged.decimalObj[0], decimal128(3)); optUnmanaged.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[0], objectId(2)); optUnmanaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optUnmanaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(managed.boolObj[0], @YES); managed.intObj = (id)@[@3]; uncheckedAssertEqualObjects(managed.intObj[0], @3); managed.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed.floatObj[0], @3.3f); managed.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(managed.doubleObj[0], @3.3); managed.stringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(managed.stringObj[0], @"b"); managed.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(managed.dataObj[0], data(2)); managed.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(managed.dateObj[0], date(2)); managed.decimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(managed.decimalObj[0], decimal128(3)); managed.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed.objectIdObj[0], objectId(2)); managed.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed.anyBoolObj = (id)@[@YES]; uncheckedAssertEqualObjects(managed.anyBoolObj[0], @YES); managed.anyIntObj = (id)@[@3]; uncheckedAssertEqualObjects(managed.anyIntObj[0], @3); managed.anyFloatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed.anyFloatObj[0], @3.3f); managed.anyDoubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(managed.anyDoubleObj[0], @3.3); managed.anyStringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(managed.anyStringObj[0], @"b"); managed.anyDataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(managed.anyDataObj[0], data(2)); managed.anyDateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(managed.anyDateObj[0], date(2)); managed.anyDecimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(managed.anyDecimalObj[0], decimal128(3)); managed.anyObjectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed.anyObjectIdObj[0], objectId(2)); managed.anyUUIDObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed.anyUUIDObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optManaged.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(optManaged.boolObj[0], @YES); optManaged.intObj = (id)@[@3]; uncheckedAssertEqualObjects(optManaged.intObj[0], @3); optManaged.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(optManaged.floatObj[0], @3.3f); optManaged.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(optManaged.doubleObj[0], @3.3); optManaged.stringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(optManaged.stringObj[0], @"b"); optManaged.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(optManaged.dataObj[0], data(2)); optManaged.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(optManaged.dateObj[0], date(2)); optManaged.decimalObj = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(optManaged.decimalObj[0], decimal128(3)); optManaged.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(optManaged.objectIdObj[0], objectId(2)); optManaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optManaged.uuidObj[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); // Should replace and not append unmanaged.boolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([unmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES])); unmanaged.intObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @3])); unmanaged.floatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([unmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged.doubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged.stringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b"])); unmanaged.dataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([unmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2)])); unmanaged.dateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([unmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2)])); unmanaged.decimalObj = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged.objectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); unmanaged.anyBoolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); unmanaged.anyIntObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKey:@"self"], (@[@2, @3])); unmanaged.anyFloatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged.anyDoubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged.anyStringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); unmanaged.anyDataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); unmanaged.anyDateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); unmanaged.anyDecimalObj = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged.anyObjectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged.anyUUIDObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optUnmanaged.boolObj = (id)@[@NO, @YES, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optUnmanaged.intObj = (id)@[@2, @3, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); optUnmanaged.floatObj = (id)@[@2.2f, @3.3f, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optUnmanaged.doubleObj = (id)@[@2.2, @3.3, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optUnmanaged.stringObj = (id)@[@"a", @"b", NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optUnmanaged.dataObj = (id)@[data(1), data(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optUnmanaged.dateObj = (id)@[date(1), date(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optUnmanaged.decimalObj = (id)@[decimal128(2), decimal128(3), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optUnmanaged.objectIdObj = (id)@[objectId(1), objectId(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optUnmanaged.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); managed.boolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([managed.boolObj valueForKey:@"self"], (@[@NO, @YES])); managed.intObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @3])); managed.floatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([managed.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); managed.doubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([managed.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); managed.stringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"self"], (@[@"a", @"b"])); managed.dataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([managed.dataObj valueForKey:@"self"], (@[data(1), data(2)])); managed.dateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([managed.dateObj valueForKey:@"self"], (@[date(1), date(2)])); managed.decimalObj = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([managed.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed.objectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([managed.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([managed.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); managed.anyBoolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([managed.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); managed.anyIntObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([managed.anyIntObj valueForKey:@"self"], (@[@2, @3])); managed.anyFloatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([managed.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); managed.anyDoubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); managed.anyStringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); managed.anyDataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([managed.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); managed.anyDateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([managed.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); managed.anyDecimalObj = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed.anyObjectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed.anyUUIDObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optManaged.boolObj = (id)@[@NO, @YES, NSNull.null]; uncheckedAssertEqualObjects([optManaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optManaged.intObj = (id)@[@2, @3, NSNull.null]; uncheckedAssertEqualObjects([optManaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); optManaged.floatObj = (id)@[@2.2f, @3.3f, NSNull.null]; uncheckedAssertEqualObjects([optManaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optManaged.doubleObj = (id)@[@2.2, @3.3, NSNull.null]; uncheckedAssertEqualObjects([optManaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optManaged.stringObj = (id)@[@"a", @"b", NSNull.null]; uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optManaged.dataObj = (id)@[data(1), data(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optManaged.dateObj = (id)@[date(1), date(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optManaged.decimalObj = (id)@[decimal128(2), decimal128(3), NSNull.null]; uncheckedAssertEqualObjects([optManaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optManaged.objectIdObj = (id)@[objectId(1), objectId(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optManaged.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; uncheckedAssertEqualObjects([optManaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); // Should not clear the array unmanaged.boolObj = unmanaged.boolObj; uncheckedAssertEqualObjects([unmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES])); unmanaged.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @3])); unmanaged.floatObj = unmanaged.floatObj; uncheckedAssertEqualObjects([unmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged.doubleObj = unmanaged.doubleObj; uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged.stringObj = unmanaged.stringObj; uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b"])); unmanaged.dataObj = unmanaged.dataObj; uncheckedAssertEqualObjects([unmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2)])); unmanaged.dateObj = unmanaged.dateObj; uncheckedAssertEqualObjects([unmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2)])); unmanaged.decimalObj = unmanaged.decimalObj; uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged.objectIdObj = unmanaged.objectIdObj; uncheckedAssertEqualObjects([unmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged.uuidObj = unmanaged.uuidObj; uncheckedAssertEqualObjects([unmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); unmanaged.anyBoolObj = unmanaged.anyBoolObj; uncheckedAssertEqualObjects([unmanaged.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); unmanaged.anyIntObj = unmanaged.anyIntObj; uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKey:@"self"], (@[@2, @3])); unmanaged.anyFloatObj = unmanaged.anyFloatObj; uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged.anyDoubleObj = unmanaged.anyDoubleObj; uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged.anyStringObj = unmanaged.anyStringObj; uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); unmanaged.anyDataObj = unmanaged.anyDataObj; uncheckedAssertEqualObjects([unmanaged.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); unmanaged.anyDateObj = unmanaged.anyDateObj; uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); unmanaged.anyDecimalObj = unmanaged.anyDecimalObj; uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged.anyObjectIdObj = unmanaged.anyObjectIdObj; uncheckedAssertEqualObjects([unmanaged.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged.anyUUIDObj = unmanaged.anyUUIDObj; uncheckedAssertEqualObjects([unmanaged.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optUnmanaged.boolObj = optUnmanaged.boolObj; uncheckedAssertEqualObjects([optUnmanaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optUnmanaged.intObj = optUnmanaged.intObj; uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); optUnmanaged.floatObj = optUnmanaged.floatObj; uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optUnmanaged.doubleObj = optUnmanaged.doubleObj; uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optUnmanaged.stringObj = optUnmanaged.stringObj; uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optUnmanaged.dataObj = optUnmanaged.dataObj; uncheckedAssertEqualObjects([optUnmanaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optUnmanaged.dateObj = optUnmanaged.dateObj; uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optUnmanaged.decimalObj = optUnmanaged.decimalObj; uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optUnmanaged.objectIdObj = optUnmanaged.objectIdObj; uncheckedAssertEqualObjects([optUnmanaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optUnmanaged.uuidObj = optUnmanaged.uuidObj; uncheckedAssertEqualObjects([optUnmanaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); managed.boolObj = managed.boolObj; uncheckedAssertEqualObjects([managed.boolObj valueForKey:@"self"], (@[@NO, @YES])); managed.intObj = managed.intObj; uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @3])); managed.floatObj = managed.floatObj; uncheckedAssertEqualObjects([managed.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); managed.doubleObj = managed.doubleObj; uncheckedAssertEqualObjects([managed.doubleObj valueForKey:@"self"], (@[@2.2, @3.3])); managed.stringObj = managed.stringObj; uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"self"], (@[@"a", @"b"])); managed.dataObj = managed.dataObj; uncheckedAssertEqualObjects([managed.dataObj valueForKey:@"self"], (@[data(1), data(2)])); managed.dateObj = managed.dateObj; uncheckedAssertEqualObjects([managed.dateObj valueForKey:@"self"], (@[date(1), date(2)])); managed.decimalObj = managed.decimalObj; uncheckedAssertEqualObjects([managed.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed.objectIdObj = managed.objectIdObj; uncheckedAssertEqualObjects([managed.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed.uuidObj = managed.uuidObj; uncheckedAssertEqualObjects([managed.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); managed.anyBoolObj = managed.anyBoolObj; uncheckedAssertEqualObjects([managed.anyBoolObj valueForKey:@"self"], (@[@NO, @YES])); managed.anyIntObj = managed.anyIntObj; uncheckedAssertEqualObjects([managed.anyIntObj valueForKey:@"self"], (@[@2, @3])); managed.anyFloatObj = managed.anyFloatObj; uncheckedAssertEqualObjects([managed.anyFloatObj valueForKey:@"self"], (@[@2.2f, @3.3f])); managed.anyDoubleObj = managed.anyDoubleObj; uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKey:@"self"], (@[@2.2, @3.3])); managed.anyStringObj = managed.anyStringObj; uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"self"], (@[@"a", @"b"])); managed.anyDataObj = managed.anyDataObj; uncheckedAssertEqualObjects([managed.anyDataObj valueForKey:@"self"], (@[data(1), data(2)])); managed.anyDateObj = managed.anyDateObj; uncheckedAssertEqualObjects([managed.anyDateObj valueForKey:@"self"], (@[date(1), date(2)])); managed.anyDecimalObj = managed.anyDecimalObj; uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed.anyObjectIdObj = managed.anyObjectIdObj; uncheckedAssertEqualObjects([managed.anyObjectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed.anyUUIDObj = managed.anyUUIDObj; uncheckedAssertEqualObjects([managed.anyUUIDObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optManaged.boolObj = optManaged.boolObj; uncheckedAssertEqualObjects([optManaged.boolObj valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optManaged.intObj = optManaged.intObj; uncheckedAssertEqualObjects([optManaged.intObj valueForKey:@"self"], (@[@2, @3, NSNull.null])); optManaged.floatObj = optManaged.floatObj; uncheckedAssertEqualObjects([optManaged.floatObj valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optManaged.doubleObj = optManaged.doubleObj; uncheckedAssertEqualObjects([optManaged.doubleObj valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optManaged.stringObj = optManaged.stringObj; uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optManaged.dataObj = optManaged.dataObj; uncheckedAssertEqualObjects([optManaged.dataObj valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optManaged.dateObj = optManaged.dateObj; uncheckedAssertEqualObjects([optManaged.dateObj valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optManaged.decimalObj = optManaged.decimalObj; uncheckedAssertEqualObjects([optManaged.decimalObj valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optManaged.objectIdObj = optManaged.objectIdObj; uncheckedAssertEqualObjects([optManaged.objectIdObj valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optManaged.uuidObj = optManaged.uuidObj; uncheckedAssertEqualObjects([optManaged.uuidObj valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @3])); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @3])); } - (void)testDynamicAssignment { unmanaged[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged[@"boolObj"][0], @YES); unmanaged[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged[@"intObj"][0], @3); unmanaged[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged[@"floatObj"][0], @3.3f); unmanaged[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged[@"doubleObj"][0], @3.3); unmanaged[@"stringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(unmanaged[@"stringObj"][0], @"b"); unmanaged[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged[@"dataObj"][0], data(2)); unmanaged[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged[@"dateObj"][0], date(2)); unmanaged[@"decimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(unmanaged[@"decimalObj"][0], decimal128(3)); unmanaged[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged[@"objectIdObj"][0], objectId(2)); unmanaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged[@"uuidObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); unmanaged[@"anyBoolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged[@"anyBoolObj"][0], @YES); unmanaged[@"anyIntObj"] = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged[@"anyIntObj"][0], @3); unmanaged[@"anyFloatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged[@"anyFloatObj"][0], @3.3f); unmanaged[@"anyDoubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged[@"anyDoubleObj"][0], @3.3); unmanaged[@"anyStringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(unmanaged[@"anyStringObj"][0], @"b"); unmanaged[@"anyDataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged[@"anyDataObj"][0], data(2)); unmanaged[@"anyDateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged[@"anyDateObj"][0], date(2)); unmanaged[@"anyDecimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(unmanaged[@"anyDecimalObj"][0], decimal128(3)); unmanaged[@"anyObjectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged[@"anyObjectIdObj"][0], objectId(2)); unmanaged[@"anyUUIDObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged[@"anyUUIDObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optUnmanaged[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(optUnmanaged[@"boolObj"][0], @YES); optUnmanaged[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(optUnmanaged[@"intObj"][0], @3); optUnmanaged[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(optUnmanaged[@"floatObj"][0], @3.3f); optUnmanaged[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(optUnmanaged[@"doubleObj"][0], @3.3); optUnmanaged[@"stringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(optUnmanaged[@"stringObj"][0], @"b"); optUnmanaged[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(optUnmanaged[@"dataObj"][0], data(2)); optUnmanaged[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(optUnmanaged[@"dateObj"][0], date(2)); optUnmanaged[@"decimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(optUnmanaged[@"decimalObj"][0], decimal128(3)); optUnmanaged[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(optUnmanaged[@"objectIdObj"][0], objectId(2)); optUnmanaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optUnmanaged[@"uuidObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(managed[@"boolObj"][0], @YES); managed[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(managed[@"intObj"][0], @3); managed[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed[@"floatObj"][0], @3.3f); managed[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(managed[@"doubleObj"][0], @3.3); managed[@"stringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(managed[@"stringObj"][0], @"b"); managed[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(managed[@"dataObj"][0], data(2)); managed[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(managed[@"dateObj"][0], date(2)); managed[@"decimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(managed[@"decimalObj"][0], decimal128(3)); managed[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed[@"objectIdObj"][0], objectId(2)); managed[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed[@"uuidObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed[@"anyBoolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(managed[@"anyBoolObj"][0], @YES); managed[@"anyIntObj"] = (id)@[@3]; uncheckedAssertEqualObjects(managed[@"anyIntObj"][0], @3); managed[@"anyFloatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed[@"anyFloatObj"][0], @3.3f); managed[@"anyDoubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(managed[@"anyDoubleObj"][0], @3.3); managed[@"anyStringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(managed[@"anyStringObj"][0], @"b"); managed[@"anyDataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(managed[@"anyDataObj"][0], data(2)); managed[@"anyDateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(managed[@"anyDateObj"][0], date(2)); managed[@"anyDecimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(managed[@"anyDecimalObj"][0], decimal128(3)); managed[@"anyObjectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed[@"anyObjectIdObj"][0], objectId(2)); managed[@"anyUUIDObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed[@"anyUUIDObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optManaged[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(optManaged[@"boolObj"][0], @YES); optManaged[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(optManaged[@"intObj"][0], @3); optManaged[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(optManaged[@"floatObj"][0], @3.3f); optManaged[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(optManaged[@"doubleObj"][0], @3.3); optManaged[@"stringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(optManaged[@"stringObj"][0], @"b"); optManaged[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(optManaged[@"dataObj"][0], data(2)); optManaged[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(optManaged[@"dateObj"][0], date(2)); optManaged[@"decimalObj"] = (id)@[decimal128(3)]; uncheckedAssertEqualObjects(optManaged[@"decimalObj"][0], decimal128(3)); optManaged[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(optManaged[@"objectIdObj"][0], objectId(2)); optManaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optManaged[@"uuidObj"][0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); // Should replace and not append unmanaged[@"boolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([unmanaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES])); unmanaged[@"intObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([unmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3])); unmanaged[@"floatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([unmanaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged[@"doubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([unmanaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged[@"stringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([unmanaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b"])); unmanaged[@"dataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([unmanaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2)])); unmanaged[@"dateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([unmanaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2)])); unmanaged[@"decimalObj"] = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([unmanaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged[@"objectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([unmanaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([unmanaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); unmanaged[@"anyBoolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([unmanaged[@"anyBoolObj"] valueForKey:@"self"], (@[@NO, @YES])); unmanaged[@"anyIntObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([unmanaged[@"anyIntObj"] valueForKey:@"self"], (@[@2, @3])); unmanaged[@"anyFloatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([unmanaged[@"anyFloatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged[@"anyDoubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([unmanaged[@"anyDoubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged[@"anyStringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([unmanaged[@"anyStringObj"] valueForKey:@"self"], (@[@"a", @"b"])); unmanaged[@"anyDataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([unmanaged[@"anyDataObj"] valueForKey:@"self"], (@[data(1), data(2)])); unmanaged[@"anyDateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([unmanaged[@"anyDateObj"] valueForKey:@"self"], (@[date(1), date(2)])); unmanaged[@"anyDecimalObj"] = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([unmanaged[@"anyDecimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged[@"anyObjectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([unmanaged[@"anyObjectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged[@"anyUUIDObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([unmanaged[@"anyUUIDObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optUnmanaged[@"boolObj"] = (id)@[@NO, @YES, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optUnmanaged[@"intObj"] = (id)@[@2, @3, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3, NSNull.null])); optUnmanaged[@"floatObj"] = (id)@[@2.2f, @3.3f, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optUnmanaged[@"doubleObj"] = (id)@[@2.2, @3.3, NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optUnmanaged[@"stringObj"] = (id)@[@"a", @"b", NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optUnmanaged[@"dataObj"] = (id)@[data(1), data(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optUnmanaged[@"dateObj"] = (id)@[date(1), date(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optUnmanaged[@"decimalObj"] = (id)@[decimal128(2), decimal128(3), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optUnmanaged[@"objectIdObj"] = (id)@[objectId(1), objectId(2), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optUnmanaged[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; uncheckedAssertEqualObjects([optUnmanaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); managed[@"boolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([managed[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES])); managed[@"intObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([managed[@"intObj"] valueForKey:@"self"], (@[@2, @3])); managed[@"floatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([managed[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); managed[@"doubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([managed[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); managed[@"stringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([managed[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b"])); managed[@"dataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([managed[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2)])); managed[@"dateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([managed[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2)])); managed[@"decimalObj"] = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([managed[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed[@"objectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([managed[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([managed[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); managed[@"anyBoolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([managed[@"anyBoolObj"] valueForKey:@"self"], (@[@NO, @YES])); managed[@"anyIntObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([managed[@"anyIntObj"] valueForKey:@"self"], (@[@2, @3])); managed[@"anyFloatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([managed[@"anyFloatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); managed[@"anyDoubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([managed[@"anyDoubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); managed[@"anyStringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([managed[@"anyStringObj"] valueForKey:@"self"], (@[@"a", @"b"])); managed[@"anyDataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([managed[@"anyDataObj"] valueForKey:@"self"], (@[data(1), data(2)])); managed[@"anyDateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([managed[@"anyDateObj"] valueForKey:@"self"], (@[date(1), date(2)])); managed[@"anyDecimalObj"] = (id)@[decimal128(2), decimal128(3)]; uncheckedAssertEqualObjects([managed[@"anyDecimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed[@"anyObjectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([managed[@"anyObjectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed[@"anyUUIDObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([managed[@"anyUUIDObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optManaged[@"boolObj"] = (id)@[@NO, @YES, NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optManaged[@"intObj"] = (id)@[@2, @3, NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"intObj"] valueForKey:@"self"], (@[@2, @3, NSNull.null])); optManaged[@"floatObj"] = (id)@[@2.2f, @3.3f, NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optManaged[@"doubleObj"] = (id)@[@2.2, @3.3, NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optManaged[@"stringObj"] = (id)@[@"a", @"b", NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optManaged[@"dataObj"] = (id)@[data(1), data(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optManaged[@"dateObj"] = (id)@[date(1), date(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optManaged[@"decimalObj"] = (id)@[decimal128(2), decimal128(3), NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optManaged[@"objectIdObj"] = (id)@[objectId(1), objectId(2), NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optManaged[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]; uncheckedAssertEqualObjects([optManaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); // Should not clear the array unmanaged[@"boolObj"] = unmanaged[@"boolObj"]; uncheckedAssertEqualObjects([unmanaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES])); unmanaged[@"intObj"] = unmanaged[@"intObj"]; uncheckedAssertEqualObjects([unmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3])); unmanaged[@"floatObj"] = unmanaged[@"floatObj"]; uncheckedAssertEqualObjects([unmanaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged[@"doubleObj"] = unmanaged[@"doubleObj"]; uncheckedAssertEqualObjects([unmanaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged[@"stringObj"] = unmanaged[@"stringObj"]; uncheckedAssertEqualObjects([unmanaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b"])); unmanaged[@"dataObj"] = unmanaged[@"dataObj"]; uncheckedAssertEqualObjects([unmanaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2)])); unmanaged[@"dateObj"] = unmanaged[@"dateObj"]; uncheckedAssertEqualObjects([unmanaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2)])); unmanaged[@"decimalObj"] = unmanaged[@"decimalObj"]; uncheckedAssertEqualObjects([unmanaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged[@"objectIdObj"] = unmanaged[@"objectIdObj"]; uncheckedAssertEqualObjects([unmanaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged[@"uuidObj"] = unmanaged[@"uuidObj"]; uncheckedAssertEqualObjects([unmanaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); unmanaged[@"anyBoolObj"] = unmanaged[@"anyBoolObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyBoolObj"] valueForKey:@"self"], (@[@NO, @YES])); unmanaged[@"anyIntObj"] = unmanaged[@"anyIntObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyIntObj"] valueForKey:@"self"], (@[@2, @3])); unmanaged[@"anyFloatObj"] = unmanaged[@"anyFloatObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyFloatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); unmanaged[@"anyDoubleObj"] = unmanaged[@"anyDoubleObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyDoubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); unmanaged[@"anyStringObj"] = unmanaged[@"anyStringObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyStringObj"] valueForKey:@"self"], (@[@"a", @"b"])); unmanaged[@"anyDataObj"] = unmanaged[@"anyDataObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyDataObj"] valueForKey:@"self"], (@[data(1), data(2)])); unmanaged[@"anyDateObj"] = unmanaged[@"anyDateObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyDateObj"] valueForKey:@"self"], (@[date(1), date(2)])); unmanaged[@"anyDecimalObj"] = unmanaged[@"anyDecimalObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyDecimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); unmanaged[@"anyObjectIdObj"] = unmanaged[@"anyObjectIdObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyObjectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); unmanaged[@"anyUUIDObj"] = unmanaged[@"anyUUIDObj"]; uncheckedAssertEqualObjects([unmanaged[@"anyUUIDObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optUnmanaged[@"boolObj"] = optUnmanaged[@"boolObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optUnmanaged[@"intObj"] = optUnmanaged[@"intObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3, NSNull.null])); optUnmanaged[@"floatObj"] = optUnmanaged[@"floatObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optUnmanaged[@"doubleObj"] = optUnmanaged[@"doubleObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optUnmanaged[@"stringObj"] = optUnmanaged[@"stringObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optUnmanaged[@"dataObj"] = optUnmanaged[@"dataObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optUnmanaged[@"dateObj"] = optUnmanaged[@"dateObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optUnmanaged[@"decimalObj"] = optUnmanaged[@"decimalObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optUnmanaged[@"objectIdObj"] = optUnmanaged[@"objectIdObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optUnmanaged[@"uuidObj"] = optUnmanaged[@"uuidObj"]; uncheckedAssertEqualObjects([optUnmanaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); managed[@"boolObj"] = managed[@"boolObj"]; uncheckedAssertEqualObjects([managed[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES])); managed[@"intObj"] = managed[@"intObj"]; uncheckedAssertEqualObjects([managed[@"intObj"] valueForKey:@"self"], (@[@2, @3])); managed[@"floatObj"] = managed[@"floatObj"]; uncheckedAssertEqualObjects([managed[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); managed[@"doubleObj"] = managed[@"doubleObj"]; uncheckedAssertEqualObjects([managed[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); managed[@"stringObj"] = managed[@"stringObj"]; uncheckedAssertEqualObjects([managed[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b"])); managed[@"dataObj"] = managed[@"dataObj"]; uncheckedAssertEqualObjects([managed[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2)])); managed[@"dateObj"] = managed[@"dateObj"]; uncheckedAssertEqualObjects([managed[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2)])); managed[@"decimalObj"] = managed[@"decimalObj"]; uncheckedAssertEqualObjects([managed[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed[@"objectIdObj"] = managed[@"objectIdObj"]; uncheckedAssertEqualObjects([managed[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed[@"uuidObj"] = managed[@"uuidObj"]; uncheckedAssertEqualObjects([managed[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); managed[@"anyBoolObj"] = managed[@"anyBoolObj"]; uncheckedAssertEqualObjects([managed[@"anyBoolObj"] valueForKey:@"self"], (@[@NO, @YES])); managed[@"anyIntObj"] = managed[@"anyIntObj"]; uncheckedAssertEqualObjects([managed[@"anyIntObj"] valueForKey:@"self"], (@[@2, @3])); managed[@"anyFloatObj"] = managed[@"anyFloatObj"]; uncheckedAssertEqualObjects([managed[@"anyFloatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f])); managed[@"anyDoubleObj"] = managed[@"anyDoubleObj"]; uncheckedAssertEqualObjects([managed[@"anyDoubleObj"] valueForKey:@"self"], (@[@2.2, @3.3])); managed[@"anyStringObj"] = managed[@"anyStringObj"]; uncheckedAssertEqualObjects([managed[@"anyStringObj"] valueForKey:@"self"], (@[@"a", @"b"])); managed[@"anyDataObj"] = managed[@"anyDataObj"]; uncheckedAssertEqualObjects([managed[@"anyDataObj"] valueForKey:@"self"], (@[data(1), data(2)])); managed[@"anyDateObj"] = managed[@"anyDateObj"]; uncheckedAssertEqualObjects([managed[@"anyDateObj"] valueForKey:@"self"], (@[date(1), date(2)])); managed[@"anyDecimalObj"] = managed[@"anyDecimalObj"]; uncheckedAssertEqualObjects([managed[@"anyDecimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); managed[@"anyObjectIdObj"] = managed[@"anyObjectIdObj"]; uncheckedAssertEqualObjects([managed[@"anyObjectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2)])); managed[@"anyUUIDObj"] = managed[@"anyUUIDObj"]; uncheckedAssertEqualObjects([managed[@"anyUUIDObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); optManaged[@"boolObj"] = optManaged[@"boolObj"]; uncheckedAssertEqualObjects([optManaged[@"boolObj"] valueForKey:@"self"], (@[@NO, @YES, NSNull.null])); optManaged[@"intObj"] = optManaged[@"intObj"]; uncheckedAssertEqualObjects([optManaged[@"intObj"] valueForKey:@"self"], (@[@2, @3, NSNull.null])); optManaged[@"floatObj"] = optManaged[@"floatObj"]; uncheckedAssertEqualObjects([optManaged[@"floatObj"] valueForKey:@"self"], (@[@2.2f, @3.3f, NSNull.null])); optManaged[@"doubleObj"] = optManaged[@"doubleObj"]; uncheckedAssertEqualObjects([optManaged[@"doubleObj"] valueForKey:@"self"], (@[@2.2, @3.3, NSNull.null])); optManaged[@"stringObj"] = optManaged[@"stringObj"]; uncheckedAssertEqualObjects([optManaged[@"stringObj"] valueForKey:@"self"], (@[@"a", @"b", NSNull.null])); optManaged[@"dataObj"] = optManaged[@"dataObj"]; uncheckedAssertEqualObjects([optManaged[@"dataObj"] valueForKey:@"self"], (@[data(1), data(2), NSNull.null])); optManaged[@"dateObj"] = optManaged[@"dateObj"]; uncheckedAssertEqualObjects([optManaged[@"dateObj"] valueForKey:@"self"], (@[date(1), date(2), NSNull.null])); optManaged[@"decimalObj"] = optManaged[@"decimalObj"]; uncheckedAssertEqualObjects([optManaged[@"decimalObj"] valueForKey:@"self"], (@[decimal128(2), decimal128(3), NSNull.null])); optManaged[@"objectIdObj"] = optManaged[@"objectIdObj"]; uncheckedAssertEqualObjects([optManaged[@"objectIdObj"] valueForKey:@"self"], (@[objectId(1), objectId(2), NSNull.null])); optManaged[@"uuidObj"] = optManaged[@"uuidObj"]; uncheckedAssertEqualObjects([optManaged[@"uuidObj"] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null])); [unmanaged[@"intObj"] removeAllObjects]; unmanaged[@"intObj"] = managed.intObj; uncheckedAssertEqualObjects([unmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3])); [managed[@"intObj"] removeAllObjects]; managed[@"intObj"] = unmanaged.intObj; uncheckedAssertEqualObjects([managed[@"intObj"] valueForKey:@"self"], (@[@2, @3])); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); } - (void)testAllMethodsCheckThread { RLMArray *array = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([array count], @"thread"); RLMAssertThrowsWithReason([array objectAtIndex:0], @"thread"); RLMAssertThrowsWithReason([array firstObject], @"thread"); RLMAssertThrowsWithReason([array lastObject], @"thread"); RLMAssertThrowsWithReason([array addObject:@0], @"thread"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"thread"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"thread"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"thread"); RLMAssertThrowsWithReason([array removeLastObject], @"thread"); RLMAssertThrowsWithReason([array removeAllObjects], @"thread"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"thread"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"thread"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"thread"); RLMAssertThrowsWithReason([array indexOfObject:@1], @"thread"); /* RLMAssertThrowsWithReason([array indexOfObjectWhere:@"TRUEPREDICATE"], @"thread"); */ /* RLMAssertThrowsWithReason([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]], @"thread"); */ /* RLMAssertThrowsWithReason([array objectsWhere:@"TRUEPREDICATE"], @"thread"); */ /* RLMAssertThrowsWithReason([array objectsWithPredicate:[NSPredicate predicateWithValue:NO]], @"thread"); */ RLMAssertThrowsWithReason([array sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(array[0], @"thread"); RLMAssertThrowsWithReason(array[0] = @0, @"thread"); RLMAssertThrowsWithReason([array valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in array);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMArray *array = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); RLMAssertThrowsWithReason([array count], @"invalidated"); RLMAssertThrowsWithReason([array objectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array firstObject], @"invalidated"); RLMAssertThrowsWithReason([array lastObject], @"invalidated"); RLMAssertThrowsWithReason([array addObject:@0], @"invalidated"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"invalidated"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array removeLastObject], @"invalidated"); RLMAssertThrowsWithReason([array removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"invalidated"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"invalidated"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"invalidated"); RLMAssertThrowsWithReason([array indexOfObject:@1], @"invalidated"); /* RLMAssertThrowsWithReason([array indexOfObjectWhere:@"TRUEPREDICATE"], @"invalidated"); */ /* RLMAssertThrowsWithReason([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"invalidated"); */ /* RLMAssertThrowsWithReason([array objectsWhere:@"TRUEPREDICATE"], @"invalidated"); */ /* RLMAssertThrowsWithReason([array objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"invalidated"); */ RLMAssertThrowsWithReason([array sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(array[0], @"invalidated"); RLMAssertThrowsWithReason(array[0] = @0, @"invalidated"); RLMAssertThrowsWithReason([array valueForKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in array);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMArray *array = managed.intObj; [array addObject:@0]; [realm commitWriteTransaction]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); XCTAssertNoThrow([array count]); XCTAssertNoThrow([array objectAtIndex:0]); XCTAssertNoThrow([array firstObject]); XCTAssertNoThrow([array lastObject]); XCTAssertNoThrow([array indexOfObject:@1]); /* XCTAssertNoThrow([array indexOfObjectWhere:@"TRUEPREDICATE"]); */ /* XCTAssertNoThrow([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); */ /* XCTAssertNoThrow([array objectsWhere:@"TRUEPREDICATE"]); */ /* XCTAssertNoThrow([array objectsWithPredicate:[NSPredicate predicateWithValue:YES]]); */ XCTAssertNoThrow([array sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(array[0]); XCTAssertNoThrow([array valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in array);})); RLMAssertThrowsWithReason([array addObject:@0], @"write transaction"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"write transaction"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"write transaction"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"write transaction"); RLMAssertThrowsWithReason([array removeLastObject], @"write transaction"); RLMAssertThrowsWithReason([array removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"write transaction"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"write transaction"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"write transaction"); RLMAssertThrowsWithReason(array[0] = @0, @"write transaction"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMArray *array = managed.intObj; uncheckedAssertFalse(array.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(array.isInvalidated); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else { uncheckedAssertEqualObjects(change.insertions, @[@0]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMArray *array = [(AllPrimitiveArrays *)[AllPrimitiveArrays allObjectsInRealm:r].firstObject intObj]; [array addObject:@0]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMArray *array, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveArrays createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMArray *array = [(AllPrimitiveArrays *)[AllPrimitiveArrays allObjectsInRealm:r].firstObject intObj]; [array addObject:@0]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { [managed.intObj addObjects:@[@10, @20]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); first = false; } else { uncheckedAssertEqualObjects(change.deletions, (@[@0, @1])); } [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:managed]; [realm commitWriteTransaction]; expectation = [self expectationWithDescription:@""]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObjectWithValueIndex:(NSUInteger)index { NSRange range = {index, 1}; id obj = [AllPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": [@[@NO, @YES] subarrayWithRange:range], @"intObj": [@[@2, @3] subarrayWithRange:range], @"floatObj": [@[@2.2f, @3.3f] subarrayWithRange:range], @"doubleObj": [@[@2.2, @3.3] subarrayWithRange:range], @"stringObj": [@[@"a", @"b"] subarrayWithRange:range], @"dataObj": [@[data(1), data(2)] subarrayWithRange:range], @"dateObj": [@[date(1), date(2)] subarrayWithRange:range], @"decimalObj": [@[decimal128(2), decimal128(3)] subarrayWithRange:range], @"objectIdObj": [@[objectId(1), objectId(2)] subarrayWithRange:range], @"uuidObj": [@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] subarrayWithRange:range], @"anyBoolObj": [@[@NO, @YES] subarrayWithRange:range], @"anyIntObj": [@[@2, @3] subarrayWithRange:range], @"anyFloatObj": [@[@2.2f, @3.3f] subarrayWithRange:range], @"anyDoubleObj": [@[@2.2, @3.3] subarrayWithRange:range], @"anyStringObj": [@[@"a", @"b"] subarrayWithRange:range], @"anyDataObj": [@[data(1), data(2)] subarrayWithRange:range], @"anyDateObj": [@[date(1), date(2)] subarrayWithRange:range], @"anyDecimalObj": [@[decimal128(2), decimal128(3)] subarrayWithRange:range], @"anyObjectIdObj": [@[objectId(1), objectId(2)] subarrayWithRange:range], @"anyUUIDObj": [@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] subarrayWithRange:range], }]; [LinkToAllPrimitiveArrays createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": [@[@NO, @YES, NSNull.null] subarrayWithRange:range], @"intObj": [@[@2, @3, NSNull.null] subarrayWithRange:range], @"floatObj": [@[@2.2f, @3.3f, NSNull.null] subarrayWithRange:range], @"doubleObj": [@[@2.2, @3.3, NSNull.null] subarrayWithRange:range], @"stringObj": [@[@"a", @"b", NSNull.null] subarrayWithRange:range], @"dataObj": [@[data(1), data(2), NSNull.null] subarrayWithRange:range], @"dateObj": [@[date(1), date(2), NSNull.null] subarrayWithRange:range], @"decimalObj": [@[decimal128(2), decimal128(3), NSNull.null] subarrayWithRange:range], @"objectIdObj": [@[objectId(1), objectId(2), NSNull.null] subarrayWithRange:range], @"uuidObj": [@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null] subarrayWithRange:range], }]; [LinkToAllOptionalPrimitiveArrays createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj <= %@", decimal128(2)); [self createObjectWithValueIndex:0]; RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj = %@", @YES); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj = %@", @3); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj = %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj = %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj = %@", @YES); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj = %@", @3); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj = %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj = %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj = %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj = %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj = %@", @YES); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj = %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj = %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj = %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj != %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj != %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj != %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj != %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj != %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj != %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj != %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj != %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj < %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj < %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj < %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj < %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj < %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj < %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj <= %@", decimal128(2)); [self createObjectWithValueIndex:1]; RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj = %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj = %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj = %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj = %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj = %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj = %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj = %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj = %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj = %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj = %@", @YES); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj = %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj = %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj = %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj != %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj != %@", @YES); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj != %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj != %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj != %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj != %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj != %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj != %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj != %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj > %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj > %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY decimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj < %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj < %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj < %@", @3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj < %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj < %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj < %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj < %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY intObj <= %@", @3); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY floatObj <= %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY doubleObj <= %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY stringObj <= %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY dataObj <= %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY dateObj <= %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY decimalObj <= %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyIntObj <= %@", @3); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyFloatObj <= %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDoubleObj <= %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyStringObj <= %@", @"b"); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDataObj <= %@", data(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDateObj <= %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 2, @"ANY anyDecimalObj <= %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY intObj <= %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY floatObj <= %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY doubleObj <= %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY stringObj <= %@", @"b"); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY dataObj <= %@", data(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY dateObj <= %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2, @"ANY decimalObj <= %@", decimal128(3)); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY objectIdObj > %@", objectId(1)]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY uuidObj > %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Operator '>' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY objectIdObj > %@", objectId(1)]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY uuidObj > %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Operator '>' not supported for type 'uuid'"); } - (void)testQueryBetween { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[@NO, @YES]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[@"a", @"b"]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY dataObj BETWEEN %@", @[data(1), data(2)]]), @"Operator 'BETWEEN' not supported for type 'data'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY objectIdObj BETWEEN %@", @[objectId(1), objectId(2)]]), @"Operator 'BETWEEN' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"ANY uuidObj BETWEEN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]), @"Operator 'BETWEEN' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[@NO, @YES]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[@"a", @"b"]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY dataObj BETWEEN %@", @[data(1), data(2)]]), @"Operator 'BETWEEN' not supported for type 'data'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY objectIdObj BETWEEN %@", @[objectId(1), objectId(2)]]), @"Operator 'BETWEEN' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY uuidObj BETWEEN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]), @"Operator 'BETWEEN' not supported for type 'uuid'"); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); [self createObjectWithValueIndex:0]; RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(3), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(3), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(3), decimal128(3)]); } - (void)testQueryIn { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); [self createObjectWithValueIndex:0]; RLMAssertCount(AllPrimitiveArrays, 0, @"ANY boolObj IN %@", @[@YES]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY intObj IN %@", @[@3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY floatObj IN %@", @[@3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY doubleObj IN %@", @[@3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY stringObj IN %@", @[@"b"]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dataObj IN %@", @[data(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY dateObj IN %@", @[date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY decimalObj IN %@", @[decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY objectIdObj IN %@", @[objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY uuidObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyBoolObj IN %@", @[@YES]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyIntObj IN %@", @[@3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyFloatObj IN %@", @[@3.3f]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDoubleObj IN %@", @[@3.3]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyStringObj IN %@", @[@"b"]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDataObj IN %@", @[data(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDateObj IN %@", @[date(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyDecimalObj IN %@", @[decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyObjectIdObj IN %@", @[objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY boolObj IN %@", @[@YES]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY intObj IN %@", @[@3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY floatObj IN %@", @[@3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY doubleObj IN %@", @[@3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY stringObj IN %@", @[@"b"]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dataObj IN %@", @[data(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY dateObj IN %@", @[date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY decimalObj IN %@", @[decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY objectIdObj IN %@", @[objectId(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 0, @"ANY uuidObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY stringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY decimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY uuidObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyDecimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyObjectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveArrays, 1, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY stringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY decimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllOptionalPrimitiveArrays, 1, @"ANY uuidObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[], @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"stringObj": @[], @"dataObj": @[], @"dateObj": @[], @"decimalObj": @[], @"objectIdObj": @[], @"uuidObj": @[], @"anyBoolObj": @[], @"anyIntObj": @[], @"anyFloatObj": @[], @"anyDoubleObj": @[], @"anyStringObj": @[], @"anyDataObj": @[], @"anyDateObj": @[], @"anyDecimalObj": @[], @"anyObjectIdObj": @[], @"anyUUIDObj": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[], @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"stringObj": @[], @"dataObj": @[], @"dateObj": @[], @"decimalObj": @[], @"objectIdObj": @[], @"uuidObj": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[@NO], @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"stringObj": @[@"a"], @"dataObj": @[data(1)], @"dateObj": @[date(1)], @"decimalObj": @[decimal128(2)], @"objectIdObj": @[objectId(1)], @"uuidObj": @[uuid(@"00000000-0000-0000-0000-000000000000")], @"anyBoolObj": @[@NO], @"anyIntObj": @[@2], @"anyFloatObj": @[@2.2f], @"anyDoubleObj": @[@2.2], @"anyStringObj": @[@"a"], @"anyDataObj": @[data(1)], @"anyDateObj": @[date(1)], @"anyDecimalObj": @[decimal128(2)], @"anyObjectIdObj": @[objectId(1)], @"anyUUIDObj": @[uuid(@"00000000-0000-0000-0000-000000000000")], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[@NO], @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"stringObj": @[@"a"], @"dataObj": @[data(1)], @"dateObj": @[date(1)], @"decimalObj": @[decimal128(2)], @"objectIdObj": @[objectId(1)], @"uuidObj": @[uuid(@"00000000-0000-0000-0000-000000000000")], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[@NO, @NO], @"intObj": @[@2, @2], @"floatObj": @[@2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2], @"stringObj": @[@"a", @"a"], @"dataObj": @[data(1), data(1)], @"dateObj": @[date(1), date(1)], @"decimalObj": @[decimal128(2), decimal128(2)], @"objectIdObj": @[objectId(1), objectId(1)], @"uuidObj": @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000")], @"anyBoolObj": @[@NO, @NO], @"anyIntObj": @[@2, @2], @"anyFloatObj": @[@2.2f, @2.2f], @"anyDoubleObj": @[@2.2, @2.2], @"anyStringObj": @[@"a", @"a"], @"anyDataObj": @[data(1), data(1)], @"anyDateObj": @[date(1), date(1)], @"anyDecimalObj": @[decimal128(2), decimal128(2)], @"anyObjectIdObj": @[objectId(1), objectId(1)], @"anyUUIDObj": @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000")], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"boolObj": @[@NO, @NO], @"intObj": @[@2, @2], @"floatObj": @[@2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2], @"stringObj": @[@"a", @"a"], @"dataObj": @[data(1), data(1)], @"dateObj": @[date(1), date(1)], @"decimalObj": @[decimal128(2), decimal128(2)], @"objectIdObj": @[objectId(1), objectId(1)], @"uuidObj": @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000")], }]; for (unsigned int i = 0; i < 3; ++i) { RLMAssertCount(AllPrimitiveArrays, 1U, @"boolObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"stringObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"dataObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"objectIdObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"uuidObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyBoolObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyStringObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDataObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyObjectIdObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyUUIDObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"boolObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"stringObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dataObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"objectIdObj.@count == %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"uuidObj.@count == %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"boolObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"stringObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"dataObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"dateObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"objectIdObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"uuidObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyBoolObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyIntObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyFloatObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDoubleObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyStringObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDataObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDateObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDecimalObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyObjectIdObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyUUIDObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"boolObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"stringObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"dataObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"dateObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"objectIdObj.@count != %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"uuidObj.@count != %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"boolObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"intObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"floatObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"doubleObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"stringObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"dataObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"dateObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"decimalObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"objectIdObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"uuidObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyBoolObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyIntObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyFloatObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyDoubleObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyStringObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyDataObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyDateObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyDecimalObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyObjectIdObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 2 - i, @"anyUUIDObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"boolObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"intObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"floatObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"doubleObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"stringObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"dataObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"dateObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"decimalObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"objectIdObj.@count > %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 2 - i, @"uuidObj.@count > %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"boolObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"intObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"floatObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"doubleObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"stringObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"dataObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"dateObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"decimalObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"objectIdObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"uuidObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyBoolObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyIntObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyFloatObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyDoubleObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyStringObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyDataObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyDateObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyDecimalObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyObjectIdObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, 3 - i, @"anyUUIDObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"boolObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"intObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"floatObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"doubleObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"stringObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"dataObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"dateObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"decimalObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"objectIdObj.@count >= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, 3 - i, @"uuidObj.@count >= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"boolObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"intObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"floatObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"doubleObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"stringObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"dataObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"dateObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"decimalObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"objectIdObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"uuidObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyBoolObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyIntObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyFloatObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyDoubleObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyStringObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyDataObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyDateObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyDecimalObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyObjectIdObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i, @"anyUUIDObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"boolObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"intObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"floatObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"doubleObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"stringObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"dataObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"dateObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"decimalObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"objectIdObj.@count < %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i, @"uuidObj.@count < %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"boolObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"intObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"floatObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"doubleObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"stringObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"dataObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"dateObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"decimalObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"objectIdObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"uuidObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyBoolObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyIntObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyFloatObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyDoubleObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyStringObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyDataObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyDateObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyDecimalObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyObjectIdObj.@count <= %@", @(i)); RLMAssertCount(AllPrimitiveArrays, i + 1, @"anyUUIDObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"boolObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"intObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"floatObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"doubleObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"stringObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"dataObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"dateObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"decimalObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"objectIdObj.@count <= %@", @(i)); RLMAssertCount(AllOptionalPrimitiveArrays, i + 1, @"uuidObj.@count <= %@", @(i)); } } - (void)testQuerySum { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"boolObj.@sum = %@", @NO]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"stringObj.@sum = %@", @"a"]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dataObj.@sum = %@", data(1)]), @"Invalid keypath 'dataObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@sum = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@sum = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"boolObj.@sum = %@", @NO]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"stringObj.@sum = %@", @"a"]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dataObj.@sum = %@", data(1)]), @"Invalid keypath 'dataObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@sum = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@sum = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@sum = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@sum = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum = %@", @"a"]), @"@sum on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum = %@", @"a"]), @"@sum on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum = %@", @"a"]), @"@sum on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum = %@", @"a"]), @"@sum on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum = %@", @"a"]), @"@sum on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum = %@", @"a"]), @"@sum on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum = %@", NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum = %@", NSNull.null]), @"@sum on a property of type float cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum = %@", NSNull.null]), @"@sum on a property of type double cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum = %@", NSNull.null]), @"@sum on a property of type decimal128 cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@sum = %@", NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@sum = %@", NSNull.null]), @"@sum on a property of type float cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@sum = %@", NSNull.null]), @"@sum on a property of type double cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@sum = %@", NSNull.null]), @"@sum on a property of type decimal128 cannot be compared with ''"); [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(2)], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @2], @"floatObj": @[@2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2], @"decimalObj": @[decimal128(2), decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @2], @"floatObj": @[@2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2], @"decimalObj": @[decimal128(2), decimal128(2)], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @2, @2], @"floatObj": @[@2.2f, @2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2, @2.2], @"decimalObj": @[decimal128(2), decimal128(2), decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @2, @2], @"floatObj": @[@2.2f, @2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2, @2.2], @"decimalObj": @[decimal128(2), decimal128(2), decimal128(2)], }]; RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@sum == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@sum == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@sum == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@sum == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@sum == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@sum == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@sum == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 3U, @"intObj.@sum != %@", @2); RLMAssertCount(AllPrimitiveArrays, 3U, @"floatObj.@sum != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 3U, @"doubleObj.@sum != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 3U, @"decimalObj.@sum != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"intObj.@sum != %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"floatObj.@sum != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"doubleObj.@sum != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"decimalObj.@sum != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 3U, @"floatObj.@sum >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 3U, @"doubleObj.@sum >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 3U, @"decimalObj.@sum >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"floatObj.@sum >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"doubleObj.@sum >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"decimalObj.@sum >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@sum > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@sum > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@sum > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@sum > %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@sum > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@sum > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@sum > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@sum < %@", @3); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@sum < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@sum < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@sum < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@sum < %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@sum < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@sum < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@sum < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@sum <= %@", @3); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@sum <= %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@sum <= %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@sum <= %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@sum <= %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@sum <= %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@sum <= %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@sum <= %@", decimal128(3)); } - (void)testQueryAverage { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"boolObj.@avg = %@", @NO]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"stringObj.@avg = %@", @"a"]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dataObj.@avg = %@", data(1)]), @"Invalid keypath 'dataObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@avg = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@avg = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"boolObj.@avg = %@", @NO]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"stringObj.@avg = %@", @"a"]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dataObj.@avg = %@", data(1)]), @"Invalid keypath 'dataObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@avg = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@avg = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@avg = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@avg = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@avg = %@", @"a"]), @"@avg on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@avg = %@", @"a"]), @"@avg on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@avg = %@", @"a"]), @"@avg on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@avg = %@", @"a"]), @"@avg on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@avg = %@", @"a"]), @"@avg on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@avg = %@", @"a"]), @"@avg on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(2)], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"decimalObj": @[decimal128(2), decimal128(3)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"decimalObj": @[decimal128(2), decimal128(3)], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(3)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(3)], }]; RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@avg == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@avg == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@avg == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@avg == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@avg == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@avg == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 3U, @"intObj.@avg != %@", @2); RLMAssertCount(AllPrimitiveArrays, 3U, @"floatObj.@avg != %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 3U, @"doubleObj.@avg != %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 3U, @"decimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"intObj.@avg != %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"floatObj.@avg != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"doubleObj.@avg != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"decimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 3U, @"intObj.@avg >= %@", @2); RLMAssertCount(AllPrimitiveArrays, 3U, @"floatObj.@avg >= %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 3U, @"doubleObj.@avg >= %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 3U, @"decimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"intObj.@avg >= %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"floatObj.@avg >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"doubleObj.@avg >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"decimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@avg > %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@avg > %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@avg > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@avg > %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@avg > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@avg > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@avg > %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@avg < %@", @3); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@avg < %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@avg < %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@avg < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@avg < %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@avg < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@avg < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@avg < %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 3U, @"intObj.@avg <= %@", @3); RLMAssertCount(AllPrimitiveArrays, 3U, @"floatObj.@avg <= %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 3U, @"doubleObj.@avg <= %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 3U, @"decimalObj.@avg <= %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"intObj.@avg <= %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"floatObj.@avg <= %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"doubleObj.@avg <= %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 3U, @"decimalObj.@avg <= %@", decimal128(3)); } - (void)testQueryMin { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"boolObj.@min = %@", @NO]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"stringObj.@min = %@", @"a"]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dataObj.@min = %@", data(1)]), @"Invalid keypath 'dataObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@min = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@min = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"boolObj.@min = %@", @NO]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"stringObj.@min = %@", @"a"]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dataObj.@min = %@", data(1)]), @"Invalid keypath 'dataObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@min = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@min = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@min = %@", @"a"]), @"@min on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@min = %@", @"a"]), @"@min on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@min = %@", @"a"]), @"@min on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@min = %@", @"a"]), @"@min on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@min = %@", @"a"]), @"@min on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@min = %@", @"a"]), @"@min on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyIntObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyFloatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDoubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDecimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(2)); [AllPrimitiveArrays createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == %@", NSNull.null); [self createObjectWithValueIndex:0]; // One object where v0 is min and zero with v1 RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@min == %@", decimal128(3)); [self createObjectWithValueIndex:1]; // One object where v0 is min and one with v1 RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(3)); [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(3), decimal128(2)], @"anyIntObj": @[@3, @2], @"anyFloatObj": @[@3.3f, @2.2f], @"anyDoubleObj": @[@3.3, @2.2], @"anyDateObj": @[date(2), date(1)], @"anyDecimalObj": @[decimal128(3), decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(3), decimal128(2)], }]; // New object with both v0 and v1 matches v0 but not v1 RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@min == %@", decimal128(3)); } - (void)testQueryMax { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"boolObj.@max = %@", @NO]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"stringObj.@max = %@", @"a"]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dataObj.@max = %@", data(1)]), @"Invalid keypath 'dataObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@max = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@max = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"boolObj.@max = %@", @NO]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"stringObj.@max = %@", @"a"]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dataObj.@max = %@", data(1)]), @"Invalid keypath 'dataObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"objectIdObj.@max = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"uuidObj.@max = %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Invalid keypath 'uuidObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@max = %@", @"a"]), @"@max on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@max = %@", @"a"]), @"@max on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@max = %@", @"a"]), @"@max on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@max = %@", @"a"]), @"@max on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@max = %@", @"a"]), @"@max on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@max = %@", @"a"]), @"@max on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"floatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyIntObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyFloatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDoubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveArrays objectsInRealm:realm where:@"anyDecimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"floatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"doubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveArrays objectsInRealm:realm where:@"decimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(2)); [AllPrimitiveArrays createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == nil"); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == %@", NSNull.null); [self createObjectWithValueIndex:0]; // One object where v0 is min and zero with v1 RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 0U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 0U, @"decimalObj.@max == %@", decimal128(3)); [self createObjectWithValueIndex:1]; // One object where v0 is min and one with v1 RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(3)); [AllPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(3), decimal128(2)], @"anyIntObj": @[@3, @2], @"anyFloatObj": @[@3.3f, @2.2f], @"anyDoubleObj": @[@3.3, @2.2], @"anyDateObj": @[date(2), date(1)], @"anyDecimalObj": @[decimal128(3), decimal128(2)], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(3), decimal128(2)], }]; // New object with both v0 and v1 matches v1 but not v0 RLMAssertCount(AllPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveArrays, 1U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveArrays, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 2U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"decimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveArrays, 2U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveArrays, 2U, @"decimalObj.@max == %@", decimal128(3)); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyObjectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyObjectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj <= %@", decimal128(2)); [self createObjectWithValueIndex:0]; RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.objectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyBoolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyObjectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj = %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj = %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj = %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj = %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.objectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyObjectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj != %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj != %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj != %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj != %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj != %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj != %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj != %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj != %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj != %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj != %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj != %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj != %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj != %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj < %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj < %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj < %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj < %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj < %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj < %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj < %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj < %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj < %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj < %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj < %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj < %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj < %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj < %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj <= %@", decimal128(2)); [self createObjectWithValueIndex:1]; RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj = %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj = %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj = %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj = %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.boolObj != %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj != %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj != %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyBoolObj != %@", @YES); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj != %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj != %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj != %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj != %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj != %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj != %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyObjectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.boolObj != %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj != %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj != %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj != %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj != %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj != %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj != %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.objectIdObj != %@", objectId(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyIntObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyStringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.decimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyIntObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyStringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 0, @"ANY link.anyDecimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 0, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj < %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj < %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj < %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj < %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj < %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj < %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj < %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj < %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj < %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj < %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj < %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj < %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj < %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj < %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.decimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyIntObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyStringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveArrays, 1, @"ANY link.anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 1, @"ANY link.decimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.intObj <= %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.floatObj <= %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.doubleObj <= %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.stringObj <= %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.dataObj <= %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.dateObj <= %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.decimalObj <= %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyIntObj <= %@", @3); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyFloatObj <= %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDoubleObj <= %@", @3.3); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyStringObj <= %@", @"b"); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDataObj <= %@", data(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDateObj <= %@", date(2)); RLMAssertCount(LinkToAllPrimitiveArrays, 2, @"ANY link.anyDecimalObj <= %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.intObj <= %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.floatObj <= %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.doubleObj <= %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.stringObj <= %@", @"b"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.dataObj <= %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.dateObj <= %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, 2, @"ANY link.decimalObj <= %@", decimal128(3)); RLMAssertThrowsWithReason(([LinkToAllPrimitiveArrays objectsInRealm:realm where:@"ANY link.boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([LinkToAllPrimitiveArrays objectsInRealm:realm where:@"ANY link.objectIdObj > %@", objectId(1)]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([LinkToAllPrimitiveArrays objectsInRealm:realm where:@"ANY link.uuidObj > %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Operator '>' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY link.boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY link.objectIdObj > %@", objectId(1)]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveArrays objectsInRealm:realm where:@"ANY link.uuidObj > %@", uuid(@"00000000-0000-0000-0000-000000000000")]), @"Operator '>' not supported for type 'uuid'"); } - (void)testSubstringQueries { NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveArrays createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllPrimitiveArrays createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllOptionalPrimitiveArrays createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveArrays, count, query, value); RLMAssertCount(AllPrimitiveArrays, count, query, value); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveArrays, count, query, value); RLMAssertCount(LinkToAllPrimitiveArrays, count, query, value); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveArrays, count, query, data); RLMAssertCount(AllPrimitiveArrays, count, query, data); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveArrays, count, query, data); RLMAssertCount(LinkToAllPrimitiveArrays, count, query, data); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } @end ================================================ FILE: Realm/Tests/PrimitiveArrayPropertyTests.tpl.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveArrays : RLMObject @property (nonatomic) AllPrimitiveArrays *link; @end @implementation LinkToAllPrimitiveArrays @end @interface LinkToAllOptionalPrimitiveArrays : RLMObject @property (nonatomic) AllOptionalPrimitiveArrays *link; @end @implementation LinkToAllOptionalPrimitiveArrays @end @interface PrimitiveArrayPropertyTests : RLMTestCase @end @implementation PrimitiveArrayPropertyTests { AllPrimitiveArrays *unmanaged; AllPrimitiveArrays *managed; AllOptionalPrimitiveArrays *optUnmanaged; AllOptionalPrimitiveArrays *optManaged; RLMRealm *realm; NSArray *allArrays; } - (void)setUp { unmanaged = [[AllPrimitiveArrays alloc] init]; optUnmanaged = [[AllOptionalPrimitiveArrays alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveArrays createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@[]]; allArrays = @[ $array, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [$array addObjects:$values]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); [unmanaged.intObj addObject:@1]; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(unmanaged.anyBoolObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyIntObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyFloatObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDoubleObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyStringObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDataObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDateObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyDecimalObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyObjectIdObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(unmanaged.anyUUIDObj.type, RLMPropertyTypeAny); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertFalse(unmanaged.anyBoolObj.optional); uncheckedAssertFalse(unmanaged.anyIntObj.optional); uncheckedAssertFalse(unmanaged.anyFloatObj.optional); uncheckedAssertFalse(unmanaged.anyDoubleObj.optional); uncheckedAssertFalse(unmanaged.anyStringObj.optional); uncheckedAssertFalse(unmanaged.anyDataObj.optional); uncheckedAssertFalse(unmanaged.anyDateObj.optional); uncheckedAssertFalse(unmanaged.anyDecimalObj.optional); uncheckedAssertFalse(unmanaged.anyObjectIdObj.optional); uncheckedAssertFalse(unmanaged.anyUUIDObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(unmanaged.anyBoolObj.objectClassName); uncheckedAssertNil(unmanaged.anyIntObj.objectClassName); uncheckedAssertNil(unmanaged.anyFloatObj.objectClassName); uncheckedAssertNil(unmanaged.anyDoubleObj.objectClassName); uncheckedAssertNil(unmanaged.anyStringObj.objectClassName); uncheckedAssertNil(unmanaged.anyDataObj.objectClassName); uncheckedAssertNil(unmanaged.anyDateObj.objectClassName); uncheckedAssertNil(unmanaged.anyDecimalObj.objectClassName); uncheckedAssertNil(unmanaged.anyObjectIdObj.objectClassName); uncheckedAssertNil(unmanaged.anyUUIDObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(unmanaged.anyBoolObj.realm); uncheckedAssertNil(unmanaged.anyIntObj.realm); uncheckedAssertNil(unmanaged.anyFloatObj.realm); uncheckedAssertNil(unmanaged.anyDoubleObj.realm); uncheckedAssertNil(unmanaged.anyStringObj.realm); uncheckedAssertNil(unmanaged.anyDataObj.realm); uncheckedAssertNil(unmanaged.anyDateObj.realm); uncheckedAssertNil(unmanaged.anyDecimalObj.realm); uncheckedAssertNil(unmanaged.anyObjectIdObj.realm); uncheckedAssertNil(unmanaged.anyUUIDObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMArray *array; @autoreleasepool { AllPrimitiveArrays *obj = [[AllPrimitiveArrays alloc] init]; array = obj.intObj; uncheckedAssertFalse(array.invalidated); } uncheckedAssertFalse(array.invalidated); } - (void)testDeleteObjectsInRealm { RLMAssertThrowsWithReason([realm deleteObjects:$allArrays], @"Cannot delete objects from RLMArray"); } - (void)testObjectAtIndex { RLMAssertThrowsWithReason([unmanaged.intObj objectAtIndex:0], @"Index 0 is out of bounds (must be less than 0)."); [unmanaged.intObj addObject:@1]; uncheckedAssertEqualObjects([unmanaged.intObj objectAtIndex:0], @1); } - (void)testObjectsAtIndexes { NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; [indexSet addIndex:0]; [indexSet addIndex:2]; XCTAssertNil([unmanaged.intObj objectsAtIndexes:indexSet]); XCTAssertNil([managed.intObj objectsAtIndexes:indexSet]); [unmanaged.intObj addObject:@1]; [unmanaged.intObj addObject:@2]; [unmanaged.intObj addObject:@3]; uncheckedAssertEqualObjects([unmanaged.intObj objectsAtIndexes:indexSet], (@[@1, @3])); [managed.intObj addObject:@1]; [managed.intObj addObject:@2]; [managed.intObj addObject:@3]; uncheckedAssertEqualObjects([managed.intObj objectsAtIndexes:indexSet], (@[@1, @3])); [indexSet addIndex:3]; XCTAssertNil([unmanaged.intObj objectsAtIndexes:indexSet]); XCTAssertNil([managed.intObj objectsAtIndexes:indexSet]); } - (void)testFirstObject { uncheckedAssertNil($allArrays.firstObject); [self addObjects]; uncheckedAssertEqualObjects($array.firstObject, $first); [$allArrays removeAllObjects]; %o [$array addObject:NSNull.null]; %o uncheckedAssertEqualObjects($array.firstObject, NSNull.null); } - (void)testLastObject { uncheckedAssertNil($allArrays.lastObject); [self addObjects]; uncheckedAssertEqualObjects($array.lastObject, $last); [$allArrays removeLastObject]; %o uncheckedAssertEqualObjects($array.lastObject, $v1); } - (void)testAddObject { %noany RLMAssertThrowsWithReason([$array addObject:$wrong], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array addObject:NSNull.null], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [$array addObject:$v0]; uncheckedAssertEqualObjects($array[0], $v0); %o [$array addObject:NSNull.null]; %o uncheckedAssertEqualObjects($array[1], NSNull.null); } - (void)testAddObjects { %noany RLMAssertThrowsWithReason([$array addObjects:@[$wrong]], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array addObjects:@[NSNull.null]], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; uncheckedAssertEqualObjects($array[0], $v0); uncheckedAssertEqualObjects($array[1], $v1); %o uncheckedAssertEqualObjects($array[2], NSNull.null); } - (void)testInsertObject { %noany RLMAssertThrowsWithReason([$array insertObject:$wrong atIndex:0], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array insertObject:NSNull.null atIndex:0], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); RLMAssertThrowsWithReason([$array insertObject:$v0 atIndex:1], ^n @"Index 1 is out of bounds (must be less than 1)."); [$array insertObject:$v0 atIndex:0]; uncheckedAssertEqualObjects($array[0], $v0); [$array insertObject:$v1 atIndex:0]; uncheckedAssertEqualObjects($array[0], $v1); uncheckedAssertEqualObjects($array[1], $v0); %o [$array insertObject:NSNull.null atIndex:1]; %o uncheckedAssertEqualObjects($array[0], $v1); %o uncheckedAssertEqualObjects($array[1], NSNull.null); %o uncheckedAssertEqualObjects($array[2], $v0); } - (void)testRemoveObject { RLMAssertThrowsWithReason([$allArrays removeObjectAtIndex:0], ^n @"Index 0 is out of bounds (must be less than 0)."); [self addObjects]; %r uncheckedAssertEqual($array.count, 2U); %o uncheckedAssertEqual($array.count, 3U); %r RLMAssertThrowsWithReason([$array removeObjectAtIndex:2], ^n @"Index 2 is out of bounds (must be less than 2)."); %o RLMAssertThrowsWithReason([$array removeObjectAtIndex:3], ^n @"Index 3 is out of bounds (must be less than 3)."); [$allArrays removeObjectAtIndex:0]; %r uncheckedAssertEqual($array.count, 1U); %o uncheckedAssertEqual($array.count, 2U); uncheckedAssertEqualObjects($array[0], $v1); %o uncheckedAssertEqualObjects($array[1], NSNull.null); } - (void)testRemoveLastObject { XCTAssertNoThrow([$allArrays removeLastObject]); [self addObjects]; %r uncheckedAssertEqual($array.count, 2U); %o uncheckedAssertEqual($array.count, 3U); [$allArrays removeLastObject]; %r uncheckedAssertEqual($array.count, 1U); %o uncheckedAssertEqual($array.count, 2U); uncheckedAssertEqualObjects($array[0], $v0); %o uncheckedAssertEqualObjects($array[1], $v1); } - (void)testReplace { RLMAssertThrowsWithReason([$array replaceObjectAtIndex:0 withObject:$v0], ^n @"Index 0 is out of bounds (must be less than 0)."); [$array addObject:$v0]; ^nl [$array replaceObjectAtIndex:0 withObject:$v1]; ^nl uncheckedAssertEqualObjects($array[0], $v1); ^nl %o [$array replaceObjectAtIndex:0 withObject:NSNull.null]; ^nl uncheckedAssertEqualObjects($array[0], NSNull.null); %noany RLMAssertThrowsWithReason([$array replaceObjectAtIndex:0 withObject:$wrong], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array replaceObjectAtIndex:0 withObject:NSNull.null], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); } - (void)testMove { RLMAssertThrowsWithReason([$allArrays moveObjectAtIndex:0 toIndex:1], ^n @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([$allArrays moveObjectAtIndex:1 toIndex:0], ^n @"Index 1 is out of bounds (must be less than 0)."); [$array addObjects:@[$v0, $v1, $v0, $v1]]; [$allArrays moveObjectAtIndex:2 toIndex:0]; uncheckedAssertEqualObjects([$array valueForKey:@"self"], ^n (@[$v0, $v0, $v1, $v1])); } - (void)testExchange { RLMAssertThrowsWithReason([$allArrays exchangeObjectAtIndex:0 withObjectAtIndex:1], ^n @"Index 0 is out of bounds (must be less than 0)."); RLMAssertThrowsWithReason([$allArrays exchangeObjectAtIndex:1 withObjectAtIndex:0], ^n @"Index 1 is out of bounds (must be less than 0)."); [$array addObjects:@[$v0, $v1, $v0, $v1]]; [$allArrays exchangeObjectAtIndex:2 withObjectAtIndex:1]; uncheckedAssertEqualObjects([$array valueForKey:@"self"], ^n (@[$v0, $v0, $v1, $v1])); } - (void)testIndexOfObject { uncheckedAssertEqual(NSNotFound, [$array indexOfObject:$v0]); %noany RLMAssertThrowsWithReason([$array indexOfObject:$wrong], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array indexOfObject:NSNull.null], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); %o uncheckedAssertEqual(NSNotFound, [$array indexOfObject:NSNull.null]); [self addObjects]; uncheckedAssertEqual(1U, [$array indexOfObject:$v1]); } - (void)testIndexOfObjectSorted { %man %r [$array addObjects:@[$v0, $v1, $v0, $v1]]; %man %o [$array addObjects:@[$v0, $v1, NSNull.null, $v1, $v0]]; %man %r uncheckedAssertEqual(0U, [[$array sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1]); %man %r uncheckedAssertEqual(2U, [[$array sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0]); %man %o uncheckedAssertEqual(0U, [[$array sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1]); %man %o uncheckedAssertEqual(2U, [[$array sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0]); %man %o uncheckedAssertEqual(4U, [[$array sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null]); } - (void)testIndexOfObjectDistinct { %man %r [$array addObjects:@[$v0, $v0, $v1]]; %man %o [$array addObjects:@[$v0, $v0, NSNull.null, $v1, $v0]]; %man %r uncheckedAssertEqual(0U, [[$array distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0]); %man %r uncheckedAssertEqual(1U, [[$array distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1]); %man %o uncheckedAssertEqual(0U, [[$array distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0]); %man %o uncheckedAssertEqual(2U, [[$array distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1]); %man %o uncheckedAssertEqual(1U, [[$array distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null]); } - (void)testIndexOfObjectWhere { %man RLMAssertThrowsWithReason([$array indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); %man RLMAssertThrowsWithReason([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] ^n indexOfObjectWhere:@"TRUEPREDICATE"], @"implemented"); %unman uncheckedAssertEqual(NSNotFound, [$array indexOfObjectWhere:@"TRUEPREDICATE"]); [self addObjects]; %unman uncheckedAssertEqual(0U, [$array indexOfObjectWhere:@"TRUEPREDICATE"]); %unman uncheckedAssertEqual(NSNotFound, [$array indexOfObjectWhere:@"FALSEPREDICATE"]); } - (void)testIndexOfObjectWithPredicate { %man RLMAssertThrowsWithReason([$array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); %man RLMAssertThrowsWithReason([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] ^n indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); %unman uncheckedAssertEqual(NSNotFound, [$array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); [self addObjects]; %unman uncheckedAssertEqual(0U, [$array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); %unman uncheckedAssertEqual(NSNotFound, [$array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]]); } - (void)testSort { %unman RLMAssertThrowsWithReason([$array sortedResultsUsingKeyPath:@"self" ascending:NO], ^n @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$array sortedResultsUsingDescriptors:@[]], ^n @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$array sortedResultsUsingKeyPath:@"not self" ascending:NO], ^n @"can only be sorted on 'self'"); %man %r [$array addObjects:@[$v0, $v1, $v0]]; %man %o [$array addObjects:@[$v0, $v1, NSNull.null, $v1, $v0]]; %man %r uncheckedAssertEqualObjects([[$array sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], ^n (@[$v0, $v1, $v0])); %man %o uncheckedAssertEqualObjects([[$array sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], ^n (@[$v0, $v1, NSNull.null, $v1, $v0])); %man %r uncheckedAssertEqualObjects([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], ^n (@[$v1, $v0, $v0])); %man %o uncheckedAssertEqualObjects([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], ^n (@[$v1, $v1, $v0, $v0, NSNull.null])); %man %r uncheckedAssertEqualObjects([[$array sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], ^n (@[$v0, $v0, $v1])); %man %o uncheckedAssertEqualObjects([[$array sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], ^n (@[NSNull.null, $v0, $v0, $v1, $v1])); } - (void)testFilter { %unman RLMAssertThrowsWithReason([$array objectsWhere:@"TRUEPREDICATE"], ^n @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$array objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"This method may only be called on RLMArray instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$array objectsWhere:@"TRUEPREDICATE"], ^n @"implemented"); %man RLMAssertThrowsWithReason([$array objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"implemented"); %man RLMAssertThrowsWithReason([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWhere:@"TRUEPREDICATE"], @"implemented"); %man RLMAssertThrowsWithReason([[$array sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { %unman RLMAssertThrowsWithReason([$array addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], ^n @"Change notifications are only supported on managed collections."); } - (void)testMin { %noany %nominmax %unman RLMAssertThrowsWithReason([$array minOfProperty:@"self"], ^n @"minOfProperty: is not supported for $type array"); %noany %nominmax %man RLMAssertThrowsWithReason([$array minOfProperty:@"self"], ^n @"Operation 'min' not supported for $type list '$class.$prop'"); %minmax uncheckedAssertNil([$array minOfProperty:@"self"]); [self addObjects]; %minmax uncheckedAssertEqualObjects([$array minOfProperty:@"self"], $v0); } - (void)testMax { %noany %nominmax %unman RLMAssertThrowsWithReason([$array maxOfProperty:@"self"], ^n @"maxOfProperty: is not supported for $type array"); %noany %nominmax %man RLMAssertThrowsWithReason([$array maxOfProperty:@"self"], ^n @"Operation 'max' not supported for $type list '$class.$prop'"); %minmax uncheckedAssertNil([$array maxOfProperty:@"self"]); [self addObjects]; %minmax uncheckedAssertEqualObjects([$array maxOfProperty:@"self"], $v1); } - (void)testSum { %noany %nosum %unman RLMAssertThrowsWithReason([$array sumOfProperty:@"self"], ^n @"sumOfProperty: is not supported for $type array"); %noany %nosum %man RLMAssertThrowsWithReason([$array sumOfProperty:@"self"], ^n @"Operation 'sum' not supported for $type list '$class.$prop'"); %sum uncheckedAssertEqualObjects([$array sumOfProperty:@"self"], @0); [self addObjects]; %sum XCTAssertEqualWithAccuracy([$array sumOfProperty:@"self"].doubleValue, sum($values), .001); } - (void)testAverage { %noany %noavg %unman RLMAssertThrowsWithReason([$array averageOfProperty:@"self"], ^n @"averageOfProperty: is not supported for $type array"); %noany %noavg %man RLMAssertThrowsWithReason([$array averageOfProperty:@"self"], ^n @"Operation 'average' not supported for $type list '$class.$prop'"); %avg uncheckedAssertNil([$array averageOfProperty:@"self"]); [self addObjects]; %avg XCTAssertEqualWithAccuracy([$array averageOfProperty:@"self"].doubleValue, average($values), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ ^nl NSUInteger i = 0; ^nl NSArray *values = $values; ^nl for (id value in $array) { ^nl uncheckedAssertEqualObjects(values[i++ % values.count], value); ^nl } ^nl uncheckedAssertEqual(i, $array.count); ^nl }(); ^nl } - (void)testValueForKeySelf { uncheckedAssertEqualObjects([$allArrays valueForKey:@"self"], @[]); [self addObjects]; uncheckedAssertEqualObjects([$array valueForKey:@"self"], ($values)); } - (void)testValueForKeyNumericAggregates { %minmax uncheckedAssertNil([$array valueForKeyPath:@"@min.self"]); %minmax uncheckedAssertNil([$array valueForKeyPath:@"@max.self"]); %noany %sum uncheckedAssertEqualObjects([$array valueForKeyPath:@"@sum.self"], @0); %noany %avg uncheckedAssertNil([$array valueForKeyPath:@"@avg.self"]); [self addObjects]; %minmax uncheckedAssertEqualObjects([$array valueForKeyPath:@"@min.self"], $v0); %minmax uncheckedAssertEqualObjects([$array valueForKeyPath:@"@max.self"], $v1); %noany %sum XCTAssertEqualWithAccuracy([[$array valueForKeyPath:@"@sum.self"] doubleValue], sum($values), .001); %noany %avg XCTAssertEqualWithAccuracy([[$array valueForKeyPath:@"@avg.self"] doubleValue], average($values), .001); } - (void)testValueForKeyLength { uncheckedAssertEqualObjects([$allArrays valueForKey:@"length"], @[]); [self addObjects]; %string uncheckedAssertEqualObjects([$array valueForKey:@"length"], ([$values valueForKey:@"length"])); } // Sort the distinct results to match the order used in values, as it // doesn't preserve the order naturally static NSArray *sortedDistinctUnion(id array, NSString *type, NSString *prop) { return [[array valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOf%@.%@", type, prop]] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { bool aIsNull = a == NSNull.null; bool bIsNull = b == NSNull.null; if (aIsNull && bIsNull) { return 0; } if (aIsNull) { return 1; } if (bIsNull) { return -1; } if ([a isKindOfClass:[NSData class]]) { if ([a length] != [b length]) { return [a length] < [b length] ? -1 : 1; } int result = memcmp([a bytes], [b bytes], [a length]); if (!result) { return 0; } return result < 0 ? -1 : 1; } if ([a isKindOfClass:[RLMObjectId class]]) { int64_t idx1 = [objectIds indexOfObject:a]; int64_t idx2 = [objectIds indexOfObject:b]; return idx1 - idx2; } if ([a respondsToSelector:@selector(objCType)] && [b respondsToSelector:@selector(objCType)]) { return [a compare:b]; } else { if ([a isKindOfClass:[RLMDecimal128 class]]) { a = [NSNumber numberWithDouble:[(RLMDecimal128 *)a doubleValue]]; } if ([b isKindOfClass:[RLMDecimal128 class]]) { b = [NSNumber numberWithDouble:[(RLMDecimal128 *)b doubleValue]]; } return [a compare:b]; } }]; } - (void)testUnionOfObjects { uncheckedAssertEqualObjects([$allArrays valueForKeyPath:@"@unionOfObjects.self"], @[]); uncheckedAssertEqualObjects([$allArrays valueForKeyPath:@"@distinctUnionOfObjects.self"], @[]); [self addObjects]; [self addObjects]; uncheckedAssertEqualObjects([$array valueForKeyPath:@"@unionOfObjects.self"], ^n ($values2)); uncheckedAssertEqualObjects(sortedDistinctUnion($array, @"Objects", @"self"), ^n ($values)); } - (void)testUnionOfArrays { RLMResults *allRequired = [AllPrimitiveArrays allObjectsInRealm:realm]; RLMResults *allOptional = [AllOptionalPrimitiveArrays allObjectsInRealm:realm]; %man %r uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.$prop"], @[]); %man %o uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.$prop"], @[]); %man %r uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.$prop"], @[]); %man %o uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@distinctUnionOfArrays.$prop"], @[]); %man %any XCTAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.$prop"], @[]); %man %any XCTAssertEqualObjects([allRequired valueForKeyPath:@"@distinctUnionOfArrays.$prop"], @[]); [self addObjects]; [AllPrimitiveArrays createInRealm:realm withValue:managed]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:optManaged]; %man %r uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.$prop"], ^n ($values2)); %man %o uncheckedAssertEqualObjects([allOptional valueForKeyPath:@"@unionOfArrays.$prop"], ^n ($values2)); %man %r uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"$prop"), ^n ($values)); %man %o uncheckedAssertEqualObjects(sortedDistinctUnion(allOptional, @"Arrays", @"$prop"), ^n ($values)); %man %any uncheckedAssertEqualObjects([allRequired valueForKeyPath:@"@unionOfArrays.$prop"], ^n ($values2)); %man %any uncheckedAssertEqualObjects(sortedDistinctUnion(allRequired, @"Arrays", @"$prop"), ^n ($values)); } - (void)testSetValueForKey { RLMAssertThrowsWithReason([$allArrays setValue:@0 forKey:@"not self"], ^n @"this class is not key value coding-compliant for the key not self."); %noany RLMAssertThrowsWithReason([$array setValue:$wrong forKey:@"self"], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$array setValue:NSNull.null forKey:@"self"], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; [$array setValue:$v0 forKey:@"self"]; uncheckedAssertEqualObjects($array[0], $v0); uncheckedAssertEqualObjects($array[1], $v0); %o uncheckedAssertEqualObjects($array[2], $v0); %o [$array setValue:NSNull.null forKey:@"self"]; %o uncheckedAssertEqualObjects($array[0], NSNull.null); } - (void)testAssignment { $array = (id)@[$v1]; ^nl uncheckedAssertEqualObjects($array[0], $v1); // Should replace and not append $array = (id)$values; ^nl uncheckedAssertEqualObjects([$array valueForKey:@"self"], ($values)); ^nl // Should not clear the array $array = $array; ^nl uncheckedAssertEqualObjects([$array valueForKey:@"self"], ($values)); ^nl [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqualObjects([unmanaged.intObj valueForKey:@"self"], (@[@2, @3])); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([managed.intObj valueForKey:@"self"], (@[@2, @3])); } - (void)testDynamicAssignment { $obj[@"$prop"] = (id)@[$v1]; ^nl uncheckedAssertEqualObjects($obj[@"$prop"][0], $v1); // Should replace and not append $obj[@"$prop"] = (id)$values; ^nl uncheckedAssertEqualObjects([$obj[@"$prop"] valueForKey:@"self"], ($values)); ^nl // Should not clear the array $obj[@"$prop"] = $obj[@"$prop"]; ^nl uncheckedAssertEqualObjects([$obj[@"$prop"] valueForKey:@"self"], ($values)); ^nl [unmanaged[@"intObj"] removeAllObjects]; unmanaged[@"intObj"] = managed.intObj; uncheckedAssertEqualObjects([unmanaged[@"intObj"] valueForKey:@"self"], (@[@2, @3])); [managed[@"intObj"] removeAllObjects]; managed[@"intObj"] = unmanaged.intObj; uncheckedAssertEqualObjects([managed[@"intObj"] valueForKey:@"self"], (@[@2, @3])); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' array property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMArray does not match expected type 'int' for property 'AllPrimitiveArrays.intObj'."); } - (void)testAllMethodsCheckThread { RLMArray *array = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([array count], @"thread"); RLMAssertThrowsWithReason([array objectAtIndex:0], @"thread"); RLMAssertThrowsWithReason([array firstObject], @"thread"); RLMAssertThrowsWithReason([array lastObject], @"thread"); RLMAssertThrowsWithReason([array addObject:@0], @"thread"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"thread"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"thread"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"thread"); RLMAssertThrowsWithReason([array removeLastObject], @"thread"); RLMAssertThrowsWithReason([array removeAllObjects], @"thread"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"thread"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"thread"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"thread"); RLMAssertThrowsWithReason([array indexOfObject:@1], @"thread"); /* RLMAssertThrowsWithReason([array indexOfObjectWhere:@"TRUEPREDICATE"], @"thread"); */ /* RLMAssertThrowsWithReason([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:NO]], @"thread"); */ /* RLMAssertThrowsWithReason([array objectsWhere:@"TRUEPREDICATE"], @"thread"); */ /* RLMAssertThrowsWithReason([array objectsWithPredicate:[NSPredicate predicateWithValue:NO]], @"thread"); */ RLMAssertThrowsWithReason([array sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(array[0], @"thread"); RLMAssertThrowsWithReason(array[0] = @0, @"thread"); RLMAssertThrowsWithReason([array valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in array);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMArray *array = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); RLMAssertThrowsWithReason([array count], @"invalidated"); RLMAssertThrowsWithReason([array objectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array firstObject], @"invalidated"); RLMAssertThrowsWithReason([array lastObject], @"invalidated"); RLMAssertThrowsWithReason([array addObject:@0], @"invalidated"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"invalidated"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"invalidated"); RLMAssertThrowsWithReason([array removeLastObject], @"invalidated"); RLMAssertThrowsWithReason([array removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"invalidated"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"invalidated"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"invalidated"); RLMAssertThrowsWithReason([array indexOfObject:@1], @"invalidated"); /* RLMAssertThrowsWithReason([array indexOfObjectWhere:@"TRUEPREDICATE"], @"invalidated"); */ /* RLMAssertThrowsWithReason([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]], @"invalidated"); */ /* RLMAssertThrowsWithReason([array objectsWhere:@"TRUEPREDICATE"], @"invalidated"); */ /* RLMAssertThrowsWithReason([array objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"invalidated"); */ RLMAssertThrowsWithReason([array sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(array[0], @"invalidated"); RLMAssertThrowsWithReason(array[0] = @0, @"invalidated"); RLMAssertThrowsWithReason([array valueForKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in array);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMArray *array = managed.intObj; [array addObject:@0]; [realm commitWriteTransaction]; XCTAssertNoThrow([array objectClassName]); XCTAssertNoThrow([array realm]); XCTAssertNoThrow([array isInvalidated]); XCTAssertNoThrow([array count]); XCTAssertNoThrow([array objectAtIndex:0]); XCTAssertNoThrow([array firstObject]); XCTAssertNoThrow([array lastObject]); XCTAssertNoThrow([array indexOfObject:@1]); /* XCTAssertNoThrow([array indexOfObjectWhere:@"TRUEPREDICATE"]); */ /* XCTAssertNoThrow([array indexOfObjectWithPredicate:[NSPredicate predicateWithValue:YES]]); */ /* XCTAssertNoThrow([array objectsWhere:@"TRUEPREDICATE"]); */ /* XCTAssertNoThrow([array objectsWithPredicate:[NSPredicate predicateWithValue:YES]]); */ XCTAssertNoThrow([array sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([array sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(array[0]); XCTAssertNoThrow([array valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in array);})); RLMAssertThrowsWithReason([array addObject:@0], @"write transaction"); RLMAssertThrowsWithReason([array addObjects:@[@0]], @"write transaction"); RLMAssertThrowsWithReason([array insertObject:@0 atIndex:0], @"write transaction"); RLMAssertThrowsWithReason([array removeObjectAtIndex:0], @"write transaction"); RLMAssertThrowsWithReason([array removeLastObject], @"write transaction"); RLMAssertThrowsWithReason([array removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([array replaceObjectAtIndex:0 withObject:@0], @"write transaction"); RLMAssertThrowsWithReason([array moveObjectAtIndex:0 toIndex:1], @"write transaction"); RLMAssertThrowsWithReason([array exchangeObjectAtIndex:0 withObjectAtIndex:1], @"write transaction"); RLMAssertThrowsWithReason(array[0] = @0, @"write transaction"); RLMAssertThrowsWithReason([array setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMArray *array = managed.intObj; uncheckedAssertFalse(array.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(array.isInvalidated); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else { uncheckedAssertEqualObjects(change.insertions, @[@0]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMArray *array = [(AllPrimitiveArrays *)[AllPrimitiveArrays allObjectsInRealm:r].firstObject intObj]; [array addObject:@0]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMArray *array, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveArrays createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMArray *array = [(AllPrimitiveArrays *)[AllPrimitiveArrays allObjectsInRealm:r].firstObject intObj]; [array addObject:@0]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { [managed.intObj addObjects:@[@10, @20]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMArray *array, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(array); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); first = false; } else { uncheckedAssertEqualObjects(change.deletions, (@[@0, @1])); } [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:managed]; [realm commitWriteTransaction]; expectation = [self expectationWithDescription:@""]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObjectWithValueIndex:(NSUInteger)index { NSRange range = {index, 1}; id obj = [AllPrimitiveArrays createInRealm:realm withValue:@{ %r %man @"$prop": [$values subarrayWithRange:range], %any %man @"$prop": [$values subarrayWithRange:range], }]; [LinkToAllPrimitiveArrays createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %o %man @"$prop": [$values subarrayWithRange:range], }]; [LinkToAllOptionalPrimitiveArrays createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop <= %@", $v0); [self createObjectWithValueIndex:0]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v1); %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v1); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 1, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v1); %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); [self createObjectWithValueIndex:1]; %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v1); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v1); %man %comp RLMAssertCount($class, 1, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 2, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v1); %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); %man %comp RLMAssertCount($class, 2, @"ANY $prop <= %@", $v1); %man %nocomp %noany RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"ANY $prop > %@", $v0]), ^n @"Operator '>' not supported for type '$basetype'"); } - (void)testQueryBetween { [realm deleteAllObjects]; %noany %man %nominmax RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"ANY $prop BETWEEN %@", @[$v0, $v1]]), ^n @"Operator 'BETWEEN' not supported for type '$basetype'"); %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v0, $v1]); [self createObjectWithValueIndex:0]; %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v0]); %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v1]); %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v1, $v1]); } - (void)testQueryIn { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v0, $v1]); [self createObjectWithValueIndex:0]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v1]); %man RLMAssertCount($class, 1, @"ANY $prop IN %@", @[$v0, $v1]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %r %man @"$prop": @[], %any %man @"$prop": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %o %man @"$prop": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %r %man @"$prop": @[$v0], %any %man @"$prop": @[$v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %o %man @"$prop": @[$v0], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %r %man @"$prop": @[$v0, $v0], %any %man @"$prop": @[$v0, $v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %o %man @"$prop": @[$v0, $v0], }]; for (unsigned int i = 0; i < 3; ++i) { %man RLMAssertCount($class, 1U, @"$prop.@count == %@", @(i)); %man RLMAssertCount($class, 2U, @"$prop.@count != %@", @(i)); %man RLMAssertCount($class, 2 - i, @"$prop.@count > %@", @(i)); %man RLMAssertCount($class, 3 - i, @"$prop.@count >= %@", @(i)); %man RLMAssertCount($class, i, @"$prop.@count < %@", @(i)); %man RLMAssertCount($class, i + 1, @"$prop.@count <= %@", @(i)); } } - (void)testQuerySum { [realm deleteAllObjects]; %noany %nodate %nosum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Invalid keypath '$prop.@sum': @sum can only be applied to a collection of numeric values."); %noany %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $wrong]), ^n @"@sum on a property of type $basetype cannot be compared with '$wdesc'"); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", NSNull.null]), ^n @"@sum on a property of type $basetype cannot be compared with ''"); [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v0], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v0, $v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v0, $v0], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v0, $v0, $v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v0, $v0, $v0], }]; %noany %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", @0); %noany %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", $v0); %noany %sum %man RLMAssertCount($class, 3U, @"$prop.@sum != %@", $v0); %noany %sum %man RLMAssertCount($class, 3U, @"$prop.@sum >= %@", $v0); %noany %sum %man RLMAssertCount($class, 2U, @"$prop.@sum > %@", $v0); %noany %sum %man RLMAssertCount($class, 2U, @"$prop.@sum < %@", $v1); %noany %sum %man RLMAssertCount($class, 2U, @"$prop.@sum <= %@", $v1); } - (void)testQueryAverage { [realm deleteAllObjects]; %noany %nodate %noavg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Invalid keypath '$prop.@avg': @avg can only be applied to a collection of numeric values."); %noany %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %any %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $wrong]), ^n @"@avg on a property of type $basetype cannot be compared with '$wdesc'"); %noany %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v0], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v0, $v1], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v0, $v1], }]; [AllPrimitiveArrays createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v1], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v1], }]; %noany %avg %man RLMAssertCount($class, 1U, @"$prop.@avg == %@", NSNull.null); %noany %avg %man RLMAssertCount($class, 1U, @"$prop.@avg == %@", $v0); %noany %avg %man RLMAssertCount($class, 3U, @"$prop.@avg != %@", $v0); %noany %avg %man RLMAssertCount($class, 3U, @"$prop.@avg >= %@", $v0); %noany %avg %man RLMAssertCount($class, 2U, @"$prop.@avg > %@", $v0); %noany %avg %man RLMAssertCount($class, 2U, @"$prop.@avg < %@", $v1); %noany %avg %man RLMAssertCount($class, 3U, @"$prop.@avg <= %@", $v1); } - (void)testQueryMin { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $v0]), ^n @"Invalid keypath '$prop.@min': @min can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $wrong]), ^n @"@min on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); [AllPrimitiveArrays createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == nil"); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", NSNull.null); [self createObjectWithValueIndex:0]; // One object where v0 is min and zero with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v0); %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); [self createObjectWithValueIndex:1]; // One object where v0 is min and one with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v0); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); [AllPrimitiveArrays createInRealm:realm withValue:@{ %minmax %r %man @"$prop": @[$v1, $v0], %minmax %any %man @"$prop": @[$v1, $v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %minmax %o %man @"$prop": @[$v1, $v0], }]; // New object with both v0 and v1 matches v0 but not v1 %minmax %man RLMAssertCount($class, 2U, @"$prop.@min == %@", $v0); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); } - (void)testQueryMax { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $v0]), ^n @"Invalid keypath '$prop.@max': @max can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $wrong]), ^n @"@max on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); [AllPrimitiveArrays createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == nil"); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", NSNull.null); [self createObjectWithValueIndex:0]; // One object where v0 is min and zero with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); [self createObjectWithValueIndex:1]; // One object where v0 is min and one with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v1); [AllPrimitiveArrays createInRealm:realm withValue:@{ %minmax %r %man @"$prop": @[$v1, $v0], %any %minmax %man @"$prop": @[$v1, $v0], }]; [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ %minmax %o %man @"$prop": @[$v1, $v0], }]; // New object with both v0 and v1 matches v1 but not v0 %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %minmax %man RLMAssertCount($class, 2U, @"$prop.@max == %@", $v1); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop != %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop <= %@", $v0); [self createObjectWithValueIndex:0]; %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop = %@", $v1); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop != %@", $v0); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop != %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop < %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop <= %@", $v0); [self createObjectWithValueIndex:1]; %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop = %@", $v1); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop != %@", $v0); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop != %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 2, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop < %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop <= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 2, @"ANY link.$prop <= %@", $v1); %man %nocomp %noany RLMAssertThrowsWithReason(([LinkTo$class objectsInRealm:realm where:@"ANY link.$prop > %@", $v0]), ^n @"Operator '>' not supported for type '$basetype'"); } - (void)testSubstringQueries { NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveArrays createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllPrimitiveArrays createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveArrays createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllOptionalPrimitiveArrays createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveArrays, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveArrays objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveArrays, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } @end ================================================ FILE: Realm/Tests/PrimitiveDictionaryPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSDictionary *dictionary) { NSArray *values = dictionary.allValues; double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSDictionary *dictionary) { NSArray *values = dictionary.allValues; double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveDictionaries : RLMObject @property (nonatomic) AllPrimitiveDictionaries *link; @end @implementation LinkToAllPrimitiveDictionaries @end @interface LinkToAllOptionalPrimitiveDictionaries : RLMObject @property (nonatomic) AllOptionalPrimitiveDictionaries *link; @end @implementation LinkToAllOptionalPrimitiveDictionaries @end @interface PrimitiveDictionaryPropertyTests : RLMTestCase @end @implementation PrimitiveDictionaryPropertyTests { AllPrimitiveDictionaries *unmanaged; AllPrimitiveDictionaries *managed; AllOptionalPrimitiveDictionaries *optUnmanaged; AllOptionalPrimitiveDictionaries *optManaged; RLMRealm *realm; NSArray *allDictionaries; } - (void)setUp { unmanaged = [[AllPrimitiveDictionaries alloc] init]; optUnmanaged = [[AllOptionalPrimitiveDictionaries alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveDictionaries createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[]]; allDictionaries = @[ unmanaged.boolObj, optUnmanaged.boolObj, managed.boolObj, optManaged.boolObj, unmanaged.intObj, optUnmanaged.intObj, managed.intObj, optManaged.intObj, unmanaged.stringObj, optUnmanaged.stringObj, managed.stringObj, optManaged.stringObj, unmanaged.dateObj, optUnmanaged.dateObj, managed.dateObj, optManaged.dateObj, unmanaged.floatObj, optUnmanaged.floatObj, managed.floatObj, optManaged.floatObj, unmanaged.doubleObj, optUnmanaged.doubleObj, managed.doubleObj, optManaged.doubleObj, unmanaged.dataObj, optUnmanaged.dataObj, managed.dataObj, optManaged.dataObj, unmanaged.decimalObj, optUnmanaged.decimalObj, managed.decimalObj, optManaged.decimalObj, unmanaged.objectIdObj, optUnmanaged.objectIdObj, managed.objectIdObj, optManaged.objectIdObj, unmanaged.uuidObj, optUnmanaged.uuidObj, managed.uuidObj, optManaged.uuidObj, unmanaged.anyBoolObj, unmanaged.anyIntObj, unmanaged.anyFloatObj, unmanaged.anyDoubleObj, unmanaged.anyStringObj, unmanaged.anyDataObj, unmanaged.anyDateObj, unmanaged.anyDecimalObj, unmanaged.anyObjectIdObj, unmanaged.anyUUIDObj, managed.anyBoolObj, managed.anyIntObj, managed.anyFloatObj, managed.anyDoubleObj, managed.anyStringObj, managed.anyDataObj, managed.anyDateObj, managed.anyDecimalObj, managed.anyObjectIdObj, managed.anyUUIDObj, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [unmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optUnmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [managed.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optManaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [unmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optUnmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [managed.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optManaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [unmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optUnmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [managed.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optManaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [unmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optUnmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [managed.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optManaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [unmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optUnmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [managed.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optManaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [unmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optUnmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [managed.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optManaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [unmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optUnmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [managed.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optManaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [unmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optUnmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [managed.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optManaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [unmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optUnmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [managed.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optManaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [unmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optUnmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [managed.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optManaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [unmanaged.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [unmanaged.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [unmanaged.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [unmanaged.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [unmanaged.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [unmanaged.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [unmanaged.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [unmanaged.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [unmanaged.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [unmanaged.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [managed.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [managed.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [managed.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [managed.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [managed.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [managed.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [managed.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [managed.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [managed.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [managed.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); unmanaged.intObj[@"testVal"] = @1; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMDictionary *dictionary; @autoreleasepool { AllPrimitiveDictionaries *obj = [[AllPrimitiveDictionaries alloc] init]; dictionary = obj.intObj; uncheckedAssertFalse(dictionary.invalidated); } uncheckedAssertFalse(dictionary.invalidated); } - (void)testDeleteObjectsInRealm { RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.boolObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.boolObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.intObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.intObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.stringObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.stringObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.dateObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.dateObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.floatObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.floatObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.doubleObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.doubleObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.dataObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.dataObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.decimalObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.decimalObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.objectIdObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.objectIdObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.uuidObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:optUnmanaged.uuidObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyBoolObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyIntObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyFloatObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyDoubleObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyStringObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyDataObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyDateObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyDecimalObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyObjectIdObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:unmanaged.anyUUIDObj], @"Cannot delete objects from RLMDictionary"); RLMAssertThrowsWithReason([realm deleteObjects:managed.boolObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:optManaged.boolObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.intObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:optManaged.intObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.stringObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:optManaged.stringObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.dateObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:optManaged.dateObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyBoolObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyIntObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyFloatObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyDoubleObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyStringObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyDataObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyDateObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyDecimalObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); RLMAssertThrowsWithReason([realm deleteObjects:managed.anyUUIDObj], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - (void)testSetObject { // Managed non-optional uncheckedAssertNil(managed.boolObj[@"key1"]); uncheckedAssertNil(managed.intObj[@"key1"]); uncheckedAssertNil(managed.stringObj[@"key1"]); uncheckedAssertNil(managed.dateObj[@"key1"]); uncheckedAssertNil(managed.anyBoolObj[@"key1"]); uncheckedAssertNil(managed.anyIntObj[@"key1"]); uncheckedAssertNil(managed.anyFloatObj[@"key1"]); uncheckedAssertNil(managed.anyDoubleObj[@"key1"]); uncheckedAssertNil(managed.anyStringObj[@"key1"]); uncheckedAssertNil(managed.anyDataObj[@"key1"]); uncheckedAssertNil(managed.anyDateObj[@"key1"]); uncheckedAssertNil(managed.anyDecimalObj[@"key1"]); uncheckedAssertNil(managed.anyUUIDObj[@"key1"]); XCTAssertNoThrow(managed.boolObj[@"key1"] = @NO); XCTAssertNoThrow(managed.intObj[@"key1"] = @2); XCTAssertNoThrow(managed.stringObj[@"key1"] = @"bar"); XCTAssertNoThrow(managed.dateObj[@"key1"] = date(1)); XCTAssertNoThrow(managed.anyBoolObj[@"key1"] = @NO); XCTAssertNoThrow(managed.anyIntObj[@"key1"] = @2); XCTAssertNoThrow(managed.anyFloatObj[@"key1"] = @2.2f); XCTAssertNoThrow(managed.anyDoubleObj[@"key1"] = @2.2); XCTAssertNoThrow(managed.anyStringObj[@"key1"] = @"a"); XCTAssertNoThrow(managed.anyDataObj[@"key1"] = data(1)); XCTAssertNoThrow(managed.anyDateObj[@"key1"] = date(1)); XCTAssertNoThrow(managed.anyDecimalObj[@"key1"] = decimal128(2)); XCTAssertNoThrow(managed.anyUUIDObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertThrowsWithReason(managed.boolObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'bool'."); RLMAssertThrowsWithReason(managed.intObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'int'."); RLMAssertThrowsWithReason(managed.stringObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'string'."); RLMAssertThrowsWithReason(managed.dateObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'date'."); XCTAssertNoThrow(managed.boolObj[@"key1"] = nil); XCTAssertNoThrow(managed.intObj[@"key1"] = nil); XCTAssertNoThrow(managed.stringObj[@"key1"] = nil); XCTAssertNoThrow(managed.dateObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyBoolObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyIntObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyFloatObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyDoubleObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyStringObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyDataObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyDateObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyDecimalObj[@"key1"] = nil); XCTAssertNoThrow(managed.anyUUIDObj[@"key1"] = nil); uncheckedAssertNil(managed.boolObj[@"key1"]); uncheckedAssertNil(managed.intObj[@"key1"]); uncheckedAssertNil(managed.stringObj[@"key1"]); uncheckedAssertNil(managed.dateObj[@"key1"]); uncheckedAssertNil(managed.anyBoolObj[@"key1"]); uncheckedAssertNil(managed.anyIntObj[@"key1"]); uncheckedAssertNil(managed.anyFloatObj[@"key1"]); uncheckedAssertNil(managed.anyDoubleObj[@"key1"]); uncheckedAssertNil(managed.anyStringObj[@"key1"]); uncheckedAssertNil(managed.anyDataObj[@"key1"]); uncheckedAssertNil(managed.anyDateObj[@"key1"]); uncheckedAssertNil(managed.anyDecimalObj[@"key1"]); uncheckedAssertNil(managed.anyUUIDObj[@"key1"]); // Managed optional uncheckedAssertNil(optManaged.boolObj[@"key1"]); uncheckedAssertNil(optManaged.intObj[@"key1"]); uncheckedAssertNil(optManaged.stringObj[@"key1"]); uncheckedAssertNil(optManaged.dateObj[@"key1"]); XCTAssertNoThrow(optManaged.boolObj[@"key1"] = @NO); XCTAssertNoThrow(optManaged.intObj[@"key1"] = @2); XCTAssertNoThrow(optManaged.stringObj[@"key1"] = @"bar"); XCTAssertNoThrow(optManaged.dateObj[@"key1"] = date(1)); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); XCTAssertNoThrow(optManaged.boolObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optManaged.intObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optManaged.stringObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optManaged.dateObj[@"key1"] = (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], (id)NSNull.null); XCTAssertNoThrow(optManaged.boolObj[@"key1"] = nil); XCTAssertNoThrow(optManaged.intObj[@"key1"] = nil); XCTAssertNoThrow(optManaged.stringObj[@"key1"] = nil); XCTAssertNoThrow(optManaged.dateObj[@"key1"] = nil); uncheckedAssertNil(optManaged.boolObj[@"key1"]); uncheckedAssertNil(optManaged.intObj[@"key1"]); uncheckedAssertNil(optManaged.stringObj[@"key1"]); uncheckedAssertNil(optManaged.dateObj[@"key1"]); // Unmanaged non-optional uncheckedAssertNil(unmanaged.boolObj[@"key1"]); uncheckedAssertNil(unmanaged.intObj[@"key1"]); uncheckedAssertNil(unmanaged.stringObj[@"key1"]); uncheckedAssertNil(unmanaged.dateObj[@"key1"]); uncheckedAssertNil(unmanaged.floatObj[@"key1"]); uncheckedAssertNil(unmanaged.doubleObj[@"key1"]); uncheckedAssertNil(unmanaged.dataObj[@"key1"]); uncheckedAssertNil(unmanaged.decimalObj[@"key1"]); uncheckedAssertNil(unmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.uuidObj[@"key1"]); uncheckedAssertNil(unmanaged.anyBoolObj[@"key1"]); uncheckedAssertNil(unmanaged.anyIntObj[@"key1"]); uncheckedAssertNil(unmanaged.anyFloatObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDoubleObj[@"key1"]); uncheckedAssertNil(unmanaged.anyStringObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDataObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDateObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDecimalObj[@"key1"]); uncheckedAssertNil(unmanaged.anyObjectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.anyUUIDObj[@"key1"]); XCTAssertNoThrow(unmanaged.boolObj[@"key1"] = @NO); XCTAssertNoThrow(unmanaged.intObj[@"key1"] = @2); XCTAssertNoThrow(unmanaged.stringObj[@"key1"] = @"bar"); XCTAssertNoThrow(unmanaged.dateObj[@"key1"] = date(1)); XCTAssertNoThrow(unmanaged.floatObj[@"key1"] = @2.2f); XCTAssertNoThrow(unmanaged.doubleObj[@"key1"] = @2.2); XCTAssertNoThrow(unmanaged.dataObj[@"key1"] = data(1)); XCTAssertNoThrow(unmanaged.decimalObj[@"key1"] = decimal128(2)); XCTAssertNoThrow(unmanaged.objectIdObj[@"key1"] = objectId(1)); XCTAssertNoThrow(unmanaged.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000")); XCTAssertNoThrow(unmanaged.anyBoolObj[@"key1"] = @NO); XCTAssertNoThrow(unmanaged.anyIntObj[@"key1"] = @2); XCTAssertNoThrow(unmanaged.anyFloatObj[@"key1"] = @2.2f); XCTAssertNoThrow(unmanaged.anyDoubleObj[@"key1"] = @2.2); XCTAssertNoThrow(unmanaged.anyStringObj[@"key1"] = @"a"); XCTAssertNoThrow(unmanaged.anyDataObj[@"key1"] = data(1)); XCTAssertNoThrow(unmanaged.anyDateObj[@"key1"] = date(1)); XCTAssertNoThrow(unmanaged.anyDecimalObj[@"key1"] = decimal128(2)); XCTAssertNoThrow(unmanaged.anyObjectIdObj[@"key1"] = objectId(1)); XCTAssertNoThrow(unmanaged.anyUUIDObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertThrowsWithReason(unmanaged.boolObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'bool'."); RLMAssertThrowsWithReason(unmanaged.intObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'int'."); RLMAssertThrowsWithReason(unmanaged.stringObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'string'."); RLMAssertThrowsWithReason(unmanaged.dateObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'date'."); RLMAssertThrowsWithReason(unmanaged.floatObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'float'."); RLMAssertThrowsWithReason(unmanaged.doubleObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'double'."); RLMAssertThrowsWithReason(unmanaged.dataObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'data'."); RLMAssertThrowsWithReason(unmanaged.decimalObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'decimal128'."); RLMAssertThrowsWithReason(unmanaged.objectIdObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'object id'."); RLMAssertThrowsWithReason(unmanaged.uuidObj[@"key1"] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type 'uuid'."); XCTAssertNoThrow(unmanaged.boolObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.intObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.stringObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.dateObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.floatObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.doubleObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.dataObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.decimalObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.objectIdObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.uuidObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyBoolObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyIntObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyFloatObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyDoubleObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyStringObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyDataObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyDateObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyDecimalObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyObjectIdObj[@"key1"] = nil); XCTAssertNoThrow(unmanaged.anyUUIDObj[@"key1"] = nil); uncheckedAssertNil(unmanaged.boolObj[@"key1"]); uncheckedAssertNil(unmanaged.intObj[@"key1"]); uncheckedAssertNil(unmanaged.stringObj[@"key1"]); uncheckedAssertNil(unmanaged.dateObj[@"key1"]); uncheckedAssertNil(unmanaged.floatObj[@"key1"]); uncheckedAssertNil(unmanaged.doubleObj[@"key1"]); uncheckedAssertNil(unmanaged.dataObj[@"key1"]); uncheckedAssertNil(unmanaged.decimalObj[@"key1"]); uncheckedAssertNil(unmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.uuidObj[@"key1"]); uncheckedAssertNil(unmanaged.anyBoolObj[@"key1"]); uncheckedAssertNil(unmanaged.anyIntObj[@"key1"]); uncheckedAssertNil(unmanaged.anyFloatObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDoubleObj[@"key1"]); uncheckedAssertNil(unmanaged.anyStringObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDataObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDateObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDecimalObj[@"key1"]); uncheckedAssertNil(unmanaged.anyObjectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.anyUUIDObj[@"key1"]); // Unmanaged optional uncheckedAssertNil(optUnmanaged.boolObj[@"key1"]); uncheckedAssertNil(optUnmanaged.intObj[@"key1"]); uncheckedAssertNil(optUnmanaged.stringObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dateObj[@"key1"]); uncheckedAssertNil(optUnmanaged.floatObj[@"key1"]); uncheckedAssertNil(optUnmanaged.doubleObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dataObj[@"key1"]); uncheckedAssertNil(optUnmanaged.decimalObj[@"key1"]); uncheckedAssertNil(optUnmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(optUnmanaged.uuidObj[@"key1"]); XCTAssertNoThrow(optUnmanaged.boolObj[@"key1"] = @NO); XCTAssertNoThrow(optUnmanaged.intObj[@"key1"] = @2); XCTAssertNoThrow(optUnmanaged.stringObj[@"key1"] = @"bar"); XCTAssertNoThrow(optUnmanaged.dateObj[@"key1"] = date(1)); XCTAssertNoThrow(optUnmanaged.floatObj[@"key1"] = @2.2f); XCTAssertNoThrow(optUnmanaged.doubleObj[@"key1"] = @2.2); XCTAssertNoThrow(optUnmanaged.dataObj[@"key1"] = data(1)); XCTAssertNoThrow(optUnmanaged.decimalObj[@"key1"] = decimal128(2)); XCTAssertNoThrow(optUnmanaged.objectIdObj[@"key1"] = objectId(1)); XCTAssertNoThrow(optUnmanaged.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); XCTAssertNoThrow(optUnmanaged.boolObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.intObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.stringObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.dateObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.floatObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.doubleObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.dataObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.decimalObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.objectIdObj[@"key1"] = (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.uuidObj[@"key1"] = (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.dateObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.floatObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.doubleObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.dataObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.decimalObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.objectIdObj[@"key1"], (id)NSNull.null); uncheckedAssertEqual(optUnmanaged.uuidObj[@"key1"], (id)NSNull.null); XCTAssertNoThrow(optUnmanaged.boolObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.intObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.stringObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.dateObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.floatObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.doubleObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.dataObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.decimalObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.objectIdObj[@"key1"] = nil); XCTAssertNoThrow(optUnmanaged.uuidObj[@"key1"] = nil); uncheckedAssertNil(optUnmanaged.boolObj[@"key1"]); uncheckedAssertNil(optUnmanaged.intObj[@"key1"]); uncheckedAssertNil(optUnmanaged.stringObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dateObj[@"key1"]); uncheckedAssertNil(optUnmanaged.floatObj[@"key1"]); uncheckedAssertNil(optUnmanaged.doubleObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dataObj[@"key1"]); uncheckedAssertNil(optUnmanaged.decimalObj[@"key1"]); uncheckedAssertNil(optUnmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(optUnmanaged.uuidObj[@"key1"]); // Fail with nil key RLMAssertThrowsWithReason([unmanaged.boolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.boolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.boolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.boolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.intObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.intObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.intObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.intObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.stringObj setObject:@"bar" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.stringObj setObject:@"bar" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.stringObj setObject:@"bar" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.stringObj setObject:@"bar" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.dateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.dateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.dateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.dateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.floatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.floatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.floatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.floatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.doubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.doubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.doubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.dataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.dataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.dataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.dataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.decimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.decimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.decimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.objectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.objectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.objectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.uuidObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.uuidObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([optManaged.uuidObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyIntObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyStringObj setObject:@"a" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyDataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyDateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyBoolObj setObject:@NO forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyIntObj setObject:@2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyFloatObj setObject:@2.2f forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyDoubleObj setObject:@2.2 forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyStringObj setObject:@"a" forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyDataObj setObject:data(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyDateObj setObject:date(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyDecimalObj setObject:decimal128(2) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyObjectIdObj setObject:objectId(1) forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); RLMAssertThrowsWithReason([managed.anyUUIDObj setObject:uuid(@"00000000-0000-0000-0000-000000000000") forKey:nil], @"Invalid nil key for dictionary expecting key of type 'string'."); // Fail on set nil for non-optional RLMAssertThrowsWithReason([unmanaged.boolObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.boolObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.intObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.stringObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.stringObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dateObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.dateObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.floatObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.floatObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.doubleObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.dataObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dataObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.decimalObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.objectIdObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.uuidObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([unmanaged.boolObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([managed.boolObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optManaged.boolObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([unmanaged.intObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optUnmanaged.intObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([managed.intObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([unmanaged.stringObj setObject:(id)@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj setObject:(id)@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([managed.stringObj setObject:(id)@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optManaged.stringObj setObject:(id)@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([unmanaged.dateObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([managed.dateObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optManaged.dateObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([unmanaged.floatObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([managed.floatObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optManaged.floatObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([managed.doubleObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optManaged.doubleObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([unmanaged.dataObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([managed.dataObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optManaged.dataObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([managed.decimalObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optManaged.decimalObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([managed.objectIdObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optManaged.objectIdObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.uuidObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.uuidObj setObject:(id)@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.boolObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.intObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.stringObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.stringObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dateObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.dateObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.floatObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.floatObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.doubleObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.dataObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dataObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.decimalObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.objectIdObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.uuidObj setObject:(id)NSNull.null forKey:@"key1"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); unmanaged.boolObj[@"key1"] = @NO; optUnmanaged.boolObj[@"key1"] = @NO; managed.boolObj[@"key1"] = @NO; optManaged.boolObj[@"key1"] = @NO; unmanaged.intObj[@"key1"] = @2; optUnmanaged.intObj[@"key1"] = @2; managed.intObj[@"key1"] = @2; optManaged.intObj[@"key1"] = @2; unmanaged.stringObj[@"key1"] = @"bar"; optUnmanaged.stringObj[@"key1"] = @"bar"; managed.stringObj[@"key1"] = @"bar"; optManaged.stringObj[@"key1"] = @"bar"; unmanaged.dateObj[@"key1"] = date(1); optUnmanaged.dateObj[@"key1"] = date(1); managed.dateObj[@"key1"] = date(1); optManaged.dateObj[@"key1"] = date(1); unmanaged.floatObj[@"key1"] = @2.2f; optUnmanaged.floatObj[@"key1"] = @2.2f; managed.floatObj[@"key1"] = @2.2f; optManaged.floatObj[@"key1"] = @2.2f; unmanaged.doubleObj[@"key1"] = @2.2; optUnmanaged.doubleObj[@"key1"] = @2.2; managed.doubleObj[@"key1"] = @2.2; optManaged.doubleObj[@"key1"] = @2.2; unmanaged.dataObj[@"key1"] = data(1); optUnmanaged.dataObj[@"key1"] = data(1); managed.dataObj[@"key1"] = data(1); optManaged.dataObj[@"key1"] = data(1); unmanaged.decimalObj[@"key1"] = decimal128(2); optUnmanaged.decimalObj[@"key1"] = decimal128(2); managed.decimalObj[@"key1"] = decimal128(2); optManaged.decimalObj[@"key1"] = decimal128(2); unmanaged.objectIdObj[@"key1"] = objectId(1); optUnmanaged.objectIdObj[@"key1"] = objectId(1); managed.objectIdObj[@"key1"] = objectId(1); optManaged.objectIdObj[@"key1"] = objectId(1); unmanaged.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); optUnmanaged.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); managed.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); optManaged.uuidObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); unmanaged.anyBoolObj[@"key1"] = @NO; unmanaged.anyIntObj[@"key1"] = @2; unmanaged.anyFloatObj[@"key1"] = @2.2f; unmanaged.anyDoubleObj[@"key1"] = @2.2; unmanaged.anyStringObj[@"key1"] = @"a"; unmanaged.anyDataObj[@"key1"] = data(1); unmanaged.anyDateObj[@"key1"] = date(1); unmanaged.anyDecimalObj[@"key1"] = decimal128(2); unmanaged.anyObjectIdObj[@"key1"] = objectId(1); unmanaged.anyUUIDObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); managed.anyBoolObj[@"key1"] = @NO; managed.anyIntObj[@"key1"] = @2; managed.anyFloatObj[@"key1"] = @2.2f; managed.anyDoubleObj[@"key1"] = @2.2; managed.anyStringObj[@"key1"] = @"a"; managed.anyDataObj[@"key1"] = data(1); managed.anyDateObj[@"key1"] = date(1); managed.anyDecimalObj[@"key1"] = decimal128(2); managed.anyObjectIdObj[@"key1"] = objectId(1); managed.anyUUIDObj[@"key1"] = uuid(@"00000000-0000-0000-0000-000000000000"); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); optUnmanaged.boolObj[@"key1"] = (id)NSNull.null; optManaged.boolObj[@"key1"] = (id)NSNull.null; optUnmanaged.intObj[@"key1"] = (id)NSNull.null; optManaged.intObj[@"key1"] = (id)NSNull.null; optUnmanaged.stringObj[@"key1"] = (id)NSNull.null; optManaged.stringObj[@"key1"] = (id)NSNull.null; optUnmanaged.dateObj[@"key1"] = (id)NSNull.null; optManaged.dateObj[@"key1"] = (id)NSNull.null; optUnmanaged.floatObj[@"key1"] = (id)NSNull.null; optManaged.floatObj[@"key1"] = (id)NSNull.null; optUnmanaged.doubleObj[@"key1"] = (id)NSNull.null; optManaged.doubleObj[@"key1"] = (id)NSNull.null; optUnmanaged.dataObj[@"key1"] = (id)NSNull.null; optManaged.dataObj[@"key1"] = (id)NSNull.null; optUnmanaged.decimalObj[@"key1"] = (id)NSNull.null; optManaged.decimalObj[@"key1"] = (id)NSNull.null; optUnmanaged.objectIdObj[@"key1"] = (id)NSNull.null; optManaged.objectIdObj[@"key1"] = (id)NSNull.null; optUnmanaged.uuidObj[@"key1"] = (id)NSNull.null; optManaged.uuidObj[@"key1"] = (id)NSNull.null; uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], (id)NSNull.null); } #pragma clang diagnostic pop - (void)testAddObjects { RLMAssertThrowsWithReason([unmanaged.boolObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([managed.boolObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optManaged.boolObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([unmanaged.intObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optUnmanaged.intObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([managed.intObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optManaged.intObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([unmanaged.stringObj addEntriesFromDictionary:@{@"key1": @2}], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj addEntriesFromDictionary:@{@"key1": @2}], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([managed.stringObj addEntriesFromDictionary:@{@"key1": @2}], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optManaged.stringObj addEntriesFromDictionary:@{@"key1": @2}], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([unmanaged.dateObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([managed.dateObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optManaged.dateObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([unmanaged.floatObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([managed.floatObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optManaged.floatObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([managed.doubleObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optManaged.doubleObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([unmanaged.dataObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([managed.dataObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optManaged.dataObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([managed.decimalObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optManaged.decimalObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([managed.objectIdObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optManaged.objectIdObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.uuidObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.uuidObj addEntriesFromDictionary:@{@"key1": @"a"}], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.boolObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.intObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.stringObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.stringObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dateObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.dateObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.floatObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.floatObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.doubleObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.dataObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dataObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.decimalObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.objectIdObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.uuidObj addEntriesFromDictionary:@{@"key1": (id)NSNull.null}], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key2"], @YES); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.boolObj[@"key2"], @YES); uncheckedAssertEqualObjects(optManaged.boolObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.intObj[@"key2"], @3); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.intObj[@"key2"], @3); uncheckedAssertEqualObjects(optManaged.intObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key2"], @"foo"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.stringObj[@"key2"], @"foo"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.dateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.floatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.doubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.dataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.decimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.objectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key2"], @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key2"], @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key2"], @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key2"], @YES); uncheckedAssertEqualObjects(managed.anyIntObj[@"key2"], @3); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(managed.anyStringObj[@"key2"], @"b"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key2"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key2"], (id)NSNull.null); } - (void)testRemoveObject { [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 2U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 2U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 2U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 2U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 2U); uncheckedAssertEqual(managed.anyBoolObj.count, 2U); uncheckedAssertEqual(managed.anyIntObj.count, 2U); uncheckedAssertEqual(managed.anyFloatObj.count, 2U); uncheckedAssertEqual(managed.anyDoubleObj.count, 2U); uncheckedAssertEqual(managed.anyStringObj.count, 2U); uncheckedAssertEqual(managed.anyDataObj.count, 2U); uncheckedAssertEqual(managed.anyDateObj.count, 2U); uncheckedAssertEqual(managed.anyDecimalObj.count, 2U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 2U); uncheckedAssertEqual(managed.anyUUIDObj.count, 2U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); [unmanaged.boolObj removeObjectForKey:@"key1"]; [optUnmanaged.boolObj removeObjectForKey:@"key1"]; [managed.boolObj removeObjectForKey:@"key1"]; [optManaged.boolObj removeObjectForKey:@"key1"]; [unmanaged.intObj removeObjectForKey:@"key1"]; [optUnmanaged.intObj removeObjectForKey:@"key1"]; [managed.intObj removeObjectForKey:@"key1"]; [optManaged.intObj removeObjectForKey:@"key1"]; [unmanaged.stringObj removeObjectForKey:@"key1"]; [optUnmanaged.stringObj removeObjectForKey:@"key1"]; [managed.stringObj removeObjectForKey:@"key1"]; [optManaged.stringObj removeObjectForKey:@"key1"]; [unmanaged.dateObj removeObjectForKey:@"key1"]; [optUnmanaged.dateObj removeObjectForKey:@"key1"]; [managed.dateObj removeObjectForKey:@"key1"]; [optManaged.dateObj removeObjectForKey:@"key1"]; [unmanaged.floatObj removeObjectForKey:@"key1"]; [optUnmanaged.floatObj removeObjectForKey:@"key1"]; [managed.floatObj removeObjectForKey:@"key1"]; [optManaged.floatObj removeObjectForKey:@"key1"]; [unmanaged.doubleObj removeObjectForKey:@"key1"]; [optUnmanaged.doubleObj removeObjectForKey:@"key1"]; [managed.doubleObj removeObjectForKey:@"key1"]; [optManaged.doubleObj removeObjectForKey:@"key1"]; [unmanaged.dataObj removeObjectForKey:@"key1"]; [optUnmanaged.dataObj removeObjectForKey:@"key1"]; [managed.dataObj removeObjectForKey:@"key1"]; [optManaged.dataObj removeObjectForKey:@"key1"]; [unmanaged.decimalObj removeObjectForKey:@"key1"]; [optUnmanaged.decimalObj removeObjectForKey:@"key1"]; [managed.decimalObj removeObjectForKey:@"key1"]; [optManaged.decimalObj removeObjectForKey:@"key1"]; [unmanaged.objectIdObj removeObjectForKey:@"key1"]; [optUnmanaged.objectIdObj removeObjectForKey:@"key1"]; [managed.objectIdObj removeObjectForKey:@"key1"]; [optManaged.objectIdObj removeObjectForKey:@"key1"]; [unmanaged.uuidObj removeObjectForKey:@"key1"]; [optUnmanaged.uuidObj removeObjectForKey:@"key1"]; [managed.uuidObj removeObjectForKey:@"key1"]; [optManaged.uuidObj removeObjectForKey:@"key1"]; [unmanaged.anyBoolObj removeObjectForKey:@"key1"]; [unmanaged.anyIntObj removeObjectForKey:@"key1"]; [unmanaged.anyFloatObj removeObjectForKey:@"key1"]; [unmanaged.anyDoubleObj removeObjectForKey:@"key1"]; [unmanaged.anyStringObj removeObjectForKey:@"key1"]; [unmanaged.anyDataObj removeObjectForKey:@"key1"]; [unmanaged.anyDateObj removeObjectForKey:@"key1"]; [unmanaged.anyDecimalObj removeObjectForKey:@"key1"]; [unmanaged.anyObjectIdObj removeObjectForKey:@"key1"]; [unmanaged.anyUUIDObj removeObjectForKey:@"key1"]; [managed.anyBoolObj removeObjectForKey:@"key1"]; [managed.anyIntObj removeObjectForKey:@"key1"]; [managed.anyFloatObj removeObjectForKey:@"key1"]; [managed.anyDoubleObj removeObjectForKey:@"key1"]; [managed.anyStringObj removeObjectForKey:@"key1"]; [managed.anyDataObj removeObjectForKey:@"key1"]; [managed.anyDateObj removeObjectForKey:@"key1"]; [managed.anyDecimalObj removeObjectForKey:@"key1"]; [managed.anyObjectIdObj removeObjectForKey:@"key1"]; [managed.anyUUIDObj removeObjectForKey:@"key1"]; uncheckedAssertEqual(unmanaged.boolObj.count, 1U); uncheckedAssertEqual(managed.boolObj.count, 1U); uncheckedAssertEqual(unmanaged.intObj.count, 1U); uncheckedAssertEqual(managed.intObj.count, 1U); uncheckedAssertEqual(unmanaged.stringObj.count, 1U); uncheckedAssertEqual(managed.stringObj.count, 1U); uncheckedAssertEqual(unmanaged.dateObj.count, 1U); uncheckedAssertEqual(managed.dateObj.count, 1U); uncheckedAssertEqual(unmanaged.floatObj.count, 1U); uncheckedAssertEqual(managed.floatObj.count, 1U); uncheckedAssertEqual(unmanaged.doubleObj.count, 1U); uncheckedAssertEqual(managed.doubleObj.count, 1U); uncheckedAssertEqual(unmanaged.dataObj.count, 1U); uncheckedAssertEqual(managed.dataObj.count, 1U); uncheckedAssertEqual(unmanaged.decimalObj.count, 1U); uncheckedAssertEqual(managed.decimalObj.count, 1U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 1U); uncheckedAssertEqual(managed.objectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.uuidObj.count, 1U); uncheckedAssertEqual(managed.uuidObj.count, 1U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 1U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 1U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 1U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 1U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 1U); uncheckedAssertEqual(managed.anyBoolObj.count, 1U); uncheckedAssertEqual(managed.anyIntObj.count, 1U); uncheckedAssertEqual(managed.anyFloatObj.count, 1U); uncheckedAssertEqual(managed.anyDoubleObj.count, 1U); uncheckedAssertEqual(managed.anyStringObj.count, 1U); uncheckedAssertEqual(managed.anyDataObj.count, 1U); uncheckedAssertEqual(managed.anyDateObj.count, 1U); uncheckedAssertEqual(managed.anyDecimalObj.count, 1U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 1U); uncheckedAssertEqual(managed.anyUUIDObj.count, 1U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 1U); uncheckedAssertEqual(optManaged.boolObj.count, 1U); uncheckedAssertEqual(optUnmanaged.intObj.count, 1U); uncheckedAssertEqual(optManaged.intObj.count, 1U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 1U); uncheckedAssertEqual(optManaged.stringObj.count, 1U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 1U); uncheckedAssertEqual(optManaged.dateObj.count, 1U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 1U); uncheckedAssertEqual(optManaged.floatObj.count, 1U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 1U); uncheckedAssertEqual(optManaged.doubleObj.count, 1U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 1U); uncheckedAssertEqual(optManaged.dataObj.count, 1U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 1U); uncheckedAssertEqual(optManaged.decimalObj.count, 1U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 1U); uncheckedAssertEqual(optManaged.objectIdObj.count, 1U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 1U); uncheckedAssertEqual(optManaged.uuidObj.count, 1U); uncheckedAssertNil(unmanaged.boolObj[@"key1"]); uncheckedAssertNil(optUnmanaged.boolObj[@"key1"]); uncheckedAssertNil(managed.boolObj[@"key1"]); uncheckedAssertNil(optManaged.boolObj[@"key1"]); uncheckedAssertNil(unmanaged.intObj[@"key1"]); uncheckedAssertNil(optUnmanaged.intObj[@"key1"]); uncheckedAssertNil(managed.intObj[@"key1"]); uncheckedAssertNil(optManaged.intObj[@"key1"]); uncheckedAssertNil(unmanaged.stringObj[@"key1"]); uncheckedAssertNil(optUnmanaged.stringObj[@"key1"]); uncheckedAssertNil(managed.stringObj[@"key1"]); uncheckedAssertNil(optManaged.stringObj[@"key1"]); uncheckedAssertNil(unmanaged.dateObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dateObj[@"key1"]); uncheckedAssertNil(managed.dateObj[@"key1"]); uncheckedAssertNil(optManaged.dateObj[@"key1"]); uncheckedAssertNil(unmanaged.floatObj[@"key1"]); uncheckedAssertNil(optUnmanaged.floatObj[@"key1"]); uncheckedAssertNil(managed.floatObj[@"key1"]); uncheckedAssertNil(optManaged.floatObj[@"key1"]); uncheckedAssertNil(unmanaged.doubleObj[@"key1"]); uncheckedAssertNil(optUnmanaged.doubleObj[@"key1"]); uncheckedAssertNil(managed.doubleObj[@"key1"]); uncheckedAssertNil(optManaged.doubleObj[@"key1"]); uncheckedAssertNil(unmanaged.dataObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dataObj[@"key1"]); uncheckedAssertNil(managed.dataObj[@"key1"]); uncheckedAssertNil(optManaged.dataObj[@"key1"]); uncheckedAssertNil(unmanaged.decimalObj[@"key1"]); uncheckedAssertNil(optUnmanaged.decimalObj[@"key1"]); uncheckedAssertNil(managed.decimalObj[@"key1"]); uncheckedAssertNil(optManaged.decimalObj[@"key1"]); uncheckedAssertNil(unmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(optUnmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(managed.objectIdObj[@"key1"]); uncheckedAssertNil(optManaged.objectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.uuidObj[@"key1"]); uncheckedAssertNil(optUnmanaged.uuidObj[@"key1"]); uncheckedAssertNil(managed.uuidObj[@"key1"]); uncheckedAssertNil(optManaged.uuidObj[@"key1"]); uncheckedAssertNil(unmanaged.anyBoolObj[@"key1"]); uncheckedAssertNil(unmanaged.anyIntObj[@"key1"]); uncheckedAssertNil(unmanaged.anyFloatObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDoubleObj[@"key1"]); uncheckedAssertNil(unmanaged.anyStringObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDataObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDateObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDecimalObj[@"key1"]); uncheckedAssertNil(unmanaged.anyObjectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.anyUUIDObj[@"key1"]); uncheckedAssertNil(managed.anyBoolObj[@"key1"]); uncheckedAssertNil(managed.anyIntObj[@"key1"]); uncheckedAssertNil(managed.anyFloatObj[@"key1"]); uncheckedAssertNil(managed.anyDoubleObj[@"key1"]); uncheckedAssertNil(managed.anyStringObj[@"key1"]); uncheckedAssertNil(managed.anyDataObj[@"key1"]); uncheckedAssertNil(managed.anyDateObj[@"key1"]); uncheckedAssertNil(managed.anyDecimalObj[@"key1"]); uncheckedAssertNil(managed.anyObjectIdObj[@"key1"]); uncheckedAssertNil(managed.anyUUIDObj[@"key1"]); } - (void)testRemoveObjects { [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 2U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 2U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 2U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 2U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 2U); uncheckedAssertEqual(managed.anyBoolObj.count, 2U); uncheckedAssertEqual(managed.anyIntObj.count, 2U); uncheckedAssertEqual(managed.anyFloatObj.count, 2U); uncheckedAssertEqual(managed.anyDoubleObj.count, 2U); uncheckedAssertEqual(managed.anyStringObj.count, 2U); uncheckedAssertEqual(managed.anyDataObj.count, 2U); uncheckedAssertEqual(managed.anyDateObj.count, 2U); uncheckedAssertEqual(managed.anyDecimalObj.count, 2U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 2U); uncheckedAssertEqual(managed.anyUUIDObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); [unmanaged.boolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.boolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.boolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.boolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.intObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.intObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.intObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.intObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.stringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.stringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.stringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.stringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.dateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.dateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.dateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.dateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.floatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.floatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.floatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.floatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.doubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.doubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.doubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.doubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.dataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.dataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.dataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.dataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.decimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.decimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.decimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.decimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.objectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.objectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.objectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.objectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.uuidObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optUnmanaged.uuidObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.uuidObj removeObjectsForKeys:@[@"key1", @"key2"]]; [optManaged.uuidObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyBoolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyIntObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyFloatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyDoubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyStringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyDataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyDateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyDecimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyObjectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [unmanaged.anyUUIDObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyBoolObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyIntObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyFloatObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyDoubleObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyStringObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyDataObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyDateObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyDecimalObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyObjectIdObj removeObjectsForKeys:@[@"key1", @"key2"]]; [managed.anyUUIDObj removeObjectsForKeys:@[@"key1", @"key2"]]; uncheckedAssertEqual(unmanaged.boolObj.count, 0U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 0U); uncheckedAssertEqual(managed.boolObj.count, 0U); uncheckedAssertEqual(optManaged.boolObj.count, 0U); uncheckedAssertEqual(unmanaged.intObj.count, 0U); uncheckedAssertEqual(optUnmanaged.intObj.count, 0U); uncheckedAssertEqual(managed.intObj.count, 0U); uncheckedAssertEqual(optManaged.intObj.count, 0U); uncheckedAssertEqual(unmanaged.stringObj.count, 0U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 0U); uncheckedAssertEqual(managed.stringObj.count, 0U); uncheckedAssertEqual(optManaged.stringObj.count, 0U); uncheckedAssertEqual(unmanaged.dateObj.count, 0U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 0U); uncheckedAssertEqual(managed.dateObj.count, 0U); uncheckedAssertEqual(optManaged.dateObj.count, 0U); uncheckedAssertEqual(unmanaged.floatObj.count, 0U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 0U); uncheckedAssertEqual(managed.floatObj.count, 0U); uncheckedAssertEqual(optManaged.floatObj.count, 0U); uncheckedAssertEqual(unmanaged.doubleObj.count, 0U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 0U); uncheckedAssertEqual(managed.doubleObj.count, 0U); uncheckedAssertEqual(optManaged.doubleObj.count, 0U); uncheckedAssertEqual(unmanaged.dataObj.count, 0U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 0U); uncheckedAssertEqual(managed.dataObj.count, 0U); uncheckedAssertEqual(optManaged.dataObj.count, 0U); uncheckedAssertEqual(unmanaged.decimalObj.count, 0U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 0U); uncheckedAssertEqual(managed.decimalObj.count, 0U); uncheckedAssertEqual(optManaged.decimalObj.count, 0U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 0U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 0U); uncheckedAssertEqual(managed.objectIdObj.count, 0U); uncheckedAssertEqual(optManaged.objectIdObj.count, 0U); uncheckedAssertEqual(unmanaged.uuidObj.count, 0U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 0U); uncheckedAssertEqual(managed.uuidObj.count, 0U); uncheckedAssertEqual(optManaged.uuidObj.count, 0U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 0U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 0U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 0U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 0U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 0U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 0U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 0U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 0U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 0U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 0U); uncheckedAssertEqual(managed.anyBoolObj.count, 0U); uncheckedAssertEqual(managed.anyIntObj.count, 0U); uncheckedAssertEqual(managed.anyFloatObj.count, 0U); uncheckedAssertEqual(managed.anyDoubleObj.count, 0U); uncheckedAssertEqual(managed.anyStringObj.count, 0U); uncheckedAssertEqual(managed.anyDataObj.count, 0U); uncheckedAssertEqual(managed.anyDateObj.count, 0U); uncheckedAssertEqual(managed.anyDecimalObj.count, 0U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 0U); uncheckedAssertEqual(managed.anyUUIDObj.count, 0U); uncheckedAssertNil(unmanaged.boolObj[@"key1"]); uncheckedAssertNil(optUnmanaged.boolObj[@"key1"]); uncheckedAssertNil(managed.boolObj[@"key1"]); uncheckedAssertNil(optManaged.boolObj[@"key1"]); uncheckedAssertNil(unmanaged.intObj[@"key1"]); uncheckedAssertNil(optUnmanaged.intObj[@"key1"]); uncheckedAssertNil(managed.intObj[@"key1"]); uncheckedAssertNil(optManaged.intObj[@"key1"]); uncheckedAssertNil(unmanaged.stringObj[@"key1"]); uncheckedAssertNil(optUnmanaged.stringObj[@"key1"]); uncheckedAssertNil(managed.stringObj[@"key1"]); uncheckedAssertNil(optManaged.stringObj[@"key1"]); uncheckedAssertNil(unmanaged.dateObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dateObj[@"key1"]); uncheckedAssertNil(managed.dateObj[@"key1"]); uncheckedAssertNil(optManaged.dateObj[@"key1"]); uncheckedAssertNil(unmanaged.floatObj[@"key1"]); uncheckedAssertNil(optUnmanaged.floatObj[@"key1"]); uncheckedAssertNil(managed.floatObj[@"key1"]); uncheckedAssertNil(optManaged.floatObj[@"key1"]); uncheckedAssertNil(unmanaged.doubleObj[@"key1"]); uncheckedAssertNil(optUnmanaged.doubleObj[@"key1"]); uncheckedAssertNil(managed.doubleObj[@"key1"]); uncheckedAssertNil(optManaged.doubleObj[@"key1"]); uncheckedAssertNil(unmanaged.dataObj[@"key1"]); uncheckedAssertNil(optUnmanaged.dataObj[@"key1"]); uncheckedAssertNil(managed.dataObj[@"key1"]); uncheckedAssertNil(optManaged.dataObj[@"key1"]); uncheckedAssertNil(unmanaged.decimalObj[@"key1"]); uncheckedAssertNil(optUnmanaged.decimalObj[@"key1"]); uncheckedAssertNil(managed.decimalObj[@"key1"]); uncheckedAssertNil(optManaged.decimalObj[@"key1"]); uncheckedAssertNil(unmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(optUnmanaged.objectIdObj[@"key1"]); uncheckedAssertNil(managed.objectIdObj[@"key1"]); uncheckedAssertNil(optManaged.objectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.uuidObj[@"key1"]); uncheckedAssertNil(optUnmanaged.uuidObj[@"key1"]); uncheckedAssertNil(managed.uuidObj[@"key1"]); uncheckedAssertNil(optManaged.uuidObj[@"key1"]); uncheckedAssertNil(unmanaged.anyBoolObj[@"key1"]); uncheckedAssertNil(unmanaged.anyIntObj[@"key1"]); uncheckedAssertNil(unmanaged.anyFloatObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDoubleObj[@"key1"]); uncheckedAssertNil(unmanaged.anyStringObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDataObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDateObj[@"key1"]); uncheckedAssertNil(unmanaged.anyDecimalObj[@"key1"]); uncheckedAssertNil(unmanaged.anyObjectIdObj[@"key1"]); uncheckedAssertNil(unmanaged.anyUUIDObj[@"key1"]); uncheckedAssertNil(managed.anyBoolObj[@"key1"]); uncheckedAssertNil(managed.anyIntObj[@"key1"]); uncheckedAssertNil(managed.anyFloatObj[@"key1"]); uncheckedAssertNil(managed.anyDoubleObj[@"key1"]); uncheckedAssertNil(managed.anyStringObj[@"key1"]); uncheckedAssertNil(managed.anyDataObj[@"key1"]); uncheckedAssertNil(managed.anyDateObj[@"key1"]); uncheckedAssertNil(managed.anyDecimalObj[@"key1"]); uncheckedAssertNil(managed.anyObjectIdObj[@"key1"]); uncheckedAssertNil(managed.anyUUIDObj[@"key1"]); } - (void)testUpdateObjects { [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(optUnmanaged.boolObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 2U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 2U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 2U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 2U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 2U); uncheckedAssertEqual(managed.anyBoolObj.count, 2U); uncheckedAssertEqual(managed.anyIntObj.count, 2U); uncheckedAssertEqual(managed.anyFloatObj.count, 2U); uncheckedAssertEqual(managed.anyDoubleObj.count, 2U); uncheckedAssertEqual(managed.anyStringObj.count, 2U); uncheckedAssertEqual(managed.anyDataObj.count, 2U); uncheckedAssertEqual(managed.anyDateObj.count, 2U); uncheckedAssertEqual(managed.anyDecimalObj.count, 2U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 2U); uncheckedAssertEqual(managed.anyUUIDObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.boolObj[@"key2"], @YES); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.boolObj[@"key2"], @YES); uncheckedAssertEqualObjects(optManaged.boolObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.intObj[@"key2"], @3); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.intObj[@"key2"], @3); uncheckedAssertEqualObjects(optManaged.intObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key2"], @"foo"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.stringObj[@"key2"], @"foo"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.dateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.floatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.doubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.dataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.decimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.objectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(managed.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key2"], NSNull.null); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key2"], @YES); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key2"], @3); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key2"], @"b"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key2"], @YES); uncheckedAssertEqualObjects(managed.anyIntObj[@"key2"], @3); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key2"], @3.3f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key2"], @3.3); uncheckedAssertEqualObjects(managed.anyStringObj[@"key2"], @"b"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key2"], data(2)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key2"], date(2)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key2"], decimal128(3)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key2"], objectId(2)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); unmanaged.boolObj[@"key2"] = unmanaged.boolObj[@"key1"]; optUnmanaged.boolObj[@"key2"] = optUnmanaged.boolObj[@"key1"]; managed.boolObj[@"key2"] = managed.boolObj[@"key1"]; optManaged.boolObj[@"key2"] = optManaged.boolObj[@"key1"]; unmanaged.intObj[@"key2"] = unmanaged.intObj[@"key1"]; optUnmanaged.intObj[@"key2"] = optUnmanaged.intObj[@"key1"]; managed.intObj[@"key2"] = managed.intObj[@"key1"]; optManaged.intObj[@"key2"] = optManaged.intObj[@"key1"]; unmanaged.stringObj[@"key2"] = unmanaged.stringObj[@"key1"]; optUnmanaged.stringObj[@"key2"] = optUnmanaged.stringObj[@"key1"]; managed.stringObj[@"key2"] = managed.stringObj[@"key1"]; optManaged.stringObj[@"key2"] = optManaged.stringObj[@"key1"]; unmanaged.dateObj[@"key2"] = unmanaged.dateObj[@"key1"]; optUnmanaged.dateObj[@"key2"] = optUnmanaged.dateObj[@"key1"]; managed.dateObj[@"key2"] = managed.dateObj[@"key1"]; optManaged.dateObj[@"key2"] = optManaged.dateObj[@"key1"]; unmanaged.floatObj[@"key2"] = unmanaged.floatObj[@"key1"]; optUnmanaged.floatObj[@"key2"] = optUnmanaged.floatObj[@"key1"]; managed.floatObj[@"key2"] = managed.floatObj[@"key1"]; optManaged.floatObj[@"key2"] = optManaged.floatObj[@"key1"]; unmanaged.doubleObj[@"key2"] = unmanaged.doubleObj[@"key1"]; optUnmanaged.doubleObj[@"key2"] = optUnmanaged.doubleObj[@"key1"]; managed.doubleObj[@"key2"] = managed.doubleObj[@"key1"]; optManaged.doubleObj[@"key2"] = optManaged.doubleObj[@"key1"]; unmanaged.dataObj[@"key2"] = unmanaged.dataObj[@"key1"]; optUnmanaged.dataObj[@"key2"] = optUnmanaged.dataObj[@"key1"]; managed.dataObj[@"key2"] = managed.dataObj[@"key1"]; optManaged.dataObj[@"key2"] = optManaged.dataObj[@"key1"]; unmanaged.decimalObj[@"key2"] = unmanaged.decimalObj[@"key1"]; optUnmanaged.decimalObj[@"key2"] = optUnmanaged.decimalObj[@"key1"]; managed.decimalObj[@"key2"] = managed.decimalObj[@"key1"]; optManaged.decimalObj[@"key2"] = optManaged.decimalObj[@"key1"]; unmanaged.objectIdObj[@"key2"] = unmanaged.objectIdObj[@"key1"]; optUnmanaged.objectIdObj[@"key2"] = optUnmanaged.objectIdObj[@"key1"]; managed.objectIdObj[@"key2"] = managed.objectIdObj[@"key1"]; optManaged.objectIdObj[@"key2"] = optManaged.objectIdObj[@"key1"]; unmanaged.uuidObj[@"key2"] = unmanaged.uuidObj[@"key1"]; optUnmanaged.uuidObj[@"key2"] = optUnmanaged.uuidObj[@"key1"]; managed.uuidObj[@"key2"] = managed.uuidObj[@"key1"]; optManaged.uuidObj[@"key2"] = optManaged.uuidObj[@"key1"]; unmanaged.anyBoolObj[@"key2"] = unmanaged.anyBoolObj[@"key1"]; unmanaged.anyIntObj[@"key2"] = unmanaged.anyIntObj[@"key1"]; unmanaged.anyFloatObj[@"key2"] = unmanaged.anyFloatObj[@"key1"]; unmanaged.anyDoubleObj[@"key2"] = unmanaged.anyDoubleObj[@"key1"]; unmanaged.anyStringObj[@"key2"] = unmanaged.anyStringObj[@"key1"]; unmanaged.anyDataObj[@"key2"] = unmanaged.anyDataObj[@"key1"]; unmanaged.anyDateObj[@"key2"] = unmanaged.anyDateObj[@"key1"]; unmanaged.anyDecimalObj[@"key2"] = unmanaged.anyDecimalObj[@"key1"]; unmanaged.anyObjectIdObj[@"key2"] = unmanaged.anyObjectIdObj[@"key1"]; unmanaged.anyUUIDObj[@"key2"] = unmanaged.anyUUIDObj[@"key1"]; managed.anyBoolObj[@"key2"] = managed.anyBoolObj[@"key1"]; managed.anyIntObj[@"key2"] = managed.anyIntObj[@"key1"]; managed.anyFloatObj[@"key2"] = managed.anyFloatObj[@"key1"]; managed.anyDoubleObj[@"key2"] = managed.anyDoubleObj[@"key1"]; managed.anyStringObj[@"key2"] = managed.anyStringObj[@"key1"]; managed.anyDataObj[@"key2"] = managed.anyDataObj[@"key1"]; managed.anyDateObj[@"key2"] = managed.anyDateObj[@"key1"]; managed.anyDecimalObj[@"key2"] = managed.anyDecimalObj[@"key1"]; managed.anyObjectIdObj[@"key2"] = managed.anyObjectIdObj[@"key1"]; managed.anyUUIDObj[@"key2"] = managed.anyUUIDObj[@"key1"]; unmanaged.boolObj[@"key1"] = unmanaged.boolObj[@"key2"]; optUnmanaged.boolObj[@"key1"] = optUnmanaged.boolObj[@"key2"]; managed.boolObj[@"key1"] = managed.boolObj[@"key2"]; optManaged.boolObj[@"key1"] = optManaged.boolObj[@"key2"]; unmanaged.intObj[@"key1"] = unmanaged.intObj[@"key2"]; optUnmanaged.intObj[@"key1"] = optUnmanaged.intObj[@"key2"]; managed.intObj[@"key1"] = managed.intObj[@"key2"]; optManaged.intObj[@"key1"] = optManaged.intObj[@"key2"]; unmanaged.stringObj[@"key1"] = unmanaged.stringObj[@"key2"]; optUnmanaged.stringObj[@"key1"] = optUnmanaged.stringObj[@"key2"]; managed.stringObj[@"key1"] = managed.stringObj[@"key2"]; optManaged.stringObj[@"key1"] = optManaged.stringObj[@"key2"]; unmanaged.dateObj[@"key1"] = unmanaged.dateObj[@"key2"]; optUnmanaged.dateObj[@"key1"] = optUnmanaged.dateObj[@"key2"]; managed.dateObj[@"key1"] = managed.dateObj[@"key2"]; optManaged.dateObj[@"key1"] = optManaged.dateObj[@"key2"]; unmanaged.floatObj[@"key1"] = unmanaged.floatObj[@"key2"]; optUnmanaged.floatObj[@"key1"] = optUnmanaged.floatObj[@"key2"]; managed.floatObj[@"key1"] = managed.floatObj[@"key2"]; optManaged.floatObj[@"key1"] = optManaged.floatObj[@"key2"]; unmanaged.doubleObj[@"key1"] = unmanaged.doubleObj[@"key2"]; optUnmanaged.doubleObj[@"key1"] = optUnmanaged.doubleObj[@"key2"]; managed.doubleObj[@"key1"] = managed.doubleObj[@"key2"]; optManaged.doubleObj[@"key1"] = optManaged.doubleObj[@"key2"]; unmanaged.dataObj[@"key1"] = unmanaged.dataObj[@"key2"]; optUnmanaged.dataObj[@"key1"] = optUnmanaged.dataObj[@"key2"]; managed.dataObj[@"key1"] = managed.dataObj[@"key2"]; optManaged.dataObj[@"key1"] = optManaged.dataObj[@"key2"]; unmanaged.decimalObj[@"key1"] = unmanaged.decimalObj[@"key2"]; optUnmanaged.decimalObj[@"key1"] = optUnmanaged.decimalObj[@"key2"]; managed.decimalObj[@"key1"] = managed.decimalObj[@"key2"]; optManaged.decimalObj[@"key1"] = optManaged.decimalObj[@"key2"]; unmanaged.objectIdObj[@"key1"] = unmanaged.objectIdObj[@"key2"]; optUnmanaged.objectIdObj[@"key1"] = optUnmanaged.objectIdObj[@"key2"]; managed.objectIdObj[@"key1"] = managed.objectIdObj[@"key2"]; optManaged.objectIdObj[@"key1"] = optManaged.objectIdObj[@"key2"]; unmanaged.uuidObj[@"key1"] = unmanaged.uuidObj[@"key2"]; optUnmanaged.uuidObj[@"key1"] = optUnmanaged.uuidObj[@"key2"]; managed.uuidObj[@"key1"] = managed.uuidObj[@"key2"]; optManaged.uuidObj[@"key1"] = optManaged.uuidObj[@"key2"]; unmanaged.anyBoolObj[@"key1"] = unmanaged.anyBoolObj[@"key2"]; unmanaged.anyIntObj[@"key1"] = unmanaged.anyIntObj[@"key2"]; unmanaged.anyFloatObj[@"key1"] = unmanaged.anyFloatObj[@"key2"]; unmanaged.anyDoubleObj[@"key1"] = unmanaged.anyDoubleObj[@"key2"]; unmanaged.anyStringObj[@"key1"] = unmanaged.anyStringObj[@"key2"]; unmanaged.anyDataObj[@"key1"] = unmanaged.anyDataObj[@"key2"]; unmanaged.anyDateObj[@"key1"] = unmanaged.anyDateObj[@"key2"]; unmanaged.anyDecimalObj[@"key1"] = unmanaged.anyDecimalObj[@"key2"]; unmanaged.anyObjectIdObj[@"key1"] = unmanaged.anyObjectIdObj[@"key2"]; unmanaged.anyUUIDObj[@"key1"] = unmanaged.anyUUIDObj[@"key2"]; managed.anyBoolObj[@"key1"] = managed.anyBoolObj[@"key2"]; managed.anyIntObj[@"key1"] = managed.anyIntObj[@"key2"]; managed.anyFloatObj[@"key1"] = managed.anyFloatObj[@"key2"]; managed.anyDoubleObj[@"key1"] = managed.anyDoubleObj[@"key2"]; managed.anyStringObj[@"key1"] = managed.anyStringObj[@"key2"]; managed.anyDataObj[@"key1"] = managed.anyDataObj[@"key2"]; managed.anyDateObj[@"key1"] = managed.anyDateObj[@"key2"]; managed.anyDecimalObj[@"key1"] = managed.anyDecimalObj[@"key2"]; managed.anyObjectIdObj[@"key1"] = managed.anyObjectIdObj[@"key2"]; managed.anyUUIDObj[@"key1"] = managed.anyUUIDObj[@"key2"]; uncheckedAssertEqualObjects(unmanaged.boolObj[@"key2"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key2"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key2"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key2"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key2"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key2"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key2"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key2"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key2"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key2"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key2"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key2"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key2"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key2"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key2"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key2"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key2"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key2"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key2"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key2"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key2"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key2"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key2"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key2"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key2"], uuid(@"00000000-0000-0000-0000-000000000000")); } - (void)testIndexOfObjectSorted { [unmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optUnmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [managed.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optManaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [unmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optUnmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [managed.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optManaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [unmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optUnmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [managed.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optManaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [unmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optUnmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [managed.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optManaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [unmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optUnmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [managed.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optManaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [unmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optUnmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [managed.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optManaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [unmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optUnmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [managed.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optManaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [unmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optUnmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [managed.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optManaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [unmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optUnmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [managed.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optManaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [unmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optUnmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [managed.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optManaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [unmanaged.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [unmanaged.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [unmanaged.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [unmanaged.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [unmanaged.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [unmanaged.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [unmanaged.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [unmanaged.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [unmanaged.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [unmanaged.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [managed.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [managed.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [managed.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [managed.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [managed.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [managed.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [managed.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [managed.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [managed.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [managed.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; uncheckedAssertEqual(0U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2]); uncheckedAssertEqual(0U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"bar"]); uncheckedAssertEqual(0U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)]); uncheckedAssertEqual(1U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO]); uncheckedAssertEqual(1U, [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2]); uncheckedAssertEqual(1U, [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"bar"]); uncheckedAssertEqual(1U, [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)]); uncheckedAssertEqual(1U, [[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO]); uncheckedAssertEqual(1U, [[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2]); uncheckedAssertEqual(1U, [[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f]); uncheckedAssertEqual(1U, [[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2]); uncheckedAssertEqual(1U, [[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"]); uncheckedAssertEqual(1U, [[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)]); uncheckedAssertEqual(1U, [[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)]); uncheckedAssertEqual(1U, [[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)]); uncheckedAssertEqual(1U, [[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(0U, [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES]); uncheckedAssertEqual(0U, [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3]); uncheckedAssertEqual(0U, [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"foo"]); uncheckedAssertEqual(0U, [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)]); uncheckedAssertEqual(0U, [[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES]); uncheckedAssertEqual(0U, [[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3]); uncheckedAssertEqual(0U, [[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f]); uncheckedAssertEqual(0U, [[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3]); uncheckedAssertEqual(0U, [[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"b"]); uncheckedAssertEqual(0U, [[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)]); uncheckedAssertEqual(0U, [[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)]); uncheckedAssertEqual(0U, [[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(3)]); uncheckedAssertEqual(0U, [[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(1U, [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); } - (void)testIndexOfObjectDistinct { [unmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optUnmanaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [managed.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [optManaged.boolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": NSNull.null }]; [unmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optUnmanaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [managed.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [optManaged.intObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": NSNull.null }]; [unmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optUnmanaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [managed.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": @"foo" }]; [optManaged.stringObj addEntriesFromDictionary:@{ @"key1": @"bar", @"key2": NSNull.null }]; [unmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optUnmanaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [managed.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [optManaged.dateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": NSNull.null }]; [unmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optUnmanaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [managed.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [optManaged.floatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": NSNull.null }]; [unmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optUnmanaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [managed.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [optManaged.doubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": NSNull.null }]; [unmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optUnmanaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [managed.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [optManaged.dataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": NSNull.null }]; [unmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optUnmanaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [managed.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [optManaged.decimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": NSNull.null }]; [unmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optUnmanaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [managed.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [optManaged.objectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": NSNull.null }]; [unmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optUnmanaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [managed.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [optManaged.uuidObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }]; [unmanaged.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [unmanaged.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [unmanaged.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [unmanaged.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [unmanaged.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [unmanaged.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [unmanaged.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [unmanaged.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [unmanaged.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [unmanaged.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; [managed.anyBoolObj addEntriesFromDictionary:@{ @"key1": @NO, @"key2": @YES }]; [managed.anyIntObj addEntriesFromDictionary:@{ @"key1": @2, @"key2": @3 }]; [managed.anyFloatObj addEntriesFromDictionary:@{ @"key1": @2.2f, @"key2": @3.3f }]; [managed.anyDoubleObj addEntriesFromDictionary:@{ @"key1": @2.2, @"key2": @3.3 }]; [managed.anyStringObj addEntriesFromDictionary:@{ @"key1": @"a", @"key2": @"b" }]; [managed.anyDataObj addEntriesFromDictionary:@{ @"key1": data(1), @"key2": data(2) }]; [managed.anyDateObj addEntriesFromDictionary:@{ @"key1": date(1), @"key2": date(2) }]; [managed.anyDecimalObj addEntriesFromDictionary:@{ @"key1": decimal128(2), @"key2": decimal128(3) }]; [managed.anyObjectIdObj addEntriesFromDictionary:@{ @"key1": objectId(1), @"key2": objectId(2) }]; [managed.anyUUIDObj addEntriesFromDictionary:@{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }]; uncheckedAssertEqual(0U, [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2]); uncheckedAssertEqual(0U, [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"bar"]); uncheckedAssertEqual(0U, [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)]); uncheckedAssertEqual(0U, [[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2]); uncheckedAssertEqual(0U, [[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f]); uncheckedAssertEqual(0U, [[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2]); uncheckedAssertEqual(0U, [[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"]); uncheckedAssertEqual(0U, [[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)]); uncheckedAssertEqual(0U, [[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)]); uncheckedAssertEqual(0U, [[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)]); uncheckedAssertEqual(0U, [[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertEqual(1U, [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES]); uncheckedAssertEqual(1U, [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3]); uncheckedAssertEqual(1U, [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"foo"]); uncheckedAssertEqual(1U, [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)]); uncheckedAssertEqual(1U, [[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES]); uncheckedAssertEqual(1U, [[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3]); uncheckedAssertEqual(1U, [[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f]); uncheckedAssertEqual(1U, [[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3]); uncheckedAssertEqual(1U, [[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"b"]); uncheckedAssertEqual(1U, [[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)]); uncheckedAssertEqual(1U, [[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)]); uncheckedAssertEqual(1U, [[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(3)]); uncheckedAssertEqual(1U, [[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertEqual(0U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO]); uncheckedAssertEqual(0U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2]); uncheckedAssertEqual(0U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"bar"]); uncheckedAssertEqual(0U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)]); uncheckedAssertEqual(1U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); uncheckedAssertEqual(1U, [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); } - (void)testSort { RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyBoolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyIntObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyFloatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDoubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyStringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDecimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyUUIDObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); [unmanaged.boolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": @YES}]; [optUnmanaged.boolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": NSNull.null}]; [managed.boolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": @YES}]; [optManaged.boolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": NSNull.null}]; [unmanaged.intObj addEntriesFromDictionary:@{@"key1": @2, @"key2": @3}]; [optUnmanaged.intObj addEntriesFromDictionary:@{@"key1": @2, @"key2": NSNull.null}]; [managed.intObj addEntriesFromDictionary:@{@"key1": @2, @"key2": @3}]; [optManaged.intObj addEntriesFromDictionary:@{@"key1": @2, @"key2": NSNull.null}]; [unmanaged.stringObj addEntriesFromDictionary:@{@"key1": @"bar", @"key2": @"foo"}]; [optUnmanaged.stringObj addEntriesFromDictionary:@{@"key1": @"bar", @"key2": NSNull.null}]; [managed.stringObj addEntriesFromDictionary:@{@"key1": @"bar", @"key2": @"foo"}]; [optManaged.stringObj addEntriesFromDictionary:@{@"key1": @"bar", @"key2": NSNull.null}]; [unmanaged.dateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": date(2)}]; [optUnmanaged.dateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": NSNull.null}]; [managed.dateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": date(2)}]; [optManaged.dateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": NSNull.null}]; [unmanaged.floatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": @3.3f}]; [optUnmanaged.floatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": NSNull.null}]; [managed.floatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": @3.3f}]; [optManaged.floatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": NSNull.null}]; [unmanaged.doubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": @3.3}]; [optUnmanaged.doubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": NSNull.null}]; [managed.doubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": @3.3}]; [optManaged.doubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": NSNull.null}]; [unmanaged.dataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": data(2)}]; [optUnmanaged.dataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": NSNull.null}]; [managed.dataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": data(2)}]; [optManaged.dataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": NSNull.null}]; [unmanaged.decimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": decimal128(3)}]; [optUnmanaged.decimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": NSNull.null}]; [managed.decimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": decimal128(3)}]; [optManaged.decimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": NSNull.null}]; [unmanaged.objectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": objectId(2)}]; [optUnmanaged.objectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": NSNull.null}]; [managed.objectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": objectId(2)}]; [optManaged.objectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": NSNull.null}]; [unmanaged.uuidObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}]; [optUnmanaged.uuidObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null}]; [managed.uuidObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}]; [optManaged.uuidObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null}]; [unmanaged.anyBoolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": @YES}]; [unmanaged.anyIntObj addEntriesFromDictionary:@{@"key1": @2, @"key2": @3}]; [unmanaged.anyFloatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": @3.3f}]; [unmanaged.anyDoubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": @3.3}]; [unmanaged.anyStringObj addEntriesFromDictionary:@{@"key1": @"a", @"key2": @"b"}]; [unmanaged.anyDataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": data(2)}]; [unmanaged.anyDateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": date(2)}]; [unmanaged.anyDecimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": decimal128(3)}]; [unmanaged.anyObjectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": objectId(2)}]; [unmanaged.anyUUIDObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}]; [managed.anyBoolObj addEntriesFromDictionary:@{@"key1": @NO, @"key2": @YES}]; [managed.anyIntObj addEntriesFromDictionary:@{@"key1": @2, @"key2": @3}]; [managed.anyFloatObj addEntriesFromDictionary:@{@"key1": @2.2f, @"key2": @3.3f}]; [managed.anyDoubleObj addEntriesFromDictionary:@{@"key1": @2.2, @"key2": @3.3}]; [managed.anyStringObj addEntriesFromDictionary:@{@"key1": @"a", @"key2": @"b"}]; [managed.anyDataObj addEntriesFromDictionary:@{@"key1": data(1), @"key2": data(2)}]; [managed.anyDateObj addEntriesFromDictionary:@{@"key1": date(1), @"key2": date(2)}]; [managed.anyDecimalObj addEntriesFromDictionary:@{@"key1": decimal128(2), @"key2": decimal128(3)}]; [managed.anyObjectIdObj addEntriesFromDictionary:@{@"key1": objectId(1), @"key2": objectId(2)}]; [managed.anyUUIDObj addEntriesFromDictionary:@{@"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}]; uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@NO, NSNull.null])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2, NSNull.null])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@"bar", @"foo"])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@"bar", NSNull.null])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[date(1), NSNull.null])); uncheckedAssertEqualObjects([[managed.anyBoolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([[managed.anyIntObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([[managed.anyFloatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([[managed.anyDoubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([[managed.anyStringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([[managed.anyDataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([[managed.anyDateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([[managed.anyDecimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([[managed.anyUUIDObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@YES, @NO])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3, @2])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@"foo", @"bar"])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[date(2), date(1)])); uncheckedAssertEqualObjects([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@YES, @NO])); uncheckedAssertEqualObjects([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3, @2])); uncheckedAssertEqualObjects([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3f, @2.2f])); uncheckedAssertEqualObjects([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@3.3, @2.2])); uncheckedAssertEqualObjects([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@"b", @"a"])); uncheckedAssertEqualObjects([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[data(2), data(1)])); uncheckedAssertEqualObjects([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[date(2), date(1)])); uncheckedAssertEqualObjects([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[decimal128(3), decimal128(2)])); uncheckedAssertEqualObjects([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@NO, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@2, NSNull.null])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[@"bar", NSNull.null])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], (@[date(1), NSNull.null])); uncheckedAssertEqualObjects([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@"bar", @"foo"])); uncheckedAssertEqualObjects([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@NO, @YES])); uncheckedAssertEqualObjects([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2, @3])); uncheckedAssertEqualObjects([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2.2f, @3.3f])); uncheckedAssertEqualObjects([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@2.2, @3.3])); uncheckedAssertEqualObjects([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[@"a", @"b"])); uncheckedAssertEqualObjects([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[data(1), data(2)])); uncheckedAssertEqualObjects([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[date(1), date(2)])); uncheckedAssertEqualObjects([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[decimal128(2), decimal128(3)])); uncheckedAssertEqualObjects([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); uncheckedAssertEqualObjects([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @NO])); uncheckedAssertEqualObjects([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @2])); uncheckedAssertEqualObjects([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, @"bar"])); uncheckedAssertEqualObjects([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], (@[NSNull.null, date(1)])); } - (void)testFilter { RLMAssertThrowsWithReason([unmanaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { RLMAssertThrowsWithReason([unmanaged.boolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.boolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyIntObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyStringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); } - (void)testMin { RLMAssertThrowsWithReason([unmanaged.boolObj minOfProperty:@"self"], @"minOfProperty: is not supported for bool dictionary"); RLMAssertThrowsWithReason([optUnmanaged.boolObj minOfProperty:@"self"], @"minOfProperty: is not supported for bool? dictionary"); RLMAssertThrowsWithReason([unmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string dictionary"); RLMAssertThrowsWithReason([optUnmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string? dictionary"); RLMAssertThrowsWithReason([unmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data? dictionary"); RLMAssertThrowsWithReason([unmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id dictionary"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id? dictionary"); RLMAssertThrowsWithReason([unmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid dictionary"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid? dictionary"); RLMAssertThrowsWithReason([managed.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool dictionary 'AllPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([optManaged.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool? dictionary 'AllOptionalPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string dictionary 'AllPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([optManaged.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string? dictionary 'AllOptionalPrimitiveDictionaries.stringObj'"); uncheckedAssertNil([unmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([managed.intObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj minOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optUnmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([managed.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optManaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([unmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optManaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optManaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optManaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.decimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj minOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj minOfProperty:@"self"], decimal128(2)); } - (void)testMax { RLMAssertThrowsWithReason([unmanaged.boolObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for bool dictionary"); RLMAssertThrowsWithReason([optUnmanaged.boolObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for bool? dictionary"); RLMAssertThrowsWithReason([unmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string dictionary"); RLMAssertThrowsWithReason([optUnmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string? dictionary"); RLMAssertThrowsWithReason([unmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data? dictionary"); RLMAssertThrowsWithReason([unmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id dictionary"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id? dictionary"); RLMAssertThrowsWithReason([unmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid dictionary"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid? dictionary"); RLMAssertThrowsWithReason([managed.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool dictionary 'AllPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([optManaged.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool? dictionary 'AllOptionalPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string dictionary 'AllPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([optManaged.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string? dictionary 'AllOptionalPrimitiveDictionaries.stringObj'"); uncheckedAssertNil([unmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj maxOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([managed.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([unmanaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.decimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([managed.decimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([managed.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.anyDecimalObj maxOfProperty:@"self"], decimal128(3)); uncheckedAssertEqualObjects([optUnmanaged.intObj maxOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optManaged.intObj maxOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optUnmanaged.dateObj maxOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optManaged.dateObj maxOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.floatObj maxOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optManaged.floatObj maxOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj maxOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optManaged.doubleObj maxOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.decimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.decimalObj maxOfProperty:@"self"], decimal128(2)); } - (void)testSum { RLMAssertThrowsWithReason([unmanaged.boolObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for bool dictionary"); RLMAssertThrowsWithReason([optUnmanaged.boolObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for bool? dictionary"); RLMAssertThrowsWithReason([unmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string dictionary"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string? dictionary"); RLMAssertThrowsWithReason([unmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date? dictionary"); RLMAssertThrowsWithReason([unmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data? dictionary"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id dictionary"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id? dictionary"); RLMAssertThrowsWithReason([unmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid dictionary"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid? dictionary"); RLMAssertThrowsWithReason([managed.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool dictionary 'AllPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([optManaged.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool? dictionary 'AllOptionalPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string dictionary 'AllPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([optManaged.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string? dictionary 'AllOptionalPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([managed.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date dictionary 'AllPrimitiveDictionaries.dateObj'"); RLMAssertThrowsWithReason([optManaged.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date? dictionary 'AllOptionalPrimitiveDictionaries.dateObj'"); uncheckedAssertEqualObjects([unmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDecimalObj sumOfProperty:@"self"], @0); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.intObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([optManaged.intObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.floatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.doubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.decimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); } - (void)testAverage { RLMAssertThrowsWithReason([unmanaged.boolObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for bool dictionary"); RLMAssertThrowsWithReason([optUnmanaged.boolObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for bool? dictionary"); RLMAssertThrowsWithReason([unmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string dictionary"); RLMAssertThrowsWithReason([optUnmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string? dictionary"); RLMAssertThrowsWithReason([unmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date? dictionary"); RLMAssertThrowsWithReason([unmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data dictionary"); RLMAssertThrowsWithReason([optUnmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data? dictionary"); RLMAssertThrowsWithReason([unmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id dictionary"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id? dictionary"); RLMAssertThrowsWithReason([unmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid dictionary"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid? dictionary"); RLMAssertThrowsWithReason([managed.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool dictionary 'AllPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([optManaged.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool? dictionary 'AllOptionalPrimitiveDictionaries.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string dictionary 'AllPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([optManaged.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string? dictionary 'AllOptionalPrimitiveDictionaries.stringObj'"); RLMAssertThrowsWithReason([managed.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date dictionary 'AllPrimitiveDictionaries.dateObj'"); RLMAssertThrowsWithReason([optManaged.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date? dictionary 'AllOptionalPrimitiveDictionaries.dateObj'"); uncheckedAssertNil([unmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj averageOfProperty:@"self"]); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.intObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([optManaged.intObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.floatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.doubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([managed.decimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": @YES }; for (id key in unmanaged.boolObj) { id value = unmanaged.boolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.boolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": NSNull.null }; for (id key in optUnmanaged.boolObj) { id value = optUnmanaged.boolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.boolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": @YES }; for (id key in managed.boolObj) { id value = managed.boolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.boolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": NSNull.null }; for (id key in optManaged.boolObj) { id value = optManaged.boolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.boolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": @3 }; for (id key in unmanaged.intObj) { id value = unmanaged.intObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.intObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": NSNull.null }; for (id key in optUnmanaged.intObj) { id value = optUnmanaged.intObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.intObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": @3 }; for (id key in managed.intObj) { id value = managed.intObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.intObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": NSNull.null }; for (id key in optManaged.intObj) { id value = optManaged.intObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.intObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"bar", @"key2": @"foo" }; for (id key in unmanaged.stringObj) { id value = unmanaged.stringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.stringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"bar", @"key2": NSNull.null }; for (id key in optUnmanaged.stringObj) { id value = optUnmanaged.stringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.stringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"bar", @"key2": @"foo" }; for (id key in managed.stringObj) { id value = managed.stringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.stringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"bar", @"key2": NSNull.null }; for (id key in optManaged.stringObj) { id value = optManaged.stringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.stringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": date(2) }; for (id key in unmanaged.dateObj) { id value = unmanaged.dateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.dateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": NSNull.null }; for (id key in optUnmanaged.dateObj) { id value = optUnmanaged.dateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.dateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": date(2) }; for (id key in managed.dateObj) { id value = managed.dateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.dateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": NSNull.null }; for (id key in optManaged.dateObj) { id value = optManaged.dateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.dateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": @3.3f }; for (id key in unmanaged.floatObj) { id value = unmanaged.floatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.floatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": NSNull.null }; for (id key in optUnmanaged.floatObj) { id value = optUnmanaged.floatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.floatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": @3.3f }; for (id key in managed.floatObj) { id value = managed.floatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.floatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": NSNull.null }; for (id key in optManaged.floatObj) { id value = optManaged.floatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.floatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": @3.3 }; for (id key in unmanaged.doubleObj) { id value = unmanaged.doubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.doubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": NSNull.null }; for (id key in optUnmanaged.doubleObj) { id value = optUnmanaged.doubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.doubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": @3.3 }; for (id key in managed.doubleObj) { id value = managed.doubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.doubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": NSNull.null }; for (id key in optManaged.doubleObj) { id value = optManaged.doubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.doubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": data(2) }; for (id key in unmanaged.dataObj) { id value = unmanaged.dataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.dataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": NSNull.null }; for (id key in optUnmanaged.dataObj) { id value = optUnmanaged.dataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.dataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": data(2) }; for (id key in managed.dataObj) { id value = managed.dataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.dataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": NSNull.null }; for (id key in optManaged.dataObj) { id value = optManaged.dataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.dataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": decimal128(3) }; for (id key in unmanaged.decimalObj) { id value = unmanaged.decimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.decimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": NSNull.null }; for (id key in optUnmanaged.decimalObj) { id value = optUnmanaged.decimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.decimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": decimal128(3) }; for (id key in managed.decimalObj) { id value = managed.decimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.decimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": NSNull.null }; for (id key in optManaged.decimalObj) { id value = optManaged.decimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.decimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": objectId(2) }; for (id key in unmanaged.objectIdObj) { id value = unmanaged.objectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.objectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": NSNull.null }; for (id key in optUnmanaged.objectIdObj) { id value = optUnmanaged.objectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.objectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": objectId(2) }; for (id key in managed.objectIdObj) { id value = managed.objectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.objectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": NSNull.null }; for (id key in optManaged.objectIdObj) { id value = optManaged.objectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.objectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }; for (id key in unmanaged.uuidObj) { id value = unmanaged.uuidObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.uuidObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }; for (id key in optUnmanaged.uuidObj) { id value = optUnmanaged.uuidObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optUnmanaged.uuidObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }; for (id key in managed.uuidObj) { id value = managed.uuidObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.uuidObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": NSNull.null }; for (id key in optManaged.uuidObj) { id value = optManaged.uuidObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, optManaged.uuidObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": @YES }; for (id key in unmanaged.anyBoolObj) { id value = unmanaged.anyBoolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyBoolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": @3 }; for (id key in unmanaged.anyIntObj) { id value = unmanaged.anyIntObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyIntObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": @3.3f }; for (id key in unmanaged.anyFloatObj) { id value = unmanaged.anyFloatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyFloatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": @3.3 }; for (id key in unmanaged.anyDoubleObj) { id value = unmanaged.anyDoubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyDoubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"a", @"key2": @"b" }; for (id key in unmanaged.anyStringObj) { id value = unmanaged.anyStringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyStringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": data(2) }; for (id key in unmanaged.anyDataObj) { id value = unmanaged.anyDataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyDataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": date(2) }; for (id key in unmanaged.anyDateObj) { id value = unmanaged.anyDateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyDateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": decimal128(3) }; for (id key in unmanaged.anyDecimalObj) { id value = unmanaged.anyDecimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyDecimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": objectId(2) }; for (id key in unmanaged.anyObjectIdObj) { id value = unmanaged.anyObjectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyObjectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }; for (id key in unmanaged.anyUUIDObj) { id value = unmanaged.anyUUIDObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, unmanaged.anyUUIDObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @NO, @"key2": @YES }; for (id key in managed.anyBoolObj) { id value = managed.anyBoolObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyBoolObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2, @"key2": @3 }; for (id key in managed.anyIntObj) { id value = managed.anyIntObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyIntObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2f, @"key2": @3.3f }; for (id key in managed.anyFloatObj) { id value = managed.anyFloatObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyFloatObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @2.2, @"key2": @3.3 }; for (id key in managed.anyDoubleObj) { id value = managed.anyDoubleObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyDoubleObj.count); }(); ^{ NSDictionary *values = @{ @"key1": @"a", @"key2": @"b" }; for (id key in managed.anyStringObj) { id value = managed.anyStringObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyStringObj.count); }(); ^{ NSDictionary *values = @{ @"key1": data(1), @"key2": data(2) }; for (id key in managed.anyDataObj) { id value = managed.anyDataObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyDataObj.count); }(); ^{ NSDictionary *values = @{ @"key1": date(1), @"key2": date(2) }; for (id key in managed.anyDateObj) { id value = managed.anyDateObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyDateObj.count); }(); ^{ NSDictionary *values = @{ @"key1": decimal128(2), @"key2": decimal128(3) }; for (id key in managed.anyDecimalObj) { id value = managed.anyDecimalObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyDecimalObj.count); }(); ^{ NSDictionary *values = @{ @"key1": objectId(1), @"key2": objectId(2) }; for (id key in managed.anyObjectIdObj) { id value = managed.anyObjectIdObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyObjectIdObj.count); }(); ^{ NSDictionary *values = @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }; for (id key in managed.anyUUIDObj) { id value = managed.anyUUIDObj[key]; uncheckedAssertEqualObjects(values[key], value); } uncheckedAssertEqual(values.count, managed.anyUUIDObj.count); }(); } - (void)testValueForKeyNumericAggregates { uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyIntObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@avg.self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@max.self"], decimal128(3)); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@max.self"], @2); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@max.self"], @2); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@max.self"], date(1)); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@max.self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@max.self"], @2.2f); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@max.self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"], @2.2); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@max.self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(2)); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyIntObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyFloatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDoubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDecimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[managed.anyIntObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[managed.anyFloatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[managed.anyDoubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[managed.anyDecimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": NSNull.null }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyIntObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyFloatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDoubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDecimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); XCTAssertEqualWithAccuracy([[managed.anyIntObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2, @"key2": @3 }), .001); XCTAssertEqualWithAccuracy([[managed.anyFloatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2f, @"key2": @3.3f }), .001); XCTAssertEqualWithAccuracy([[managed.anyDoubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": @2.2, @"key2": @3.3 }), .001); XCTAssertEqualWithAccuracy([[managed.anyDecimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@{ @"key1": decimal128(2), @"key2": decimal128(3) }), .001); } - (void)testSetValueForKey { RLMAssertThrowsWithReason([unmanaged.boolObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([managed.boolObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([optManaged.boolObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optUnmanaged.intObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([managed.intObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([optManaged.intObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj setValue:@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([managed.stringObj setValue:@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([optManaged.stringObj setValue:@2 forKey:@"key1"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([managed.dateObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([optManaged.dateObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([managed.floatObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([optManaged.floatObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([optManaged.doubleObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([managed.dataObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([optManaged.dataObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([optManaged.decimalObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([optManaged.objectIdObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.uuidObj setValue:@"a" forKey:@"key1"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.boolObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.intObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.stringObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.dateObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.floatObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dataObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:(id)NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; uncheckedAssertEqualObjects(unmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.intObj[@"key1"], @2); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(managed.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], @"bar"); uncheckedAssertEqualObjects(unmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(managed.anyBoolObj[@"key1"], @NO); uncheckedAssertEqualObjects(managed.anyIntObj[@"key1"], @2); uncheckedAssertEqualObjects(managed.anyFloatObj[@"key1"], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key1"], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj[@"key1"], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj[@"key1"], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj[@"key1"], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key1"], decimal128(2)); uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key1"], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key1"], uuid(@"00000000-0000-0000-0000-000000000000")); [optUnmanaged.boolObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.boolObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.intObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.intObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.stringObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.stringObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.dateObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.dateObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.floatObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.floatObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.doubleObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.doubleObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.dataObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.dataObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.decimalObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.decimalObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.objectIdObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.objectIdObj setValue:(id)NSNull.null forKey:@"key1"]; [optUnmanaged.uuidObj setValue:(id)NSNull.null forKey:@"key1"]; [optManaged.uuidObj setValue:(id)NSNull.null forKey:@"key1"]; uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key1"], (id)NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj[@"key1"], (id)NSNull.null); } - (void)testAssignment { unmanaged.boolObj = (id)@{@"key2": @YES}; uncheckedAssertEqualObjects(unmanaged.boolObj[@"key2"], @YES); optUnmanaged.boolObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.boolObj[@"key2"], NSNull.null); managed.boolObj = (id)@{@"key2": @YES}; uncheckedAssertEqualObjects(managed.boolObj[@"key2"], @YES); optManaged.boolObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.boolObj[@"key2"], NSNull.null); unmanaged.intObj = (id)@{@"key2": @3}; uncheckedAssertEqualObjects(unmanaged.intObj[@"key2"], @3); optUnmanaged.intObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.intObj[@"key2"], NSNull.null); managed.intObj = (id)@{@"key2": @3}; uncheckedAssertEqualObjects(managed.intObj[@"key2"], @3); optManaged.intObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.intObj[@"key2"], NSNull.null); unmanaged.stringObj = (id)@{@"key2": @"foo"}; uncheckedAssertEqualObjects(unmanaged.stringObj[@"key2"], @"foo"); optUnmanaged.stringObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.stringObj[@"key2"], NSNull.null); managed.stringObj = (id)@{@"key2": @"foo"}; uncheckedAssertEqualObjects(managed.stringObj[@"key2"], @"foo"); optManaged.stringObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.stringObj[@"key2"], NSNull.null); unmanaged.dateObj = (id)@{@"key2": date(2)}; uncheckedAssertEqualObjects(unmanaged.dateObj[@"key2"], date(2)); optUnmanaged.dateObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.dateObj[@"key2"], NSNull.null); managed.dateObj = (id)@{@"key2": date(2)}; uncheckedAssertEqualObjects(managed.dateObj[@"key2"], date(2)); optManaged.dateObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.dateObj[@"key2"], NSNull.null); unmanaged.floatObj = (id)@{@"key2": @3.3f}; uncheckedAssertEqualObjects(unmanaged.floatObj[@"key2"], @3.3f); optUnmanaged.floatObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.floatObj[@"key2"], NSNull.null); managed.floatObj = (id)@{@"key2": @3.3f}; uncheckedAssertEqualObjects(managed.floatObj[@"key2"], @3.3f); optManaged.floatObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.floatObj[@"key2"], NSNull.null); unmanaged.doubleObj = (id)@{@"key2": @3.3}; uncheckedAssertEqualObjects(unmanaged.doubleObj[@"key2"], @3.3); optUnmanaged.doubleObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.doubleObj[@"key2"], NSNull.null); managed.doubleObj = (id)@{@"key2": @3.3}; uncheckedAssertEqualObjects(managed.doubleObj[@"key2"], @3.3); optManaged.doubleObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.doubleObj[@"key2"], NSNull.null); unmanaged.dataObj = (id)@{@"key2": data(2)}; uncheckedAssertEqualObjects(unmanaged.dataObj[@"key2"], data(2)); optUnmanaged.dataObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.dataObj[@"key2"], NSNull.null); managed.dataObj = (id)@{@"key2": data(2)}; uncheckedAssertEqualObjects(managed.dataObj[@"key2"], data(2)); optManaged.dataObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.dataObj[@"key2"], NSNull.null); unmanaged.decimalObj = (id)@{@"key2": decimal128(3)}; uncheckedAssertEqualObjects(unmanaged.decimalObj[@"key2"], decimal128(3)); optUnmanaged.decimalObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.decimalObj[@"key2"], NSNull.null); managed.decimalObj = (id)@{@"key2": decimal128(3)}; uncheckedAssertEqualObjects(managed.decimalObj[@"key2"], decimal128(3)); optManaged.decimalObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.decimalObj[@"key2"], NSNull.null); unmanaged.objectIdObj = (id)@{@"key2": objectId(2)}; uncheckedAssertEqualObjects(unmanaged.objectIdObj[@"key2"], objectId(2)); optUnmanaged.objectIdObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.objectIdObj[@"key2"], NSNull.null); managed.objectIdObj = (id)@{@"key2": objectId(2)}; uncheckedAssertEqualObjects(managed.objectIdObj[@"key2"], objectId(2)); optManaged.objectIdObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.objectIdObj[@"key2"], NSNull.null); unmanaged.uuidObj = (id)@{@"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}; uncheckedAssertEqualObjects(unmanaged.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optUnmanaged.uuidObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optUnmanaged.uuidObj[@"key2"], NSNull.null); managed.uuidObj = (id)@{@"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}; uncheckedAssertEqualObjects(managed.uuidObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optManaged.uuidObj = (id)@{@"key2": NSNull.null}; uncheckedAssertEqualObjects(optManaged.uuidObj[@"key2"], NSNull.null); unmanaged.anyBoolObj = (id)@{@"key2": @YES}; uncheckedAssertEqualObjects(unmanaged.anyBoolObj[@"key2"], @YES); unmanaged.anyIntObj = (id)@{@"key2": @3}; uncheckedAssertEqualObjects(unmanaged.anyIntObj[@"key2"], @3); unmanaged.anyFloatObj = (id)@{@"key2": @3.3f}; uncheckedAssertEqualObjects(unmanaged.anyFloatObj[@"key2"], @3.3f); unmanaged.anyDoubleObj = (id)@{@"key2": @3.3}; uncheckedAssertEqualObjects(unmanaged.anyDoubleObj[@"key2"], @3.3); unmanaged.anyStringObj = (id)@{@"key2": @"b"}; uncheckedAssertEqualObjects(unmanaged.anyStringObj[@"key2"], @"b"); unmanaged.anyDataObj = (id)@{@"key2": data(2)}; uncheckedAssertEqualObjects(unmanaged.anyDataObj[@"key2"], data(2)); unmanaged.anyDateObj = (id)@{@"key2": date(2)}; uncheckedAssertEqualObjects(unmanaged.anyDateObj[@"key2"], date(2)); unmanaged.anyDecimalObj = (id)@{@"key2": decimal128(3)}; uncheckedAssertEqualObjects(unmanaged.anyDecimalObj[@"key2"], decimal128(3)); unmanaged.anyObjectIdObj = (id)@{@"key2": objectId(2)}; uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj[@"key2"], objectId(2)); unmanaged.anyUUIDObj = (id)@{@"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}; uncheckedAssertEqualObjects(unmanaged.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed.anyBoolObj = (id)@{@"key2": @YES}; uncheckedAssertEqualObjects(managed.anyBoolObj[@"key2"], @YES); managed.anyIntObj = (id)@{@"key2": @3}; uncheckedAssertEqualObjects(managed.anyIntObj[@"key2"], @3); managed.anyFloatObj = (id)@{@"key2": @3.3f}; uncheckedAssertEqualObjects(managed.anyFloatObj[@"key2"], @3.3f); managed.anyDoubleObj = (id)@{@"key2": @3.3}; uncheckedAssertEqualObjects(managed.anyDoubleObj[@"key2"], @3.3); managed.anyStringObj = (id)@{@"key2": @"b"}; uncheckedAssertEqualObjects(managed.anyStringObj[@"key2"], @"b"); managed.anyDataObj = (id)@{@"key2": data(2)}; uncheckedAssertEqualObjects(managed.anyDataObj[@"key2"], data(2)); managed.anyDateObj = (id)@{@"key2": date(2)}; uncheckedAssertEqualObjects(managed.anyDateObj[@"key2"], date(2)); managed.anyDecimalObj = (id)@{@"key2": decimal128(3)}; uncheckedAssertEqualObjects(managed.anyDecimalObj[@"key2"], decimal128(3)); managed.anyObjectIdObj = (id)@{@"key2": objectId(2)}; uncheckedAssertEqualObjects(managed.anyObjectIdObj[@"key2"], objectId(2)); managed.anyUUIDObj = (id)@{@"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")}; uncheckedAssertEqualObjects(managed.anyUUIDObj[@"key2"], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqual(unmanaged.intObj.count, 1); uncheckedAssertEqualObjects(unmanaged.intObj.allValues, managed.intObj.allValues); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqual(managed.intObj.count, 1); uncheckedAssertEqualObjects(managed.intObj.allValues, unmanaged.intObj.allValues); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@{@"0": (id)NSNull.null}, @"Invalid value '' of type 'NSNull' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@{@"0": @"a"}, @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@{@"0": @1, @"1": @"a"}), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@{@"0": (id)NSNull.null}, @"Invalid value '' of type 'NSNull' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@{@"0": @"a"}, @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@{@"0": @1, @"1": @"a"}), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); } - (void)testAllMethodsCheckThread { RLMDictionary *dictionary = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([dictionary count], @"thread"); RLMAssertThrowsWithReason(dictionary[@"0"], @"thread"); RLMAssertThrowsWithReason([dictionary count], @"thread"); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"thread": @0}], @"thread"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"thread"]], @"thread"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"thread"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(dictionary[@"thread"], @"thread"); RLMAssertThrowsWithReason(dictionary[@"thread"] = @0, @"thread"); RLMAssertThrowsWithReason([dictionary valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in dictionary);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMDictionary *dictionary = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); RLMAssertThrowsWithReason([dictionary count], @"invalidated"); uncheckedAssertNil(dictionary[@"0"]); RLMAssertThrowsWithReason([dictionary count], @"invalidated"); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"thread"], @"invalidated"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"invalidated": @0}], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"invalidated"], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"invalidated"]], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"invalidated"], @"invalidated"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(dictionary[@"invalidated"] = @0, @"invalidated"); uncheckedAssertNil([dictionary valueForKey:@"self"]); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in dictionary);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMDictionary *dictionary = managed.intObj; [dictionary setObject:@0 forKey:@"testKey"]; [realm commitWriteTransaction]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); XCTAssertNoThrow([dictionary count]); XCTAssertNoThrow(dictionary[@"0"]); XCTAssertNoThrow([dictionary count]); XCTAssertNoThrow([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(dictionary[@"0"]); XCTAssertNoThrow([dictionary valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in dictionary);})); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"testKey": @0}], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"testKey"]], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason(dictionary[@"testKey"] = @0, @"write transaction"); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMDictionary *dictionary = managed.intObj; uncheckedAssertFalse(dictionary.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(dictionary.isInvalidated); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block bool second = false; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else if (!second) { uncheckedAssertEqualObjects(change.insertions, @[@"testKey"]); } else { uncheckedAssertEqualObjects(change.deletions, @[@"testKey"]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; dictionary[@"testKey"] = @0; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; second = true; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; [dictionary removeObjectForKey:@"testKey"]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMDictionary *dictionary, __unused RLMDictionaryChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveDictionaries createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, __unused RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; dictionary[@"testKey"] = @0; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObject { id boolObj = @{@"key1": @NO}; id intObj = @{@"key1": @2}; id stringObj = @{@"key1": @"bar"}; id dateObj = @{@"key1": date(1)}; id anyBoolObj = @{@"key1": @NO}; id anyIntObj = @{@"key1": @2}; id anyFloatObj = @{@"key1": @2.2f}; id anyDoubleObj = @{@"key1": @2.2}; id anyStringObj = @{@"key1": @"a"}; id anyDataObj = @{@"key1": data(1)}; id anyDateObj = @{@"key1": date(1)}; id anyDecimalObj = @{@"key1": decimal128(2)}; id anyUUIDObj = @{@"key1": uuid(@"00000000-0000-0000-0000-000000000000")}; id obj = [AllPrimitiveDictionaries createInRealm:realm withValue: @{ @"boolObj": boolObj, @"intObj": intObj, @"stringObj": stringObj, @"dateObj": dateObj, @"anyBoolObj": anyBoolObj, @"anyIntObj": anyIntObj, @"anyFloatObj": anyFloatObj, @"anyDoubleObj": anyDoubleObj, @"anyStringObj": anyStringObj, @"anyDataObj": anyDataObj, @"anyDateObj": anyDateObj, @"anyDecimalObj": anyDecimalObj, @"anyUUIDObj": anyUUIDObj, }]; [LinkToAllPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"boolObj": boolObj, @"intObj": intObj, @"stringObj": stringObj, @"dateObj": dateObj, }]; [LinkToAllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj = %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj = %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj != %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj != %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj > %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj > %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj >= %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj >= %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj < %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj < %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj <= %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj <= %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj <= %@", decimal128(2)); [self createObject]; RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj = %@", @YES); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj = %@", @3); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj = %@", @"foo"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj = %@", @YES); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj = %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj = %@", @"b"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj = %@", data(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj = %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj = %@", decimal128(3)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj = %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY stringObj = %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj != %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj != %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY boolObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj != %@", @"foo"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY stringObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyBoolObj != %@", @YES); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyIntObj != %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyStringObj != %@", @"b"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDataObj != %@", data(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDateObj != %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj != %@", decimal128(3)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj > %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj > %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj > %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj >= %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY stringObj >= %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj < %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj < %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj < %@", @"foo"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj < %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj <= %@", @"bar"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY stringObj <= %@", @"bar"); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj <= %@", decimal128(2)); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"ANY boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"ANY boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); } - (void)testQueryBetween { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[@NO, @YES]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[@NO, NSNull.null]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[@"bar", @"foo"]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[@"bar", NSNull.null]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj BETWEEN %@", @[@2, NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj BETWEEN %@", @[date(1), NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); [self createObject]; RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(3), decimal128(3)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj BETWEEN %@", @[@2, NSNull.null]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj BETWEEN %@", @[date(1), NSNull.null]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj BETWEEN %@", @[NSNull.null, NSNull.null]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj BETWEEN %@", @[NSNull.null, NSNull.null]); } - (void)testQueryIn { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj IN %@", @[@NO, NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj IN %@", @[@2, NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj IN %@", @[@"bar", @"foo"]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj IN %@", @[@"bar", NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj IN %@", @[date(1), NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); [self createObject]; RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY boolObj IN %@", @[@YES]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY boolObj IN %@", @[NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY intObj IN %@", @[@3]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY intObj IN %@", @[NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY stringObj IN %@", @[@"foo"]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY stringObj IN %@", @[NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY dateObj IN %@", @[date(2)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"ANY dateObj IN %@", @[NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyBoolObj IN %@", @[@YES]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyIntObj IN %@", @[@3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyFloatObj IN %@", @[@3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDoubleObj IN %@", @[@3.3]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyStringObj IN %@", @[@"b"]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDataObj IN %@", @[data(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDateObj IN %@", @[date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyDecimalObj IN %@", @[decimal128(3)]); RLMAssertCount(AllPrimitiveDictionaries, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY boolObj IN %@", @[@NO, NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY intObj IN %@", @[@2, NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY stringObj IN %@", @[@"bar", @"foo"]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY stringObj IN %@", @[@"bar", NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"ANY dateObj IN %@", @[date(1), NSNull.null]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyDecimalObj IN %@", @[decimal128(2), decimal128(3)]); RLMAssertCount(AllPrimitiveDictionaries, 1, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"boolObj": @{ @"key1": @NO, @"key2": @YES }, @"intObj": @{ @"key1": @2, @"key2": @3 }, @"stringObj": @{ @"key1": @"bar", @"key2": @"foo" }, @"dateObj": @{ @"key1": date(1), @"key2": date(2) }, @"anyBoolObj": @{ @"key1": @NO, @"key2": @YES }, @"anyIntObj": @{ @"key1": @2, @"key2": @3 }, @"anyFloatObj": @{ @"key1": @2.2f, @"key2": @3.3f }, @"anyDoubleObj": @{ @"key1": @2.2, @"key2": @3.3 }, @"anyStringObj": @{ @"key1": @"a", @"key2": @"b" }, @"anyDataObj": @{ @"key1": data(1), @"key2": data(2) }, @"anyDateObj": @{ @"key1": date(1), @"key2": date(2) }, @"anyDecimalObj": @{ @"key1": decimal128(2), @"key2": decimal128(3) }, @"anyUUIDObj": @{ @"key1": uuid(@"00000000-0000-0000-0000-000000000000"), @"key2": uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") }, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"boolObj": @{ @"key1": @NO, @"key2": NSNull.null }, @"intObj": @{ @"key1": @2, @"key2": NSNull.null }, @"stringObj": @{ @"key1": @"bar", @"key2": NSNull.null }, @"dateObj": @{ @"key1": date(1), @"key2": NSNull.null }, }]; RLMAssertCount(AllPrimitiveDictionaries, 1U, @"boolObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"boolObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"stringObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"stringObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyBoolObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyIntObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyStringObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDataObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyUUIDObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"boolObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"boolObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"stringObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"stringObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"dateObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyBoolObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyIntObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyStringObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDataObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyUUIDObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"boolObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"boolObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"intObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"intObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"stringObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"stringObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"dateObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"dateObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyBoolObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyIntObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyFloatObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDoubleObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyStringObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDataObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDateObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDecimalObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyUUIDObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"boolObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"boolObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"intObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"intObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"stringObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"stringObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"dateObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"dateObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyBoolObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyIntObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyFloatObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDoubleObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyStringObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDataObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDateObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDecimalObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyUUIDObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"boolObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"boolObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"intObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"intObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"stringObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"stringObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"dateObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0, @"dateObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyBoolObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyIntObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyFloatObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDoubleObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyStringObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDataObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDateObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyDecimalObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 0, @"anyUUIDObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"boolObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"boolObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"intObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"intObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"stringObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"stringObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"dateObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1, @"dateObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyBoolObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyIntObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyFloatObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDoubleObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyStringObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDataObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDateObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyDecimalObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveDictionaries, 1, @"anyUUIDObj.@count <= %@", @(2)); } - (void)testQuerySum { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@sum = %@", @NO]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@sum = %@", @NO]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@sum = %@", @"bar"]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@sum = %@", @"bar"]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@sum = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@sum = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyIntObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyFloatObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDoubleObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDecimalObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyIntObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type mixed cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyFloatObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type mixed cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDoubleObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type mixed cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDecimalObj.@sum = %@", (id)NSNull.null]), @"@sum on a property of type mixed cannot be compared with ''"); [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{}, @"anyIntObj": @{}, @"anyFloatObj": @{}, @"anyDoubleObj": @{}, @"anyDecimalObj": @{}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @2}, @"anyIntObj": @{@"key1": @2}, @"anyFloatObj": @{@"key1": @2.2f}, @"anyDoubleObj": @{@"key1": @2.2}, @"anyDecimalObj": @{@"key1": decimal128(2)}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @2}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": @3 }, @"anyIntObj": @{ @"key1": @2, @"key2": @3 }, @"anyFloatObj": @{ @"key1": @2.2f, @"key2": @3.3f }, @"anyDoubleObj": @{ @"key1": @2.2, @"key2": @3.3 }, @"anyDecimalObj": @{ @"key1": decimal128(2), @"key2": decimal128(3) }, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": NSNull.null }, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": @3 }, @"anyIntObj": @{ @"key1": @2, @"key2": @3 }, @"anyFloatObj": @{ @"key1": @2.2f, @"key2": @3.3f }, @"anyDoubleObj": @{ @"key1": @2.2, @"key2": @3.3 }, @"anyDecimalObj": @{ @"key1": decimal128(2), @"key2": decimal128(3) }, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": NSNull.null }, }]; RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyIntObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyIntObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@sum == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@sum == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@sum == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 3U, @"intObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"intObj.@sum != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyIntObj.@sum != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyFloatObj.@sum != %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDoubleObj.@sum != %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDecimalObj.@sum != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@sum != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyIntObj.@sum >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyFloatObj.@sum >= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDoubleObj.@sum >= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDecimalObj.@sum >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"intObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyIntObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyFloatObj.@sum > %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDoubleObj.@sum > %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDecimalObj.@sum > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"intObj.@sum < %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyIntObj.@sum < %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyFloatObj.@sum < %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDoubleObj.@sum < %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDecimalObj.@sum < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@sum < %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"intObj.@sum <= %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyIntObj.@sum <= %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyFloatObj.@sum <= %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDoubleObj.@sum <= %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDecimalObj.@sum <= %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 4U, @"intObj.@sum <= %@", @2); } - (void)testQueryAverage { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@avg = %@", @NO]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@avg = %@", @NO]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@avg = %@", @"bar"]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@avg = %@", @"bar"]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@avg = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@avg = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyIntObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyFloatObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDoubleObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDecimalObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{}, @"anyIntObj": @{}, @"anyFloatObj": @{}, @"anyDoubleObj": @{}, @"anyDecimalObj": @{}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @2}, @"anyIntObj": @{@"key1": @2}, @"anyFloatObj": @{@"key1": @2.2f}, @"anyDoubleObj": @{@"key1": @2.2}, @"anyDecimalObj": @{@"key1": decimal128(2)}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @2}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": @3 }, @"anyIntObj": @{ @"key1": @2, @"key2": @3 }, @"anyFloatObj": @{ @"key1": @2.2f, @"key2": @3.3f }, @"anyDoubleObj": @{ @"key1": @2.2, @"key2": @3.3 }, @"anyDecimalObj": @{ @"key1": decimal128(2), @"key2": decimal128(3) }, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{ @"key1": @2, @"key2": NSNull.null }, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @3}, @"anyIntObj": @{@"key1": @3}, @"anyFloatObj": @{@"key1": @3.3f}, @"anyDoubleObj": @{@"key1": @3.3}, @"anyDecimalObj": @{@"key1": decimal128(3)}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"intObj": @{@"key1": @2}, }]; RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyIntObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@avg == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyIntObj.@avg == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@avg == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@avg == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 3U, @"intObj.@avg == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"intObj.@avg != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyIntObj.@avg != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyFloatObj.@avg != %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDoubleObj.@avg != %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDecimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@avg != %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"intObj.@avg >= %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 3U, @"intObj.@avg >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyIntObj.@avg >= %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyFloatObj.@avg >= %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDoubleObj.@avg >= %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDecimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"intObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyIntObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyFloatObj.@avg > %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDoubleObj.@avg > %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDecimalObj.@avg > %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"intObj.@avg < %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyIntObj.@avg < %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyFloatObj.@avg < %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDoubleObj.@avg < %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 2U, @"anyDecimalObj.@avg < %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@avg < %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"intObj.@avg <= %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyIntObj.@avg <= %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyFloatObj.@avg <= %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDoubleObj.@avg <= %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 3U, @"anyDecimalObj.@avg <= %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@avg <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 3U, @"intObj.@avg <= %@", @2); } - (void)testQueryMin { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@min = %@", @NO]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@min = %@", @NO]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@min = %@", @"bar"]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@min = %@", @"bar"]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyFloatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDoubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDecimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@min == %@", decimal128(2)); [AllPrimitiveDictionaries createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{}]; // Only empty dictionarys, so count is zero RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@min == nil"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@min == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@min == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@min == %@", NSNull.null); [self createObject]; // One object where v0 is min and zero with v1 RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@min == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@min == %@", NSNull.null); } - (void)testQueryMax { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@max = %@", @NO]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"boolObj.@max = %@", @NO]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@max = %@", @"bar"]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"stringObj.@max = %@", @"bar"]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyFloatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDoubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveDictionaries objectsInRealm:realm where:@"anyDecimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@max == %@", decimal128(2)); [AllPrimitiveDictionaries createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{}]; // Only empty dictionarys, so count is zero. RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@max == nil"); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@max == %@", NSNull.null); [self createObject]; // One object where v0 is min and zero with v1 RLMAssertCount(AllPrimitiveDictionaries, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveDictionaries, 1U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveDictionaries, 0U, @"anyDecimalObj.@max == %@", decimal128(3)); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveDictionaries, 1U, @"dateObj.@max == %@", NSNull.null); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj = %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj = %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj != %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj != %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj > %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj > %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj >= %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj >= %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj < %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj < %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj <= %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj <= %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj <= %@", decimal128(2)); [self createObject]; RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.boolObj = %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj = %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj = %@", @"foo"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj = %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj = %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyBoolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyIntObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyStringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj = %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.stringObj = %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.stringObj = %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj != %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj != %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj != %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.boolObj != %@", @YES); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.boolObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.intObj != %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.intObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.stringObj != %@", @"foo"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.stringObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.dateObj != %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.dateObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyBoolObj != %@", @YES); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyIntObj != %@", @3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyFloatObj != %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDoubleObj != %@", @3.3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyStringObj != %@", @"b"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDataObj != %@", data(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDateObj != %@", date(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDecimalObj != %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj > %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj > %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj > %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.stringObj >= %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.stringObj >= %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDecimalObj >= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.stringObj < %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj < %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 0, @"ANY link.anyDecimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.stringObj < %@", @"foo"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyFloatObj < %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDoubleObj < %@", @3.3); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDecimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.intObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.stringObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 0, @"ANY link.dateObj < %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.stringObj <= %@", @"bar"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.stringObj <= %@", @"bar"); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveDictionaries, 1, @"ANY link.anyDecimalObj <= %@", decimal128(2)); RLMAssertThrowsWithReason(([LinkToAllPrimitiveDictionaries objectsInRealm:realm where:@"ANY link.boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveDictionaries objectsInRealm:realm where:@"ANY link.boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); } - (void)testSubstringQueries { [realm deleteAllObjects]; NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"stringObj": @{@"key": value}, @"dataObj": @{@"key": [value dataUsingEncoding:NSUTF8StringEncoding]} }]; [LinkToAllPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"stringObj": @{@"key": value}, @"dataObj": @{@"key": [value dataUsingEncoding:NSUTF8StringEncoding]} }]; [LinkToAllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveDictionaries, count, query, value); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, value); RLMAssertCount(AllPrimitiveDictionaries, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveDictionaries, count, query, value); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, value); RLMAssertCount(LinkToAllPrimitiveDictionaries, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveDictionaries, count, query, data); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, data); RLMAssertCount(AllPrimitiveDictionaries, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveDictionaries, count, query, data); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, data); RLMAssertCount(LinkToAllPrimitiveDictionaries, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } @end ================================================ FILE: Realm/Tests/PrimitiveDictionaryPropertyTests.tpl.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSDictionary *dictionary) { NSArray *values = dictionary.allValues; double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSDictionary *dictionary) { NSArray *values = dictionary.allValues; double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveDictionaries : RLMObject @property (nonatomic) AllPrimitiveDictionaries *link; @end @implementation LinkToAllPrimitiveDictionaries @end @interface LinkToAllOptionalPrimitiveDictionaries : RLMObject @property (nonatomic) AllOptionalPrimitiveDictionaries *link; @end @implementation LinkToAllOptionalPrimitiveDictionaries @end @interface PrimitiveDictionaryPropertyTests : RLMTestCase @end @implementation PrimitiveDictionaryPropertyTests { AllPrimitiveDictionaries *unmanaged; AllPrimitiveDictionaries *managed; AllOptionalPrimitiveDictionaries *optUnmanaged; AllOptionalPrimitiveDictionaries *optManaged; RLMRealm *realm; NSArray *allDictionaries; } - (void)setUp { unmanaged = [[AllPrimitiveDictionaries alloc] init]; optUnmanaged = [[AllOptionalPrimitiveDictionaries alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveDictionaries createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[]]; allDictionaries = @[ $dictionary, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [$dictionary addEntriesFromDictionary:$values]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); unmanaged.intObj[@"testVal"] = @1; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMDictionary *dictionary; @autoreleasepool { AllPrimitiveDictionaries *obj = [[AllPrimitiveDictionaries alloc] init]; dictionary = obj.intObj; uncheckedAssertFalse(dictionary.invalidated); } uncheckedAssertFalse(dictionary.invalidated); } - (void)testDeleteObjectsInRealm { %unman RLMAssertThrowsWithReason([realm deleteObjects:$dictionary], @"Cannot delete objects from RLMDictionary"); %man RLMAssertThrowsWithReason([realm deleteObjects:$dictionary], @"Cannot delete objects from RLMManagedDictionary: only RLMObjects can be deleted."); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - (void)testSetObject { // Managed non-optional %man %r uncheckedAssertNil($dictionary[$firstKey]); %man %r XCTAssertNoThrow($dictionary[$firstKey] = $firstValue); %man %r uncheckedAssertEqualObjects($dictionary[$firstKey], $firstValue); %noany %man %r RLMAssertThrowsWithReason($dictionary[$firstKey] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type '$type'."); %man %r XCTAssertNoThrow($dictionary[$firstKey] = nil); %man %r uncheckedAssertNil($dictionary[$firstKey]); // Managed optional %man %o uncheckedAssertNil($dictionary[$firstKey]); %man %o XCTAssertNoThrow($dictionary[$firstKey] = $firstValue); %man %o uncheckedAssertEqualObjects($dictionary[$firstKey], $firstValue); %man %o XCTAssertNoThrow($dictionary[$firstKey] = (id)NSNull.null); %man %o uncheckedAssertEqualObjects($dictionary[$firstKey], (id)NSNull.null); %man %o XCTAssertNoThrow($dictionary[$firstKey] = nil); %man %o uncheckedAssertNil($dictionary[$firstKey]); // Unmanaged non-optional %unman %r uncheckedAssertNil($dictionary[$firstKey]); %unman %r XCTAssertNoThrow($dictionary[$firstKey] = $firstValue); %unman %r uncheckedAssertEqualObjects($dictionary[$firstKey], $firstValue); %noany %unman %r RLMAssertThrowsWithReason($dictionary[$firstKey] = (id)NSNull.null, @"Invalid value '' of type 'NSNull' for expected type '$type'."); %unman %r XCTAssertNoThrow($dictionary[$firstKey] = nil); %unman %r uncheckedAssertNil($dictionary[$firstKey]); // Unmanaged optional %unman %o uncheckedAssertNil($dictionary[$firstKey]); %unman %o XCTAssertNoThrow($dictionary[$firstKey] = $firstValue); %unman %o uncheckedAssertEqualObjects($dictionary[$firstKey], $firstValue); %unman %o XCTAssertNoThrow($dictionary[$firstKey] = (id)NSNull.null); %unman %o uncheckedAssertEqual($dictionary[$firstKey], (id)NSNull.null); %unman %o XCTAssertNoThrow($dictionary[$firstKey] = nil); %unman %o uncheckedAssertNil($dictionary[$firstKey]); // Fail with nil key RLMAssertThrowsWithReason([$dictionary setObject:$firstValue forKey:nil], ^n @"Invalid nil key for dictionary expecting key of type 'string'."); // Fail on set nil for non-optional %noany %r RLMAssertThrowsWithReason([$dictionary setObject:(id)NSNull.null forKey:$firstKey], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); %noany RLMAssertThrowsWithReason([$dictionary setObject:(id)$wrong forKey:$firstKey], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$dictionary setObject:(id)NSNull.null forKey:$firstKey], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); $dictionary[$firstKey] = $v0; uncheckedAssertEqualObjects($dictionary[$firstKey], $v0); %o $dictionary[$firstKey] = (id)NSNull.null; %o uncheckedAssertEqualObjects($dictionary[$firstKey], (id)NSNull.null); } #pragma clang diagnostic pop - (void)testAddObjects { %noany RLMAssertThrowsWithReason([$dictionary addEntriesFromDictionary:@{$firstKey: $wrong}], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$dictionary addEntriesFromDictionary:@{$firstKey: (id)NSNull.null}], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; uncheckedAssertEqualObjects($dictionary[$k0], $v0); uncheckedAssertEqualObjects($dictionary[$k1], $v1); %o uncheckedAssertEqualObjects($dictionary[$k1], (id)NSNull.null); } - (void)testRemoveObject { [self addObjects]; %r uncheckedAssertEqual($dictionary.count, 2U); %o uncheckedAssertEqual($dictionary.count, 2U); uncheckedAssertEqualObjects($dictionary[$k0], $v0); [$dictionary removeObjectForKey:$k0]; %r uncheckedAssertEqual($dictionary.count, 1U); %o uncheckedAssertEqual($dictionary.count, 1U); uncheckedAssertNil($dictionary[$k0]); } - (void)testRemoveObjects { [self addObjects]; uncheckedAssertEqual($dictionary.count, 2U); uncheckedAssertEqualObjects($dictionary[$k0], $v0); [$dictionary removeObjectsForKeys:@[$k0, $k1]]; uncheckedAssertEqual($dictionary.count, 0U); uncheckedAssertNil($dictionary[$k0]); } - (void)testUpdateObjects { [self addObjects]; uncheckedAssertEqual($dictionary.count, 2U); uncheckedAssertEqualObjects($dictionary[$k0], $v0); uncheckedAssertEqualObjects($dictionary[$k1], $v1); $dictionary[$k1] = $dictionary[$k0]; $dictionary[$k0] = $dictionary[$k1]; uncheckedAssertEqualObjects($dictionary[$k1], $v0); } - (void)testIndexOfObjectSorted { [$dictionary addEntriesFromDictionary:@{ $k0: $v0, $k1: $v1 }]; %man %o uncheckedAssertEqual(0U, [[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0]); %man %o uncheckedAssertEqual(1U, [[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)$v1]); %man %r uncheckedAssertEqual(1U, [[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0]); %man %r uncheckedAssertEqual(0U, [[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1]); %man %o uncheckedAssertEqual(1U, [[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:(id)NSNull.null]); } - (void)testIndexOfObjectDistinct { [$dictionary addEntriesFromDictionary:@{ $k0: $v0, $k1: $v1 }]; %man %r uncheckedAssertEqual(0U, [[$dictionary distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0]); %man %r uncheckedAssertEqual(1U, [[$dictionary distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1]); %man %o uncheckedAssertEqual(0U, [[$dictionary distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0]); %man %o uncheckedAssertEqual(1U, [[$dictionary distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)$v1]); %man %o uncheckedAssertEqual(1U, [[$dictionary distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:(id)NSNull.null]); } - (void)testSort { %unman RLMAssertThrowsWithReason([$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO], ^n @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$dictionary sortedResultsUsingDescriptors:@[]], ^n @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$dictionary sortedResultsUsingKeyPath:@"not self" ascending:NO], ^n @"can only be sorted on 'self'"); [$dictionary addEntriesFromDictionary:@{$k0: $v0, $k1: $v1}]; %man uncheckedAssertEqualObjects([[$dictionary sortedResultsUsingDescriptors:@[]] valueForKey:@"self"], ^n (@[$v0, $v1])); %man %r uncheckedAssertEqualObjects([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], ^n (@[$v1, $v0])); %man %o uncheckedAssertEqualObjects([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"], ^n (@[$v0, $v1])); %man %r uncheckedAssertEqualObjects([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], ^n (@[$v0, $v1])); %man %o uncheckedAssertEqualObjects([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"], ^n (@[$v1, $v0])); } - (void)testFilter { %unman RLMAssertThrowsWithReason([$dictionary objectsWhere:@"TRUEPREDICATE"], ^n @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$dictionary objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"This method may only be called on RLMDictionary instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$dictionary objectsWhere:@"TRUEPREDICATE"], ^n @"implemented"); %man RLMAssertThrowsWithReason([$dictionary objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"implemented"); %man RLMAssertThrowsWithReason([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWhere:@"TRUEPREDICATE"], @"implemented"); %man RLMAssertThrowsWithReason([[$dictionary sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { %unman RLMAssertThrowsWithReason([$dictionary addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], ^n @"Change notifications are only supported on managed collections."); } - (void)testMin { %noany %nominmax %unman RLMAssertThrowsWithReason([$dictionary minOfProperty:@"self"], ^n @"minOfProperty: is not supported for $type dictionary"); %noany %nominmax %man RLMAssertThrowsWithReason([$dictionary minOfProperty:@"self"], ^n @"Operation 'min' not supported for $type dictionary '$class.$prop'"); %minmax uncheckedAssertNil([$dictionary minOfProperty:@"self"]); [self addObjects]; %minmax uncheckedAssertEqualObjects([$dictionary minOfProperty:@"self"], $v0); } - (void)testMax { %noany %nominmax %unman RLMAssertThrowsWithReason([$dictionary maxOfProperty:@"self"], ^n @"maxOfProperty: is not supported for $type dictionary"); %noany %nominmax %man RLMAssertThrowsWithReason([$dictionary maxOfProperty:@"self"], ^n @"Operation 'max' not supported for $type dictionary '$class.$prop'"); %minmax uncheckedAssertNil([$dictionary maxOfProperty:@"self"]); [self addObjects]; %r %minmax uncheckedAssertEqualObjects([$dictionary maxOfProperty:@"self"], $v1); %o %minmax uncheckedAssertEqualObjects([$dictionary maxOfProperty:@"self"], $v0); } - (void)testSum { %noany %nosum %unman RLMAssertThrowsWithReason([$dictionary sumOfProperty:@"self"], ^n @"sumOfProperty: is not supported for $type dictionary"); %noany %nosum %man RLMAssertThrowsWithReason([$dictionary sumOfProperty:@"self"], ^n @"Operation 'sum' not supported for $type dictionary '$class.$prop'"); %sum uncheckedAssertEqualObjects([$dictionary sumOfProperty:@"self"], @0); [self addObjects]; %sum XCTAssertEqualWithAccuracy([$dictionary sumOfProperty:@"self"].doubleValue, sum($values), .001); } - (void)testAverage { %noany %noavg %unman RLMAssertThrowsWithReason([$dictionary averageOfProperty:@"self"], ^n @"averageOfProperty: is not supported for $type dictionary"); %noany %noavg %man RLMAssertThrowsWithReason([$dictionary averageOfProperty:@"self"], ^n @"Operation 'average' not supported for $type dictionary '$class.$prop'"); %avg uncheckedAssertNil([$dictionary averageOfProperty:@"self"]); [self addObjects]; %avg XCTAssertEqualWithAccuracy([$dictionary averageOfProperty:@"self"].doubleValue, average($values), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ ^nl NSDictionary *values = $values; ^nl for (id key in $dictionary) { ^nl id value = $dictionary[key]; ^nl uncheckedAssertEqualObjects(values[key], value); ^nl } ^nl uncheckedAssertEqual(values.count, $dictionary.count); ^nl }(); ^nl } - (void)testValueForKeyNumericAggregates { %minmax uncheckedAssertNil([$dictionary valueForKeyPath:@"@min.self"]); %minmax uncheckedAssertNil([$dictionary valueForKeyPath:@"@max.self"]); %sum uncheckedAssertEqualObjects([$dictionary valueForKeyPath:@"@sum.self"], @0); %avg uncheckedAssertNil([$dictionary valueForKeyPath:@"@avg.self"]); [self addObjects]; %minmax uncheckedAssertEqualObjects([$dictionary valueForKeyPath:@"@min.self"], $v0); %r %minmax uncheckedAssertEqualObjects([$dictionary valueForKeyPath:@"@max.self"], $v1); %o %minmax uncheckedAssertEqualObjects([$dictionary valueForKeyPath:@"@max.self"], $v0); %sum XCTAssertEqualWithAccuracy([[$dictionary valueForKeyPath:@"@sum.self"] doubleValue], sum($values), .001); %avg XCTAssertEqualWithAccuracy([[$dictionary valueForKeyPath:@"@avg.self"] doubleValue], average($values), .001); } - (void)testSetValueForKey { %noany RLMAssertThrowsWithReason([$dictionary setValue:$wrong forKey:$k0], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$dictionary setValue:(id)NSNull.null forKey:@"self"], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; uncheckedAssertEqualObjects($dictionary[$k0], $v0); %o [$dictionary setValue:(id)NSNull.null forKey:$k0]; %o uncheckedAssertEqualObjects($dictionary[$k0], (id)NSNull.null); } - (void)testAssignment { $dictionary = (id)@{$k1: $v1}; ^nl uncheckedAssertEqualObjects($dictionary[$k1], $v1); [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqual(unmanaged.intObj.count, 1); uncheckedAssertEqualObjects(unmanaged.intObj.allValues, managed.intObj.allValues); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqual(managed.intObj.count, 1); uncheckedAssertEqualObjects(managed.intObj.allValues, unmanaged.intObj.allValues); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@{@"0": (id)NSNull.null}, @"Invalid value '' of type 'NSNull' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@{@"0": @"a"}, @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@{@"0": @1, @"1": @"a"}), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@{@"0": (id)NSNull.null}, @"Invalid value '' of type 'NSNull' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@{@"0": @"a"}, @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@{@"0": @1, @"1": @"a"}), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' dictionary property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMDictionary does not match expected type 'int' for property 'AllPrimitiveDictionaries.intObj'."); } - (void)testAllMethodsCheckThread { RLMDictionary *dictionary = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([dictionary count], @"thread"); RLMAssertThrowsWithReason(dictionary[@"0"], @"thread"); RLMAssertThrowsWithReason([dictionary count], @"thread"); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"thread": @0}], @"thread"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"thread"]], @"thread"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"thread"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"thread"], @"thread"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(dictionary[@"thread"], @"thread"); RLMAssertThrowsWithReason(dictionary[@"thread"] = @0, @"thread"); RLMAssertThrowsWithReason([dictionary valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in dictionary);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMDictionary *dictionary = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); RLMAssertThrowsWithReason([dictionary count], @"invalidated"); uncheckedAssertNil(dictionary[@"0"]); RLMAssertThrowsWithReason([dictionary count], @"invalidated"); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"thread"], @"invalidated"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"invalidated": @0}], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"invalidated"], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"invalidated"]], @"invalidated"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"invalidated"], @"invalidated"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(dictionary[@"invalidated"] = @0, @"invalidated"); uncheckedAssertNil([dictionary valueForKey:@"self"]); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in dictionary);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMDictionary *dictionary = managed.intObj; [dictionary setObject:@0 forKey:@"testKey"]; [realm commitWriteTransaction]; XCTAssertNoThrow([dictionary objectClassName]); XCTAssertNoThrow([dictionary realm]); XCTAssertNoThrow([dictionary isInvalidated]); XCTAssertNoThrow([dictionary count]); XCTAssertNoThrow(dictionary[@"0"]); XCTAssertNoThrow([dictionary count]); XCTAssertNoThrow([dictionary sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([dictionary sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(dictionary[@"0"]); XCTAssertNoThrow([dictionary valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in dictionary);})); RLMAssertThrowsWithReason([dictionary setObject:@0 forKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason([dictionary addEntriesFromDictionary:@{@"testKey": @0}], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeObjectForKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeObjectsForKeys:(id)@[@"testKey"]], @"write transaction"); RLMAssertThrowsWithReason([dictionary removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([optManaged.intObj setObject:(id)NSNull.null forKey:@"testKey"], @"write transaction"); RLMAssertThrowsWithReason(dictionary[@"testKey"] = @0, @"write transaction"); RLMAssertThrowsWithReason([dictionary setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMDictionary *dictionary = managed.intObj; uncheckedAssertFalse(dictionary.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(dictionary.isInvalidated); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block bool second = false; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else if (!second) { uncheckedAssertEqualObjects(change.insertions, @[@"testKey"]); } else { uncheckedAssertEqualObjects(change.deletions, @[@"testKey"]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; dictionary[@"testKey"] = @0; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; second = true; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; [dictionary removeObjectForKey:@"testKey"]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMDictionary *dictionary, __unused RLMDictionaryChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveDictionaries createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMDictionary *dictionary, __unused RLMDictionaryChange *change, NSError *error) { XCTAssertNotNil(dictionary); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMDictionary *dictionary = [(AllPrimitiveDictionaries *)[AllPrimitiveDictionaries allObjectsInRealm:r].firstObject intObj]; dictionary[@"testKey"] = @0; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObject { %r %man id $prop = @{$k0: $v0}; id obj = [AllPrimitiveDictionaries createInRealm:realm withValue: @{ %r %man @"$prop": $prop, }]; [LinkToAllPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %o %man @"$prop": $prop, }]; [LinkToAllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop <= %@", $v0); [self createObject]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v1); %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v1); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 1, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %r %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v1); %o %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v1); %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); %noany %man %nocomp RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"ANY $prop > %@", $v0]), ^n @"Operator '>' not supported for type '$basetype'"); } - (void)testQueryBetween { [realm deleteAllObjects]; %noany %man %nominmax RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"ANY $prop BETWEEN %@", @[$v0, $v1]]), ^n @"Operator 'BETWEEN' not supported for type '$basetype'"); %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v0, $v1]); [self createObject]; %r %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v0]); %r %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v1]); %r %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v1, $v1]); %o %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v0]); %o %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v0, $v1]); %o %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v1, $v1]); } - (void)testQueryIn { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v0, $v1]); [self createObject]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v1]); %man RLMAssertCount($class, 1, @"ANY $prop IN %@", @[$v0, $v1]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %r %man @"$prop": $values, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %o %man @"$prop": $values, }]; %man RLMAssertCount($class, 1U, @"$prop.@count == %@", @(2)); %man RLMAssertCount($class, 0U, @"$prop.@count != %@", @(2)); %man RLMAssertCount($class, 0, @"$prop.@count > %@", @(2)); %man RLMAssertCount($class, 1, @"$prop.@count >= %@", @(2)); %man RLMAssertCount($class, 0, @"$prop.@count < %@", @(2)); %man RLMAssertCount($class, 1, @"$prop.@count <= %@", @(2)); } - (void)testQuerySum { [realm deleteAllObjects]; %noany %nodate %nosum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Invalid keypath '$prop.@sum': @sum can only be applied to a collection of numeric values."); %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $wrong]), ^n @"@sum on a property of type $basetype cannot be compared with '$wdesc'"); %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", (id)NSNull.null]), ^n @"@sum on a property of type $basetype cannot be compared with ''"); [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %sum @"$prop": @{}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %sum @"$prop": @{}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %sum @"$prop": @{$k0: $v0}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %sum @"$prop": @{$k0: $v0}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %sum @"$prop": $values, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %sum @"$prop": $values, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %sum @"$prop": $values, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %sum @"$prop": $values, }]; %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", @0); %r %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", $v0); %o %sum %man RLMAssertCount($class, 3U, @"$prop.@sum == %@", $v0); %r %sum %man RLMAssertCount($class, 3U, @"$prop.@sum != %@", $v0); %o %sum %man RLMAssertCount($class, 1U, @"$prop.@sum != %@", $v0); %sum %man RLMAssertCount($class, 3U, @"$prop.@sum >= %@", $v0); %r %sum %man RLMAssertCount($class, 2U, @"$prop.@sum > %@", $v0); %o %sum %man RLMAssertCount($class, 0U, @"$prop.@sum > %@", $v0); %r %sum %man RLMAssertCount($class, 2U, @"$prop.@sum < %@", $v1); %o %sum %man RLMAssertCount($class, 1U, @"$prop.@sum < %@", $v0); %r %sum %man RLMAssertCount($class, 2U, @"$prop.@sum <= %@", $v1); %o %sum %man RLMAssertCount($class, 4U, @"$prop.@sum <= %@", $v0); } - (void)testQueryAverage { [realm deleteAllObjects]; %noany %nodate %noavg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Invalid keypath '$prop.@avg': @avg can only be applied to a collection of numeric values."); %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $wrong]), ^n @"@avg on a property of type $basetype cannot be compared with '$wdesc'"); %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %avg @"$prop": @{}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %avg @"$prop": @{}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %avg @"$prop": @{$k0: $v0}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %avg @"$prop": @{$k0: $v0}, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %avg @"$prop": $values, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %avg @"$prop": $values, }]; [AllPrimitiveDictionaries createInRealm:realm withValue:@{ %man %r %avg @"$prop": @{$k0: $v1}, }]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ %man %o %avg @"$prop": @{$k0: $v0}, }]; %avg %man RLMAssertCount($class, 1U, @"$prop.@avg == %@", NSNull.null); %r %avg %man RLMAssertCount($class, 1U, @"$prop.@avg == %@", $v0); %o %avg %man RLMAssertCount($class, 3U, @"$prop.@avg == %@", $v0); %r %avg %man RLMAssertCount($class, 3U, @"$prop.@avg != %@", $v0); %o %avg %man RLMAssertCount($class, 1U, @"$prop.@avg != %@", $v0); %avg %man RLMAssertCount($class, 3U, @"$prop.@avg >= %@", $v0); %r %avg %man RLMAssertCount($class, 2U, @"$prop.@avg > %@", $v0); %o %avg %man RLMAssertCount($class, 0U, @"$prop.@avg > %@", $v0); %r %avg %man RLMAssertCount($class, 2U, @"$prop.@avg < %@", $v1); %o %avg %man RLMAssertCount($class, 0U, @"$prop.@avg < %@", $v0); %r %avg %man RLMAssertCount($class, 3U, @"$prop.@avg <= %@", $v1); %o %avg %man RLMAssertCount($class, 1U, @"$prop.@avg <= %@", $v1); %o %avg %man RLMAssertCount($class, 3U, @"$prop.@avg <= %@", $v0); } - (void)testQueryMin { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $v0]), ^n @"Invalid keypath '$prop.@min': @min can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $wrong]), ^n @"@min on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); [AllPrimitiveDictionaries createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{}]; // Only empty dictionarys, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == nil"); %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", NSNull.null); [self createObject]; // One object where v0 is min and zero with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); } - (void)testQueryMax { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $v0]), ^n @"Invalid keypath '$prop.@max': @max can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $wrong]), ^n @"@max on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); [AllPrimitiveDictionaries createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{}]; // Only empty dictionarys, so count is zero. %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v1); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == nil"); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", NSNull.null); [self createObject]; // One object where v0 is min and zero with v1 %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v1); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop != %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop <= %@", $v0); [self createObject]; %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop = %@", $v1); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop != %@", $v0); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop != %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %r %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop < %@", $v1); %o %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop <= %@", $v0); %noany %man %nocomp RLMAssertThrowsWithReason(([LinkTo$class objectsInRealm:realm where:@"ANY link.$prop > %@", $v0]), ^n @"Operator '>' not supported for type '$basetype'"); } - (void)testSubstringQueries { [realm deleteAllObjects]; NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveDictionaries createInRealm:realm withValue:@{ @"stringObj": @{@"key": value}, @"dataObj": @{@"key": [value dataUsingEncoding:NSUTF8StringEncoding]} }]; [LinkToAllPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveDictionaries createInRealm:realm withValue:@{ @"stringObj": @{@"key": value}, @"dataObj": @{@"key": [value dataUsingEncoding:NSUTF8StringEncoding]} }]; [LinkToAllOptionalPrimitiveDictionaries createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveDictionaries, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveDictionaries objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveDictionaries, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } @end ================================================ FILE: Realm/Tests/PrimitiveRLMValuePropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } @interface PrimitiveRLMValuePropertyTests : RLMTestCase @end @implementation PrimitiveRLMValuePropertyTests { AllPrimitiveRLMValues *unmanaged; AllPrimitiveRLMValues *managed; RLMRealm *realm; RLMArray *allMixed; NSArray *allVals; } - (void)setUp { realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [self initValues]; [allMixed addObjects:@[ unmanaged.boolVal, unmanaged.intVal, unmanaged.floatVal, unmanaged.doubleVal, unmanaged.stringVal, unmanaged.dataVal, unmanaged.dateVal, unmanaged.decimalVal, unmanaged.objectIdVal, unmanaged.uuidVal, managed.boolVal, managed.intVal, managed.floatVal, managed.doubleVal, managed.stringVal, managed.dataVal, managed.dateVal, managed.decimalVal, managed.objectIdVal, managed.uuidVal, ]]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)initValues { unmanaged = [[AllPrimitiveRLMValues alloc] initWithValue:@{ @"boolVal": @NO, @"intVal": @2, @"floatVal": @2.2f, @"doubleVal": @2.2, @"stringVal": @"a", @"dataVal": data(1), @"dateVal": date(1), @"decimalVal": decimal128(2), @"objectIdVal": objectId(1), @"uuidVal": uuid(@"00000000-0000-0000-0000-000000000000"), }]; XCTAssertNil(unmanaged.realm); managed = [AllPrimitiveRLMValues createInRealm:realm withValue:@{ @"boolVal": @NO, @"intVal": @2, @"floatVal": @2.2f, @"doubleVal": @2.2, @"stringVal": @"a", @"dataVal": data(1), @"dateVal": date(1), @"decimalVal": decimal128(2), @"objectIdVal": objectId(1), @"uuidVal": uuid(@"00000000-0000-0000-0000-000000000000"), }]; XCTAssertNotNil(managed.realm); XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@2.2]); XCTAssert([(NSString *)unmanaged.stringVal isEqual:@"a"]); XCTAssert([(NSData *)unmanaged.dataVal isEqual:data(1)]); XCTAssert([(NSDate *)unmanaged.dateVal isEqual:date(1)]); XCTAssert([(RLMDecimal128 *)unmanaged.decimalVal isEqual:decimal128(2)]); XCTAssert([(RLMObjectId *)unmanaged.objectIdVal isEqual:objectId(1)]); XCTAssert([(NSUUID *)unmanaged.uuidVal isEqual:uuid(@"00000000-0000-0000-0000-000000000000")]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.intVal isEqual:@2]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@2.2]); XCTAssert([(NSString *)managed.stringVal isEqual:@"a"]); XCTAssert([(NSData *)managed.dataVal isEqual:data(1)]); XCTAssert([(NSDate *)managed.dateVal isEqual:date(1)]); XCTAssert([(RLMDecimal128 *)managed.decimalVal isEqual:decimal128(2)]); XCTAssert([(RLMObjectId *)managed.objectIdVal isEqual:objectId(1)]); XCTAssert([(NSUUID *)managed.uuidVal isEqual:uuid(@"00000000-0000-0000-0000-000000000000")]); } - (void)testType { XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testInitNull { AllPrimitiveRLMValues *unman = [[AllPrimitiveRLMValues alloc] init]; AllPrimitiveRLMValues *man = [AllPrimitiveRLMValues createInRealm:realm withValue:@[]]; XCTAssertNil(unman.boolVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.intVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.floatVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.doubleVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.stringVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.dataVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.dateVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.decimalVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.objectIdVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(unman.uuidVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.boolVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.intVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.floatVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.doubleVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.stringVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.dataVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.dateVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.decimalVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.objectIdVal, @"RLMValue should be able to initialize as null"); XCTAssertNil(man.uuidVal, @"RLMValue should be able to initialize as null"); } - (void)testUpdateBoolType { unmanaged.boolVal = @NO; unmanaged.intVal = @NO; unmanaged.floatVal = @NO; unmanaged.doubleVal = @NO; unmanaged.stringVal = @NO; unmanaged.dataVal = @NO; unmanaged.dateVal = @NO; unmanaged.decimalVal = @NO; unmanaged.objectIdVal = @NO; unmanaged.uuidVal = @NO; managed.boolVal = @NO; managed.intVal = @NO; managed.floatVal = @NO; managed.doubleVal = @NO; managed.stringVal = @NO; managed.dataVal = @NO; managed.dateVal = @NO; managed.decimalVal = @NO; managed.objectIdVal = @NO; managed.uuidVal = @NO; XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:@NO]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.intVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.stringVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.dataVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.dateVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:@NO]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:@NO]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeBool); } - (void)testUpdateIntType { unmanaged.boolVal = @2; unmanaged.intVal = @2; unmanaged.floatVal = @2; unmanaged.doubleVal = @2; unmanaged.stringVal = @2; unmanaged.dataVal = @2; unmanaged.dateVal = @2; unmanaged.decimalVal = @2; unmanaged.objectIdVal = @2; unmanaged.uuidVal = @2; managed.boolVal = @2; managed.intVal = @2; managed.floatVal = @2; managed.doubleVal = @2; managed.stringVal = @2; managed.dataVal = @2; managed.dateVal = @2; managed.decimalVal = @2; managed.objectIdVal = @2; managed.uuidVal = @2; XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:@2]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:@2]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@2]); XCTAssert([(NSNumber *)managed.intVal isEqual:@2]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@2]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@2]); XCTAssert([(NSNumber *)managed.stringVal isEqual:@2]); XCTAssert([(NSNumber *)managed.dataVal isEqual:@2]); XCTAssert([(NSNumber *)managed.dateVal isEqual:@2]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:@2]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:@2]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:@2]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeInt); } - (void)testUpdateFloatType { unmanaged.boolVal = @2.2f; unmanaged.intVal = @2.2f; unmanaged.floatVal = @2.2f; unmanaged.doubleVal = @2.2f; unmanaged.stringVal = @2.2f; unmanaged.dataVal = @2.2f; unmanaged.dateVal = @2.2f; unmanaged.decimalVal = @2.2f; unmanaged.objectIdVal = @2.2f; unmanaged.uuidVal = @2.2f; managed.boolVal = @2.2f; managed.intVal = @2.2f; managed.floatVal = @2.2f; managed.doubleVal = @2.2f; managed.stringVal = @2.2f; managed.dataVal = @2.2f; managed.dateVal = @2.2f; managed.decimalVal = @2.2f; managed.objectIdVal = @2.2f; managed.uuidVal = @2.2f; XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:@2.2f]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.intVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.stringVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.dataVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.dateVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:@2.2f]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:@2.2f]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeFloat); } - (void)testUpdateDoubleType { unmanaged.boolVal = @3.3; unmanaged.intVal = @3.3; unmanaged.floatVal = @3.3; unmanaged.doubleVal = @3.3; unmanaged.stringVal = @3.3; unmanaged.dataVal = @3.3; unmanaged.dateVal = @3.3; unmanaged.decimalVal = @3.3; unmanaged.objectIdVal = @3.3; unmanaged.uuidVal = @3.3; managed.boolVal = @3.3; managed.intVal = @3.3; managed.floatVal = @3.3; managed.doubleVal = @3.3; managed.stringVal = @3.3; managed.dataVal = @3.3; managed.dateVal = @3.3; managed.decimalVal = @3.3; managed.objectIdVal = @3.3; managed.uuidVal = @3.3; XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:@3.3]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.intVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.stringVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.dataVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.dateVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:@3.3]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:@3.3]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeDouble); } - (void)testUpdateStringType { unmanaged.boolVal = @"four"; unmanaged.intVal = @"four"; unmanaged.floatVal = @"four"; unmanaged.doubleVal = @"four"; unmanaged.stringVal = @"four"; unmanaged.dataVal = @"four"; unmanaged.dateVal = @"four"; unmanaged.decimalVal = @"four"; unmanaged.objectIdVal = @"four"; unmanaged.uuidVal = @"four"; managed.boolVal = @"four"; managed.intVal = @"four"; managed.floatVal = @"four"; managed.doubleVal = @"four"; managed.stringVal = @"four"; managed.dataVal = @"four"; managed.dateVal = @"four"; managed.decimalVal = @"four"; managed.objectIdVal = @"four"; managed.uuidVal = @"four"; XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:@"four"]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.boolVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.intVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.floatVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.stringVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.dataVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.dateVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:@"four"]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:@"four"]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeString); } - (void)testUpdateDataType { unmanaged.boolVal = data(5); unmanaged.intVal = data(5); unmanaged.floatVal = data(5); unmanaged.doubleVal = data(5); unmanaged.stringVal = data(5); unmanaged.dataVal = data(5); unmanaged.dateVal = data(5); unmanaged.decimalVal = data(5); unmanaged.objectIdVal = data(5); unmanaged.uuidVal = data(5); managed.boolVal = data(5); managed.intVal = data(5); managed.floatVal = data(5); managed.doubleVal = data(5); managed.stringVal = data(5); managed.dataVal = data(5); managed.dateVal = data(5); managed.decimalVal = data(5); managed.objectIdVal = data(5); managed.uuidVal = data(5); XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:data(5)]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.boolVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.intVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.floatVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.stringVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.dataVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.dateVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:data(5)]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:data(5)]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeData); } - (void)testUpdateDateType { unmanaged.boolVal = date(6); unmanaged.intVal = date(6); unmanaged.floatVal = date(6); unmanaged.doubleVal = date(6); unmanaged.stringVal = date(6); unmanaged.dataVal = date(6); unmanaged.dateVal = date(6); unmanaged.decimalVal = date(6); unmanaged.objectIdVal = date(6); unmanaged.uuidVal = date(6); managed.boolVal = date(6); managed.intVal = date(6); managed.floatVal = date(6); managed.doubleVal = date(6); managed.stringVal = date(6); managed.dataVal = date(6); managed.dateVal = date(6); managed.decimalVal = date(6); managed.objectIdVal = date(6); managed.uuidVal = date(6); XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:date(6)]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.boolVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.intVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.floatVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.stringVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.dataVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.dateVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:date(6)]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:date(6)]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testUpdateDecimal { unmanaged.boolVal = decimal128(7); unmanaged.intVal = decimal128(7); unmanaged.floatVal = decimal128(7); unmanaged.doubleVal = decimal128(7); unmanaged.stringVal = decimal128(7); unmanaged.dataVal = decimal128(7); unmanaged.dateVal = decimal128(7); unmanaged.decimalVal = decimal128(7); unmanaged.objectIdVal = decimal128(7); unmanaged.uuidVal = decimal128(7); managed.boolVal = decimal128(7); managed.intVal = decimal128(7); managed.floatVal = decimal128(7); managed.doubleVal = decimal128(7); managed.stringVal = decimal128(7); managed.dataVal = decimal128(7); managed.dateVal = decimal128(7); managed.decimalVal = decimal128(7); managed.objectIdVal = decimal128(7); managed.uuidVal = decimal128(7); XCTAssert([(NSNumber *)unmanaged.boolVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.intVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.floatVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.doubleVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.stringVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.dataVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.dateVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.decimalVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.objectIdVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)unmanaged.uuidVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.boolVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.intVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.floatVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.doubleVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.stringVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.dataVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.dateVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.decimalVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.objectIdVal isEqual:decimal128(7)]); XCTAssert([(NSNumber *)managed.uuidVal isEqual:decimal128(7)]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeDecimal128); } - (void)testUpdateObjectIdType { unmanaged.boolVal = objectId(8); unmanaged.intVal = objectId(8); unmanaged.floatVal = objectId(8); unmanaged.doubleVal = objectId(8); unmanaged.stringVal = objectId(8); unmanaged.dataVal = objectId(8); unmanaged.dateVal = objectId(8); unmanaged.decimalVal = objectId(8); unmanaged.objectIdVal = objectId(8); unmanaged.uuidVal = objectId(8); managed.boolVal = objectId(8); managed.intVal = objectId(8); managed.floatVal = objectId(8); managed.doubleVal = objectId(8); managed.stringVal = objectId(8); managed.dataVal = objectId(8); managed.dateVal = objectId(8); managed.decimalVal = objectId(8); managed.objectIdVal = objectId(8); managed.uuidVal = objectId(8); XCTAssert([(NSUUID *)unmanaged.boolVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.intVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.floatVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.doubleVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.stringVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.dataVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.dateVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.decimalVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.objectIdVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)unmanaged.uuidVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.boolVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.intVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.floatVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.doubleVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.stringVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.dataVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.dateVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.decimalVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.objectIdVal isEqual:objectId(8)]); XCTAssert([(NSUUID *)managed.uuidVal isEqual:objectId(8)]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeObjectId); } - (void)testUpdateUuidType { unmanaged.boolVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.intVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.floatVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.doubleVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.stringVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.dataVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.dateVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.decimalVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.objectIdVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); unmanaged.uuidVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.boolVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.intVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.floatVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.doubleVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.stringVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.dataVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.dateVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.decimalVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.objectIdVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); managed.uuidVal = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); XCTAssert([(NSUUID *)unmanaged.boolVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.intVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.floatVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.doubleVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.stringVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.dataVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.dateVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.decimalVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.objectIdVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)unmanaged.uuidVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.boolVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.intVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.floatVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.doubleVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.stringVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.dataVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.dateVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.decimalVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.objectIdVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssert([(NSUUID *)managed.uuidVal isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.decimalVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.objectIdVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(unmanaged.uuidVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.decimalVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.objectIdVal.rlm_anyValueType, RLMAnyValueTypeUUID); XCTAssertEqual(managed.uuidVal.rlm_anyValueType, RLMAnyValueTypeUUID); } @end ================================================ FILE: Realm/Tests/PrimitiveRLMValuePropertyTests.tpl.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } @interface PrimitiveRLMValuePropertyTests : RLMTestCase @end @implementation PrimitiveRLMValuePropertyTests { AllPrimitiveRLMValues *unmanaged; AllPrimitiveRLMValues *managed; RLMRealm *realm; RLMArray *allMixed; NSArray *allVals; } - (void)setUp { realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [self initValues]; [allMixed addObjects:@[ $rlmValue, ]]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)initValues { unmanaged = [[AllPrimitiveRLMValues alloc] initWithValue:@{ %unman @"$member": $value0, }]; XCTAssertNil(unmanaged.realm); managed = [AllPrimitiveRLMValues createInRealm:realm withValue:@{ %man @"$member": $value0, }]; XCTAssertNotNil(managed.realm); %unman XCTAssert([$cast$rlmValue isEqual:$value0]); %man XCTAssert([$cast$rlmValue isEqual:$value0]); } - (void)testType { XCTAssertEqual(unmanaged.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(unmanaged.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(unmanaged.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(unmanaged.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(unmanaged.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(unmanaged.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(unmanaged.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(managed.boolVal.rlm_anyValueType, RLMAnyValueTypeBool); XCTAssertEqual(managed.intVal.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(managed.floatVal.rlm_anyValueType, RLMAnyValueTypeFloat); XCTAssertEqual(managed.doubleVal.rlm_anyValueType, RLMAnyValueTypeDouble); XCTAssertEqual(managed.stringVal.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(managed.dataVal.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(managed.dateVal.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testInitNull { AllPrimitiveRLMValues *unman = [[AllPrimitiveRLMValues alloc] init]; AllPrimitiveRLMValues *man = [AllPrimitiveRLMValues createInRealm:realm withValue:@[]]; %unman XCTAssertNil(unman.$member, @"RLMValue should be able to initialize as null"); %man XCTAssertNil(man.$member, @"RLMValue should be able to initialize as null"); } - (void)testUpdateBoolType { $rlmValue = @NO; XCTAssert([(NSNumber *)$rlmValue isEqual:@NO]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeBool); } - (void)testUpdateIntType { $rlmValue = @2; XCTAssert([(NSNumber *)$rlmValue isEqual:@2]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeInt); } - (void)testUpdateFloatType { $rlmValue = @2.2f; XCTAssert([(NSNumber *)$rlmValue isEqual:@2.2f]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeFloat); } - (void)testUpdateDoubleType { $rlmValue = @3.3; XCTAssert([(NSNumber *)$rlmValue isEqual:@3.3]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeDouble); } - (void)testUpdateStringType { $rlmValue = @"four"; XCTAssert([(NSNumber *)$rlmValue isEqual:@"four"]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeString); } - (void)testUpdateDataType { $rlmValue = data(5); XCTAssert([(NSNumber *)$rlmValue isEqual:data(5)]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeData); } - (void)testUpdateDateType { $rlmValue = date(6); XCTAssert([(NSNumber *)$rlmValue isEqual:date(6)]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testUpdateDecimal { $rlmValue = decimal128(7); XCTAssert([(NSNumber *)$rlmValue isEqual:decimal128(7)]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeDecimal128); } - (void)testUpdateObjectIdType { $rlmValue = objectId(8); XCTAssert([(NSUUID *)$rlmValue isEqual:objectId(8)]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeObjectId); } - (void)testUpdateUuidType { $rlmValue = uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"); XCTAssert([(NSUUID *)$rlmValue isEqual:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); XCTAssertEqual($rlmValue.rlm_anyValueType, RLMAnyValueTypeUUID); } @end ================================================ FILE: Realm/Tests/PrimitiveSetPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveSets : RLMObject @property (nonatomic) AllPrimitiveSets *link; @end @implementation LinkToAllPrimitiveSets @end @interface LinkToAllOptionalPrimitiveSets : RLMObject @property (nonatomic) AllOptionalPrimitiveSets *link; @end @implementation LinkToAllOptionalPrimitiveSets @end @interface PrimitiveSetPropertyTests : RLMTestCase @end @implementation PrimitiveSetPropertyTests { AllPrimitiveSets *unmanaged; AllPrimitiveSets *managed; AllOptionalPrimitiveSets *optUnmanaged; AllOptionalPrimitiveSets *optManaged; RLMRealm *realm; NSArray *allSets; } - (void)setUp { unmanaged = [[AllPrimitiveSets alloc] init]; optUnmanaged = [[AllOptionalPrimitiveSets alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveSets createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveSets createInRealm:realm withValue:@[]]; allSets = @[ unmanaged.boolObj, unmanaged.intObj, unmanaged.floatObj, unmanaged.doubleObj, unmanaged.stringObj, unmanaged.dataObj, unmanaged.dateObj, unmanaged.decimalObj, unmanaged.objectIdObj, unmanaged.uuidObj, unmanaged.anyBoolObj, unmanaged.anyIntObj, unmanaged.anyFloatObj, unmanaged.anyDoubleObj, unmanaged.anyStringObj, unmanaged.anyDataObj, unmanaged.anyDateObj, unmanaged.anyDecimalObj, unmanaged.anyObjectIdObj, unmanaged.anyUUIDObj, optUnmanaged.boolObj, optUnmanaged.intObj, optUnmanaged.floatObj, optUnmanaged.doubleObj, optUnmanaged.stringObj, optUnmanaged.dataObj, optUnmanaged.dateObj, optUnmanaged.decimalObj, optUnmanaged.objectIdObj, optUnmanaged.uuidObj, managed.boolObj, managed.intObj, managed.floatObj, managed.doubleObj, managed.stringObj, managed.dataObj, managed.dateObj, managed.decimalObj, managed.objectIdObj, managed.uuidObj, managed.anyBoolObj, managed.anyIntObj, managed.anyFloatObj, managed.anyDoubleObj, managed.anyStringObj, managed.anyDataObj, managed.anyDateObj, managed.anyDecimalObj, managed.anyObjectIdObj, managed.anyUUIDObj, optManaged.boolObj, optManaged.intObj, optManaged.floatObj, optManaged.doubleObj, optManaged.stringObj, optManaged.dataObj, optManaged.dateObj, optManaged.decimalObj, optManaged.objectIdObj, optManaged.uuidObj, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.boolObj addObjects:@[NSNull.null, @NO, @YES]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, @3]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, @3.3f]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, @3.3]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", @"bc"]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), data(2)]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), date(2)]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), decimal128(2)]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), objectId(2)]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, @YES]]; [optManaged.intObj addObjects:@[NSNull.null, @2, @3]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, @3.3f]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, @3.3]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", @"bc"]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), data(2)]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), date(2)]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), decimal128(2)]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), objectId(2)]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); [unmanaged.intObj addObject:@1]; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMSet *set; @autoreleasepool { AllPrimitiveSets *obj = [[AllPrimitiveSets alloc] init]; set = obj.intObj; uncheckedAssertFalse(set.invalidated); } uncheckedAssertFalse(set.invalidated); } - (void)testDeleteObjectsInRealm { for (RLMSet *set in allSets) { RLMAssertThrowsWithReason([realm deleteObjects:set], @"Cannot delete objects from RLMSet"); } } - (void)testObjectAtIndex { RLMAssertThrowsWithReason([unmanaged.intObj objectAtIndex:0], @"Index 0 is out of bounds (must be less than 0)."); [unmanaged.intObj addObject:@1]; uncheckedAssertEqualObjects([unmanaged.intObj objectAtIndex:0], @1); } - (void)testContainsObject { uncheckedAssertFalse([unmanaged.boolObj containsObject:@NO]); uncheckedAssertFalse([unmanaged.intObj containsObject:@2]); uncheckedAssertFalse([unmanaged.floatObj containsObject:@2.2f]); uncheckedAssertFalse([unmanaged.doubleObj containsObject:@2.2]); uncheckedAssertFalse([unmanaged.stringObj containsObject:@"a"]); uncheckedAssertFalse([unmanaged.dataObj containsObject:data(1)]); uncheckedAssertFalse([unmanaged.dateObj containsObject:date(1)]); uncheckedAssertFalse([unmanaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertFalse([unmanaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertFalse([unmanaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertFalse([unmanaged.anyBoolObj containsObject:@NO]); uncheckedAssertFalse([unmanaged.anyIntObj containsObject:@2]); uncheckedAssertFalse([unmanaged.anyFloatObj containsObject:@2.2f]); uncheckedAssertFalse([unmanaged.anyDoubleObj containsObject:@2.2]); uncheckedAssertFalse([unmanaged.anyStringObj containsObject:@"a"]); uncheckedAssertFalse([unmanaged.anyDataObj containsObject:data(1)]); uncheckedAssertFalse([unmanaged.anyDateObj containsObject:date(1)]); uncheckedAssertFalse([unmanaged.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertFalse([unmanaged.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertFalse([unmanaged.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertFalse([optUnmanaged.boolObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.intObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.floatObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.doubleObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.stringObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.dataObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.dateObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.decimalObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertFalse([optUnmanaged.uuidObj containsObject:NSNull.null]); uncheckedAssertFalse([managed.boolObj containsObject:@NO]); uncheckedAssertFalse([managed.intObj containsObject:@2]); uncheckedAssertFalse([managed.floatObj containsObject:@2.2f]); uncheckedAssertFalse([managed.doubleObj containsObject:@2.2]); uncheckedAssertFalse([managed.stringObj containsObject:@"a"]); uncheckedAssertFalse([managed.dataObj containsObject:data(1)]); uncheckedAssertFalse([managed.dateObj containsObject:date(1)]); uncheckedAssertFalse([managed.decimalObj containsObject:decimal128(1)]); uncheckedAssertFalse([managed.objectIdObj containsObject:objectId(1)]); uncheckedAssertFalse([managed.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertFalse([managed.anyBoolObj containsObject:@NO]); uncheckedAssertFalse([managed.anyIntObj containsObject:@2]); uncheckedAssertFalse([managed.anyFloatObj containsObject:@2.2f]); uncheckedAssertFalse([managed.anyDoubleObj containsObject:@2.2]); uncheckedAssertFalse([managed.anyStringObj containsObject:@"a"]); uncheckedAssertFalse([managed.anyDataObj containsObject:data(1)]); uncheckedAssertFalse([managed.anyDateObj containsObject:date(1)]); uncheckedAssertFalse([managed.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertFalse([managed.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertFalse([managed.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertFalse([optManaged.boolObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.intObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.floatObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.doubleObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.stringObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.dataObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.dateObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.decimalObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertFalse([optManaged.uuidObj containsObject:NSNull.null]); [unmanaged.boolObj addObject:@NO]; [unmanaged.intObj addObject:@2]; [unmanaged.floatObj addObject:@2.2f]; [unmanaged.doubleObj addObject:@2.2]; [unmanaged.stringObj addObject:@"a"]; [unmanaged.dataObj addObject:data(1)]; [unmanaged.dateObj addObject:date(1)]; [unmanaged.decimalObj addObject:decimal128(1)]; [unmanaged.objectIdObj addObject:objectId(1)]; [unmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [unmanaged.anyBoolObj addObject:@NO]; [unmanaged.anyIntObj addObject:@2]; [unmanaged.anyFloatObj addObject:@2.2f]; [unmanaged.anyDoubleObj addObject:@2.2]; [unmanaged.anyStringObj addObject:@"a"]; [unmanaged.anyDataObj addObject:data(1)]; [unmanaged.anyDateObj addObject:date(1)]; [unmanaged.anyDecimalObj addObject:decimal128(1)]; [unmanaged.anyObjectIdObj addObject:objectId(1)]; [unmanaged.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optUnmanaged.boolObj addObject:NSNull.null]; [optUnmanaged.intObj addObject:NSNull.null]; [optUnmanaged.floatObj addObject:NSNull.null]; [optUnmanaged.doubleObj addObject:NSNull.null]; [optUnmanaged.stringObj addObject:NSNull.null]; [optUnmanaged.dataObj addObject:NSNull.null]; [optUnmanaged.dateObj addObject:NSNull.null]; [optUnmanaged.decimalObj addObject:NSNull.null]; [optUnmanaged.objectIdObj addObject:NSNull.null]; [optUnmanaged.uuidObj addObject:NSNull.null]; [managed.boolObj addObject:@NO]; [managed.intObj addObject:@2]; [managed.floatObj addObject:@2.2f]; [managed.doubleObj addObject:@2.2]; [managed.stringObj addObject:@"a"]; [managed.dataObj addObject:data(1)]; [managed.dateObj addObject:date(1)]; [managed.decimalObj addObject:decimal128(1)]; [managed.objectIdObj addObject:objectId(1)]; [managed.uuidObj addObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; [managed.anyBoolObj addObject:@NO]; [managed.anyIntObj addObject:@2]; [managed.anyFloatObj addObject:@2.2f]; [managed.anyDoubleObj addObject:@2.2]; [managed.anyStringObj addObject:@"a"]; [managed.anyDataObj addObject:data(1)]; [managed.anyDateObj addObject:date(1)]; [managed.anyDecimalObj addObject:decimal128(1)]; [managed.anyObjectIdObj addObject:objectId(1)]; [managed.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optManaged.boolObj addObject:NSNull.null]; [optManaged.intObj addObject:NSNull.null]; [optManaged.floatObj addObject:NSNull.null]; [optManaged.doubleObj addObject:NSNull.null]; [optManaged.stringObj addObject:NSNull.null]; [optManaged.dataObj addObject:NSNull.null]; [optManaged.dateObj addObject:NSNull.null]; [optManaged.decimalObj addObject:NSNull.null]; [optManaged.objectIdObj addObject:NSNull.null]; [optManaged.uuidObj addObject:NSNull.null]; uncheckedAssertTrue([unmanaged.boolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.intObj containsObject:@2]); uncheckedAssertTrue([unmanaged.floatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.doubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.stringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.dataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.dateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([unmanaged.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.anyIntObj containsObject:@2]); uncheckedAssertTrue([unmanaged.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optUnmanaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:NSNull.null]); uncheckedAssertTrue([managed.boolObj containsObject:@NO]); uncheckedAssertTrue([managed.intObj containsObject:@2]); uncheckedAssertTrue([managed.floatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.doubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.stringObj containsObject:@"a"]); uncheckedAssertTrue([managed.dataObj containsObject:data(1)]); uncheckedAssertTrue([managed.dateObj containsObject:date(1)]); uncheckedAssertTrue([managed.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([managed.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([managed.anyIntObj containsObject:@2]); uncheckedAssertTrue([managed.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([managed.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([managed.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([managed.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optManaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.uuidObj containsObject:NSNull.null]); } - (void)testAddObject { RLMAssertThrowsWithReason([unmanaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj addObject:@2], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj addObject:@"a"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObject:NSNull.null], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [unmanaged.boolObj addObject:@NO]; [unmanaged.intObj addObject:@2]; [unmanaged.floatObj addObject:@2.2f]; [unmanaged.doubleObj addObject:@2.2]; [unmanaged.stringObj addObject:@"a"]; [unmanaged.dataObj addObject:data(1)]; [unmanaged.dateObj addObject:date(1)]; [unmanaged.decimalObj addObject:decimal128(1)]; [unmanaged.objectIdObj addObject:objectId(1)]; [unmanaged.uuidObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [unmanaged.anyBoolObj addObject:@NO]; [unmanaged.anyIntObj addObject:@2]; [unmanaged.anyFloatObj addObject:@2.2f]; [unmanaged.anyDoubleObj addObject:@2.2]; [unmanaged.anyStringObj addObject:@"a"]; [unmanaged.anyDataObj addObject:data(1)]; [unmanaged.anyDateObj addObject:date(1)]; [unmanaged.anyDecimalObj addObject:decimal128(1)]; [unmanaged.anyObjectIdObj addObject:objectId(1)]; [unmanaged.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optUnmanaged.boolObj addObject:NSNull.null]; [optUnmanaged.intObj addObject:NSNull.null]; [optUnmanaged.floatObj addObject:NSNull.null]; [optUnmanaged.doubleObj addObject:NSNull.null]; [optUnmanaged.stringObj addObject:NSNull.null]; [optUnmanaged.dataObj addObject:NSNull.null]; [optUnmanaged.dateObj addObject:NSNull.null]; [optUnmanaged.decimalObj addObject:NSNull.null]; [optUnmanaged.objectIdObj addObject:NSNull.null]; [optUnmanaged.uuidObj addObject:NSNull.null]; [managed.boolObj addObject:@NO]; [managed.intObj addObject:@2]; [managed.floatObj addObject:@2.2f]; [managed.doubleObj addObject:@2.2]; [managed.stringObj addObject:@"a"]; [managed.dataObj addObject:data(1)]; [managed.dateObj addObject:date(1)]; [managed.decimalObj addObject:decimal128(1)]; [managed.objectIdObj addObject:objectId(1)]; [managed.uuidObj addObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; [managed.anyBoolObj addObject:@NO]; [managed.anyIntObj addObject:@2]; [managed.anyFloatObj addObject:@2.2f]; [managed.anyDoubleObj addObject:@2.2]; [managed.anyStringObj addObject:@"a"]; [managed.anyDataObj addObject:data(1)]; [managed.anyDateObj addObject:date(1)]; [managed.anyDecimalObj addObject:decimal128(1)]; [managed.anyObjectIdObj addObject:objectId(1)]; [managed.anyUUIDObj addObject:uuid(@"00000000-0000-0000-0000-000000000000")]; [optManaged.boolObj addObject:NSNull.null]; [optManaged.intObj addObject:NSNull.null]; [optManaged.floatObj addObject:NSNull.null]; [optManaged.doubleObj addObject:NSNull.null]; [optManaged.stringObj addObject:NSNull.null]; [optManaged.dataObj addObject:NSNull.null]; [optManaged.dateObj addObject:NSNull.null]; [optManaged.decimalObj addObject:NSNull.null]; [optManaged.objectIdObj addObject:NSNull.null]; [optManaged.uuidObj addObject:NSNull.null]; uncheckedAssertTrue([unmanaged.boolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.intObj containsObject:@2]); uncheckedAssertTrue([unmanaged.floatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.doubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.stringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.dataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.dateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([unmanaged.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.anyIntObj containsObject:@2]); uncheckedAssertTrue([unmanaged.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optUnmanaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:NSNull.null]); uncheckedAssertTrue([managed.boolObj containsObject:@NO]); uncheckedAssertTrue([managed.intObj containsObject:@2]); uncheckedAssertTrue([managed.floatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.doubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.stringObj containsObject:@"a"]); uncheckedAssertTrue([managed.dataObj containsObject:data(1)]); uncheckedAssertTrue([managed.dateObj containsObject:date(1)]); uncheckedAssertTrue([managed.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([managed.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([managed.anyIntObj containsObject:@2]); uncheckedAssertTrue([managed.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([managed.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([managed.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([managed.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optManaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.uuidObj containsObject:NSNull.null]); [optUnmanaged.intObj addObject:NSNull.null]; [optUnmanaged.floatObj addObject:NSNull.null]; [optUnmanaged.doubleObj addObject:NSNull.null]; [optUnmanaged.stringObj addObject:NSNull.null]; [optUnmanaged.dataObj addObject:NSNull.null]; [optUnmanaged.dateObj addObject:NSNull.null]; [optUnmanaged.decimalObj addObject:NSNull.null]; [optUnmanaged.objectIdObj addObject:NSNull.null]; [optUnmanaged.uuidObj addObject:NSNull.null]; [optManaged.boolObj addObject:NSNull.null]; [optManaged.intObj addObject:NSNull.null]; [optManaged.floatObj addObject:NSNull.null]; [optManaged.doubleObj addObject:NSNull.null]; [optManaged.stringObj addObject:NSNull.null]; [optManaged.dataObj addObject:NSNull.null]; [optManaged.dateObj addObject:NSNull.null]; [optManaged.decimalObj addObject:NSNull.null]; [optManaged.objectIdObj addObject:NSNull.null]; [optManaged.uuidObj addObject:NSNull.null]; uncheckedAssertTrue([optUnmanaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.uuidObj containsObject:NSNull.null]); } - (void)testAddObjects { RLMAssertThrowsWithReason([unmanaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj addObjects:@[@2]], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj addObjects:@[@"a"]], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj addObjects:@[NSNull.null]], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; uncheckedAssertTrue([unmanaged.boolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.intObj containsObject:@2]); uncheckedAssertTrue([unmanaged.floatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.doubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.stringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.dataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.dateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([unmanaged.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([unmanaged.anyIntObj containsObject:@2]); uncheckedAssertTrue([unmanaged.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([unmanaged.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([unmanaged.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([unmanaged.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([unmanaged.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([unmanaged.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([unmanaged.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([unmanaged.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optUnmanaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:NSNull.null]); uncheckedAssertTrue([managed.boolObj containsObject:@NO]); uncheckedAssertTrue([managed.intObj containsObject:@2]); uncheckedAssertTrue([managed.floatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.doubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.stringObj containsObject:@"a"]); uncheckedAssertTrue([managed.dataObj containsObject:data(1)]); uncheckedAssertTrue([managed.dateObj containsObject:date(1)]); uncheckedAssertTrue([managed.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([managed.anyBoolObj containsObject:@NO]); uncheckedAssertTrue([managed.anyIntObj containsObject:@2]); uncheckedAssertTrue([managed.anyFloatObj containsObject:@2.2f]); uncheckedAssertTrue([managed.anyDoubleObj containsObject:@2.2]); uncheckedAssertTrue([managed.anyStringObj containsObject:@"a"]); uncheckedAssertTrue([managed.anyDataObj containsObject:data(1)]); uncheckedAssertTrue([managed.anyDateObj containsObject:date(1)]); uncheckedAssertTrue([managed.anyDecimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([managed.anyObjectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([managed.anyUUIDObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optManaged.boolObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.intObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.floatObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.doubleObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.stringObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dataObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.dateObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.decimalObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:NSNull.null]); uncheckedAssertTrue([optManaged.uuidObj containsObject:NSNull.null]); uncheckedAssertTrue([unmanaged.boolObj containsObject:@YES]); uncheckedAssertTrue([unmanaged.intObj containsObject:@3]); uncheckedAssertTrue([unmanaged.floatObj containsObject:@3.3f]); uncheckedAssertTrue([unmanaged.doubleObj containsObject:@3.3]); uncheckedAssertTrue([unmanaged.stringObj containsObject:@"bc"]); uncheckedAssertTrue([unmanaged.dataObj containsObject:data(2)]); uncheckedAssertTrue([unmanaged.dateObj containsObject:date(2)]); uncheckedAssertTrue([unmanaged.decimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([unmanaged.objectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([unmanaged.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([unmanaged.anyBoolObj containsObject:@YES]); uncheckedAssertTrue([unmanaged.anyIntObj containsObject:@3]); uncheckedAssertTrue([unmanaged.anyFloatObj containsObject:@3.3f]); uncheckedAssertTrue([unmanaged.anyDoubleObj containsObject:@3.3]); uncheckedAssertTrue([unmanaged.anyStringObj containsObject:@"b"]); uncheckedAssertTrue([unmanaged.anyDataObj containsObject:data(2)]); uncheckedAssertTrue([unmanaged.anyDateObj containsObject:date(2)]); uncheckedAssertTrue([unmanaged.anyDecimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([unmanaged.anyObjectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([unmanaged.anyUUIDObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([optUnmanaged.boolObj containsObject:@NO]); uncheckedAssertTrue([optUnmanaged.intObj containsObject:@2]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:@2.2f]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:@2.2]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:@"a"]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:data(1)]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:date(1)]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([managed.boolObj containsObject:@YES]); uncheckedAssertTrue([managed.intObj containsObject:@3]); uncheckedAssertTrue([managed.floatObj containsObject:@3.3f]); uncheckedAssertTrue([managed.doubleObj containsObject:@3.3]); uncheckedAssertTrue([managed.stringObj containsObject:@"bc"]); uncheckedAssertTrue([managed.dataObj containsObject:data(2)]); uncheckedAssertTrue([managed.dateObj containsObject:date(2)]); uncheckedAssertTrue([managed.decimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([managed.objectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([managed.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([managed.anyBoolObj containsObject:@YES]); uncheckedAssertTrue([managed.anyIntObj containsObject:@3]); uncheckedAssertTrue([managed.anyFloatObj containsObject:@3.3f]); uncheckedAssertTrue([managed.anyDoubleObj containsObject:@3.3]); uncheckedAssertTrue([managed.anyStringObj containsObject:@"b"]); uncheckedAssertTrue([managed.anyDataObj containsObject:data(2)]); uncheckedAssertTrue([managed.anyDateObj containsObject:date(2)]); uncheckedAssertTrue([managed.anyDecimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([managed.anyObjectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([managed.anyUUIDObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([optManaged.boolObj containsObject:@NO]); uncheckedAssertTrue([optManaged.intObj containsObject:@2]); uncheckedAssertTrue([optManaged.floatObj containsObject:@2.2f]); uncheckedAssertTrue([optManaged.doubleObj containsObject:@2.2]); uncheckedAssertTrue([optManaged.stringObj containsObject:@"a"]); uncheckedAssertTrue([optManaged.dataObj containsObject:data(1)]); uncheckedAssertTrue([optManaged.dateObj containsObject:date(1)]); uncheckedAssertTrue([optManaged.decimalObj containsObject:decimal128(1)]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:objectId(1)]); uncheckedAssertTrue([optManaged.uuidObj containsObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); uncheckedAssertTrue([optUnmanaged.intObj containsObject:@3]); uncheckedAssertTrue([optUnmanaged.floatObj containsObject:@3.3f]); uncheckedAssertTrue([optUnmanaged.doubleObj containsObject:@3.3]); uncheckedAssertTrue([optUnmanaged.stringObj containsObject:@"bc"]); uncheckedAssertTrue([optUnmanaged.dataObj containsObject:data(2)]); uncheckedAssertTrue([optUnmanaged.dateObj containsObject:date(2)]); uncheckedAssertTrue([optUnmanaged.decimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([optUnmanaged.objectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([optUnmanaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); uncheckedAssertTrue([optManaged.boolObj containsObject:@YES]); uncheckedAssertTrue([optManaged.intObj containsObject:@3]); uncheckedAssertTrue([optManaged.floatObj containsObject:@3.3f]); uncheckedAssertTrue([optManaged.doubleObj containsObject:@3.3]); uncheckedAssertTrue([optManaged.stringObj containsObject:@"bc"]); uncheckedAssertTrue([optManaged.dataObj containsObject:data(2)]); uncheckedAssertTrue([optManaged.dateObj containsObject:date(2)]); uncheckedAssertTrue([optManaged.decimalObj containsObject:decimal128(2)]); uncheckedAssertTrue([optManaged.objectIdObj containsObject:objectId(2)]); uncheckedAssertTrue([optManaged.uuidObj containsObject:uuid(@"00000000-0000-0000-0000-000000000000")]); } - (void)testRemoveObject { [self addObjects]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqual(unmanaged.intObj.count, 2U); uncheckedAssertEqual(unmanaged.floatObj.count, 2U); uncheckedAssertEqual(unmanaged.doubleObj.count, 2U); uncheckedAssertEqual(unmanaged.stringObj.count, 2U); uncheckedAssertEqual(unmanaged.dataObj.count, 2U); uncheckedAssertEqual(unmanaged.dateObj.count, 2U); uncheckedAssertEqual(unmanaged.decimalObj.count, 2U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.uuidObj.count, 2U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 2U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 2U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 2U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 2U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 2U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 2U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 2U); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqual(managed.intObj.count, 2U); uncheckedAssertEqual(managed.floatObj.count, 2U); uncheckedAssertEqual(managed.doubleObj.count, 2U); uncheckedAssertEqual(managed.stringObj.count, 2U); uncheckedAssertEqual(managed.dataObj.count, 2U); uncheckedAssertEqual(managed.dateObj.count, 2U); uncheckedAssertEqual(managed.decimalObj.count, 2U); uncheckedAssertEqual(managed.objectIdObj.count, 2U); uncheckedAssertEqual(managed.uuidObj.count, 2U); uncheckedAssertEqual(managed.anyBoolObj.count, 2U); uncheckedAssertEqual(managed.anyIntObj.count, 2U); uncheckedAssertEqual(managed.anyFloatObj.count, 2U); uncheckedAssertEqual(managed.anyDoubleObj.count, 2U); uncheckedAssertEqual(managed.anyStringObj.count, 2U); uncheckedAssertEqual(managed.anyDataObj.count, 2U); uncheckedAssertEqual(managed.anyDateObj.count, 2U); uncheckedAssertEqual(managed.anyDecimalObj.count, 2U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 2U); uncheckedAssertEqual(managed.anyUUIDObj.count, 2U); uncheckedAssertEqual(optUnmanaged.intObj.count, 3U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 3U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 3U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 3U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 3U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 3U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 3U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 3U); uncheckedAssertEqual(optManaged.boolObj.count, 3U); uncheckedAssertEqual(optManaged.intObj.count, 3U); uncheckedAssertEqual(optManaged.floatObj.count, 3U); uncheckedAssertEqual(optManaged.doubleObj.count, 3U); uncheckedAssertEqual(optManaged.stringObj.count, 3U); uncheckedAssertEqual(optManaged.dataObj.count, 3U); uncheckedAssertEqual(optManaged.dateObj.count, 3U); uncheckedAssertEqual(optManaged.decimalObj.count, 3U); uncheckedAssertEqual(optManaged.objectIdObj.count, 3U); uncheckedAssertEqual(optManaged.uuidObj.count, 3U); for (RLMSet *set in allSets) { [set removeObject:set.allObjects[0]]; } uncheckedAssertEqual(unmanaged.boolObj.count, 1U); uncheckedAssertEqual(unmanaged.intObj.count, 1U); uncheckedAssertEqual(unmanaged.floatObj.count, 1U); uncheckedAssertEqual(unmanaged.doubleObj.count, 1U); uncheckedAssertEqual(unmanaged.stringObj.count, 1U); uncheckedAssertEqual(unmanaged.dataObj.count, 1U); uncheckedAssertEqual(unmanaged.dateObj.count, 1U); uncheckedAssertEqual(unmanaged.decimalObj.count, 1U); uncheckedAssertEqual(unmanaged.objectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.uuidObj.count, 1U); uncheckedAssertEqual(unmanaged.anyBoolObj.count, 1U); uncheckedAssertEqual(unmanaged.anyIntObj.count, 1U); uncheckedAssertEqual(unmanaged.anyFloatObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDoubleObj.count, 1U); uncheckedAssertEqual(unmanaged.anyStringObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDataObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDateObj.count, 1U); uncheckedAssertEqual(unmanaged.anyDecimalObj.count, 1U); uncheckedAssertEqual(unmanaged.anyObjectIdObj.count, 1U); uncheckedAssertEqual(unmanaged.anyUUIDObj.count, 1U); uncheckedAssertEqual(managed.boolObj.count, 1U); uncheckedAssertEqual(managed.intObj.count, 1U); uncheckedAssertEqual(managed.floatObj.count, 1U); uncheckedAssertEqual(managed.doubleObj.count, 1U); uncheckedAssertEqual(managed.stringObj.count, 1U); uncheckedAssertEqual(managed.dataObj.count, 1U); uncheckedAssertEqual(managed.dateObj.count, 1U); uncheckedAssertEqual(managed.decimalObj.count, 1U); uncheckedAssertEqual(managed.objectIdObj.count, 1U); uncheckedAssertEqual(managed.uuidObj.count, 1U); uncheckedAssertEqual(managed.anyBoolObj.count, 1U); uncheckedAssertEqual(managed.anyIntObj.count, 1U); uncheckedAssertEqual(managed.anyFloatObj.count, 1U); uncheckedAssertEqual(managed.anyDoubleObj.count, 1U); uncheckedAssertEqual(managed.anyStringObj.count, 1U); uncheckedAssertEqual(managed.anyDataObj.count, 1U); uncheckedAssertEqual(managed.anyDateObj.count, 1U); uncheckedAssertEqual(managed.anyDecimalObj.count, 1U); uncheckedAssertEqual(managed.anyObjectIdObj.count, 1U); uncheckedAssertEqual(managed.anyUUIDObj.count, 1U); uncheckedAssertEqual(optUnmanaged.intObj.count, 2U); uncheckedAssertEqual(optUnmanaged.floatObj.count, 2U); uncheckedAssertEqual(optUnmanaged.doubleObj.count, 2U); uncheckedAssertEqual(optUnmanaged.stringObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dataObj.count, 2U); uncheckedAssertEqual(optUnmanaged.dateObj.count, 2U); uncheckedAssertEqual(optUnmanaged.decimalObj.count, 2U); uncheckedAssertEqual(optUnmanaged.objectIdObj.count, 2U); uncheckedAssertEqual(optUnmanaged.uuidObj.count, 2U); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqual(optManaged.intObj.count, 2U); uncheckedAssertEqual(optManaged.floatObj.count, 2U); uncheckedAssertEqual(optManaged.doubleObj.count, 2U); uncheckedAssertEqual(optManaged.stringObj.count, 2U); uncheckedAssertEqual(optManaged.dataObj.count, 2U); uncheckedAssertEqual(optManaged.dateObj.count, 2U); uncheckedAssertEqual(optManaged.decimalObj.count, 2U); uncheckedAssertEqual(optManaged.objectIdObj.count, 2U); uncheckedAssertEqual(optManaged.uuidObj.count, 2U); } - (void)testIndexOfObjectSorted { [managed.boolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.intObj addObjects:@[@2, @3, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc", @"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2), decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES, @NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3, @2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b", @"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2), data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2), date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2), decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; // ordering can't be guaranteed in set, so just verify the indexes are between 0 and 1 uncheckedAssertTrue([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES] == 0U || [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES] == 1U); uncheckedAssertTrue([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3] == 0U || [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3] == 1U); uncheckedAssertTrue([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f] == 0U || [[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f] == 1U); uncheckedAssertTrue([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3] == 0U || [[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3] == 1U); uncheckedAssertTrue([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"bc"] == 0U || [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"bc"] == 1U); uncheckedAssertTrue([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)] == 0U || [[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)] == 1U); uncheckedAssertTrue([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)] == 0U || [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)] == 1U); uncheckedAssertTrue([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)] == 0U || [[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)] == 1U); uncheckedAssertTrue([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)] == 0U || [[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)] == 1U); uncheckedAssertTrue([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 0U || [[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 1U); uncheckedAssertTrue([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES] == 0U || [[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@YES] == 1U); uncheckedAssertTrue([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3] == 0U || [[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3] == 1U); uncheckedAssertTrue([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f] == 0U || [[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3f] == 1U); uncheckedAssertTrue([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3] == 0U || [[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@3.3] == 1U); uncheckedAssertTrue([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"b"] == 0U || [[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"b"] == 1U); uncheckedAssertTrue([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)] == 0U || [[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(2)] == 1U); uncheckedAssertTrue([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)] == 0U || [[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(2)] == 1U); uncheckedAssertTrue([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)] == 0U || [[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(2)] == 1U); uncheckedAssertTrue([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)] == 0U || [[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(2)] == 1U); uncheckedAssertTrue([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 0U || [[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 0U || [[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 1U); uncheckedAssertTrue([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 0U || [[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 0U || [[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 0U || [[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 0U || [[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 0U || [[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 0U || [[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 0U || [[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 0U || [[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 0U || [[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 1U); uncheckedAssertTrue([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 0U || [[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 0U || [[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 0U || [[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 0U || [[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 0U || [[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 0U || [[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 0U || [[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 0U || [[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 1U); uncheckedAssertTrue([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 0U || [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 0U || [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2] == 1U); uncheckedAssertTrue([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 0U || [[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 0U || [[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 0U || [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 0U || [[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 0U || [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 0U || [[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 0U || [[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 0U || [[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:NSNull.null] == 1U); } - (void)testIndexOfObjectDistinct { [managed.boolObj addObjects:@[@NO, @NO, @YES]]; [managed.intObj addObjects:@[@2, @2, @3]]; [managed.floatObj addObjects:@[@2.2f, @2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, NSNull.null, NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; // ordering can't be guaranteed in set, so just verify the indexes are between 0 and 1 uncheckedAssertTrue([[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 0U || [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 0U || [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 1U); uncheckedAssertTrue([[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 0U || [[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 0U || [[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 0U || [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 0U || [[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 0U || [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 0U || [[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 0U || [[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 0U || [[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 0U || [[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 1U); uncheckedAssertTrue([[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 0U || [[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 0U || [[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 0U || [[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 0U || [[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 0U || [[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 0U || [[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[managed.anyObjectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 0U || [[managed.anyObjectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 0U || [[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 1U); uncheckedAssertTrue([[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES] == 0U || [[managed.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES] == 1U); uncheckedAssertTrue([[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3] == 0U || [[managed.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3] == 1U); uncheckedAssertTrue([[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f] == 0U || [[managed.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f] == 1U); uncheckedAssertTrue([[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3] == 0U || [[managed.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3] == 1U); uncheckedAssertTrue([[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"bc"] == 0U || [[managed.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"bc"] == 1U); uncheckedAssertTrue([[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)] == 0U || [[managed.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)] == 1U); uncheckedAssertTrue([[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)] == 0U || [[managed.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)] == 1U); uncheckedAssertTrue([[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)] == 0U || [[managed.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)] == 1U); uncheckedAssertTrue([[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)] == 0U || [[managed.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)] == 1U); uncheckedAssertTrue([[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 0U || [[managed.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"00000000-0000-0000-0000-000000000000")] == 1U); uncheckedAssertTrue([[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES] == 0U || [[managed.anyBoolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@YES] == 1U); uncheckedAssertTrue([[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3] == 0U || [[managed.anyIntObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3] == 1U); uncheckedAssertTrue([[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f] == 0U || [[managed.anyFloatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3f] == 1U); uncheckedAssertTrue([[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3] == 0U || [[managed.anyDoubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@3.3] == 1U); uncheckedAssertTrue([[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"b"] == 0U || [[managed.anyStringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"b"] == 1U); uncheckedAssertTrue([[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)] == 0U || [[managed.anyDataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(2)] == 1U); uncheckedAssertTrue([[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)] == 0U || [[managed.anyDateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(2)] == 1U); uncheckedAssertTrue([[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)] == 0U || [[managed.anyDecimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(2)] == 1U); uncheckedAssertTrue([[managed.anyObjectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)] == 0U || [[managed.anyObjectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(2)] == 1U); uncheckedAssertTrue([[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[managed.anyUUIDObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 0U || [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@NO] == 1U); uncheckedAssertTrue([[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 0U || [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2] == 1U); uncheckedAssertTrue([[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 0U || [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2f] == 1U); uncheckedAssertTrue([[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 0U || [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@2.2] == 1U); uncheckedAssertTrue([[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 0U || [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:@"a"] == 1U); uncheckedAssertTrue([[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 0U || [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:data(1)] == 1U); uncheckedAssertTrue([[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 0U || [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:date(1)] == 1U); uncheckedAssertTrue([[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 0U || [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:decimal128(1)] == 1U); uncheckedAssertTrue([[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 0U || [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:objectId(1)] == 1U); uncheckedAssertTrue([[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 0U || [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] == 1U); uncheckedAssertTrue([[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.boolObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.intObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.floatObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.doubleObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.stringObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.dataObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.dateObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.decimalObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.objectIdObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); uncheckedAssertTrue([[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || [[optManaged.uuidObj distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); } - (void)testSort { RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sortedResultsUsingDescriptors:@[]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.floatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.doubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.dataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.decimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.objectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.uuidObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyBoolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyIntObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyFloatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDoubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyStringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyDecimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyObjectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([managed.anyUUIDObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.boolObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.intObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.floatObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.doubleObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.stringObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.dataObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.dateObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.decimalObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.objectIdObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); RLMAssertThrowsWithReason([optManaged.uuidObj sortedResultsUsingKeyPath:@"not self" ascending:NO], @"can only be sorted on 'self'"); [managed.boolObj addObjects:@[@NO, @YES, @NO]]; [managed.intObj addObjects:@[@2, @3, @2]]; [managed.floatObj addObjects:@[@2.2f, @3.3f, @2.2f]]; [managed.doubleObj addObjects:@[@2.2, @3.3, @2.2]]; [managed.stringObj addObjects:@[@"a", @"bc", @"a"]]; [managed.dataObj addObjects:@[data(1), data(2), data(1)]]; [managed.dateObj addObjects:@[date(1), date(2), date(1)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2), decimal128(1)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2), objectId(1)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj addObjects:@[@NO, @YES, @NO]]; [managed.anyIntObj addObjects:@[@2, @3, @2]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f, @2.2f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3, @2.2]]; [managed.anyStringObj addObjects:@[@"a", @"b", @"a"]]; [managed.anyDataObj addObjects:@[data(1), data(2), data(1)]]; [managed.anyDateObj addObjects:@[date(1), date(2), date(1)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2), decimal128(1)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2), objectId(1)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.floatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.doubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.decimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.objectIdObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.uuidObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyBoolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyIntObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyFloatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDoubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyStringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDecimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyObjectIdObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyUUIDObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.boolObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.intObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.floatObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.doubleObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.stringObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dataObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dateObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.decimalObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.objectIdObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.uuidObj sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@YES, @NO]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3, @2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3.3f, @2.2f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3.3, @2.2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"bc", @"a"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(2), data(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(2), date(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(2), decimal128(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(2), objectId(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@YES, @NO]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3, @2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3.3f, @2.2f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@3.3, @2.2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"b", @"a"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(2), data(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(2), date(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(2), decimal128(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(2), objectId(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); } - (void)testFilter { RLMAssertThrowsWithReason([unmanaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); RLMAssertThrowsWithReason([managed.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([managed.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyBoolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyIntObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyFloatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDoubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyStringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyDecimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyObjectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([managed.anyUUIDObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.boolObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.intObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.floatObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.doubleObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.stringObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dataObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.dateObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.decimalObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.objectIdObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([optManaged.uuidObj objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWhere:@"TRUEPREDICATE"], @"implemented"); RLMAssertThrowsWithReason([[managed.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyBoolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyIntObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyFloatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDoubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyStringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyDecimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyObjectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[managed.anyUUIDObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.boolObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.intObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.floatObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.doubleObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.stringObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dataObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.dateObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.decimalObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.objectIdObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); RLMAssertThrowsWithReason([[optManaged.uuidObj sortedResultsUsingKeyPath:@"self" ascending:NO] objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { RLMAssertThrowsWithReason([unmanaged.boolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyBoolObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyIntObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyFloatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDoubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyStringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyDecimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyObjectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([unmanaged.anyUUIDObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.intObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.floatObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.doubleObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.stringObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dataObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.dateObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.decimalObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); RLMAssertThrowsWithReason([optUnmanaged.uuidObj addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], @"Change notifications are only supported on managed collections."); } - (void)testSetSet { [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj2 addObjects:@[@YES, @NO]]; [managed.intObj2 addObjects:@[@3, @4]]; [managed.floatObj2 addObjects:@[@3.3f, @4.4f]]; [managed.doubleObj2 addObjects:@[@3.3, @4.4]]; [managed.stringObj2 addObjects:@[@"bc", @"de"]]; [managed.dataObj2 addObjects:@[data(2), data(3)]]; [managed.dateObj2 addObjects:@[date(2), date(3)]]; [managed.decimalObj2 addObjects:@[decimal128(2), decimal128(3)]]; [managed.objectIdObj2 addObjects:@[objectId(2), objectId(3)]]; [managed.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj2 addObjects:@[@NO, @YES]]; [managed.anyIntObj2 addObjects:@[@2, @4]]; [managed.anyFloatObj2 addObjects:@[@2.2f, @4.4f]]; [managed.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [managed.anyStringObj2 addObjects:@[@"a", @"d"]]; [managed.anyDataObj2 addObjects:@[data(1), data(3)]]; [managed.anyDateObj2 addObjects:@[date(1), date(3)]]; [managed.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [managed.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [managed.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj2 addObjects:@[@YES, @NO, NSNull.null]]; [optManaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optManaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optManaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optManaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optManaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optManaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optManaged.decimalObj2 addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj2 addObjects:@[objectId(2), objectId(3), NSNull.null]]; [optManaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [realm commitWriteTransaction]; [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj2 addObjects:@[@NO, @YES]]; [unmanaged.intObj2 addObjects:@[@2, @4]]; [unmanaged.floatObj2 addObjects:@[@2.2f, @4.4f]]; [unmanaged.doubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.stringObj2 addObjects:@[@"a", @"de"]]; [unmanaged.dataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.dateObj2 addObjects:@[date(1), date(3)]]; [unmanaged.decimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.objectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj2 addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj2 addObjects:@[@2, @4]]; [unmanaged.anyFloatObj2 addObjects:@[@4.4f, @3.3f]]; [unmanaged.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.anyStringObj2 addObjects:@[@"a", @"d"]]; [unmanaged.anyDataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.anyDateObj2 addObjects:@[date(1), date(4)]]; [unmanaged.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optUnmanaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optUnmanaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optUnmanaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optUnmanaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optUnmanaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optUnmanaged.decimalObj2 addObjects:@[decimal128(2), decimal128(4), NSNull.null]]; [optUnmanaged.objectIdObj2 addObjects:@[objectId(2), objectId(4), NSNull.null]]; [optUnmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj setSet:unmanaged.boolObj2]; [unmanaged.intObj setSet:unmanaged.intObj2]; [unmanaged.floatObj setSet:unmanaged.floatObj2]; [unmanaged.doubleObj setSet:unmanaged.doubleObj2]; [unmanaged.stringObj setSet:unmanaged.stringObj2]; [unmanaged.dataObj setSet:unmanaged.dataObj2]; [unmanaged.dateObj setSet:unmanaged.dateObj2]; [unmanaged.decimalObj setSet:unmanaged.decimalObj2]; [unmanaged.objectIdObj setSet:unmanaged.objectIdObj2]; [unmanaged.uuidObj setSet:unmanaged.uuidObj2]; [unmanaged.anyBoolObj setSet:unmanaged.anyBoolObj2]; [unmanaged.anyIntObj setSet:unmanaged.anyIntObj2]; [unmanaged.anyFloatObj setSet:unmanaged.anyFloatObj2]; [unmanaged.anyDoubleObj setSet:unmanaged.anyDoubleObj2]; [unmanaged.anyStringObj setSet:unmanaged.anyStringObj2]; [unmanaged.anyDataObj setSet:unmanaged.anyDataObj2]; [unmanaged.anyDateObj setSet:unmanaged.anyDateObj2]; [unmanaged.anyDecimalObj setSet:unmanaged.anyDecimalObj2]; [unmanaged.anyObjectIdObj setSet:unmanaged.anyObjectIdObj2]; [unmanaged.anyUUIDObj setSet:unmanaged.anyUUIDObj2]; [optUnmanaged.intObj setSet:optUnmanaged.intObj2]; [optUnmanaged.floatObj setSet:optUnmanaged.floatObj2]; [optUnmanaged.doubleObj setSet:optUnmanaged.doubleObj2]; [optUnmanaged.stringObj setSet:optUnmanaged.stringObj2]; [optUnmanaged.dataObj setSet:optUnmanaged.dataObj2]; [optUnmanaged.dateObj setSet:optUnmanaged.dateObj2]; [optUnmanaged.decimalObj setSet:optUnmanaged.decimalObj2]; [optUnmanaged.objectIdObj setSet:optUnmanaged.objectIdObj2]; [optUnmanaged.uuidObj setSet:optUnmanaged.uuidObj2]; [realm beginWriteTransaction]; [managed.boolObj setSet:managed.boolObj2]; [managed.intObj setSet:managed.intObj2]; [managed.floatObj setSet:managed.floatObj2]; [managed.doubleObj setSet:managed.doubleObj2]; [managed.stringObj setSet:managed.stringObj2]; [managed.dataObj setSet:managed.dataObj2]; [managed.dateObj setSet:managed.dateObj2]; [managed.decimalObj setSet:managed.decimalObj2]; [managed.objectIdObj setSet:managed.objectIdObj2]; [managed.uuidObj setSet:managed.uuidObj2]; [managed.anyBoolObj setSet:managed.anyBoolObj2]; [managed.anyIntObj setSet:managed.anyIntObj2]; [managed.anyFloatObj setSet:managed.anyFloatObj2]; [managed.anyDoubleObj setSet:managed.anyDoubleObj2]; [managed.anyStringObj setSet:managed.anyStringObj2]; [managed.anyDataObj setSet:managed.anyDataObj2]; [managed.anyDateObj setSet:managed.anyDateObj2]; [managed.anyDecimalObj setSet:managed.anyDecimalObj2]; [managed.anyObjectIdObj setSet:managed.anyObjectIdObj2]; [managed.anyUUIDObj setSet:managed.anyUUIDObj2]; [optManaged.boolObj setSet:optManaged.boolObj2]; [optManaged.intObj setSet:optManaged.intObj2]; [optManaged.floatObj setSet:optManaged.floatObj2]; [optManaged.doubleObj setSet:optManaged.doubleObj2]; [optManaged.stringObj setSet:optManaged.stringObj2]; [optManaged.dataObj setSet:optManaged.dataObj2]; [optManaged.dateObj setSet:optManaged.dateObj2]; [optManaged.decimalObj setSet:optManaged.decimalObj2]; [optManaged.objectIdObj setSet:optManaged.objectIdObj2]; [optManaged.uuidObj setSet:optManaged.uuidObj2]; [realm commitWriteTransaction]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqualObjects(unmanaged.boolObj.allObjects, (@[@NO, @YES])); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:managed.boolObj.allObjects], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqual(optManaged.boolObj.count, 3U); } - (void)testUnion { [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj2 addObjects:@[@YES, @NO]]; [managed.intObj2 addObjects:@[@3, @4]]; [managed.floatObj2 addObjects:@[@3.3f, @4.4f]]; [managed.doubleObj2 addObjects:@[@3.3, @4.4]]; [managed.stringObj2 addObjects:@[@"bc", @"de"]]; [managed.dataObj2 addObjects:@[data(2), data(3)]]; [managed.dateObj2 addObjects:@[date(2), date(3)]]; [managed.decimalObj2 addObjects:@[decimal128(2), decimal128(3)]]; [managed.objectIdObj2 addObjects:@[objectId(2), objectId(3)]]; [managed.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj2 addObjects:@[@NO, @YES]]; [managed.anyIntObj2 addObjects:@[@2, @4]]; [managed.anyFloatObj2 addObjects:@[@2.2f, @4.4f]]; [managed.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [managed.anyStringObj2 addObjects:@[@"a", @"d"]]; [managed.anyDataObj2 addObjects:@[data(1), data(3)]]; [managed.anyDateObj2 addObjects:@[date(1), date(3)]]; [managed.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [managed.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [managed.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj2 addObjects:@[@YES, @NO, NSNull.null]]; [optManaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optManaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optManaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optManaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optManaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optManaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optManaged.decimalObj2 addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj2 addObjects:@[objectId(2), objectId(3), NSNull.null]]; [optManaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [realm commitWriteTransaction]; [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj2 addObjects:@[@NO, @YES]]; [unmanaged.intObj2 addObjects:@[@2, @4]]; [unmanaged.floatObj2 addObjects:@[@2.2f, @4.4f]]; [unmanaged.doubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.stringObj2 addObjects:@[@"a", @"de"]]; [unmanaged.dataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.dateObj2 addObjects:@[date(1), date(3)]]; [unmanaged.decimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.objectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj2 addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj2 addObjects:@[@2, @4]]; [unmanaged.anyFloatObj2 addObjects:@[@4.4f, @3.3f]]; [unmanaged.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.anyStringObj2 addObjects:@[@"a", @"d"]]; [unmanaged.anyDataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.anyDateObj2 addObjects:@[date(1), date(4)]]; [unmanaged.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optUnmanaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optUnmanaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optUnmanaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optUnmanaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optUnmanaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optUnmanaged.decimalObj2 addObjects:@[decimal128(2), decimal128(4), NSNull.null]]; [optUnmanaged.objectIdObj2 addObjects:@[objectId(2), objectId(4), NSNull.null]]; [optUnmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; XCTAssertThrows([managed.boolObj unionSet:managed.boolObj2]); XCTAssertThrows([managed.intObj unionSet:managed.intObj2]); XCTAssertThrows([managed.floatObj unionSet:managed.floatObj2]); XCTAssertThrows([managed.doubleObj unionSet:managed.doubleObj2]); XCTAssertThrows([managed.stringObj unionSet:managed.stringObj2]); XCTAssertThrows([managed.dataObj unionSet:managed.dataObj2]); XCTAssertThrows([managed.dateObj unionSet:managed.dateObj2]); XCTAssertThrows([managed.decimalObj unionSet:managed.decimalObj2]); XCTAssertThrows([managed.objectIdObj unionSet:managed.objectIdObj2]); XCTAssertThrows([managed.uuidObj unionSet:managed.uuidObj2]); XCTAssertThrows([managed.anyBoolObj unionSet:managed.anyBoolObj2]); XCTAssertThrows([managed.anyIntObj unionSet:managed.anyIntObj2]); XCTAssertThrows([managed.anyFloatObj unionSet:managed.anyFloatObj2]); XCTAssertThrows([managed.anyDoubleObj unionSet:managed.anyDoubleObj2]); XCTAssertThrows([managed.anyStringObj unionSet:managed.anyStringObj2]); XCTAssertThrows([managed.anyDataObj unionSet:managed.anyDataObj2]); XCTAssertThrows([managed.anyDateObj unionSet:managed.anyDateObj2]); XCTAssertThrows([managed.anyDecimalObj unionSet:managed.anyDecimalObj2]); XCTAssertThrows([managed.anyObjectIdObj unionSet:managed.anyObjectIdObj2]); XCTAssertThrows([managed.anyUUIDObj unionSet:managed.anyUUIDObj2]); XCTAssertThrows([optManaged.boolObj unionSet:optManaged.boolObj2]); XCTAssertThrows([optManaged.intObj unionSet:optManaged.intObj2]); XCTAssertThrows([optManaged.floatObj unionSet:optManaged.floatObj2]); XCTAssertThrows([optManaged.doubleObj unionSet:optManaged.doubleObj2]); XCTAssertThrows([optManaged.stringObj unionSet:optManaged.stringObj2]); XCTAssertThrows([optManaged.dataObj unionSet:optManaged.dataObj2]); XCTAssertThrows([optManaged.dateObj unionSet:optManaged.dateObj2]); XCTAssertThrows([optManaged.decimalObj unionSet:optManaged.decimalObj2]); XCTAssertThrows([optManaged.objectIdObj unionSet:optManaged.objectIdObj2]); XCTAssertThrows([optManaged.uuidObj unionSet:optManaged.uuidObj2]); [unmanaged.boolObj unionSet:unmanaged.boolObj2]; [unmanaged.intObj unionSet:unmanaged.intObj2]; [unmanaged.floatObj unionSet:unmanaged.floatObj2]; [unmanaged.doubleObj unionSet:unmanaged.doubleObj2]; [unmanaged.stringObj unionSet:unmanaged.stringObj2]; [unmanaged.dataObj unionSet:unmanaged.dataObj2]; [unmanaged.dateObj unionSet:unmanaged.dateObj2]; [unmanaged.decimalObj unionSet:unmanaged.decimalObj2]; [unmanaged.objectIdObj unionSet:unmanaged.objectIdObj2]; [unmanaged.uuidObj unionSet:unmanaged.uuidObj2]; [unmanaged.anyBoolObj unionSet:unmanaged.anyBoolObj2]; [unmanaged.anyIntObj unionSet:unmanaged.anyIntObj2]; [unmanaged.anyFloatObj unionSet:unmanaged.anyFloatObj2]; [unmanaged.anyDoubleObj unionSet:unmanaged.anyDoubleObj2]; [unmanaged.anyStringObj unionSet:unmanaged.anyStringObj2]; [unmanaged.anyDataObj unionSet:unmanaged.anyDataObj2]; [unmanaged.anyDateObj unionSet:unmanaged.anyDateObj2]; [unmanaged.anyDecimalObj unionSet:unmanaged.anyDecimalObj2]; [unmanaged.anyObjectIdObj unionSet:unmanaged.anyObjectIdObj2]; [unmanaged.anyUUIDObj unionSet:unmanaged.anyUUIDObj2]; [optUnmanaged.intObj unionSet:optUnmanaged.intObj2]; [optUnmanaged.floatObj unionSet:optUnmanaged.floatObj2]; [optUnmanaged.doubleObj unionSet:optUnmanaged.doubleObj2]; [optUnmanaged.stringObj unionSet:optUnmanaged.stringObj2]; [optUnmanaged.dataObj unionSet:optUnmanaged.dataObj2]; [optUnmanaged.dateObj unionSet:optUnmanaged.dateObj2]; [optUnmanaged.decimalObj unionSet:optUnmanaged.decimalObj2]; [optUnmanaged.objectIdObj unionSet:optUnmanaged.objectIdObj2]; [optUnmanaged.uuidObj unionSet:optUnmanaged.uuidObj2]; [realm beginWriteTransaction]; [managed.boolObj unionSet:managed.boolObj2]; [managed.intObj unionSet:managed.intObj2]; [managed.floatObj unionSet:managed.floatObj2]; [managed.doubleObj unionSet:managed.doubleObj2]; [managed.stringObj unionSet:managed.stringObj2]; [managed.dataObj unionSet:managed.dataObj2]; [managed.dateObj unionSet:managed.dateObj2]; [managed.decimalObj unionSet:managed.decimalObj2]; [managed.objectIdObj unionSet:managed.objectIdObj2]; [managed.uuidObj unionSet:managed.uuidObj2]; [managed.anyBoolObj unionSet:managed.anyBoolObj2]; [managed.anyIntObj unionSet:managed.anyIntObj2]; [managed.anyFloatObj unionSet:managed.anyFloatObj2]; [managed.anyDoubleObj unionSet:managed.anyDoubleObj2]; [managed.anyStringObj unionSet:managed.anyStringObj2]; [managed.anyDataObj unionSet:managed.anyDataObj2]; [managed.anyDateObj unionSet:managed.anyDateObj2]; [managed.anyDecimalObj unionSet:managed.anyDecimalObj2]; [managed.anyObjectIdObj unionSet:managed.anyObjectIdObj2]; [managed.anyUUIDObj unionSet:managed.anyUUIDObj2]; [optManaged.boolObj unionSet:optManaged.boolObj2]; [optManaged.intObj unionSet:optManaged.intObj2]; [optManaged.floatObj unionSet:optManaged.floatObj2]; [optManaged.doubleObj unionSet:optManaged.doubleObj2]; [optManaged.stringObj unionSet:optManaged.stringObj2]; [optManaged.dataObj unionSet:optManaged.dataObj2]; [optManaged.dateObj unionSet:optManaged.dateObj2]; [optManaged.decimalObj unionSet:optManaged.decimalObj2]; [optManaged.objectIdObj unionSet:optManaged.objectIdObj2]; [optManaged.uuidObj unionSet:optManaged.uuidObj2]; [realm commitWriteTransaction]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:unmanaged.boolObj.allObjects], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:managed.boolObj.allObjects], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqual(optManaged.boolObj.count, 3U); } - (void)testIntersect { [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj2 addObjects:@[@YES, @NO]]; [managed.intObj2 addObjects:@[@3, @4]]; [managed.floatObj2 addObjects:@[@3.3f, @4.4f]]; [managed.doubleObj2 addObjects:@[@3.3, @4.4]]; [managed.stringObj2 addObjects:@[@"bc", @"de"]]; [managed.dataObj2 addObjects:@[data(2), data(3)]]; [managed.dateObj2 addObjects:@[date(2), date(3)]]; [managed.decimalObj2 addObjects:@[decimal128(2), decimal128(3)]]; [managed.objectIdObj2 addObjects:@[objectId(2), objectId(3)]]; [managed.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj2 addObjects:@[@NO, @YES]]; [managed.anyIntObj2 addObjects:@[@2, @4]]; [managed.anyFloatObj2 addObjects:@[@2.2f, @4.4f]]; [managed.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [managed.anyStringObj2 addObjects:@[@"a", @"d"]]; [managed.anyDataObj2 addObjects:@[data(1), data(3)]]; [managed.anyDateObj2 addObjects:@[date(1), date(3)]]; [managed.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [managed.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [managed.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj2 addObjects:@[@YES, @NO, NSNull.null]]; [optManaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optManaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optManaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optManaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optManaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optManaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optManaged.decimalObj2 addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj2 addObjects:@[objectId(2), objectId(3), NSNull.null]]; [optManaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [realm commitWriteTransaction]; [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj2 addObjects:@[@NO, @YES]]; [unmanaged.intObj2 addObjects:@[@2, @4]]; [unmanaged.floatObj2 addObjects:@[@2.2f, @4.4f]]; [unmanaged.doubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.stringObj2 addObjects:@[@"a", @"de"]]; [unmanaged.dataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.dateObj2 addObjects:@[date(1), date(3)]]; [unmanaged.decimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.objectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj2 addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj2 addObjects:@[@2, @4]]; [unmanaged.anyFloatObj2 addObjects:@[@4.4f, @3.3f]]; [unmanaged.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.anyStringObj2 addObjects:@[@"a", @"d"]]; [unmanaged.anyDataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.anyDateObj2 addObjects:@[date(1), date(4)]]; [unmanaged.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optUnmanaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optUnmanaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optUnmanaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optUnmanaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optUnmanaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optUnmanaged.decimalObj2 addObjects:@[decimal128(2), decimal128(4), NSNull.null]]; [optUnmanaged.objectIdObj2 addObjects:@[objectId(2), objectId(4), NSNull.null]]; [optUnmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; XCTAssertThrows([managed.boolObj intersectSet:managed.boolObj2]); XCTAssertThrows([managed.intObj intersectSet:managed.intObj2]); XCTAssertThrows([managed.floatObj intersectSet:managed.floatObj2]); XCTAssertThrows([managed.doubleObj intersectSet:managed.doubleObj2]); XCTAssertThrows([managed.stringObj intersectSet:managed.stringObj2]); XCTAssertThrows([managed.dataObj intersectSet:managed.dataObj2]); XCTAssertThrows([managed.dateObj intersectSet:managed.dateObj2]); XCTAssertThrows([managed.decimalObj intersectSet:managed.decimalObj2]); XCTAssertThrows([managed.objectIdObj intersectSet:managed.objectIdObj2]); XCTAssertThrows([managed.uuidObj intersectSet:managed.uuidObj2]); XCTAssertThrows([managed.anyBoolObj intersectSet:managed.anyBoolObj2]); XCTAssertThrows([managed.anyIntObj intersectSet:managed.anyIntObj2]); XCTAssertThrows([managed.anyFloatObj intersectSet:managed.anyFloatObj2]); XCTAssertThrows([managed.anyDoubleObj intersectSet:managed.anyDoubleObj2]); XCTAssertThrows([managed.anyStringObj intersectSet:managed.anyStringObj2]); XCTAssertThrows([managed.anyDataObj intersectSet:managed.anyDataObj2]); XCTAssertThrows([managed.anyDateObj intersectSet:managed.anyDateObj2]); XCTAssertThrows([managed.anyDecimalObj intersectSet:managed.anyDecimalObj2]); XCTAssertThrows([managed.anyObjectIdObj intersectSet:managed.anyObjectIdObj2]); XCTAssertThrows([managed.anyUUIDObj intersectSet:managed.anyUUIDObj2]); XCTAssertThrows([optManaged.boolObj intersectSet:optManaged.boolObj2]); XCTAssertThrows([optManaged.intObj intersectSet:optManaged.intObj2]); XCTAssertThrows([optManaged.floatObj intersectSet:optManaged.floatObj2]); XCTAssertThrows([optManaged.doubleObj intersectSet:optManaged.doubleObj2]); XCTAssertThrows([optManaged.stringObj intersectSet:optManaged.stringObj2]); XCTAssertThrows([optManaged.dataObj intersectSet:optManaged.dataObj2]); XCTAssertThrows([optManaged.dateObj intersectSet:optManaged.dateObj2]); XCTAssertThrows([optManaged.decimalObj intersectSet:optManaged.decimalObj2]); XCTAssertThrows([optManaged.objectIdObj intersectSet:optManaged.objectIdObj2]); XCTAssertThrows([optManaged.uuidObj intersectSet:optManaged.uuidObj2]); uncheckedAssertTrue([managed.boolObj intersectsSet:managed.boolObj2]); uncheckedAssertTrue([managed.intObj intersectsSet:managed.intObj2]); uncheckedAssertTrue([managed.floatObj intersectsSet:managed.floatObj2]); uncheckedAssertTrue([managed.doubleObj intersectsSet:managed.doubleObj2]); uncheckedAssertTrue([managed.stringObj intersectsSet:managed.stringObj2]); uncheckedAssertTrue([managed.dataObj intersectsSet:managed.dataObj2]); uncheckedAssertTrue([managed.dateObj intersectsSet:managed.dateObj2]); uncheckedAssertTrue([managed.decimalObj intersectsSet:managed.decimalObj2]); uncheckedAssertTrue([managed.objectIdObj intersectsSet:managed.objectIdObj2]); uncheckedAssertTrue([managed.uuidObj intersectsSet:managed.uuidObj2]); uncheckedAssertTrue([managed.anyBoolObj intersectsSet:managed.anyBoolObj2]); uncheckedAssertTrue([managed.anyIntObj intersectsSet:managed.anyIntObj2]); uncheckedAssertTrue([managed.anyFloatObj intersectsSet:managed.anyFloatObj2]); uncheckedAssertTrue([managed.anyDoubleObj intersectsSet:managed.anyDoubleObj2]); uncheckedAssertTrue([managed.anyStringObj intersectsSet:managed.anyStringObj2]); uncheckedAssertTrue([managed.anyDataObj intersectsSet:managed.anyDataObj2]); uncheckedAssertTrue([managed.anyDateObj intersectsSet:managed.anyDateObj2]); uncheckedAssertTrue([managed.anyDecimalObj intersectsSet:managed.anyDecimalObj2]); uncheckedAssertTrue([managed.anyObjectIdObj intersectsSet:managed.anyObjectIdObj2]); uncheckedAssertTrue([managed.anyUUIDObj intersectsSet:managed.anyUUIDObj2]); uncheckedAssertTrue([optManaged.boolObj intersectsSet:optManaged.boolObj2]); uncheckedAssertTrue([optManaged.intObj intersectsSet:optManaged.intObj2]); uncheckedAssertTrue([optManaged.floatObj intersectsSet:optManaged.floatObj2]); uncheckedAssertTrue([optManaged.doubleObj intersectsSet:optManaged.doubleObj2]); uncheckedAssertTrue([optManaged.stringObj intersectsSet:optManaged.stringObj2]); uncheckedAssertTrue([optManaged.dataObj intersectsSet:optManaged.dataObj2]); uncheckedAssertTrue([optManaged.dateObj intersectsSet:optManaged.dateObj2]); uncheckedAssertTrue([optManaged.decimalObj intersectsSet:optManaged.decimalObj2]); uncheckedAssertTrue([optManaged.objectIdObj intersectsSet:optManaged.objectIdObj2]); uncheckedAssertTrue([optManaged.uuidObj intersectsSet:optManaged.uuidObj2]); uncheckedAssertTrue([unmanaged.boolObj intersectsSet:unmanaged.boolObj2]); uncheckedAssertTrue([unmanaged.intObj intersectsSet:unmanaged.intObj2]); uncheckedAssertTrue([unmanaged.floatObj intersectsSet:unmanaged.floatObj2]); uncheckedAssertTrue([unmanaged.doubleObj intersectsSet:unmanaged.doubleObj2]); uncheckedAssertTrue([unmanaged.stringObj intersectsSet:unmanaged.stringObj2]); uncheckedAssertTrue([unmanaged.dataObj intersectsSet:unmanaged.dataObj2]); uncheckedAssertTrue([unmanaged.dateObj intersectsSet:unmanaged.dateObj2]); uncheckedAssertTrue([unmanaged.decimalObj intersectsSet:unmanaged.decimalObj2]); uncheckedAssertTrue([unmanaged.objectIdObj intersectsSet:unmanaged.objectIdObj2]); uncheckedAssertTrue([unmanaged.uuidObj intersectsSet:unmanaged.uuidObj2]); uncheckedAssertTrue([unmanaged.anyBoolObj intersectsSet:unmanaged.anyBoolObj2]); uncheckedAssertTrue([unmanaged.anyIntObj intersectsSet:unmanaged.anyIntObj2]); uncheckedAssertTrue([unmanaged.anyFloatObj intersectsSet:unmanaged.anyFloatObj2]); uncheckedAssertTrue([unmanaged.anyDoubleObj intersectsSet:unmanaged.anyDoubleObj2]); uncheckedAssertTrue([unmanaged.anyStringObj intersectsSet:unmanaged.anyStringObj2]); uncheckedAssertTrue([unmanaged.anyDataObj intersectsSet:unmanaged.anyDataObj2]); uncheckedAssertTrue([unmanaged.anyDateObj intersectsSet:unmanaged.anyDateObj2]); uncheckedAssertTrue([unmanaged.anyDecimalObj intersectsSet:unmanaged.anyDecimalObj2]); uncheckedAssertTrue([unmanaged.anyObjectIdObj intersectsSet:unmanaged.anyObjectIdObj2]); uncheckedAssertTrue([unmanaged.anyUUIDObj intersectsSet:unmanaged.anyUUIDObj2]); uncheckedAssertTrue([optUnmanaged.intObj intersectsSet:optUnmanaged.intObj2]); uncheckedAssertTrue([optUnmanaged.floatObj intersectsSet:optUnmanaged.floatObj2]); uncheckedAssertTrue([optUnmanaged.doubleObj intersectsSet:optUnmanaged.doubleObj2]); uncheckedAssertTrue([optUnmanaged.stringObj intersectsSet:optUnmanaged.stringObj2]); uncheckedAssertTrue([optUnmanaged.dataObj intersectsSet:optUnmanaged.dataObj2]); uncheckedAssertTrue([optUnmanaged.dateObj intersectsSet:optUnmanaged.dateObj2]); uncheckedAssertTrue([optUnmanaged.decimalObj intersectsSet:optUnmanaged.decimalObj2]); uncheckedAssertTrue([optUnmanaged.objectIdObj intersectsSet:optUnmanaged.objectIdObj2]); uncheckedAssertTrue([optUnmanaged.uuidObj intersectsSet:optUnmanaged.uuidObj2]); [unmanaged.boolObj intersectSet:unmanaged.boolObj2]; [unmanaged.intObj intersectSet:unmanaged.intObj2]; [unmanaged.floatObj intersectSet:unmanaged.floatObj2]; [unmanaged.doubleObj intersectSet:unmanaged.doubleObj2]; [unmanaged.stringObj intersectSet:unmanaged.stringObj2]; [unmanaged.dataObj intersectSet:unmanaged.dataObj2]; [unmanaged.dateObj intersectSet:unmanaged.dateObj2]; [unmanaged.decimalObj intersectSet:unmanaged.decimalObj2]; [unmanaged.objectIdObj intersectSet:unmanaged.objectIdObj2]; [unmanaged.uuidObj intersectSet:unmanaged.uuidObj2]; [unmanaged.anyBoolObj intersectSet:unmanaged.anyBoolObj2]; [unmanaged.anyIntObj intersectSet:unmanaged.anyIntObj2]; [unmanaged.anyFloatObj intersectSet:unmanaged.anyFloatObj2]; [unmanaged.anyDoubleObj intersectSet:unmanaged.anyDoubleObj2]; [unmanaged.anyStringObj intersectSet:unmanaged.anyStringObj2]; [unmanaged.anyDataObj intersectSet:unmanaged.anyDataObj2]; [unmanaged.anyDateObj intersectSet:unmanaged.anyDateObj2]; [unmanaged.anyDecimalObj intersectSet:unmanaged.anyDecimalObj2]; [unmanaged.anyObjectIdObj intersectSet:unmanaged.anyObjectIdObj2]; [unmanaged.anyUUIDObj intersectSet:unmanaged.anyUUIDObj2]; [optUnmanaged.intObj intersectSet:optUnmanaged.intObj2]; [optUnmanaged.floatObj intersectSet:optUnmanaged.floatObj2]; [optUnmanaged.doubleObj intersectSet:optUnmanaged.doubleObj2]; [optUnmanaged.stringObj intersectSet:optUnmanaged.stringObj2]; [optUnmanaged.dataObj intersectSet:optUnmanaged.dataObj2]; [optUnmanaged.dateObj intersectSet:optUnmanaged.dateObj2]; [optUnmanaged.decimalObj intersectSet:optUnmanaged.decimalObj2]; [optUnmanaged.objectIdObj intersectSet:optUnmanaged.objectIdObj2]; [optUnmanaged.uuidObj intersectSet:optUnmanaged.uuidObj2]; [realm beginWriteTransaction]; [managed.boolObj intersectSet:managed.boolObj2]; [managed.intObj intersectSet:managed.intObj2]; [managed.floatObj intersectSet:managed.floatObj2]; [managed.doubleObj intersectSet:managed.doubleObj2]; [managed.stringObj intersectSet:managed.stringObj2]; [managed.dataObj intersectSet:managed.dataObj2]; [managed.dateObj intersectSet:managed.dateObj2]; [managed.decimalObj intersectSet:managed.decimalObj2]; [managed.objectIdObj intersectSet:managed.objectIdObj2]; [managed.uuidObj intersectSet:managed.uuidObj2]; [managed.anyBoolObj intersectSet:managed.anyBoolObj2]; [managed.anyIntObj intersectSet:managed.anyIntObj2]; [managed.anyFloatObj intersectSet:managed.anyFloatObj2]; [managed.anyDoubleObj intersectSet:managed.anyDoubleObj2]; [managed.anyStringObj intersectSet:managed.anyStringObj2]; [managed.anyDataObj intersectSet:managed.anyDataObj2]; [managed.anyDateObj intersectSet:managed.anyDateObj2]; [managed.anyDecimalObj intersectSet:managed.anyDecimalObj2]; [managed.anyObjectIdObj intersectSet:managed.anyObjectIdObj2]; [managed.anyUUIDObj intersectSet:managed.anyUUIDObj2]; [optManaged.boolObj intersectSet:optManaged.boolObj2]; [optManaged.intObj intersectSet:optManaged.intObj2]; [optManaged.floatObj intersectSet:optManaged.floatObj2]; [optManaged.doubleObj intersectSet:optManaged.doubleObj2]; [optManaged.stringObj intersectSet:optManaged.stringObj2]; [optManaged.dataObj intersectSet:optManaged.dataObj2]; [optManaged.dateObj intersectSet:optManaged.dateObj2]; [optManaged.decimalObj intersectSet:optManaged.decimalObj2]; [optManaged.objectIdObj intersectSet:optManaged.objectIdObj2]; [optManaged.uuidObj intersectSet:optManaged.uuidObj2]; [realm commitWriteTransaction]; uncheckedAssertEqual(unmanaged.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:unmanaged.boolObj.allObjects], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqual(managed.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:managed.boolObj.allObjects], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqual(optManaged.boolObj.count, 2U); uncheckedAssertEqualObjects([NSSet setWithArray:optManaged.boolObj.allObjects], ([NSSet setWithArray:@[NSNull.null, @NO]])); } - (void)testMinus { [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj2 addObjects:@[@YES, @NO]]; [managed.intObj2 addObjects:@[@3, @4]]; [managed.floatObj2 addObjects:@[@3.3f, @4.4f]]; [managed.doubleObj2 addObjects:@[@3.3, @4.4]]; [managed.stringObj2 addObjects:@[@"bc", @"de"]]; [managed.dataObj2 addObjects:@[data(2), data(3)]]; [managed.dateObj2 addObjects:@[date(2), date(3)]]; [managed.decimalObj2 addObjects:@[decimal128(2), decimal128(3)]]; [managed.objectIdObj2 addObjects:@[objectId(2), objectId(3)]]; [managed.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj2 addObjects:@[@NO, @YES]]; [managed.anyIntObj2 addObjects:@[@2, @4]]; [managed.anyFloatObj2 addObjects:@[@2.2f, @4.4f]]; [managed.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [managed.anyStringObj2 addObjects:@[@"a", @"d"]]; [managed.anyDataObj2 addObjects:@[data(1), data(3)]]; [managed.anyDateObj2 addObjects:@[date(1), date(3)]]; [managed.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [managed.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [managed.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj2 addObjects:@[@YES, @NO, NSNull.null]]; [optManaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optManaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optManaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optManaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optManaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optManaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optManaged.decimalObj2 addObjects:@[decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj2 addObjects:@[objectId(2), objectId(3), NSNull.null]]; [optManaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [realm commitWriteTransaction]; [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj2 addObjects:@[@NO, @YES]]; [unmanaged.intObj2 addObjects:@[@2, @4]]; [unmanaged.floatObj2 addObjects:@[@2.2f, @4.4f]]; [unmanaged.doubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.stringObj2 addObjects:@[@"a", @"de"]]; [unmanaged.dataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.dateObj2 addObjects:@[date(1), date(3)]]; [unmanaged.decimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.objectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj2 addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj2 addObjects:@[@2, @4]]; [unmanaged.anyFloatObj2 addObjects:@[@4.4f, @3.3f]]; [unmanaged.anyDoubleObj2 addObjects:@[@2.2, @4.4]]; [unmanaged.anyStringObj2 addObjects:@[@"a", @"d"]]; [unmanaged.anyDataObj2 addObjects:@[data(1), data(3)]]; [unmanaged.anyDateObj2 addObjects:@[date(1), date(4)]]; [unmanaged.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(3)]]; [unmanaged.anyObjectIdObj2 addObjects:@[objectId(1), objectId(3)]]; [unmanaged.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj2 addObjects:@[@3, @4, NSNull.null]]; [optUnmanaged.floatObj2 addObjects:@[@3.3f, @4.4f, NSNull.null]]; [optUnmanaged.doubleObj2 addObjects:@[@3.3, @4.4, NSNull.null]]; [optUnmanaged.stringObj2 addObjects:@[@"bc", @"de", NSNull.null]]; [optUnmanaged.dataObj2 addObjects:@[data(2), data(3), NSNull.null]]; [optUnmanaged.dateObj2 addObjects:@[date(2), date(3), NSNull.null]]; [optUnmanaged.decimalObj2 addObjects:@[decimal128(2), decimal128(4), NSNull.null]]; [optUnmanaged.objectIdObj2 addObjects:@[objectId(2), objectId(4), NSNull.null]]; [optUnmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; XCTAssertThrows([managed.boolObj minusSet:managed.boolObj2]); XCTAssertThrows([managed.intObj minusSet:managed.intObj2]); XCTAssertThrows([managed.floatObj minusSet:managed.floatObj2]); XCTAssertThrows([managed.doubleObj minusSet:managed.doubleObj2]); XCTAssertThrows([managed.stringObj minusSet:managed.stringObj2]); XCTAssertThrows([managed.dataObj minusSet:managed.dataObj2]); XCTAssertThrows([managed.dateObj minusSet:managed.dateObj2]); XCTAssertThrows([managed.decimalObj minusSet:managed.decimalObj2]); XCTAssertThrows([managed.objectIdObj minusSet:managed.objectIdObj2]); XCTAssertThrows([managed.uuidObj minusSet:managed.uuidObj2]); XCTAssertThrows([managed.anyBoolObj minusSet:managed.anyBoolObj2]); XCTAssertThrows([managed.anyIntObj minusSet:managed.anyIntObj2]); XCTAssertThrows([managed.anyFloatObj minusSet:managed.anyFloatObj2]); XCTAssertThrows([managed.anyDoubleObj minusSet:managed.anyDoubleObj2]); XCTAssertThrows([managed.anyStringObj minusSet:managed.anyStringObj2]); XCTAssertThrows([managed.anyDataObj minusSet:managed.anyDataObj2]); XCTAssertThrows([managed.anyDateObj minusSet:managed.anyDateObj2]); XCTAssertThrows([managed.anyDecimalObj minusSet:managed.anyDecimalObj2]); XCTAssertThrows([managed.anyObjectIdObj minusSet:managed.anyObjectIdObj2]); XCTAssertThrows([managed.anyUUIDObj minusSet:managed.anyUUIDObj2]); XCTAssertThrows([optManaged.boolObj minusSet:optManaged.boolObj2]); XCTAssertThrows([optManaged.intObj minusSet:optManaged.intObj2]); XCTAssertThrows([optManaged.floatObj minusSet:optManaged.floatObj2]); XCTAssertThrows([optManaged.doubleObj minusSet:optManaged.doubleObj2]); XCTAssertThrows([optManaged.stringObj minusSet:optManaged.stringObj2]); XCTAssertThrows([optManaged.dataObj minusSet:optManaged.dataObj2]); XCTAssertThrows([optManaged.dateObj minusSet:optManaged.dateObj2]); XCTAssertThrows([optManaged.decimalObj minusSet:optManaged.decimalObj2]); XCTAssertThrows([optManaged.objectIdObj minusSet:optManaged.objectIdObj2]); XCTAssertThrows([optManaged.uuidObj minusSet:optManaged.uuidObj2]); [unmanaged.boolObj minusSet:unmanaged.boolObj2]; [unmanaged.intObj minusSet:unmanaged.intObj2]; [unmanaged.floatObj minusSet:unmanaged.floatObj2]; [unmanaged.doubleObj minusSet:unmanaged.doubleObj2]; [unmanaged.stringObj minusSet:unmanaged.stringObj2]; [unmanaged.dataObj minusSet:unmanaged.dataObj2]; [unmanaged.dateObj minusSet:unmanaged.dateObj2]; [unmanaged.decimalObj minusSet:unmanaged.decimalObj2]; [unmanaged.objectIdObj minusSet:unmanaged.objectIdObj2]; [unmanaged.uuidObj minusSet:unmanaged.uuidObj2]; [unmanaged.anyBoolObj minusSet:unmanaged.anyBoolObj2]; [unmanaged.anyIntObj minusSet:unmanaged.anyIntObj2]; [unmanaged.anyFloatObj minusSet:unmanaged.anyFloatObj2]; [unmanaged.anyDoubleObj minusSet:unmanaged.anyDoubleObj2]; [unmanaged.anyStringObj minusSet:unmanaged.anyStringObj2]; [unmanaged.anyDataObj minusSet:unmanaged.anyDataObj2]; [unmanaged.anyDateObj minusSet:unmanaged.anyDateObj2]; [unmanaged.anyDecimalObj minusSet:unmanaged.anyDecimalObj2]; [unmanaged.anyObjectIdObj minusSet:unmanaged.anyObjectIdObj2]; [unmanaged.anyUUIDObj minusSet:unmanaged.anyUUIDObj2]; [optUnmanaged.intObj minusSet:optUnmanaged.intObj2]; [optUnmanaged.floatObj minusSet:optUnmanaged.floatObj2]; [optUnmanaged.doubleObj minusSet:optUnmanaged.doubleObj2]; [optUnmanaged.stringObj minusSet:optUnmanaged.stringObj2]; [optUnmanaged.dataObj minusSet:optUnmanaged.dataObj2]; [optUnmanaged.dateObj minusSet:optUnmanaged.dateObj2]; [optUnmanaged.decimalObj minusSet:optUnmanaged.decimalObj2]; [optUnmanaged.objectIdObj minusSet:optUnmanaged.objectIdObj2]; [optUnmanaged.uuidObj minusSet:optUnmanaged.uuidObj2]; [realm beginWriteTransaction]; [managed.boolObj minusSet:managed.boolObj2]; [managed.intObj minusSet:managed.intObj2]; [managed.floatObj minusSet:managed.floatObj2]; [managed.doubleObj minusSet:managed.doubleObj2]; [managed.stringObj minusSet:managed.stringObj2]; [managed.dataObj minusSet:managed.dataObj2]; [managed.dateObj minusSet:managed.dateObj2]; [managed.decimalObj minusSet:managed.decimalObj2]; [managed.objectIdObj minusSet:managed.objectIdObj2]; [managed.uuidObj minusSet:managed.uuidObj2]; [managed.anyBoolObj minusSet:managed.anyBoolObj2]; [managed.anyIntObj minusSet:managed.anyIntObj2]; [managed.anyFloatObj minusSet:managed.anyFloatObj2]; [managed.anyDoubleObj minusSet:managed.anyDoubleObj2]; [managed.anyStringObj minusSet:managed.anyStringObj2]; [managed.anyDataObj minusSet:managed.anyDataObj2]; [managed.anyDateObj minusSet:managed.anyDateObj2]; [managed.anyDecimalObj minusSet:managed.anyDecimalObj2]; [managed.anyObjectIdObj minusSet:managed.anyObjectIdObj2]; [managed.anyUUIDObj minusSet:managed.anyUUIDObj2]; [optManaged.boolObj minusSet:optManaged.boolObj2]; [optManaged.intObj minusSet:optManaged.intObj2]; [optManaged.floatObj minusSet:optManaged.floatObj2]; [optManaged.doubleObj minusSet:optManaged.doubleObj2]; [optManaged.stringObj minusSet:optManaged.stringObj2]; [optManaged.dataObj minusSet:optManaged.dataObj2]; [optManaged.dateObj minusSet:optManaged.dateObj2]; [optManaged.decimalObj minusSet:optManaged.decimalObj2]; [optManaged.objectIdObj minusSet:optManaged.objectIdObj2]; [optManaged.uuidObj minusSet:optManaged.uuidObj2]; [realm commitWriteTransaction]; uncheckedAssertEqual(unmanaged.boolObj.count, 0U); uncheckedAssertEqualObjects(unmanaged.boolObj.allObjects, (@[])); uncheckedAssertEqual(managed.boolObj.count, 0U); uncheckedAssertEqualObjects(managed.boolObj.allObjects, (@[])); uncheckedAssertEqual(optManaged.boolObj.count, 0U); uncheckedAssertEqualObjects(optManaged.boolObj.allObjects, (@[])); } - (void)testIsSubsetOfSet { [managed.boolObj addObjects:@[@NO, @YES]]; [managed.intObj addObjects:@[@2, @3]]; [managed.floatObj addObjects:@[@2.2f, @3.3f]]; [managed.doubleObj addObjects:@[@2.2, @3.3]]; [managed.stringObj addObjects:@[@"a", @"bc"]]; [managed.dataObj addObjects:@[data(1), data(2)]]; [managed.dateObj addObjects:@[date(1), date(2)]]; [managed.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.uuidObj addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]; [managed.anyBoolObj addObjects:@[@NO, @YES]]; [managed.anyIntObj addObjects:@[@2, @3]]; [managed.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [managed.anyDoubleObj addObjects:@[@2.2, @3.3]]; [managed.anyStringObj addObjects:@[@"a", @"b"]]; [managed.anyDataObj addObjects:@[data(1), data(2)]]; [managed.anyDateObj addObjects:@[date(1), date(2)]]; [managed.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [managed.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [managed.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj addObjects:@[NSNull.null, @NO, NSNull.null]]; [optManaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optManaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optManaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optManaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optManaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optManaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optManaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optManaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optManaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [managed.boolObj2 addObjects:@[@NO, @YES, @YES, @NO]]; [managed.intObj2 addObjects:@[@2, @3, @3, @4]]; [managed.floatObj2 addObjects:@[@2.2f, @3.3f, @3.3f, @4.4f]]; [managed.doubleObj2 addObjects:@[@2.2, @3.3, @3.3, @4.4]]; [managed.stringObj2 addObjects:@[@"a", @"bc", @"bc", @"de"]]; [managed.dataObj2 addObjects:@[data(1), data(2), data(2), data(3)]]; [managed.dateObj2 addObjects:@[date(1), date(2), date(2), date(3)]]; [managed.decimalObj2 addObjects:@[decimal128(1), decimal128(2), decimal128(2), decimal128(3)]]; [managed.objectIdObj2 addObjects:@[objectId(1), objectId(2), objectId(2), objectId(3)]]; [managed.uuidObj2 addObjects:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [managed.anyBoolObj2 addObjects:@[@NO, @YES, @NO, @YES]]; [managed.anyIntObj2 addObjects:@[@2, @3, @2, @4]]; [managed.anyFloatObj2 addObjects:@[@2.2f, @3.3f, @2.2f, @4.4f]]; [managed.anyDoubleObj2 addObjects:@[@2.2, @3.3, @2.2, @4.4]]; [managed.anyStringObj2 addObjects:@[@"a", @"b", @"a", @"d"]]; [managed.anyDataObj2 addObjects:@[data(1), data(2), data(1), data(3)]]; [managed.anyDateObj2 addObjects:@[date(1), date(2), date(1), date(3)]]; [managed.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(2), decimal128(1), decimal128(3)]]; [managed.anyObjectIdObj2 addObjects:@[objectId(1), objectId(2), objectId(1), objectId(3)]]; [managed.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optManaged.boolObj2 addObjects:@[NSNull.null, @NO, @YES, @NO, NSNull.null]]; [optManaged.intObj2 addObjects:@[NSNull.null, @2, @3, @4, NSNull.null]]; [optManaged.floatObj2 addObjects:@[NSNull.null, @2.2f, @3.3f, @4.4f, NSNull.null]]; [optManaged.doubleObj2 addObjects:@[NSNull.null, @2.2, @3.3, @4.4, NSNull.null]]; [optManaged.stringObj2 addObjects:@[NSNull.null, @"a", @"bc", @"de", NSNull.null]]; [optManaged.dataObj2 addObjects:@[NSNull.null, data(1), data(2), data(3), NSNull.null]]; [optManaged.dateObj2 addObjects:@[NSNull.null, date(1), date(2), date(3), NSNull.null]]; [optManaged.decimalObj2 addObjects:@[NSNull.null, decimal128(1), decimal128(2), decimal128(3), NSNull.null]]; [optManaged.objectIdObj2 addObjects:@[NSNull.null, objectId(1), objectId(2), objectId(3), NSNull.null]]; [optManaged.uuidObj2 addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [realm commitWriteTransaction]; [unmanaged.boolObj addObjects:@[@NO, @YES]]; [unmanaged.intObj addObjects:@[@2, @3]]; [unmanaged.floatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.doubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.stringObj addObjects:@[@"a", @"bc"]]; [unmanaged.dataObj addObjects:@[data(1), data(2)]]; [unmanaged.dateObj addObjects:@[date(1), date(2)]]; [unmanaged.decimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.objectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.uuidObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj addObjects:@[@NO, @YES]]; [unmanaged.anyIntObj addObjects:@[@2, @3]]; [unmanaged.anyFloatObj addObjects:@[@2.2f, @3.3f]]; [unmanaged.anyDoubleObj addObjects:@[@2.2, @3.3]]; [unmanaged.anyStringObj addObjects:@[@"a", @"b"]]; [unmanaged.anyDataObj addObjects:@[data(1), data(2)]]; [unmanaged.anyDateObj addObjects:@[date(1), date(2)]]; [unmanaged.anyDecimalObj addObjects:@[decimal128(1), decimal128(2)]]; [unmanaged.anyObjectIdObj addObjects:@[objectId(1), objectId(2)]]; [unmanaged.anyUUIDObj addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj addObjects:@[NSNull.null, @2, NSNull.null]]; [optUnmanaged.floatObj addObjects:@[NSNull.null, @2.2f, NSNull.null]]; [optUnmanaged.doubleObj addObjects:@[NSNull.null, @2.2, NSNull.null]]; [optUnmanaged.stringObj addObjects:@[NSNull.null, @"a", NSNull.null]]; [optUnmanaged.dataObj addObjects:@[NSNull.null, data(1), NSNull.null]]; [optUnmanaged.dateObj addObjects:@[NSNull.null, date(1), NSNull.null]]; [optUnmanaged.decimalObj addObjects:@[NSNull.null, decimal128(1), NSNull.null]]; [optUnmanaged.objectIdObj addObjects:@[NSNull.null, objectId(1), NSNull.null]]; [optUnmanaged.uuidObj addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; [unmanaged.boolObj2 addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.intObj2 addObjects:@[@2, @3, @2, @4]]; [unmanaged.floatObj2 addObjects:@[@2.2f, @3.3f, @2.2f, @4.4f]]; [unmanaged.doubleObj2 addObjects:@[@2.2, @3.3, @2.2, @4.4]]; [unmanaged.stringObj2 addObjects:@[@"a", @"bc", @"a", @"de"]]; [unmanaged.dataObj2 addObjects:@[data(1), data(2), data(1), data(3)]]; [unmanaged.dateObj2 addObjects:@[date(1), date(2), date(1), date(3)]]; [unmanaged.decimalObj2 addObjects:@[decimal128(1), decimal128(2), decimal128(1), decimal128(3)]]; [unmanaged.objectIdObj2 addObjects:@[objectId(1), objectId(2), objectId(1), objectId(3)]]; [unmanaged.uuidObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [unmanaged.anyBoolObj2 addObjects:@[@NO, @YES, @NO, @YES]]; [unmanaged.anyIntObj2 addObjects:@[@2, @3, @2, @4]]; [unmanaged.anyFloatObj2 addObjects:@[@2.2f, @3.3f, @4.4f, @3.3f]]; [unmanaged.anyDoubleObj2 addObjects:@[@2.2, @3.3, @2.2, @4.4]]; [unmanaged.anyStringObj2 addObjects:@[@"a", @"b", @"a", @"d"]]; [unmanaged.anyDataObj2 addObjects:@[data(1), data(2), data(1), data(3)]]; [unmanaged.anyDateObj2 addObjects:@[date(1), date(2), date(1), date(4)]]; [unmanaged.anyDecimalObj2 addObjects:@[decimal128(1), decimal128(2), decimal128(1), decimal128(3)]]; [unmanaged.anyObjectIdObj2 addObjects:@[objectId(1), objectId(2), objectId(1), objectId(3)]]; [unmanaged.anyUUIDObj2 addObjects:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")]]; [optUnmanaged.intObj2 addObjects:@[NSNull.null, @2, @3, @4, NSNull.null]]; [optUnmanaged.floatObj2 addObjects:@[NSNull.null, @2.2f, @3.3f, @4.4f, NSNull.null]]; [optUnmanaged.doubleObj2 addObjects:@[NSNull.null, @2.2, @3.3, @4.4, NSNull.null]]; [optUnmanaged.stringObj2 addObjects:@[NSNull.null, @"a", @"bc", @"de", NSNull.null]]; [optUnmanaged.dataObj2 addObjects:@[NSNull.null, data(1), data(2), data(3), NSNull.null]]; [optUnmanaged.dateObj2 addObjects:@[NSNull.null, date(1), date(2), date(3), NSNull.null]]; [optUnmanaged.decimalObj2 addObjects:@[NSNull.null, decimal128(1), decimal128(2), decimal128(4), NSNull.null]]; [optUnmanaged.objectIdObj2 addObjects:@[NSNull.null, objectId(1), objectId(2), objectId(4), NSNull.null]]; [optUnmanaged.uuidObj2 addObjects:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"123DECC8-B300-4954-A233-F89909F4FD89"), NSNull.null]]; uncheckedAssertTrue([managed.boolObj2 isSubsetOfSet:managed.boolObj]); uncheckedAssertTrue([unmanaged.boolObj2 isSubsetOfSet:unmanaged.boolObj]); uncheckedAssertFalse([optManaged.boolObj2 isSubsetOfSet:optManaged.boolObj]); uncheckedAssertTrue([managed.boolObj isSubsetOfSet:managed.boolObj2]); uncheckedAssertTrue([unmanaged.boolObj isSubsetOfSet:unmanaged.boolObj2]); uncheckedAssertTrue([optManaged.boolObj isSubsetOfSet:optManaged.boolObj2]); } - (void)testMin { RLMAssertThrowsWithReason([unmanaged.boolObj minOfProperty:@"self"], @"minOfProperty: is not supported for bool set"); RLMAssertThrowsWithReason([unmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string set"); RLMAssertThrowsWithReason([unmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data set"); RLMAssertThrowsWithReason([unmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id set"); RLMAssertThrowsWithReason([unmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid set"); RLMAssertThrowsWithReason([optUnmanaged.stringObj minOfProperty:@"self"], @"minOfProperty: is not supported for string? set"); RLMAssertThrowsWithReason([optUnmanaged.dataObj minOfProperty:@"self"], @"minOfProperty: is not supported for data? set"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj minOfProperty:@"self"], @"minOfProperty: is not supported for object id? set"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj minOfProperty:@"self"], @"minOfProperty: is not supported for uuid? set"); RLMAssertThrowsWithReason([managed.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool set 'AllPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string set 'AllPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj minOfProperty:@"self"], @"Operation 'min' not supported for data set 'AllPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([managed.objectIdObj minOfProperty:@"self"], @"Operation 'min' not supported for object id set 'AllPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj minOfProperty:@"self"], @"Operation 'min' not supported for uuid set 'AllPrimitiveSets.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj minOfProperty:@"self"], @"Operation 'min' not supported for bool? set 'AllOptionalPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj minOfProperty:@"self"], @"Operation 'min' not supported for string? set 'AllOptionalPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj minOfProperty:@"self"], @"Operation 'min' not supported for data? set 'AllOptionalPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj minOfProperty:@"self"], @"Operation 'min' not supported for object id? set 'AllOptionalPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj minOfProperty:@"self"], @"Operation 'min' not supported for uuid? set 'AllOptionalPrimitiveSets.uuidObj'"); uncheckedAssertNil([unmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.intObj minOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj minOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj minOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj minOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([unmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.decimalObj minOfProperty:@"self"], decimal128(1)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj minOfProperty:@"self"], decimal128(1)); uncheckedAssertEqualObjects([optUnmanaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optUnmanaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optUnmanaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj minOfProperty:@"self"], decimal128(1)); uncheckedAssertEqualObjects([managed.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([managed.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.decimalObj minOfProperty:@"self"], decimal128(1)); uncheckedAssertEqualObjects([managed.anyIntObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([managed.anyFloatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj minOfProperty:@"self"], decimal128(1)); uncheckedAssertEqualObjects([optManaged.intObj minOfProperty:@"self"], @2); uncheckedAssertEqualObjects([optManaged.floatObj minOfProperty:@"self"], @2.2f); uncheckedAssertEqualObjects([optManaged.doubleObj minOfProperty:@"self"], @2.2); uncheckedAssertEqualObjects([optManaged.dateObj minOfProperty:@"self"], date(1)); uncheckedAssertEqualObjects([optManaged.decimalObj minOfProperty:@"self"], decimal128(1)); } - (void)testMax { RLMAssertThrowsWithReason([unmanaged.boolObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for bool set"); RLMAssertThrowsWithReason([unmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string set"); RLMAssertThrowsWithReason([unmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data set"); RLMAssertThrowsWithReason([unmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id set"); RLMAssertThrowsWithReason([unmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid set"); RLMAssertThrowsWithReason([optUnmanaged.stringObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for string? set"); RLMAssertThrowsWithReason([optUnmanaged.dataObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for data? set"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for object id? set"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj maxOfProperty:@"self"], @"maxOfProperty: is not supported for uuid? set"); RLMAssertThrowsWithReason([managed.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool set 'AllPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string set 'AllPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj maxOfProperty:@"self"], @"Operation 'max' not supported for data set 'AllPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([managed.objectIdObj maxOfProperty:@"self"], @"Operation 'max' not supported for object id set 'AllPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj maxOfProperty:@"self"], @"Operation 'max' not supported for uuid set 'AllPrimitiveSets.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj maxOfProperty:@"self"], @"Operation 'max' not supported for bool? set 'AllOptionalPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj maxOfProperty:@"self"], @"Operation 'max' not supported for string? set 'AllOptionalPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj maxOfProperty:@"self"], @"Operation 'max' not supported for data? set 'AllOptionalPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj maxOfProperty:@"self"], @"Operation 'max' not supported for object id? set 'AllOptionalPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj maxOfProperty:@"self"], @"Operation 'max' not supported for uuid? set 'AllOptionalPrimitiveSets.uuidObj'"); uncheckedAssertNil([unmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.intObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDateObj maxOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.dateObj maxOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj maxOfProperty:@"self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([unmanaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.decimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([unmanaged.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optUnmanaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([optUnmanaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([optUnmanaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([managed.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.decimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([managed.anyIntObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([managed.anyFloatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([managed.anyDoubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([managed.anyDateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([managed.anyDecimalObj maxOfProperty:@"self"], decimal128(2)); uncheckedAssertEqualObjects([optManaged.intObj maxOfProperty:@"self"], @3); uncheckedAssertEqualObjects([optManaged.floatObj maxOfProperty:@"self"], @3.3f); uncheckedAssertEqualObjects([optManaged.doubleObj maxOfProperty:@"self"], @3.3); uncheckedAssertEqualObjects([optManaged.dateObj maxOfProperty:@"self"], date(2)); uncheckedAssertEqualObjects([optManaged.decimalObj maxOfProperty:@"self"], decimal128(2)); } - (void)testSum { RLMAssertThrowsWithReason([unmanaged.boolObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for bool set"); RLMAssertThrowsWithReason([unmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string set"); RLMAssertThrowsWithReason([unmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data set"); RLMAssertThrowsWithReason([unmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date set"); RLMAssertThrowsWithReason([unmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id set"); RLMAssertThrowsWithReason([unmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid set"); RLMAssertThrowsWithReason([optUnmanaged.stringObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for string? set"); RLMAssertThrowsWithReason([optUnmanaged.dataObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for data? set"); RLMAssertThrowsWithReason([optUnmanaged.dateObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for date? set"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for object id? set"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj sumOfProperty:@"self"], @"sumOfProperty: is not supported for uuid? set"); RLMAssertThrowsWithReason([managed.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool set 'AllPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string set 'AllPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj sumOfProperty:@"self"], @"Operation 'sum' not supported for data set 'AllPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([managed.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date set 'AllPrimitiveSets.dateObj'"); RLMAssertThrowsWithReason([managed.objectIdObj sumOfProperty:@"self"], @"Operation 'sum' not supported for object id set 'AllPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj sumOfProperty:@"self"], @"Operation 'sum' not supported for uuid set 'AllPrimitiveSets.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj sumOfProperty:@"self"], @"Operation 'sum' not supported for bool? set 'AllOptionalPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj sumOfProperty:@"self"], @"Operation 'sum' not supported for string? set 'AllOptionalPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj sumOfProperty:@"self"], @"Operation 'sum' not supported for data? set 'AllOptionalPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([optManaged.dateObj sumOfProperty:@"self"], @"Operation 'sum' not supported for date? set 'AllOptionalPrimitiveSets.dateObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj sumOfProperty:@"self"], @"Operation 'sum' not supported for object id? set 'AllOptionalPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj sumOfProperty:@"self"], @"Operation 'sum' not supported for uuid? set 'AllOptionalPrimitiveSets.uuidObj'"); uncheckedAssertEqualObjects([unmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.decimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyIntObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyFloatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDoubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([managed.anyDecimalObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.intObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj sumOfProperty:@"self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj sumOfProperty:@"self"], @0); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([managed.intObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.floatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj sumOfProperty:@"self"].doubleValue, sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj sumOfProperty:@"self"].doubleValue, sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj sumOfProperty:@"self"].doubleValue, sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([optManaged.intObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj sumOfProperty:@"self"].doubleValue, sum(@[NSNull.null, decimal128(1), decimal128(2)]), .001); } - (void)testAverage { RLMAssertThrowsWithReason([unmanaged.boolObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for bool set"); RLMAssertThrowsWithReason([unmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string set"); RLMAssertThrowsWithReason([unmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data set"); RLMAssertThrowsWithReason([unmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date set"); RLMAssertThrowsWithReason([unmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id set"); RLMAssertThrowsWithReason([unmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid set"); RLMAssertThrowsWithReason([optUnmanaged.stringObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for string? set"); RLMAssertThrowsWithReason([optUnmanaged.dataObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for data? set"); RLMAssertThrowsWithReason([optUnmanaged.dateObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for date? set"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for object id? set"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj averageOfProperty:@"self"], @"averageOfProperty: is not supported for uuid? set"); RLMAssertThrowsWithReason([managed.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool set 'AllPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([managed.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string set 'AllPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([managed.dataObj averageOfProperty:@"self"], @"Operation 'average' not supported for data set 'AllPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([managed.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date set 'AllPrimitiveSets.dateObj'"); RLMAssertThrowsWithReason([managed.objectIdObj averageOfProperty:@"self"], @"Operation 'average' not supported for object id set 'AllPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([managed.uuidObj averageOfProperty:@"self"], @"Operation 'average' not supported for uuid set 'AllPrimitiveSets.uuidObj'"); RLMAssertThrowsWithReason([optManaged.boolObj averageOfProperty:@"self"], @"Operation 'average' not supported for bool? set 'AllOptionalPrimitiveSets.boolObj'"); RLMAssertThrowsWithReason([optManaged.stringObj averageOfProperty:@"self"], @"Operation 'average' not supported for string? set 'AllOptionalPrimitiveSets.stringObj'"); RLMAssertThrowsWithReason([optManaged.dataObj averageOfProperty:@"self"], @"Operation 'average' not supported for data? set 'AllOptionalPrimitiveSets.dataObj'"); RLMAssertThrowsWithReason([optManaged.dateObj averageOfProperty:@"self"], @"Operation 'average' not supported for date? set 'AllOptionalPrimitiveSets.dateObj'"); RLMAssertThrowsWithReason([optManaged.objectIdObj averageOfProperty:@"self"], @"Operation 'average' not supported for object id? set 'AllOptionalPrimitiveSets.objectIdObj'"); RLMAssertThrowsWithReason([optManaged.uuidObj averageOfProperty:@"self"], @"Operation 'average' not supported for uuid? set 'AllOptionalPrimitiveSets.uuidObj'"); uncheckedAssertNil([unmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([unmanaged.anyDecimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optUnmanaged.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.intObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.decimalObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyIntObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyFloatObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDoubleObj averageOfProperty:@"self"]); uncheckedAssertNil([managed.anyDecimalObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.intObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.floatObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.doubleObj averageOfProperty:@"self"]); uncheckedAssertNil([optManaged.decimalObj averageOfProperty:@"self"]); [self addObjects]; XCTAssertEqualWithAccuracy([unmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyIntObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([unmanaged.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.intObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([optUnmanaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([managed.intObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.floatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.doubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.decimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([managed.anyIntObj averageOfProperty:@"self"].doubleValue, average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([managed.anyFloatObj averageOfProperty:@"self"].doubleValue, average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([managed.anyDoubleObj averageOfProperty:@"self"].doubleValue, average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([managed.anyDecimalObj averageOfProperty:@"self"].doubleValue, average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([optManaged.intObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([optManaged.floatObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([optManaged.doubleObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([optManaged.decimalObj averageOfProperty:@"self"].doubleValue, average(@[NSNull.null, decimal128(1), decimal128(2)]), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ NSArray *values = @[@NO, @YES]; for (id value in unmanaged.boolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2, @3]; for (id value in unmanaged.intObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2f, @3.3f]; for (id value in unmanaged.floatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2, @3.3]; for (id value in unmanaged.doubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@"a", @"bc"]; for (id value in unmanaged.stringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[data(1), data(2)]; for (id value in unmanaged.dataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[date(1), date(2)]; for (id value in unmanaged.dateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[decimal128(1), decimal128(2)]; for (id value in unmanaged.decimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[objectId(1), objectId(2)]; for (id value in unmanaged.objectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in unmanaged.uuidObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@NO, @YES]; for (id value in unmanaged.anyBoolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2, @3]; for (id value in unmanaged.anyIntObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2f, @3.3f]; for (id value in unmanaged.anyFloatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2, @3.3]; for (id value in unmanaged.anyDoubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@"a", @"b"]; for (id value in unmanaged.anyStringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[data(1), data(2)]; for (id value in unmanaged.anyDataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[date(1), date(2)]; for (id value in unmanaged.anyDateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[decimal128(1), decimal128(2)]; for (id value in unmanaged.anyDecimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[objectId(1), objectId(2)]; for (id value in unmanaged.anyObjectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in unmanaged.anyUUIDObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @NO, @YES]; for (id value in optUnmanaged.boolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2, @3]; for (id value in optUnmanaged.intObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2.2f, @3.3f]; for (id value in optUnmanaged.floatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2.2, @3.3]; for (id value in optUnmanaged.doubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @"a", @"bc"]; for (id value in optUnmanaged.stringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, data(1), data(2)]; for (id value in optUnmanaged.dataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, date(1), date(2)]; for (id value in optUnmanaged.dateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, decimal128(1), decimal128(2)]; for (id value in optUnmanaged.decimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, objectId(1), objectId(2)]; for (id value in optUnmanaged.objectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; for (id value in optUnmanaged.uuidObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@NO, @YES]; for (id value in managed.boolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2, @3]; for (id value in managed.intObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2f, @3.3f]; for (id value in managed.floatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2, @3.3]; for (id value in managed.doubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@"a", @"bc"]; for (id value in managed.stringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[data(1), data(2)]; for (id value in managed.dataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[date(1), date(2)]; for (id value in managed.dateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[decimal128(1), decimal128(2)]; for (id value in managed.decimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[objectId(1), objectId(2)]; for (id value in managed.objectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; for (id value in managed.uuidObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@NO, @YES]; for (id value in managed.anyBoolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2, @3]; for (id value in managed.anyIntObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2f, @3.3f]; for (id value in managed.anyFloatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@2.2, @3.3]; for (id value in managed.anyDoubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[@"a", @"b"]; for (id value in managed.anyStringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[data(1), data(2)]; for (id value in managed.anyDataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[date(1), date(2)]; for (id value in managed.anyDateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[decimal128(1), decimal128(2)]; for (id value in managed.anyDecimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[objectId(1), objectId(2)]; for (id value in managed.anyObjectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; for (id value in managed.anyUUIDObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @NO, @YES]; for (id value in optManaged.boolObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2, @3]; for (id value in optManaged.intObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2.2f, @3.3f]; for (id value in optManaged.floatObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @2.2, @3.3]; for (id value in optManaged.doubleObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, @"a", @"bc"]; for (id value in optManaged.stringObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, data(1), data(2)]; for (id value in optManaged.dataObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, date(1), date(2)]; for (id value in optManaged.dateObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, decimal128(1), decimal128(2)]; for (id value in optManaged.decimalObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, objectId(1), objectId(2)]; for (id value in optManaged.objectIdObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); ^{ NSArray *values = @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; for (id value in optManaged.uuidObj) { uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); } }(); } - (void)testValueForKeySelf { for (RLMSet *set in allSets) { uncheckedAssertEqualObjects([[set valueForKey:@"self"] allObjects], @[]); } [self addObjects]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); } - (void)testValueForKeyNumericAggregates { uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@min.self"]); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.dateObj valueForKeyPath:@"@max.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@max.self"]); uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyIntObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@sum.self"], @0); uncheckedAssertNil([unmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyIntObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyFloatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyDoubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([unmanaged.anyDecimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.decimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyIntObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyFloatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyDoubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([managed.anyDecimalObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.intObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.floatObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.doubleObj valueForKeyPath:@"@avg.self"]); uncheckedAssertNil([optManaged.decimalObj valueForKeyPath:@"@avg.self"]); [self addObjects]; uncheckedAssertEqualObjects([unmanaged.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([unmanaged.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.decimalObj valueForKeyPath:@"@min.self"], decimal128(1)); uncheckedAssertEqualObjects([unmanaged.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([unmanaged.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([unmanaged.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([unmanaged.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(1)); uncheckedAssertEqualObjects([optUnmanaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([optUnmanaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([optUnmanaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([optUnmanaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([optUnmanaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(2)); uncheckedAssertEqualObjects([managed.intObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([managed.floatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.doubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.dateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.decimalObj valueForKeyPath:@"@min.self"], decimal128(1)); uncheckedAssertEqualObjects([managed.anyIntObj valueForKeyPath:@"@min.self"], @2); uncheckedAssertEqualObjects([managed.anyFloatObj valueForKeyPath:@"@min.self"], @2.2f); uncheckedAssertEqualObjects([managed.anyDoubleObj valueForKeyPath:@"@min.self"], @2.2); uncheckedAssertEqualObjects([managed.anyDateObj valueForKeyPath:@"@min.self"], date(1)); uncheckedAssertEqualObjects([managed.anyDecimalObj valueForKeyPath:@"@min.self"], decimal128(1)); uncheckedAssertEqualObjects([optManaged.intObj valueForKeyPath:@"@max.self"], @3); uncheckedAssertEqualObjects([optManaged.floatObj valueForKeyPath:@"@max.self"], @3.3f); uncheckedAssertEqualObjects([optManaged.doubleObj valueForKeyPath:@"@max.self"], @3.3); uncheckedAssertEqualObjects([optManaged.dateObj valueForKeyPath:@"@max.self"], date(2)); uncheckedAssertEqualObjects([optManaged.decimalObj valueForKeyPath:@"@max.self"], decimal128(2)); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyIntObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyFloatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDoubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDecimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[managed.anyIntObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.anyFloatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.anyDoubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.anyDecimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@sum.self"] doubleValue], sum(@[NSNull.null, decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[unmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyIntObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyFloatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDoubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[unmanaged.anyDecimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[optUnmanaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[managed.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[managed.anyIntObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2, @3]), .001); XCTAssertEqualWithAccuracy([[managed.anyFloatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[managed.anyDoubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[@2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[managed.anyDecimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[decimal128(1), decimal128(2)]), .001); XCTAssertEqualWithAccuracy([[optManaged.intObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2, @3]), .001); XCTAssertEqualWithAccuracy([[optManaged.floatObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2.2f, @3.3f]), .001); XCTAssertEqualWithAccuracy([[optManaged.doubleObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, @2.2, @3.3]), .001); XCTAssertEqualWithAccuracy([[optManaged.decimalObj valueForKeyPath:@"@avg.self"] doubleValue], average(@[NSNull.null, decimal128(1), decimal128(2)]), .001); } - (void)testValueForKeyLength { for (RLMSet *set in allSets) { uncheckedAssertEqualObjects([[set valueForKey:@"length"] allObjects], @[]); } [self addObjects]; uncheckedAssertEqualObjects([unmanaged.stringObj valueForKey:@"length"], ([[NSSet setWithArray:@[@"a", @"bc"]] valueForKey:@"length"])); uncheckedAssertEqualObjects([unmanaged.anyStringObj valueForKey:@"length"], ([[NSSet setWithArray:@[@"a", @"b"]] valueForKey:@"length"])); uncheckedAssertEqualObjects([optUnmanaged.stringObj valueForKey:@"length"], ([[NSSet setWithArray:@[NSNull.null, @"a", @"bc"]] valueForKey:@"length"])); uncheckedAssertEqualObjects([managed.stringObj valueForKey:@"length"], ([[NSSet setWithArray:@[@"a", @"bc"]] valueForKey:@"length"])); uncheckedAssertEqualObjects([managed.anyStringObj valueForKey:@"length"], ([[NSSet setWithArray:@[@"a", @"b"]] valueForKey:@"length"])); uncheckedAssertEqualObjects([optManaged.stringObj valueForKey:@"length"], ([[NSSet setWithArray:@[NSNull.null, @"a", @"bc"]] valueForKey:@"length"])); } - (void)testSetValueForKey { for (RLMSet *set in allSets) { RLMAssertThrowsWithReason([set setValue:@0 forKey:@"not self"], @"this class is not key value coding-compliant for the key not self."); } RLMAssertThrowsWithReason([unmanaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optUnmanaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optUnmanaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optUnmanaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optUnmanaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optUnmanaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optUnmanaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optUnmanaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optUnmanaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optUnmanaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optUnmanaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([managed.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid'"); RLMAssertThrowsWithReason([optManaged.boolObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'bool?'"); RLMAssertThrowsWithReason([optManaged.intObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'int?'"); RLMAssertThrowsWithReason([optManaged.floatObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'float?'"); RLMAssertThrowsWithReason([optManaged.doubleObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'double?'"); RLMAssertThrowsWithReason([optManaged.stringObj setValue:@2 forKey:@"self"], @"Invalid value '2' of type '" RLMConstantInt "' for expected type 'string?'"); RLMAssertThrowsWithReason([optManaged.dataObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'data?'"); RLMAssertThrowsWithReason([optManaged.dateObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'date?'"); RLMAssertThrowsWithReason([optManaged.decimalObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'decimal128?'"); RLMAssertThrowsWithReason([optManaged.objectIdObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'object id?'"); RLMAssertThrowsWithReason([optManaged.uuidObj setValue:@"a" forKey:@"self"], @"Invalid value 'a' of type '" RLMConstantString "' for expected type 'uuid?'"); RLMAssertThrowsWithReason([unmanaged.boolObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([unmanaged.intObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([unmanaged.floatObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([unmanaged.doubleObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([unmanaged.stringObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([unmanaged.dataObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([unmanaged.dateObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([unmanaged.decimalObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([unmanaged.objectIdObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([unmanaged.uuidObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); RLMAssertThrowsWithReason([managed.boolObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'bool'"); RLMAssertThrowsWithReason([managed.intObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'int'"); RLMAssertThrowsWithReason([managed.floatObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'float'"); RLMAssertThrowsWithReason([managed.doubleObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'double'"); RLMAssertThrowsWithReason([managed.stringObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'string'"); RLMAssertThrowsWithReason([managed.dataObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'data'"); RLMAssertThrowsWithReason([managed.dateObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'date'"); RLMAssertThrowsWithReason([managed.decimalObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'decimal128'"); RLMAssertThrowsWithReason([managed.objectIdObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'object id'"); RLMAssertThrowsWithReason([managed.uuidObj setValue:NSNull.null forKey:@"self"], @"Invalid value '' of type 'NSNull' for expected type 'uuid'"); [self addObjects]; // setValue overrides all existing values [unmanaged.boolObj setValue:@NO forKey:@"self"]; [unmanaged.intObj setValue:@2 forKey:@"self"]; [unmanaged.floatObj setValue:@2.2f forKey:@"self"]; [unmanaged.doubleObj setValue:@2.2 forKey:@"self"]; [unmanaged.stringObj setValue:@"a" forKey:@"self"]; [unmanaged.dataObj setValue:data(1) forKey:@"self"]; [unmanaged.dateObj setValue:date(1) forKey:@"self"]; [unmanaged.decimalObj setValue:decimal128(1) forKey:@"self"]; [unmanaged.objectIdObj setValue:objectId(1) forKey:@"self"]; [unmanaged.uuidObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [unmanaged.anyBoolObj setValue:@NO forKey:@"self"]; [unmanaged.anyIntObj setValue:@2 forKey:@"self"]; [unmanaged.anyFloatObj setValue:@2.2f forKey:@"self"]; [unmanaged.anyDoubleObj setValue:@2.2 forKey:@"self"]; [unmanaged.anyStringObj setValue:@"a" forKey:@"self"]; [unmanaged.anyDataObj setValue:data(1) forKey:@"self"]; [unmanaged.anyDateObj setValue:date(1) forKey:@"self"]; [unmanaged.anyDecimalObj setValue:decimal128(1) forKey:@"self"]; [unmanaged.anyObjectIdObj setValue:objectId(1) forKey:@"self"]; [unmanaged.anyUUIDObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [optUnmanaged.boolObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.intObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.floatObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.stringObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dataObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dateObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.uuidObj setValue:NSNull.null forKey:@"self"]; [managed.boolObj setValue:@NO forKey:@"self"]; [managed.intObj setValue:@2 forKey:@"self"]; [managed.floatObj setValue:@2.2f forKey:@"self"]; [managed.doubleObj setValue:@2.2 forKey:@"self"]; [managed.stringObj setValue:@"a" forKey:@"self"]; [managed.dataObj setValue:data(1) forKey:@"self"]; [managed.dateObj setValue:date(1) forKey:@"self"]; [managed.decimalObj setValue:decimal128(1) forKey:@"self"]; [managed.objectIdObj setValue:objectId(1) forKey:@"self"]; [managed.uuidObj setValue:uuid(@"137DECC8-B300-4954-A233-F89909F4FD89") forKey:@"self"]; [managed.anyBoolObj setValue:@NO forKey:@"self"]; [managed.anyIntObj setValue:@2 forKey:@"self"]; [managed.anyFloatObj setValue:@2.2f forKey:@"self"]; [managed.anyDoubleObj setValue:@2.2 forKey:@"self"]; [managed.anyStringObj setValue:@"a" forKey:@"self"]; [managed.anyDataObj setValue:data(1) forKey:@"self"]; [managed.anyDateObj setValue:date(1) forKey:@"self"]; [managed.anyDecimalObj setValue:decimal128(1) forKey:@"self"]; [managed.anyObjectIdObj setValue:objectId(1) forKey:@"self"]; [managed.anyUUIDObj setValue:uuid(@"00000000-0000-0000-0000-000000000000") forKey:@"self"]; [optManaged.boolObj setValue:NSNull.null forKey:@"self"]; [optManaged.intObj setValue:NSNull.null forKey:@"self"]; [optManaged.floatObj setValue:NSNull.null forKey:@"self"]; [optManaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optManaged.stringObj setValue:NSNull.null forKey:@"self"]; [optManaged.dataObj setValue:NSNull.null forKey:@"self"]; [optManaged.dateObj setValue:NSNull.null forKey:@"self"]; [optManaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optManaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optManaged.uuidObj setValue:NSNull.null forKey:@"self"]; RLMAssertThrowsWithReason(unmanaged.boolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.intObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.floatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.doubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.stringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.dataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.dateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.decimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.objectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.uuidObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyBoolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyIntObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyFloatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyDoubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyStringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyDataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyDateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyDecimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyObjectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(unmanaged.anyUUIDObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.boolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.intObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.floatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.doubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.stringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.dataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.dateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.decimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.objectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optUnmanaged.uuidObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.boolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.intObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.floatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.doubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.stringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.dataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.dateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.decimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.objectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.uuidObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyBoolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyIntObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyFloatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyDoubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyStringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyDataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyDateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyDecimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyObjectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(managed.anyUUIDObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.boolObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.intObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.floatObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.doubleObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.stringObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.dataObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.dateObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.decimalObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.objectIdObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); RLMAssertThrowsWithReason(optManaged.uuidObj.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); uncheckedAssertEqualObjects(unmanaged.boolObj.allObjects[0], @NO); uncheckedAssertEqualObjects(unmanaged.intObj.allObjects[0], @2); uncheckedAssertEqualObjects(unmanaged.floatObj.allObjects[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.doubleObj.allObjects[0], @2.2); uncheckedAssertEqualObjects(unmanaged.stringObj.allObjects[0], @"a"); uncheckedAssertEqualObjects(unmanaged.dataObj.allObjects[0], data(1)); uncheckedAssertEqualObjects(unmanaged.dateObj.allObjects[0], date(1)); uncheckedAssertEqualObjects(unmanaged.decimalObj.allObjects[0], decimal128(1)); uncheckedAssertEqualObjects(unmanaged.objectIdObj.allObjects[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.uuidObj.allObjects[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(unmanaged.anyBoolObj.allObjects[0], @NO); uncheckedAssertEqualObjects(unmanaged.anyIntObj.allObjects[0], @2); uncheckedAssertEqualObjects(unmanaged.anyFloatObj.allObjects[0], @2.2f); uncheckedAssertEqualObjects(unmanaged.anyDoubleObj.allObjects[0], @2.2); uncheckedAssertEqualObjects(unmanaged.anyStringObj.allObjects[0], @"a"); uncheckedAssertEqualObjects(unmanaged.anyDataObj.allObjects[0], data(1)); uncheckedAssertEqualObjects(unmanaged.anyDateObj.allObjects[0], date(1)); uncheckedAssertEqualObjects(unmanaged.anyDecimalObj.allObjects[0], decimal128(1)); uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj.allObjects[0], objectId(1)); uncheckedAssertEqualObjects(unmanaged.anyUUIDObj.allObjects[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optUnmanaged.boolObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(managed.boolObj.allObjects[0], @NO); uncheckedAssertEqualObjects(managed.intObj.allObjects[0], @2); uncheckedAssertEqualObjects(managed.floatObj.allObjects[0], @2.2f); uncheckedAssertEqualObjects(managed.doubleObj.allObjects[0], @2.2); uncheckedAssertEqualObjects(managed.stringObj.allObjects[0], @"a"); uncheckedAssertEqualObjects(managed.dataObj.allObjects[0], data(1)); uncheckedAssertEqualObjects(managed.dateObj.allObjects[0], date(1)); uncheckedAssertEqualObjects(managed.decimalObj.allObjects[0], decimal128(1)); uncheckedAssertEqualObjects(managed.objectIdObj.allObjects[0], objectId(1)); uncheckedAssertEqualObjects(managed.uuidObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); uncheckedAssertEqualObjects(managed.anyBoolObj.allObjects[0], @NO); uncheckedAssertEqualObjects(managed.anyIntObj.allObjects[0], @2); uncheckedAssertEqualObjects(managed.anyFloatObj.allObjects[0], @2.2f); uncheckedAssertEqualObjects(managed.anyDoubleObj.allObjects[0], @2.2); uncheckedAssertEqualObjects(managed.anyStringObj.allObjects[0], @"a"); uncheckedAssertEqualObjects(managed.anyDataObj.allObjects[0], data(1)); uncheckedAssertEqualObjects(managed.anyDateObj.allObjects[0], date(1)); uncheckedAssertEqualObjects(managed.anyDecimalObj.allObjects[0], decimal128(1)); uncheckedAssertEqualObjects(managed.anyObjectIdObj.allObjects[0], objectId(1)); uncheckedAssertEqualObjects(managed.anyUUIDObj.allObjects[0], uuid(@"00000000-0000-0000-0000-000000000000")); uncheckedAssertEqualObjects(optManaged.boolObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj.allObjects[0], NSNull.null); [optUnmanaged.intObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.floatObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.stringObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dataObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.dateObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optUnmanaged.uuidObj setValue:NSNull.null forKey:@"self"]; [optManaged.boolObj setValue:NSNull.null forKey:@"self"]; [optManaged.intObj setValue:NSNull.null forKey:@"self"]; [optManaged.floatObj setValue:NSNull.null forKey:@"self"]; [optManaged.doubleObj setValue:NSNull.null forKey:@"self"]; [optManaged.stringObj setValue:NSNull.null forKey:@"self"]; [optManaged.dataObj setValue:NSNull.null forKey:@"self"]; [optManaged.dateObj setValue:NSNull.null forKey:@"self"]; [optManaged.decimalObj setValue:NSNull.null forKey:@"self"]; [optManaged.objectIdObj setValue:NSNull.null forKey:@"self"]; [optManaged.uuidObj setValue:NSNull.null forKey:@"self"]; uncheckedAssertEqualObjects(optUnmanaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optUnmanaged.uuidObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.boolObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.intObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.floatObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.doubleObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.stringObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dataObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.dateObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.decimalObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.objectIdObj.allObjects[0], NSNull.null); uncheckedAssertEqualObjects(optManaged.uuidObj.allObjects[0], NSNull.null); } - (void)testAssignment { unmanaged.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged.boolObj.allObjects[0], @YES); unmanaged.intObj = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged.intObj.allObjects[0], @3); unmanaged.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged.floatObj.allObjects[0], @3.3f); unmanaged.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged.doubleObj.allObjects[0], @3.3); unmanaged.stringObj = (id)@[@"bc"]; uncheckedAssertEqualObjects(unmanaged.stringObj.allObjects[0], @"bc"); unmanaged.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged.dataObj.allObjects[0], data(2)); unmanaged.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged.dateObj.allObjects[0], date(2)); unmanaged.decimalObj = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(unmanaged.decimalObj.allObjects[0], decimal128(2)); unmanaged.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged.objectIdObj.allObjects[0], objectId(2)); unmanaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.uuidObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); unmanaged.anyBoolObj = (id)@[@YES]; uncheckedAssertEqualObjects(unmanaged.anyBoolObj.allObjects[0], @YES); unmanaged.anyIntObj = (id)@[@3]; uncheckedAssertEqualObjects(unmanaged.anyIntObj.allObjects[0], @3); unmanaged.anyFloatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(unmanaged.anyFloatObj.allObjects[0], @3.3f); unmanaged.anyDoubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(unmanaged.anyDoubleObj.allObjects[0], @3.3); unmanaged.anyStringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(unmanaged.anyStringObj.allObjects[0], @"b"); unmanaged.anyDataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(unmanaged.anyDataObj.allObjects[0], data(2)); unmanaged.anyDateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(unmanaged.anyDateObj.allObjects[0], date(2)); unmanaged.anyDecimalObj = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(unmanaged.anyDecimalObj.allObjects[0], decimal128(2)); unmanaged.anyObjectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(unmanaged.anyObjectIdObj.allObjects[0], objectId(2)); unmanaged.anyUUIDObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(unmanaged.anyUUIDObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optUnmanaged.boolObj = (id)@[@NO]; uncheckedAssertEqualObjects(optUnmanaged.boolObj.allObjects[0], @NO); optUnmanaged.intObj = (id)@[@2]; uncheckedAssertEqualObjects(optUnmanaged.intObj.allObjects[0], @2); optUnmanaged.floatObj = (id)@[@2.2f]; uncheckedAssertEqualObjects(optUnmanaged.floatObj.allObjects[0], @2.2f); optUnmanaged.doubleObj = (id)@[@2.2]; uncheckedAssertEqualObjects(optUnmanaged.doubleObj.allObjects[0], @2.2); optUnmanaged.stringObj = (id)@[@"a"]; uncheckedAssertEqualObjects(optUnmanaged.stringObj.allObjects[0], @"a"); optUnmanaged.dataObj = (id)@[data(1)]; uncheckedAssertEqualObjects(optUnmanaged.dataObj.allObjects[0], data(1)); optUnmanaged.dateObj = (id)@[date(1)]; uncheckedAssertEqualObjects(optUnmanaged.dateObj.allObjects[0], date(1)); optUnmanaged.decimalObj = (id)@[decimal128(1)]; uncheckedAssertEqualObjects(optUnmanaged.decimalObj.allObjects[0], decimal128(1)); optUnmanaged.objectIdObj = (id)@[objectId(1)]; uncheckedAssertEqualObjects(optUnmanaged.objectIdObj.allObjects[0], objectId(1)); optUnmanaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optUnmanaged.uuidObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed.boolObj = (id)@[@YES]; uncheckedAssertEqualObjects(managed.boolObj.allObjects[0], @YES); managed.intObj = (id)@[@3]; uncheckedAssertEqualObjects(managed.intObj.allObjects[0], @3); managed.floatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed.floatObj.allObjects[0], @3.3f); managed.doubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(managed.doubleObj.allObjects[0], @3.3); managed.stringObj = (id)@[@"bc"]; uncheckedAssertEqualObjects(managed.stringObj.allObjects[0], @"bc"); managed.dataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(managed.dataObj.allObjects[0], data(2)); managed.dateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(managed.dateObj.allObjects[0], date(2)); managed.decimalObj = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(managed.decimalObj.allObjects[0], decimal128(2)); managed.objectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed.objectIdObj.allObjects[0], objectId(2)); managed.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects(managed.uuidObj.allObjects[0], uuid(@"00000000-0000-0000-0000-000000000000")); managed.anyBoolObj = (id)@[@YES]; uncheckedAssertEqualObjects(managed.anyBoolObj.allObjects[0], @YES); managed.anyIntObj = (id)@[@3]; uncheckedAssertEqualObjects(managed.anyIntObj.allObjects[0], @3); managed.anyFloatObj = (id)@[@3.3f]; uncheckedAssertEqualObjects(managed.anyFloatObj.allObjects[0], @3.3f); managed.anyDoubleObj = (id)@[@3.3]; uncheckedAssertEqualObjects(managed.anyDoubleObj.allObjects[0], @3.3); managed.anyStringObj = (id)@[@"b"]; uncheckedAssertEqualObjects(managed.anyStringObj.allObjects[0], @"b"); managed.anyDataObj = (id)@[data(2)]; uncheckedAssertEqualObjects(managed.anyDataObj.allObjects[0], data(2)); managed.anyDateObj = (id)@[date(2)]; uncheckedAssertEqualObjects(managed.anyDateObj.allObjects[0], date(2)); managed.anyDecimalObj = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(managed.anyDecimalObj.allObjects[0], decimal128(2)); managed.anyObjectIdObj = (id)@[objectId(2)]; uncheckedAssertEqualObjects(managed.anyObjectIdObj.allObjects[0], objectId(2)); managed.anyUUIDObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(managed.anyUUIDObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optManaged.boolObj = (id)@[@NO]; uncheckedAssertEqualObjects(optManaged.boolObj.allObjects[0], @NO); optManaged.intObj = (id)@[@2]; uncheckedAssertEqualObjects(optManaged.intObj.allObjects[0], @2); optManaged.floatObj = (id)@[@2.2f]; uncheckedAssertEqualObjects(optManaged.floatObj.allObjects[0], @2.2f); optManaged.doubleObj = (id)@[@2.2]; uncheckedAssertEqualObjects(optManaged.doubleObj.allObjects[0], @2.2); optManaged.stringObj = (id)@[@"a"]; uncheckedAssertEqualObjects(optManaged.stringObj.allObjects[0], @"a"); optManaged.dataObj = (id)@[data(1)]; uncheckedAssertEqualObjects(optManaged.dataObj.allObjects[0], data(1)); optManaged.dateObj = (id)@[date(1)]; uncheckedAssertEqualObjects(optManaged.dateObj.allObjects[0], date(1)); optManaged.decimalObj = (id)@[decimal128(1)]; uncheckedAssertEqualObjects(optManaged.decimalObj.allObjects[0], decimal128(1)); optManaged.objectIdObj = (id)@[objectId(1)]; uncheckedAssertEqualObjects(optManaged.objectIdObj.allObjects[0], objectId(1)); optManaged.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(optManaged.uuidObj.allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); // Should replace and not append unmanaged.boolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged.intObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged.floatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged.doubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged.stringObj = (id)@[@"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); unmanaged.dataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged.dateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged.decimalObj = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged.objectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged.uuidObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); unmanaged.anyBoolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged.anyIntObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged.anyFloatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged.anyDoubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged.anyStringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); unmanaged.anyDataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged.anyDateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged.anyDecimalObj = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged.anyObjectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged.anyUUIDObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optUnmanaged.boolObj = (id)@[NSNull.null, @NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optUnmanaged.intObj = (id)@[NSNull.null, @2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optUnmanaged.floatObj = (id)@[NSNull.null, @2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optUnmanaged.doubleObj = (id)@[NSNull.null, @2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optUnmanaged.stringObj = (id)@[NSNull.null, @"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optUnmanaged.dataObj = (id)@[NSNull.null, data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optUnmanaged.dateObj = (id)@[NSNull.null, date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optUnmanaged.decimalObj = (id)@[NSNull.null, decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optUnmanaged.objectIdObj = (id)@[NSNull.null, objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optUnmanaged.uuidObj = (id)@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed.boolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed.intObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed.floatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed.doubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed.stringObj = (id)@[@"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); managed.dataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed.dateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed.decimalObj = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed.objectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed.uuidObj = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed.anyBoolObj = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed.anyIntObj = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed.anyFloatObj = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed.anyDoubleObj = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed.anyStringObj = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); managed.anyDataObj = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed.anyDateObj = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed.anyDecimalObj = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed.anyObjectIdObj = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed.anyUUIDObj = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optManaged.boolObj = (id)@[NSNull.null, @NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optManaged.intObj = (id)@[NSNull.null, @2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optManaged.floatObj = (id)@[NSNull.null, @2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optManaged.doubleObj = (id)@[NSNull.null, @2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optManaged.stringObj = (id)@[NSNull.null, @"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optManaged.dataObj = (id)@[NSNull.null, data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optManaged.dateObj = (id)@[NSNull.null, date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optManaged.decimalObj = (id)@[NSNull.null, decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optManaged.objectIdObj = (id)@[NSNull.null, objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optManaged.uuidObj = (id)@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); // Should not clear the set unmanaged.boolObj = unmanaged.boolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged.floatObj = unmanaged.floatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged.doubleObj = unmanaged.doubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged.stringObj = unmanaged.stringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); unmanaged.dataObj = unmanaged.dataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged.dateObj = unmanaged.dateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged.decimalObj = unmanaged.decimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged.objectIdObj = unmanaged.objectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged.uuidObj = unmanaged.uuidObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); unmanaged.anyBoolObj = unmanaged.anyBoolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged.anyIntObj = unmanaged.anyIntObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged.anyFloatObj = unmanaged.anyFloatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged.anyDoubleObj = unmanaged.anyDoubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged.anyStringObj = unmanaged.anyStringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); unmanaged.anyDataObj = unmanaged.anyDataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged.anyDateObj = unmanaged.anyDateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged.anyDecimalObj = unmanaged.anyDecimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged.anyObjectIdObj = unmanaged.anyObjectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged.anyUUIDObj = unmanaged.anyUUIDObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optUnmanaged.boolObj = optUnmanaged.boolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optUnmanaged.intObj = optUnmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optUnmanaged.floatObj = optUnmanaged.floatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optUnmanaged.doubleObj = optUnmanaged.doubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optUnmanaged.stringObj = optUnmanaged.stringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optUnmanaged.dataObj = optUnmanaged.dataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optUnmanaged.dateObj = optUnmanaged.dateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optUnmanaged.decimalObj = optUnmanaged.decimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optUnmanaged.objectIdObj = optUnmanaged.objectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optUnmanaged.uuidObj = optUnmanaged.uuidObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed.boolObj = managed.boolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed.intObj = managed.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed.floatObj = managed.floatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed.doubleObj = managed.doubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed.stringObj = managed.stringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); managed.dataObj = managed.dataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed.dateObj = managed.dateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed.decimalObj = managed.decimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed.objectIdObj = managed.objectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed.uuidObj = managed.uuidObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed.anyBoolObj = managed.anyBoolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyBoolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed.anyIntObj = managed.anyIntObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyIntObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed.anyFloatObj = managed.anyFloatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyFloatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed.anyDoubleObj = managed.anyDoubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDoubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed.anyStringObj = managed.anyStringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyStringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); managed.anyDataObj = managed.anyDataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed.anyDateObj = managed.anyDateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed.anyDecimalObj = managed.anyDecimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyDecimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed.anyObjectIdObj = managed.anyObjectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyObjectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed.anyUUIDObj = managed.anyUUIDObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.anyUUIDObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optManaged.boolObj = optManaged.boolObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.boolObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optManaged.intObj = optManaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optManaged.floatObj = optManaged.floatObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.floatObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optManaged.doubleObj = optManaged.doubleObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.doubleObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optManaged.stringObj = optManaged.stringObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.stringObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optManaged.dataObj = optManaged.dataObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dataObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optManaged.dateObj = optManaged.dateObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.dateObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optManaged.decimalObj = optManaged.decimalObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.decimalObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optManaged.objectIdObj = optManaged.objectIdObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.objectIdObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optManaged.uuidObj = optManaged.uuidObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged.uuidObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); } - (void)testDynamicAssignment { unmanaged[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"boolObj"]).allObjects[0], @YES); unmanaged[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"intObj"]).allObjects[0], @3); unmanaged[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"floatObj"]).allObjects[0], @3.3f); unmanaged[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"doubleObj"]).allObjects[0], @3.3); unmanaged[@"stringObj"] = (id)@[@"bc"]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"stringObj"]).allObjects[0], @"bc"); unmanaged[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"dataObj"]).allObjects[0], data(2)); unmanaged[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"dateObj"]).allObjects[0], date(2)); unmanaged[@"decimalObj"] = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"decimalObj"]).allObjects[0], decimal128(2)); unmanaged[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"objectIdObj"]).allObjects[0], objectId(2)); unmanaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"uuidObj"]).allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); unmanaged[@"anyBoolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyBoolObj"]).allObjects[0], @YES); unmanaged[@"anyIntObj"] = (id)@[@3]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyIntObj"]).allObjects[0], @3); unmanaged[@"anyFloatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyFloatObj"]).allObjects[0], @3.3f); unmanaged[@"anyDoubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyDoubleObj"]).allObjects[0], @3.3); unmanaged[@"anyStringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyStringObj"]).allObjects[0], @"b"); unmanaged[@"anyDataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyDataObj"]).allObjects[0], data(2)); unmanaged[@"anyDateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyDateObj"]).allObjects[0], date(2)); unmanaged[@"anyDecimalObj"] = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyDecimalObj"]).allObjects[0], decimal128(2)); unmanaged[@"anyObjectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyObjectIdObj"]).allObjects[0], objectId(2)); unmanaged[@"anyUUIDObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(((RLMSet *)unmanaged[@"anyUUIDObj"]).allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optUnmanaged[@"boolObj"] = (id)@[@NO]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"boolObj"]).allObjects[0], @NO); optUnmanaged[@"intObj"] = (id)@[@2]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"intObj"]).allObjects[0], @2); optUnmanaged[@"floatObj"] = (id)@[@2.2f]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"floatObj"]).allObjects[0], @2.2f); optUnmanaged[@"doubleObj"] = (id)@[@2.2]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"doubleObj"]).allObjects[0], @2.2); optUnmanaged[@"stringObj"] = (id)@[@"a"]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"stringObj"]).allObjects[0], @"a"); optUnmanaged[@"dataObj"] = (id)@[data(1)]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"dataObj"]).allObjects[0], data(1)); optUnmanaged[@"dateObj"] = (id)@[date(1)]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"dateObj"]).allObjects[0], date(1)); optUnmanaged[@"decimalObj"] = (id)@[decimal128(1)]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"decimalObj"]).allObjects[0], decimal128(1)); optUnmanaged[@"objectIdObj"] = (id)@[objectId(1)]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"objectIdObj"]).allObjects[0], objectId(1)); optUnmanaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(((RLMSet *)optUnmanaged[@"uuidObj"]).allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); managed[@"boolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"boolObj"]).allObjects[0], @YES); managed[@"intObj"] = (id)@[@3]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"intObj"]).allObjects[0], @3); managed[@"floatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"floatObj"]).allObjects[0], @3.3f); managed[@"doubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"doubleObj"]).allObjects[0], @3.3); managed[@"stringObj"] = (id)@[@"bc"]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"stringObj"]).allObjects[0], @"bc"); managed[@"dataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"dataObj"]).allObjects[0], data(2)); managed[@"dateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"dateObj"]).allObjects[0], date(2)); managed[@"decimalObj"] = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"decimalObj"]).allObjects[0], decimal128(2)); managed[@"objectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"objectIdObj"]).allObjects[0], objectId(2)); managed[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"uuidObj"]).allObjects[0], uuid(@"00000000-0000-0000-0000-000000000000")); managed[@"anyBoolObj"] = (id)@[@YES]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyBoolObj"]).allObjects[0], @YES); managed[@"anyIntObj"] = (id)@[@3]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyIntObj"]).allObjects[0], @3); managed[@"anyFloatObj"] = (id)@[@3.3f]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyFloatObj"]).allObjects[0], @3.3f); managed[@"anyDoubleObj"] = (id)@[@3.3]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyDoubleObj"]).allObjects[0], @3.3); managed[@"anyStringObj"] = (id)@[@"b"]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyStringObj"]).allObjects[0], @"b"); managed[@"anyDataObj"] = (id)@[data(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyDataObj"]).allObjects[0], data(2)); managed[@"anyDateObj"] = (id)@[date(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyDateObj"]).allObjects[0], date(2)); managed[@"anyDecimalObj"] = (id)@[decimal128(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyDecimalObj"]).allObjects[0], decimal128(2)); managed[@"anyObjectIdObj"] = (id)@[objectId(2)]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyObjectIdObj"]).allObjects[0], objectId(2)); managed[@"anyUUIDObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(((RLMSet *)managed[@"anyUUIDObj"]).allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); optManaged[@"boolObj"] = (id)@[@NO]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"boolObj"]).allObjects[0], @NO); optManaged[@"intObj"] = (id)@[@2]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"intObj"]).allObjects[0], @2); optManaged[@"floatObj"] = (id)@[@2.2f]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"floatObj"]).allObjects[0], @2.2f); optManaged[@"doubleObj"] = (id)@[@2.2]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"doubleObj"]).allObjects[0], @2.2); optManaged[@"stringObj"] = (id)@[@"a"]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"stringObj"]).allObjects[0], @"a"); optManaged[@"dataObj"] = (id)@[data(1)]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"dataObj"]).allObjects[0], data(1)); optManaged[@"dateObj"] = (id)@[date(1)]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"dateObj"]).allObjects[0], date(1)); optManaged[@"decimalObj"] = (id)@[decimal128(1)]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"decimalObj"]).allObjects[0], decimal128(1)); optManaged[@"objectIdObj"] = (id)@[objectId(1)]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"objectIdObj"]).allObjects[0], objectId(1)); optManaged[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects(((RLMSet *)optManaged[@"uuidObj"]).allObjects[0], uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); // Should replace and not append unmanaged[@"boolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged[@"intObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged[@"floatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged[@"doubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged[@"stringObj"] = (id)@[@"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); unmanaged[@"dataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged[@"dateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged[@"decimalObj"] = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged[@"objectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged[@"uuidObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); unmanaged[@"anyBoolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyBoolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged[@"anyIntObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyIntObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged[@"anyFloatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyFloatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged[@"anyDoubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDoubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged[@"anyStringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyStringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); unmanaged[@"anyDataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged[@"anyDateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged[@"anyDecimalObj"] = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDecimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged[@"anyObjectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyObjectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged[@"anyUUIDObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyUUIDObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optUnmanaged[@"boolObj"] = (id)@[NSNull.null, @NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optUnmanaged[@"intObj"] = (id)@[NSNull.null, @2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optUnmanaged[@"floatObj"] = (id)@[NSNull.null, @2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optUnmanaged[@"doubleObj"] = (id)@[NSNull.null, @2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optUnmanaged[@"stringObj"] = (id)@[NSNull.null, @"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optUnmanaged[@"dataObj"] = (id)@[NSNull.null, data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optUnmanaged[@"dateObj"] = (id)@[NSNull.null, date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optUnmanaged[@"decimalObj"] = (id)@[NSNull.null, decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optUnmanaged[@"objectIdObj"] = (id)@[NSNull.null, objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optUnmanaged[@"uuidObj"] = (id)@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed[@"boolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed[@"intObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed[@"floatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed[@"doubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed[@"stringObj"] = (id)@[@"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); managed[@"dataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed[@"dateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed[@"decimalObj"] = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed[@"objectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed[@"uuidObj"] = (id)@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed[@"anyBoolObj"] = (id)@[@NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyBoolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed[@"anyIntObj"] = (id)@[@2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyIntObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed[@"anyFloatObj"] = (id)@[@2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyFloatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed[@"anyDoubleObj"] = (id)@[@2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDoubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed[@"anyStringObj"] = (id)@[@"a", @"b"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyStringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); managed[@"anyDataObj"] = (id)@[data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed[@"anyDateObj"] = (id)@[date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed[@"anyDecimalObj"] = (id)@[decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDecimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed[@"anyObjectIdObj"] = (id)@[objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyObjectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed[@"anyUUIDObj"] = (id)@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyUUIDObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optManaged[@"boolObj"] = (id)@[NSNull.null, @NO, @YES]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optManaged[@"intObj"] = (id)@[NSNull.null, @2, @3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optManaged[@"floatObj"] = (id)@[NSNull.null, @2.2f, @3.3f]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optManaged[@"doubleObj"] = (id)@[NSNull.null, @2.2, @3.3]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optManaged[@"stringObj"] = (id)@[NSNull.null, @"a", @"bc"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optManaged[@"dataObj"] = (id)@[NSNull.null, data(1), data(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optManaged[@"dateObj"] = (id)@[NSNull.null, date(1), date(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optManaged[@"decimalObj"] = (id)@[NSNull.null, decimal128(1), decimal128(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optManaged[@"objectIdObj"] = (id)@[NSNull.null, objectId(1), objectId(2)]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optManaged[@"uuidObj"] = (id)@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); // Should not clear the set unmanaged[@"boolObj"] = unmanaged[@"boolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged[@"intObj"] = unmanaged[@"intObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged[@"floatObj"] = unmanaged[@"floatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged[@"doubleObj"] = unmanaged[@"doubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged[@"stringObj"] = unmanaged[@"stringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); unmanaged[@"dataObj"] = unmanaged[@"dataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged[@"dateObj"] = unmanaged[@"dateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged[@"decimalObj"] = unmanaged[@"decimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged[@"objectIdObj"] = unmanaged[@"objectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged[@"uuidObj"] = unmanaged[@"uuidObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); unmanaged[@"anyBoolObj"] = unmanaged[@"anyBoolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyBoolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); unmanaged[@"anyIntObj"] = unmanaged[@"anyIntObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyIntObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); unmanaged[@"anyFloatObj"] = unmanaged[@"anyFloatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyFloatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); unmanaged[@"anyDoubleObj"] = unmanaged[@"anyDoubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDoubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); unmanaged[@"anyStringObj"] = unmanaged[@"anyStringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyStringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); unmanaged[@"anyDataObj"] = unmanaged[@"anyDataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); unmanaged[@"anyDateObj"] = unmanaged[@"anyDateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); unmanaged[@"anyDecimalObj"] = unmanaged[@"anyDecimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyDecimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); unmanaged[@"anyObjectIdObj"] = unmanaged[@"anyObjectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyObjectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); unmanaged[@"anyUUIDObj"] = unmanaged[@"anyUUIDObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"anyUUIDObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optUnmanaged[@"boolObj"] = optUnmanaged[@"boolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optUnmanaged[@"intObj"] = optUnmanaged[@"intObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optUnmanaged[@"floatObj"] = optUnmanaged[@"floatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optUnmanaged[@"doubleObj"] = optUnmanaged[@"doubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optUnmanaged[@"stringObj"] = optUnmanaged[@"stringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optUnmanaged[@"dataObj"] = optUnmanaged[@"dataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optUnmanaged[@"dateObj"] = optUnmanaged[@"dateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optUnmanaged[@"decimalObj"] = optUnmanaged[@"decimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optUnmanaged[@"objectIdObj"] = optUnmanaged[@"objectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optUnmanaged[@"uuidObj"] = optUnmanaged[@"uuidObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optUnmanaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed[@"boolObj"] = managed[@"boolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed[@"intObj"] = managed[@"intObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed[@"floatObj"] = managed[@"floatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed[@"doubleObj"] = managed[@"doubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed[@"stringObj"] = managed[@"stringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"bc"]])); managed[@"dataObj"] = managed[@"dataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed[@"dateObj"] = managed[@"dateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed[@"decimalObj"] = managed[@"decimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed[@"objectIdObj"] = managed[@"objectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed[@"uuidObj"] = managed[@"uuidObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); managed[@"anyBoolObj"] = managed[@"anyBoolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyBoolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@NO, @YES]])); managed[@"anyIntObj"] = managed[@"anyIntObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyIntObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); managed[@"anyFloatObj"] = managed[@"anyFloatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyFloatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2f, @3.3f]])); managed[@"anyDoubleObj"] = managed[@"anyDoubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDoubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2.2, @3.3]])); managed[@"anyStringObj"] = managed[@"anyStringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyStringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@"a", @"b"]])); managed[@"anyDataObj"] = managed[@"anyDataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[data(1), data(2)]])); managed[@"anyDateObj"] = managed[@"anyDateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[date(1), date(2)]])); managed[@"anyDecimalObj"] = managed[@"anyDecimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyDecimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[decimal128(1), decimal128(2)]])); managed[@"anyObjectIdObj"] = managed[@"anyObjectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyObjectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[objectId(1), objectId(2)]])); managed[@"anyUUIDObj"] = managed[@"anyUUIDObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"anyUUIDObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]])); optManaged[@"boolObj"] = optManaged[@"boolObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"boolObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @NO, @YES]])); optManaged[@"intObj"] = optManaged[@"intObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2, @3]])); optManaged[@"floatObj"] = optManaged[@"floatObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"floatObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2f, @3.3f]])); optManaged[@"doubleObj"] = optManaged[@"doubleObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"doubleObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @2.2, @3.3]])); optManaged[@"stringObj"] = optManaged[@"stringObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"stringObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, @"a", @"bc"]])); optManaged[@"dataObj"] = optManaged[@"dataObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"dataObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, data(1), data(2)]])); optManaged[@"dateObj"] = optManaged[@"dateObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"dateObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, date(1), date(2)]])); optManaged[@"decimalObj"] = optManaged[@"decimalObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"decimalObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, decimal128(1), decimal128(2)]])); optManaged[@"objectIdObj"] = optManaged[@"objectIdObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"objectIdObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, objectId(1), objectId(2)]])); optManaged[@"uuidObj"] = optManaged[@"uuidObj"]; uncheckedAssertEqualObjects([NSSet setWithArray:[[optManaged[@"uuidObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]])); [unmanaged[@"intObj"] removeAllObjects]; unmanaged[@"intObj"] = managed.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); [managed[@"intObj"] removeAllObjects]; managed[@"intObj"] = unmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); } - (void)testAllMethodsCheckThread { RLMSet *set = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([set count], @"thread"); RLMAssertThrowsWithReason([set addObject:@0], @"thread"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"thread"); RLMAssertThrowsWithReason([set removeAllObjects], @"thread"); RLMAssertThrowsWithReason([set sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(set.allObjects[0], @"thread"); RLMAssertThrowsWithReason([set valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in set);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMSet *set = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); RLMAssertThrowsWithReason([set count], @"invalidated"); RLMAssertThrowsWithReason([set addObject:@0], @"invalidated"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"invalidated"); RLMAssertThrowsWithReason([set removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([set sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(set.allObjects[0], @"invalidated"); RLMAssertThrowsWithReason([set valueForKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in set);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMSet *set = managed.intObj; [set addObject:@0]; [realm commitWriteTransaction]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); XCTAssertNoThrow([set count]); XCTAssertNoThrow([set sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(set.allObjects[0]); XCTAssertNoThrow([set valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in set);})); RLMAssertThrowsWithReason([set addObject:@0], @"write transaction"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"write transaction"); RLMAssertThrowsWithReason([set removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMSet *set = managed.intObj; uncheckedAssertFalse(set.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(set.isInvalidated); } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObjectWithValueIndex:(NSUInteger)index { NSRange range = {index, 1}; id obj = [AllPrimitiveSets createInRealm:realm withValue:@{ @"boolObj": [@[@NO, @YES] subarrayWithRange:range], @"intObj": [@[@2, @3] subarrayWithRange:range], @"floatObj": [@[@2.2f, @3.3f] subarrayWithRange:range], @"doubleObj": [@[@2.2, @3.3] subarrayWithRange:range], @"stringObj": [@[@"a", @"bc"] subarrayWithRange:range], @"dataObj": [@[data(1), data(2)] subarrayWithRange:range], @"dateObj": [@[date(1), date(2)] subarrayWithRange:range], @"decimalObj": [@[decimal128(1), decimal128(2)] subarrayWithRange:range], @"objectIdObj": [@[objectId(1), objectId(2)] subarrayWithRange:range], @"uuidObj": [@[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")] subarrayWithRange:range], @"anyBoolObj": [@[@NO, @YES] subarrayWithRange:range], @"anyIntObj": [@[@2, @3] subarrayWithRange:range], @"anyFloatObj": [@[@2.2f, @3.3f] subarrayWithRange:range], @"anyDoubleObj": [@[@2.2, @3.3] subarrayWithRange:range], @"anyStringObj": [@[@"a", @"b"] subarrayWithRange:range], @"anyDataObj": [@[data(1), data(2)] subarrayWithRange:range], @"anyDateObj": [@[date(1), date(2)] subarrayWithRange:range], @"anyDecimalObj": [@[decimal128(1), decimal128(2)] subarrayWithRange:range], @"anyObjectIdObj": [@[objectId(1), objectId(2)] subarrayWithRange:range], @"anyUUIDObj": [@[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")] subarrayWithRange:range], }]; [LinkToAllPrimitiveSets createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"boolObj": [@[NSNull.null, @NO, @YES] subarrayWithRange:range], @"intObj": [@[NSNull.null, @2, @3] subarrayWithRange:range], @"floatObj": [@[NSNull.null, @2.2f, @3.3f] subarrayWithRange:range], @"doubleObj": [@[NSNull.null, @2.2, @3.3] subarrayWithRange:range], @"stringObj": [@[NSNull.null, @"a", @"bc"] subarrayWithRange:range], @"dataObj": [@[NSNull.null, data(1), data(2)] subarrayWithRange:range], @"dateObj": [@[NSNull.null, date(1), date(2)] subarrayWithRange:range], @"decimalObj": [@[NSNull.null, decimal128(1), decimal128(2)] subarrayWithRange:range], @"objectIdObj": [@[NSNull.null, objectId(1), objectId(2)] subarrayWithRange:range], @"uuidObj": [@[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")] subarrayWithRange:range], }]; [LinkToAllOptionalPrimitiveSets createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj > %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj > %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj >= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj >= %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj < %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj < %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj < %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj <= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj <= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj <= %@", NSNull.null); [self createObjectWithValueIndex:0]; RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj = %@", @YES); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj = %@", @3); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj = %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj = %@", @YES); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj = %@", @3); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj = %@", @"b"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj = %@", data(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj = %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj = %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj != %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj != %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj != %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj != %@", @"b"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj != %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj != %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj != %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj > %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj > %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj >= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj >= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj >= %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj < %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj < %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj < %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj < %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj < %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj < %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj < %@", @"b"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj < %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj < %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj <= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj <= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj <= %@", NSNull.null); [self createObjectWithValueIndex:1]; RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj = %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj = %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj = %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj = %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj = %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj = %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj = %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj = %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj = %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj = %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj = %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj = %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj = %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj = %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj = %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj = %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj = %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj = %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj = %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj = %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj = %@", @"b"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj = %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj = %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj = %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj = %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj = %@", @NO); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj = %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj = %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj = %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj = %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj = %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj = %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj = %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj = %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj != %@", @NO); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj != %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj != %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj != %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj != %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj != %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj != %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj != %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj != %@", objectId(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj != %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj != %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj != %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj != %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj != %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj != %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj != %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj != %@", @YES); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj != %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj != %@", @"b"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj != %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj != %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj != %@", objectId(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj != %@", @NO); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj != %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj != %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj != %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj != %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj != %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj != %@", objectId(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj > %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj > %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj > %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj > %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY intObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 2, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 2, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 2, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 2, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY decimalObj >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyIntObj >= %@", @2); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyFloatObj >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDoubleObj >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyStringObj >= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDateObj >= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDecimalObj >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj < %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj < %@", @2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj < %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj < %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj < %@", @"a"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj < %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj < %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj < %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj < %@", @3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj < %@", @"b"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj < %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj <= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj <= %@", @2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj <= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj <= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj <= %@", @"a"); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj <= %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj <= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY intObj <= %@", @3); RLMAssertCount(AllPrimitiveSets, 2, @"ANY floatObj <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2, @"ANY doubleObj <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2, @"ANY stringObj <= %@", @"bc"); RLMAssertCount(AllPrimitiveSets, 2, @"ANY dataObj <= %@", data(2)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY dateObj <= %@", date(2)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY decimalObj <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyIntObj <= %@", @3); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyFloatObj <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDoubleObj <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyStringObj <= %@", @"b"); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDateObj <= %@", date(2)); RLMAssertCount(AllPrimitiveSets, 2, @"ANY anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj > %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj >= %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj >= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj >= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj >= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj >= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj < %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj < %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj < %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj < %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj < %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj < %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj < %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj < %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj < %@", @"bc"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj < %@", data(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj < %@", date(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj <= %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj <= %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj <= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj <= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj <= %@", @"a"); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj <= %@", data(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj <= %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj <= %@", decimal128(1)); RLMAssertThrows(([AllPrimitiveSets objectsInRealm:realm where:@"ANY boolObj > %@", @NO])); RLMAssertThrows(([AllPrimitiveSets objectsInRealm:realm where:@"ANY objectIdObj > %@", objectId(1)])); RLMAssertThrows(([AllPrimitiveSets objectsInRealm:realm where:@"ANY uuidObj > %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")])); RLMAssertThrows(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY boolObj > %@", NSNull.null])); RLMAssertThrows(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY objectIdObj > %@", NSNull.null])); RLMAssertThrows(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY uuidObj > %@", NSNull.null])); } - (void)testQueryBetween { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[@NO, @YES]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[@"a", @"bc"]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"ANY dataObj BETWEEN %@", @[data(1), data(2)]]), @"Operator 'BETWEEN' not supported for type 'data'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"ANY objectIdObj BETWEEN %@", @[objectId(1), objectId(2)]]), @"Operator 'BETWEEN' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"ANY uuidObj BETWEEN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]]), @"Operator 'BETWEEN' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY boolObj BETWEEN %@", @[NSNull.null, @NO]]), @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY stringObj BETWEEN %@", @[NSNull.null, @"a"]]), @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY dataObj BETWEEN %@", @[NSNull.null, data(1)]]), @"Operator 'BETWEEN' not supported for type 'data'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY objectIdObj BETWEEN %@", @[NSNull.null, objectId(1)]]), @"Operator 'BETWEEN' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY uuidObj BETWEEN %@", @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]]), @"Operator 'BETWEEN' not supported for type 'uuid'"); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj BETWEEN %@", @[NSNull.null, @2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj BETWEEN %@", @[NSNull.null, @2.2f]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj BETWEEN %@", @[NSNull.null, @2.2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj BETWEEN %@", @[NSNull.null, date(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj BETWEEN %@", @[NSNull.null, decimal128(1)]); [self createObjectWithValueIndex:1]; RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(1), decimal128(1)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj BETWEEN %@", @[decimal128(1), decimal128(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj BETWEEN %@", @[@2, @2]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @2.2f]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @2.2]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(1), decimal128(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj BETWEEN %@", @[@2, @3]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj BETWEEN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj BETWEEN %@", @[@2.2, @3.3]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj BETWEEN %@", @[date(1), date(2)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj BETWEEN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj BETWEEN %@", @[@3, @3]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj BETWEEN %@", @[@3.3f, @3.3f]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj BETWEEN %@", @[@3.3, @3.3]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj BETWEEN %@", @[date(2), date(2)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj BETWEEN %@", @[decimal128(2), decimal128(2)]); } - (void)testQueryIn { [realm deleteAllObjects]; RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj IN %@", @[@"a", @"bc"]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj IN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj IN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj IN %@", @[NSNull.null, @NO]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj IN %@", @[NSNull.null, @2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj IN %@", @[NSNull.null, @2.2f]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj IN %@", @[NSNull.null, @2.2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj IN %@", @[NSNull.null, @"a"]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj IN %@", @[NSNull.null, data(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj IN %@", @[NSNull.null, date(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj IN %@", @[NSNull.null, decimal128(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj IN %@", @[NSNull.null, objectId(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj IN %@", @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); [self createObjectWithValueIndex:0]; RLMAssertCount(AllPrimitiveSets, 0, @"ANY boolObj IN %@", @[@YES]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY intObj IN %@", @[@3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY floatObj IN %@", @[@3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY doubleObj IN %@", @[@3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY stringObj IN %@", @[@"bc"]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dataObj IN %@", @[data(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY dateObj IN %@", @[date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY decimalObj IN %@", @[decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY objectIdObj IN %@", @[objectId(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY uuidObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000")]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyBoolObj IN %@", @[@YES]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyIntObj IN %@", @[@3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyFloatObj IN %@", @[@3.3f]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDoubleObj IN %@", @[@3.3]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyStringObj IN %@", @[@"b"]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDataObj IN %@", @[data(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDateObj IN %@", @[date(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyDecimalObj IN %@", @[decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyObjectIdObj IN %@", @[objectId(2)]); RLMAssertCount(AllPrimitiveSets, 0, @"ANY anyUUIDObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY boolObj IN %@", @[@NO]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY intObj IN %@", @[@2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY floatObj IN %@", @[@2.2f]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY doubleObj IN %@", @[@2.2]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY stringObj IN %@", @[@"a"]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dataObj IN %@", @[data(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY dateObj IN %@", @[date(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY decimalObj IN %@", @[decimal128(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY objectIdObj IN %@", @[objectId(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"ANY uuidObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY boolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY intObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY floatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY doubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY stringObj IN %@", @[@"a", @"bc"]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY dateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY decimalObj IN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY objectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY uuidObj IN %@", @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyBoolObj IN %@", @[@NO, @YES]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyIntObj IN %@", @[@2, @3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyFloatObj IN %@", @[@2.2f, @3.3f]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDoubleObj IN %@", @[@2.2, @3.3]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyStringObj IN %@", @[@"a", @"b"]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDataObj IN %@", @[data(1), data(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDateObj IN %@", @[date(1), date(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyDecimalObj IN %@", @[decimal128(1), decimal128(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyObjectIdObj IN %@", @[objectId(1), objectId(2)]); RLMAssertCount(AllPrimitiveSets, 1, @"ANY anyUUIDObj IN %@", @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY boolObj IN %@", @[NSNull.null, @NO]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY intObj IN %@", @[NSNull.null, @2]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY floatObj IN %@", @[NSNull.null, @2.2f]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY doubleObj IN %@", @[NSNull.null, @2.2]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY stringObj IN %@", @[NSNull.null, @"a"]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dataObj IN %@", @[NSNull.null, data(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY dateObj IN %@", @[NSNull.null, date(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY decimalObj IN %@", @[NSNull.null, decimal128(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY objectIdObj IN %@", @[NSNull.null, objectId(1)]); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"ANY uuidObj IN %@", @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"boolObj": @[@NO, @YES], @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"stringObj": @[@"a", @"bc"], @"dataObj": @[data(1), data(2)], @"dateObj": @[date(1), date(2)], @"decimalObj": @[decimal128(1), decimal128(2)], @"objectIdObj": @[objectId(1), objectId(2)], @"uuidObj": @[uuid(@"137DECC8-B300-4954-A233-F89909F4FD89"), uuid(@"00000000-0000-0000-0000-000000000000")], @"anyBoolObj": @[@NO, @YES], @"anyIntObj": @[@2, @3], @"anyFloatObj": @[@2.2f, @3.3f], @"anyDoubleObj": @[@2.2, @3.3], @"anyStringObj": @[@"a", @"b"], @"anyDataObj": @[data(1), data(2)], @"anyDateObj": @[date(1), date(2)], @"anyDecimalObj": @[decimal128(1), decimal128(2)], @"anyObjectIdObj": @[objectId(1), objectId(2)], @"anyUUIDObj": @[uuid(@"00000000-0000-0000-0000-000000000000"), uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"boolObj": @[NSNull.null, @NO], @"intObj": @[NSNull.null, @2], @"floatObj": @[NSNull.null, @2.2f], @"doubleObj": @[NSNull.null, @2.2], @"stringObj": @[NSNull.null, @"a"], @"dataObj": @[NSNull.null, data(1)], @"dateObj": @[NSNull.null, date(1)], @"decimalObj": @[NSNull.null, decimal128(1)], @"objectIdObj": @[NSNull.null, objectId(1)], @"uuidObj": @[NSNull.null, uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")], }]; RLMAssertCount(AllPrimitiveSets, 1U, @"boolObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"stringObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"dataObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"objectIdObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"uuidObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyBoolObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyStringObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDataObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyObjectIdObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyUUIDObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"boolObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"stringObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dataObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"objectIdObj.@count == %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"uuidObj.@count == %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"boolObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"stringObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"dataObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"objectIdObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"uuidObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyBoolObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyStringObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDataObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyObjectIdObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyUUIDObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"boolObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"stringObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dataObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"objectIdObj.@count != %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"uuidObj.@count != %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"boolObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"intObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"floatObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"doubleObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"stringObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"dataObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"dateObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"decimalObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"objectIdObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"uuidObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyBoolObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyIntObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyFloatObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDoubleObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyStringObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDataObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDateObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDecimalObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyObjectIdObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyUUIDObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"boolObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"intObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"floatObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"doubleObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"stringObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"dataObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"dateObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"decimalObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"objectIdObj.@count > %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"uuidObj.@count > %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"boolObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"intObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"floatObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"doubleObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"stringObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"dataObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"dateObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"decimalObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"objectIdObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"uuidObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyBoolObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyIntObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyFloatObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDoubleObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyStringObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDataObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDateObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDecimalObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyObjectIdObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyUUIDObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"boolObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"intObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"floatObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"doubleObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"stringObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"dataObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"dateObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"decimalObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"objectIdObj.@count >= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"uuidObj.@count >= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"boolObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"intObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"floatObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"doubleObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"stringObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"dataObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"dateObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"decimalObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"objectIdObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"uuidObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyBoolObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyIntObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyFloatObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDoubleObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyStringObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDataObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDateObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyDecimalObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyObjectIdObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 0, @"anyUUIDObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"boolObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"intObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"floatObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"doubleObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"stringObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"dataObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"dateObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"decimalObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"objectIdObj.@count < %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0, @"uuidObj.@count < %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"boolObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"intObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"floatObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"doubleObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"stringObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"dataObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"dateObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"decimalObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"objectIdObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"uuidObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyBoolObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyIntObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyFloatObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDoubleObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyStringObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDataObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDateObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyDecimalObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyObjectIdObj.@count <= %@", @(2)); RLMAssertCount(AllPrimitiveSets, 1, @"anyUUIDObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"boolObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"intObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"floatObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"doubleObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"stringObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"dataObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"dateObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"decimalObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"objectIdObj.@count <= %@", @(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1, @"uuidObj.@count <= %@", @(2)); } - (void)testQuerySum { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"boolObj.@sum = %@", @NO]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"stringObj.@sum = %@", @"a"]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dataObj.@sum = %@", data(1)]), @"Invalid keypath 'dataObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@sum = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"uuidObj.@sum = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]), @"Invalid keypath 'uuidObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"boolObj.@sum = %@", NSNull.null]), @"Invalid keypath 'boolObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"stringObj.@sum = %@", NSNull.null]), @"Invalid keypath 'stringObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dataObj.@sum = %@", NSNull.null]), @"Invalid keypath 'dataObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@sum = %@", NSNull.null]), @"Invalid keypath 'objectIdObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"uuidObj.@sum = %@", NSNull.null]), @"Invalid keypath 'uuidObj.@sum': @sum can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@sum = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum = %@", @"a"]), @"@sum on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum = %@", @"a"]), @"@sum on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum = %@", @"a"]), @"@sum on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@sum = %@", @"a"]), @"@sum on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum = %@", @"a"]), @"@sum on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum = %@", @"a"]), @"@sum on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum = %@", @"a"]), @"@sum on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'intObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@sum = %@", NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum = %@", NSNull.null]), @"@sum on a property of type float cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum = %@", NSNull.null]), @"@sum on a property of type double cannot be compared with ''"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum = %@", NSNull.null]), @"@sum on a property of type decimal128 cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@sum = %@", NSNull.null]), @"@sum on a property of type int cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@sum = %@", NSNull.null]), @"@sum on a property of type float cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@sum = %@", NSNull.null]), @"@sum on a property of type double cannot be compared with ''"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@sum = %@", NSNull.null]), @"@sum on a property of type decimal128 cannot be compared with ''"); [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], @"anyIntObj": @[], @"anyFloatObj": @[], @"anyDoubleObj": @[], @"anyDecimalObj": @[], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(1)], @"anyIntObj": @[@2], @"anyFloatObj": @[@2.2f], @"anyDoubleObj": @[@2.2], @"anyDecimalObj": @[decimal128(1)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"decimalObj": @[decimal128(1)], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, @2], @"floatObj": @[@2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2], @"decimalObj": @[decimal128(1), decimal128(1)], @"anyIntObj": @[@2, @2], @"anyFloatObj": @[@2.2f, @2.2f], @"anyDoubleObj": @[@2.2, @2.2], @"anyDecimalObj": @[decimal128(1), decimal128(1)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"decimalObj": @[decimal128(1), decimal128(2)], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3, @3, @3], @"floatObj": @[@3.3f, @3.3f, @3.3f], @"doubleObj": @[@3.3, @3.3, @3.3], @"decimalObj": @[decimal128(2), decimal128(2), decimal128(2)], @"anyIntObj": @[@3, @3, @3], @"anyFloatObj": @[@3.3f, @3.3f, @3.3f], @"anyDoubleObj": @[@3.3, @3.3, @3.3], @"anyDecimalObj": @[decimal128(2), decimal128(2), decimal128(2)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, @2, @2], @"floatObj": @[@2.2f, @2.2f, @2.2f], @"doubleObj": @[@2.2, @2.2, @2.2], @"decimalObj": @[decimal128(1), decimal128(1), decimal128(1)], }]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@sum == %@", @0); RLMAssertCount(AllPrimitiveSets, 2U, @"intObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveSets, 2U, @"floatObj.@sum == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 2U, @"doubleObj.@sum == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 2U, @"decimalObj.@sum == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyIntObj.@sum == %@", @2); RLMAssertCount(AllPrimitiveSets, 2U, @"anyFloatObj.@sum == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDoubleObj.@sum == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDecimalObj.@sum == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 3U, @"intObj.@sum != %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"floatObj.@sum != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"doubleObj.@sum != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"decimalObj.@sum != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"anyIntObj.@sum != %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyFloatObj.@sum != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDoubleObj.@sum != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDecimalObj.@sum != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllPrimitiveSets, 3U, @"floatObj.@sum >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 3U, @"doubleObj.@sum >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 3U, @"decimalObj.@sum >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 3U, @"anyIntObj.@sum >= %@", @2); RLMAssertCount(AllPrimitiveSets, 3U, @"anyFloatObj.@sum >= %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDoubleObj.@sum >= %@", @2.2); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDecimalObj.@sum >= %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@sum > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@sum > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@sum > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@sum > %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@sum > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@sum > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@sum > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 3U, @"intObj.@sum < %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"floatObj.@sum < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"doubleObj.@sum < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"decimalObj.@sum < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"anyIntObj.@sum < %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyFloatObj.@sum < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDoubleObj.@sum < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDecimalObj.@sum < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 4U, @"intObj.@sum <= %@", @3); RLMAssertCount(AllPrimitiveSets, 4U, @"floatObj.@sum <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 4U, @"doubleObj.@sum <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 4U, @"decimalObj.@sum <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 4U, @"anyIntObj.@sum <= %@", @3); RLMAssertCount(AllPrimitiveSets, 4U, @"anyFloatObj.@sum <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 4U, @"anyDoubleObj.@sum <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 4U, @"anyDecimalObj.@sum <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@sum == %@", @0); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@sum == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@sum == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@sum == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@sum == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@sum != %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@sum != %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@sum != %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@sum != %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"intObj.@sum >= %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"floatObj.@sum >= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"doubleObj.@sum >= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"decimalObj.@sum >= %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@sum > %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@sum > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@sum > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@sum > %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"intObj.@sum < %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"floatObj.@sum < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"doubleObj.@sum < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"decimalObj.@sum < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"intObj.@sum <= %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"floatObj.@sum <= %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"doubleObj.@sum <= %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"decimalObj.@sum <= %@", decimal128(1)); } - (void)testQueryAverage { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"boolObj.@avg = %@", @NO]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"stringObj.@avg = %@", @"a"]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dataObj.@avg = %@", data(1)]), @"Invalid keypath 'dataObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@avg = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"uuidObj.@avg = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]), @"Invalid keypath 'uuidObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"boolObj.@avg = %@", NSNull.null]), @"Invalid keypath 'boolObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"stringObj.@avg = %@", NSNull.null]), @"Invalid keypath 'stringObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dataObj.@avg = %@", NSNull.null]), @"Invalid keypath 'dataObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@avg = %@", NSNull.null]), @"Invalid keypath 'objectIdObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"uuidObj.@avg = %@", NSNull.null]), @"Invalid keypath 'uuidObj.@avg': @avg can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@avg = %@", date(1)]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dateObj.@avg = %@", NSNull.null]), @"Cannot sum or average date properties"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@avg = %@", @"a"]), @"@avg on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@avg = %@", @"a"]), @"@avg on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@avg = %@", @"a"]), @"@avg on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@avg = %@", @"a"]), @"@avg on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@avg = %@", @"a"]), @"@avg on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@avg = %@", @"a"]), @"@avg on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@avg = %@", @"a"]), @"@avg on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'intObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@avg.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], @"anyIntObj": @[], @"anyFloatObj": @[], @"anyDoubleObj": @[], @"anyDecimalObj": @[], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[], @"floatObj": @[], @"doubleObj": @[], @"decimalObj": @[], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(2)], @"anyIntObj": @[@3], @"anyFloatObj": @[@3.3f], @"anyDoubleObj": @[@3.3], @"anyDecimalObj": @[decimal128(2)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(2)], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"decimalObj": @[decimal128(1), decimal128(2)], @"anyIntObj": @[@2, @3], @"anyFloatObj": @[@2.2f, @3.3f], @"anyDoubleObj": @[@2.2, @3.3], @"anyDecimalObj": @[decimal128(1), decimal128(2)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, @3], @"floatObj": @[@2.2f, @3.3f], @"doubleObj": @[@2.2, @3.3], @"decimalObj": @[decimal128(1), decimal128(2)], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(2)], @"anyIntObj": @[@3], @"anyFloatObj": @[@3.3f], @"anyDoubleObj": @[@3.3], @"anyDecimalObj": @[decimal128(2)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3], @"floatObj": @[@3.3f], @"doubleObj": @[@3.3], @"decimalObj": @[decimal128(2)], }]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@avg == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@avg == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 2U, @"intObj.@avg == %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"floatObj.@avg == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"doubleObj.@avg == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"decimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyIntObj.@avg == %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyFloatObj.@avg == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDoubleObj.@avg == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDecimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@avg == %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@avg == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@avg == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@avg == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"intObj.@avg != %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"floatObj.@avg != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"doubleObj.@avg != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"decimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyIntObj.@avg != %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyFloatObj.@avg != %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDoubleObj.@avg != %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDecimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@avg != %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@avg != %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@avg != %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@avg != %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"intObj.@avg >= %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"floatObj.@avg >= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"doubleObj.@avg >= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"decimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyIntObj.@avg >= %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyFloatObj.@avg >= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDoubleObj.@avg >= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDecimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@avg >= %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@avg >= %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@avg >= %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@avg >= %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"intObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveSets, 3U, @"floatObj.@avg > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 3U, @"doubleObj.@avg > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 3U, @"decimalObj.@avg > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 3U, @"anyIntObj.@avg > %@", @2); RLMAssertCount(AllPrimitiveSets, 3U, @"anyFloatObj.@avg > %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDoubleObj.@avg > %@", @2.2); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDecimalObj.@avg > %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"intObj.@avg > %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"floatObj.@avg > %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"doubleObj.@avg > %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"decimalObj.@avg > %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@avg < %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@avg < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@avg < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@avg < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@avg < %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@avg < %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@avg < %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@avg < %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@avg < %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@avg < %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@avg < %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@avg < %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"intObj.@avg <= %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"floatObj.@avg <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"doubleObj.@avg <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"decimalObj.@avg <= %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 3U, @"anyIntObj.@avg <= %@", @3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyFloatObj.@avg <= %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDoubleObj.@avg <= %@", @3.3); RLMAssertCount(AllPrimitiveSets, 3U, @"anyDecimalObj.@avg <= %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"intObj.@avg <= %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"floatObj.@avg <= %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"doubleObj.@avg <= %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 3U, @"decimalObj.@avg <= %@", decimal128(2)); } - (void)testQueryMin { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"boolObj.@min = %@", @NO]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"stringObj.@min = %@", @"a"]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dataObj.@min = %@", data(1)]), @"Invalid keypath 'dataObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@min = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"uuidObj.@min = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]), @"Invalid keypath 'uuidObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"boolObj.@min = %@", NSNull.null]), @"Invalid keypath 'boolObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"stringObj.@min = %@", NSNull.null]), @"Invalid keypath 'stringObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dataObj.@min = %@", NSNull.null]), @"Invalid keypath 'dataObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@min = %@", NSNull.null]), @"Invalid keypath 'objectIdObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"uuidObj.@min = %@", NSNull.null]), @"Invalid keypath 'uuidObj.@min': @min can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@min = %@", @"a"]), @"@min on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@min = %@", @"a"]), @"@min on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@min = %@", @"a"]), @"@min on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@min = %@", @"a"]), @"@min on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@min = %@", @"a"]), @"@min on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@min = %@", @"a"]), @"@min on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dateObj.@min = %@", @"a"]), @"@min on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@min = %@", @"a"]), @"@min on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyIntObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyFloatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDoubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDecimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@min.prop = %@", @"a"]), @"Invalid keypath 'intObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@min.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@min.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dateObj.@min.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@min.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@min == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@min == %@", NSNull.null); [AllPrimitiveSets createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(2)); [self createObjectWithValueIndex:1]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@min == %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@min == %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@min == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@min == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@min == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@min == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@min == %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@min == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@min == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@min == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@min == %@", decimal128(2)); [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(2), decimal128(1)], @"anyIntObj": @[@3, @2], @"anyFloatObj": @[@3.3f, @2.2f], @"anyDoubleObj": @[@3.3, @2.2], @"anyDateObj": @[date(2), date(1)], @"anyDecimalObj": @[decimal128(2), decimal128(1)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(2), decimal128(1)], }]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@min == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@min == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@min == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@min == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@min == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@min == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@min == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@min == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"dateObj.@min == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@min == %@", decimal128(1)); } - (void)testQueryMax { [realm deleteAllObjects]; RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"boolObj.@max = %@", @NO]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"stringObj.@max = %@", @"a"]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dataObj.@max = %@", data(1)]), @"Invalid keypath 'dataObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@max = %@", objectId(1)]), @"Invalid keypath 'objectIdObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"uuidObj.@max = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]), @"Invalid keypath 'uuidObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"boolObj.@max = %@", NSNull.null]), @"Invalid keypath 'boolObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"stringObj.@max = %@", NSNull.null]), @"Invalid keypath 'stringObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dataObj.@max = %@", NSNull.null]), @"Invalid keypath 'dataObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"objectIdObj.@max = %@", NSNull.null]), @"Invalid keypath 'objectIdObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"uuidObj.@max = %@", NSNull.null]), @"Invalid keypath 'uuidObj.@max': @max can only be applied to a collection of numeric values."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@max = %@", @"a"]), @"@max on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@max = %@", @"a"]), @"@max on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@max = %@", @"a"]), @"@max on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@max = %@", @"a"]), @"@max on a property of type int cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@max = %@", @"a"]), @"@max on a property of type float cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@max = %@", @"a"]), @"@max on a property of type double cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dateObj.@max = %@", @"a"]), @"@max on a property of type date cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@max = %@", @"a"]), @"@max on a property of type decimal128 cannot be compared with 'a'"); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"floatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"doubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"decimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyIntObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyIntObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyFloatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyFloatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDoubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDoubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllPrimitiveSets objectsInRealm:realm where:@"anyDecimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'anyDecimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"intObj.@max.prop = %@", @"a"]), @"Invalid keypath 'intObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"floatObj.@max.prop = %@", @"a"]), @"Invalid keypath 'floatObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"doubleObj.@max.prop = %@", @"a"]), @"Invalid keypath 'doubleObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"dateObj.@max.prop = %@", @"a"]), @"Invalid keypath 'dateObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); RLMAssertThrowsWithReason(([AllOptionalPrimitiveSets objectsInRealm:realm where:@"decimalObj.@max.prop = %@", @"a"]), @"Invalid keypath 'decimalObj.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@max == %@", NSNull.null); [AllPrimitiveSets createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@max == nil"); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@max == nil"); RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@max == %@", NSNull.null); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@max == %@", NSNull.null); [self createObjectWithValueIndex:1]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllPrimitiveSets, 0U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 0U, @"anyDecimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"intObj.@max == %@", @3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllOptionalPrimitiveSets, 0U, @"decimalObj.@max == %@", decimal128(2)); [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2], @"floatObj": @[@2.2f], @"doubleObj": @[@2.2], @"dateObj": @[date(1)], @"decimalObj": @[decimal128(1)], @"anyIntObj": @[@2], @"anyFloatObj": @[@2.2f], @"anyDoubleObj": @[@2.2], @"anyDateObj": @[date(1)], @"anyDecimalObj": @[decimal128(1)], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@3, @2], @"floatObj": @[@3.3f, @2.2f], @"doubleObj": @[@3.3, @2.2], @"dateObj": @[date(2), date(1)], @"decimalObj": @[decimal128(2), decimal128(1)], @"anyIntObj": @[@3, @2], @"anyFloatObj": @[@3.3f, @2.2f], @"anyDoubleObj": @[@3.3, @2.2], @"anyDateObj": @[date(2), date(1)], @"anyDecimalObj": @[decimal128(2), decimal128(1)], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"intObj": @[@2, NSNull.null], @"floatObj": @[@2.2f, NSNull.null], @"doubleObj": @[@2.2, NSNull.null], @"dateObj": @[date(1), NSNull.null], @"decimalObj": @[decimal128(1), NSNull.null], }]; RLMAssertCount(AllPrimitiveSets, 1U, @"intObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2U, @"intObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"floatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"doubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"dateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"decimalObj.@max == %@", decimal128(2)); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"intObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"floatObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"doubleObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"dateObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 1U, @"decimalObj.@max == %@", NSNull.null); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"intObj.@max == %@", @2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"floatObj.@max == %@", @2.2f); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"doubleObj.@max == %@", @2.2); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"dateObj.@max == %@", date(1)); RLMAssertCount(AllOptionalPrimitiveSets, 2U, @"decimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyIntObj.@max == %@", @2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyFloatObj.@max == %@", @2.2f); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDoubleObj.@max == %@", @2.2); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDateObj.@max == %@", date(1)); RLMAssertCount(AllPrimitiveSets, 1U, @"anyDecimalObj.@max == %@", decimal128(1)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyIntObj.@max == %@", @3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyFloatObj.@max == %@", @3.3f); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDoubleObj.@max == %@", @3.3); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDateObj.@max == %@", date(2)); RLMAssertCount(AllPrimitiveSets, 2U, @"anyDecimalObj.@max == %@", decimal128(2)); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj = %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyBoolObj = %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj = %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj = %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj = %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj = %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDataObj = %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj = %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj = %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyObjectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyUUIDObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.boolObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.objectIdObj = %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.uuidObj = %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj != %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj != %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyObjectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.boolObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.objectIdObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.uuidObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj > %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj > %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj > %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj > %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj >= %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj >= %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj >= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj >= %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj < %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj < %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj < %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj <= %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj <= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj <= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj <= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj <= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj <= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj <= %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj <= %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj <= %@", NSNull.null); [self createObjectWithValueIndex:1]; RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.boolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj = %@", @"bc"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.objectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.uuidObj = %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyBoolObj = %@", @YES); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj = %@", @3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj = %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj = %@", @3.3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj = %@", @"b"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDataObj = %@", data(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj = %@", date(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj = %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyObjectIdObj = %@", objectId(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyUUIDObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.boolObj = %@", @NO); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.intObj = %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.floatObj = %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.doubleObj = %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.stringObj = %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dataObj = %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dateObj = %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.decimalObj = %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.objectIdObj = %@", objectId(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.uuidObj = %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.boolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj != %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.objectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.uuidObj != %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyBoolObj != %@", @NO); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj != %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj != %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj != %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj != %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDataObj != %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj != %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj != %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyObjectIdObj != %@", objectId(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyUUIDObj != %@", uuid(@"00000000-0000-0000-0000-000000000000")); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.boolObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.intObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.floatObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.doubleObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.stringObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dataObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dateObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.decimalObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.objectIdObj != %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.uuidObj != %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj > %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj > %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj > %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj > %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj > %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj > %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj > %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj > %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj > %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj > %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj > %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj > %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj > %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj > %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj >= %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj >= %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj >= %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj >= %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj >= %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj >= %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj >= %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.intObj >= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.floatObj >= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.doubleObj >= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.stringObj >= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dataObj >= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dateObj >= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.decimalObj >= %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.intObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.floatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.doubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.stringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dataObj < %@", data(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.dateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.decimalObj < %@", decimal128(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyIntObj < %@", @2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyFloatObj < %@", @2.2f); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDoubleObj < %@", @2.2); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyStringObj < %@", @"a"); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDateObj < %@", date(1)); RLMAssertCount(LinkToAllPrimitiveSets, 0, @"ANY link.anyDecimalObj < %@", decimal128(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.intObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.floatObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.doubleObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.stringObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dataObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.dateObj < %@", NSNull.null); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 0, @"ANY link.decimalObj < %@", NSNull.null); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj < %@", @4); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj < %@", @4.4f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj < %@", @4.4); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj < %@", @"de"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj < %@", data(3)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj < %@", date(3)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj < %@", @4); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj < %@", @4.4f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj < %@", @4.4); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj < %@", @"d"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj < %@", date(3)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj < %@", decimal128(3)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.intObj < %@", @3); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.floatObj < %@", @3.3f); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.doubleObj < %@", @3.3); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.stringObj < %@", @"bc"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dataObj < %@", data(2)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dateObj < %@", date(2)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.decimalObj < %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.intObj <= %@", @3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.floatObj <= %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.doubleObj <= %@", @3.3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.stringObj <= %@", @"bc"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dataObj <= %@", data(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.dateObj <= %@", date(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.decimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyIntObj <= %@", @3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyFloatObj <= %@", @3.3f); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDoubleObj <= %@", @3.3); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyStringObj <= %@", @"b"); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDateObj <= %@", date(2)); RLMAssertCount(LinkToAllPrimitiveSets, 1, @"ANY link.anyDecimalObj <= %@", decimal128(2)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.intObj <= %@", @2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.floatObj <= %@", @2.2f); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.doubleObj <= %@", @2.2); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.stringObj <= %@", @"a"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dataObj <= %@", data(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.dateObj <= %@", date(1)); RLMAssertCount(LinkToAllOptionalPrimitiveSets, 1, @"ANY link.decimalObj <= %@", decimal128(1)); RLMAssertThrowsWithReason(([LinkToAllPrimitiveSets objectsInRealm:realm where:@"ANY link.boolObj > %@", @NO]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([LinkToAllPrimitiveSets objectsInRealm:realm where:@"ANY link.objectIdObj > %@", objectId(1)]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([LinkToAllPrimitiveSets objectsInRealm:realm where:@"ANY link.uuidObj > %@", uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")]), @"Operator '>' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY link.boolObj > %@", NSNull.null]), @"Operator '>' not supported for type 'bool'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY link.objectIdObj > %@", NSNull.null]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([LinkToAllOptionalPrimitiveSets objectsInRealm:realm where:@"ANY link.uuidObj > %@", NSNull.null]), @"Operator '>' not supported for type 'uuid'"); } - (void)testSubstringQueries { NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveSets createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllPrimitiveSets createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllOptionalPrimitiveSets createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveSets, count, query, value); RLMAssertCount(AllPrimitiveSets, count, query, value); RLMAssertCount(AllOptionalPrimitiveSets, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveSets, count, query, value); RLMAssertCount(LinkToAllPrimitiveSets, count, query, value); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; RLMAssertCount(AllPrimitiveSets, count, query, data); RLMAssertCount(AllPrimitiveSets, count, query, data); RLMAssertCount(AllOptionalPrimitiveSets, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; RLMAssertCount(LinkToAllPrimitiveSets, count, query, data); RLMAssertCount(LinkToAllPrimitiveSets, count, query, data); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else { uncheckedAssertEqualObjects(change.insertions, @[@0]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMSet *set = [(AllPrimitiveSets *)[AllPrimitiveSets allObjectsInRealm:r].firstObject intObj]; [set addObject:@0]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMSet *set, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveSets createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMSet *set = [(AllPrimitiveSets *)[AllPrimitiveSets allObjectsInRealm:r].firstObject intObj]; [set addObject:@0]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { [managed.intObj addObjects:@[@10, @20]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); first = false; } else { uncheckedAssertEqualObjects(change.deletions, (@[@0, @1])); } [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:managed]; [realm commitWriteTransaction]; expectation = [self expectationWithDescription:@""]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } @end ================================================ FILE: Realm/Tests/PrimitiveSetPropertyTests.tpl.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" static NSDate *date(int i) { return [NSDate dateWithTimeIntervalSince1970:i]; } static NSData *data(int i) { return [NSData dataWithBytesNoCopy:calloc(i, 1) length:i freeWhenDone:YES]; } static RLMDecimal128 *decimal128(int i) { return [RLMDecimal128 decimalWithNumber:@(i)]; } static NSMutableArray *objectIds; static RLMObjectId *objectId(NSUInteger i) { if (!objectIds) { objectIds = [NSMutableArray new]; } while (i >= objectIds.count) { [objectIds addObject:RLMObjectId.objectId]; } return objectIds[i]; } static NSUUID *uuid(NSString *uuidString) { return [[NSUUID alloc] initWithUUIDString:uuidString]; } static void count(NSArray *values, double *sum, NSUInteger *count) { for (id value in values) { if (value != NSNull.null) { ++*count; *sum += [value doubleValue]; } } } static double sum(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum; } static double average(NSArray *values) { double sum = 0; NSUInteger c = 0; count(values, &sum, &c); return sum / c; } @interface LinkToAllPrimitiveSets : RLMObject @property (nonatomic) AllPrimitiveSets *link; @end @implementation LinkToAllPrimitiveSets @end @interface LinkToAllOptionalPrimitiveSets : RLMObject @property (nonatomic) AllOptionalPrimitiveSets *link; @end @implementation LinkToAllOptionalPrimitiveSets @end @interface PrimitiveSetPropertyTests : RLMTestCase @end @implementation PrimitiveSetPropertyTests { AllPrimitiveSets *unmanaged; AllPrimitiveSets *managed; AllOptionalPrimitiveSets *optUnmanaged; AllOptionalPrimitiveSets *optManaged; RLMRealm *realm; NSArray *allSets; } - (void)setUp { unmanaged = [[AllPrimitiveSets alloc] init]; optUnmanaged = [[AllOptionalPrimitiveSets alloc] init]; realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; managed = [AllPrimitiveSets createInRealm:realm withValue:@[]]; optManaged = [AllOptionalPrimitiveSets createInRealm:realm withValue:@[]]; allSets = @[ $set, ]; } - (void)tearDown { if (realm.inWriteTransaction) { [realm cancelWriteTransaction]; } } - (void)addObjects { [$set addObjects:$values]; } - (void)testCount { uncheckedAssertEqual(unmanaged.intObj.count, 0U); [unmanaged.intObj addObject:@1]; uncheckedAssertEqual(unmanaged.intObj.count, 1U); } - (void)testType { uncheckedAssertEqual(unmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(unmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(unmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(unmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(unmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(unmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(unmanaged.dateObj.type, RLMPropertyTypeDate); uncheckedAssertEqual(optUnmanaged.boolObj.type, RLMPropertyTypeBool); uncheckedAssertEqual(optUnmanaged.intObj.type, RLMPropertyTypeInt); uncheckedAssertEqual(optUnmanaged.floatObj.type, RLMPropertyTypeFloat); uncheckedAssertEqual(optUnmanaged.doubleObj.type, RLMPropertyTypeDouble); uncheckedAssertEqual(optUnmanaged.stringObj.type, RLMPropertyTypeString); uncheckedAssertEqual(optUnmanaged.dataObj.type, RLMPropertyTypeData); uncheckedAssertEqual(optUnmanaged.dateObj.type, RLMPropertyTypeDate); } - (void)testOptional { uncheckedAssertFalse(unmanaged.boolObj.optional); uncheckedAssertFalse(unmanaged.intObj.optional); uncheckedAssertFalse(unmanaged.floatObj.optional); uncheckedAssertFalse(unmanaged.doubleObj.optional); uncheckedAssertFalse(unmanaged.stringObj.optional); uncheckedAssertFalse(unmanaged.dataObj.optional); uncheckedAssertFalse(unmanaged.dateObj.optional); uncheckedAssertTrue(optUnmanaged.boolObj.optional); uncheckedAssertTrue(optUnmanaged.intObj.optional); uncheckedAssertTrue(optUnmanaged.floatObj.optional); uncheckedAssertTrue(optUnmanaged.doubleObj.optional); uncheckedAssertTrue(optUnmanaged.stringObj.optional); uncheckedAssertTrue(optUnmanaged.dataObj.optional); uncheckedAssertTrue(optUnmanaged.dateObj.optional); } - (void)testObjectClassName { uncheckedAssertNil(unmanaged.boolObj.objectClassName); uncheckedAssertNil(unmanaged.intObj.objectClassName); uncheckedAssertNil(unmanaged.floatObj.objectClassName); uncheckedAssertNil(unmanaged.doubleObj.objectClassName); uncheckedAssertNil(unmanaged.stringObj.objectClassName); uncheckedAssertNil(unmanaged.dataObj.objectClassName); uncheckedAssertNil(unmanaged.dateObj.objectClassName); uncheckedAssertNil(optUnmanaged.boolObj.objectClassName); uncheckedAssertNil(optUnmanaged.intObj.objectClassName); uncheckedAssertNil(optUnmanaged.floatObj.objectClassName); uncheckedAssertNil(optUnmanaged.doubleObj.objectClassName); uncheckedAssertNil(optUnmanaged.stringObj.objectClassName); uncheckedAssertNil(optUnmanaged.dataObj.objectClassName); uncheckedAssertNil(optUnmanaged.dateObj.objectClassName); } - (void)testRealm { uncheckedAssertNil(unmanaged.boolObj.realm); uncheckedAssertNil(unmanaged.intObj.realm); uncheckedAssertNil(unmanaged.floatObj.realm); uncheckedAssertNil(unmanaged.doubleObj.realm); uncheckedAssertNil(unmanaged.stringObj.realm); uncheckedAssertNil(unmanaged.dataObj.realm); uncheckedAssertNil(unmanaged.dateObj.realm); uncheckedAssertNil(optUnmanaged.boolObj.realm); uncheckedAssertNil(optUnmanaged.intObj.realm); uncheckedAssertNil(optUnmanaged.floatObj.realm); uncheckedAssertNil(optUnmanaged.doubleObj.realm); uncheckedAssertNil(optUnmanaged.stringObj.realm); uncheckedAssertNil(optUnmanaged.dataObj.realm); uncheckedAssertNil(optUnmanaged.dateObj.realm); } - (void)testInvalidated { RLMSet *set; @autoreleasepool { AllPrimitiveSets *obj = [[AllPrimitiveSets alloc] init]; set = obj.intObj; uncheckedAssertFalse(set.invalidated); } uncheckedAssertFalse(set.invalidated); } - (void)testDeleteObjectsInRealm { RLMAssertThrowsWithReason([realm deleteObjects:$allSets], @"Cannot delete objects from RLMSet"); } - (void)testObjectAtIndex { RLMAssertThrowsWithReason([unmanaged.intObj objectAtIndex:0], @"Index 0 is out of bounds (must be less than 0)."); [unmanaged.intObj addObject:@1]; uncheckedAssertEqualObjects([unmanaged.intObj objectAtIndex:0], @1); } - (void)testContainsObject { uncheckedAssertFalse([$set containsObject:$v0]); [$set addObject:$v0]; uncheckedAssertTrue([$set containsObject:$v0]); } - (void)testAddObject { %noany RLMAssertThrowsWithReason([$set addObject:$wrong], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$set addObject:NSNull.null], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [$set addObject:$v0]; uncheckedAssertTrue([$set containsObject:$v0]); %o [$set addObject:NSNull.null]; %o uncheckedAssertTrue([$set containsObject:NSNull.null]); } - (void)testAddObjects { %noany RLMAssertThrowsWithReason([$set addObjects:@[$wrong]], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$set addObjects:@[NSNull.null]], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; uncheckedAssertTrue([$set containsObject:$v0]); uncheckedAssertTrue([$set containsObject:$v1]); %o uncheckedAssertTrue([$set containsObject:$v2]); } - (void)testRemoveObject { [self addObjects]; %r uncheckedAssertEqual($set.count, 2U); %o uncheckedAssertEqual($set.count, 3U); [$allSets removeObject:$allSets.allObjects[0]]; %r uncheckedAssertEqual($set.count, 1U); %o uncheckedAssertEqual($set.count, 2U); } - (void)testIndexOfObjectSorted { %man %r [$set addObjects:@[$v0, $v1, $v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null, $v1, $v0]]; // ordering can't be guaranteed in set, so just verify the indexes are between 0 and 1 %man %r uncheckedAssertTrue([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1] == 0U || ^n [[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1] == 1U); %man %r uncheckedAssertTrue([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0] == 0U || ^n [[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0] == 1U); %man %o uncheckedAssertTrue([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1] == 0U || ^n [[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v1] == 1U); %man %o uncheckedAssertTrue([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0] == 0U || ^n [[$set sortedResultsUsingKeyPath:@"self" ascending:NO] indexOfObject:$v0] == 1U); } - (void)testIndexOfObjectDistinct { %man %r [$set addObjects:@[$v0, $v0, $v1]]; %man %o [$set addObjects:@[$v0, $v0, NSNull.null, $v1, $v0]]; // ordering can't be guaranteed in set, so just verify the indexes are between 0 and 1 %man %r uncheckedAssertTrue([[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0] == 0U || ^n [[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0] == 1U); %man %r uncheckedAssertTrue([[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1] == 0U || ^n [[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1] == 1U); %man %o uncheckedAssertTrue([[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0] == 0U || ^n [[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v0] == 1U); %man %o uncheckedAssertTrue([[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1] == 0U || ^n [[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:$v1] == 1U); %man %o uncheckedAssertTrue([[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 0U || ^n [[$set distinctResultsUsingKeyPaths:@[@"self"]] indexOfObject:NSNull.null] == 1U); } - (void)testSort { %unman RLMAssertThrowsWithReason([$set sortedResultsUsingKeyPath:@"self" ascending:NO], ^n @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$set sortedResultsUsingDescriptors:@[]], ^n @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$set sortedResultsUsingKeyPath:@"not self" ascending:NO], ^n @"can only be sorted on 'self'"); %man %r [$set addObjects:@[$v0, $v1, $v0]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null, $v1, $v0]]; %man %r uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[$v0, $v1]])); %man %o uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingDescriptors:@[]] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[$v0, $v1]])); %man %r uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[$v1, $v0]])); %man %o uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingKeyPath:@"self" ascending:NO] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[$v1, $v0]])); %man %r uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[$v0, $v1]])); %man %o uncheckedAssertEqualObjects([NSSet setWithArray:[[[$set sortedResultsUsingKeyPath:@"self" ascending:YES] valueForKey:@"self"] allObjects]], ^n ([NSSet setWithArray:@[NSNull.null, $v1]])); } - (void)testFilter { %unman RLMAssertThrowsWithReason([$set objectsWhere:@"TRUEPREDICATE"], ^n @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); %unman RLMAssertThrowsWithReason([$set objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"This method may only be called on RLMSet instances retrieved from an RLMRealm"); %man RLMAssertThrowsWithReason([$set objectsWhere:@"TRUEPREDICATE"], ^n @"implemented"); %man RLMAssertThrowsWithReason([$set objectsWithPredicate:[NSPredicate predicateWithValue:YES]], ^n @"implemented"); %man RLMAssertThrowsWithReason([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWhere:@"TRUEPREDICATE"], @"implemented"); %man RLMAssertThrowsWithReason([[$set sortedResultsUsingKeyPath:@"self" ascending:NO] ^n objectsWithPredicate:[NSPredicate predicateWithValue:YES]], @"implemented"); } - (void)testNotifications { %unman RLMAssertThrowsWithReason([$set addNotificationBlock:^(__unused id a, __unused id c, __unused id e) { }], ^n @"Change notifications are only supported on managed collections."); } - (void)testSetSet { %man %r [$set addObjects:@[$v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %man %r [$set2 addObjects:@[$v3, $v4]]; %man %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; [realm commitWriteTransaction]; %unman %r [$set addObjects:@[$v0, $v1]]; %unman %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %unman %r [$set2 addObjects:@[$v3, $v4]]; %unman %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; %unman [$set setSet:$set2]; [realm beginWriteTransaction]; %man [$set setSet:$set2]; [realm commitWriteTransaction]; %unman %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %unman %r %maxtwovalues uncheckedAssertEqualObjects($set.allObjects, (@[$v0, $v1])); %unman %r %nomaxvalues uncheckedAssertEqual($set.count, 2U); %unman %r %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v4]])); %unman %o %maxtwovalues uncheckedAssertEqual($set.count, 3U); %unman %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, $v2]])); %unman %o %nomaxvalues uncheckedAssertEqual($set.count, 3U); %unman %o %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v3, $v4]])); %man %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %man %r %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %man %r %nomaxvalues uncheckedAssertEqual($set.count, 2U); %man %r %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v1, $v4]])); %man %o %maxtwovalues uncheckedAssertEqual($set.count, 3U); %man %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, %v2]])); %man %o %nomaxvalues uncheckedAssertEqual($set.count, 3U); %man %o %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, %v3, %v4]])); } - (void)testUnion { %man %r [$set addObjects:@[$v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %man %r [$set2 addObjects:@[$v3, $v4]]; %man %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; [realm commitWriteTransaction]; %unman %r [$set addObjects:@[$v0, $v1]]; %unman %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %unman %r [$set2 addObjects:@[$v3, $v4]]; %unman %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; %man XCTAssertThrows([$set unionSet:$set2]); %unman [$set unionSet:$set2]; [realm beginWriteTransaction]; %man [$set unionSet:$set2]; [realm commitWriteTransaction]; %unman %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %unman %r %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %unman %r %nomaxvalues uncheckedAssertEqual($set.count, 3U); %unman %r %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, $v4]])); %unman %o %maxtwovalues uncheckedAssertEqual($set.count, 3U); %unman %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, $v2]])); %unman %o %nomaxvalues uncheckedAssertEqual($set.count, 4U); %unman %o %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, $v3, $v4]])); %man %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %man %r %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %man %r %nomaxvalues uncheckedAssertEqual($set.count, 3U); %man %r %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, $v4]])); %man %o %maxtwovalues uncheckedAssertEqual($set.count, 3U); %man %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, %v2]])); %man %o %nomaxvalues uncheckedAssertEqual($set.count, 4U); %man %o %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1, %v3, %v4]])); } - (void)testIntersect { %man %r [$set addObjects:@[$v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %man %r [$set2 addObjects:@[$v3, $v4]]; %man %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; [realm commitWriteTransaction]; %unman %r [$set addObjects:@[$v0, $v1]]; %unman %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %unman %r [$set2 addObjects:@[$v3, $v4]]; %unman %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; %man XCTAssertThrows([$set intersectSet:$set2]); %man uncheckedAssertTrue([$set intersectsSet:$set2]); %unman uncheckedAssertTrue([$set intersectsSet:$set2]); %unman [$set intersectSet:$set2]; [realm beginWriteTransaction]; %man [$set intersectSet:$set2]; [realm commitWriteTransaction]; %unman %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %unman %r %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %unman %r %nomaxvalues uncheckedAssertEqual($set.count, 1U); %unman %r %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v0])); %unman %o %maxtwovalues uncheckedAssertEqual($set.count, 2U); %unman %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %unman %o %nomaxvalues uncheckedAssertEqual($set.count, 1U); %unman %o %nomaxvalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0]])); %man %r %maxtwovalues uncheckedAssertEqual($set.count, 2U); %man %r %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %man %r %nomaxvalues uncheckedAssertEqual($set.count, 1U); %man %r %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v1])); %man %o %maxtwovalues uncheckedAssertEqual($set.count, 2U); %man %o %maxtwovalues uncheckedAssertEqualObjects([NSSet setWithArray:$set.allObjects], ([NSSet setWithArray:@[$v0, $v1]])); %man %o %nomaxvalues uncheckedAssertEqual($set.count, 1U); %man %o %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v0])); } - (void)testMinus { %man %r [$set addObjects:@[$v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %man %r [$set2 addObjects:@[$v3, $v4]]; %man %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; [realm commitWriteTransaction]; %unman %r [$set addObjects:@[$v0, $v1]]; %unman %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %unman %r [$set2 addObjects:@[$v3, $v4]]; %unman %o [$set2 addObjects:@[$v3, $v4, NSNull.null]]; %man XCTAssertThrows([$set minusSet:$set2]); %unman [$set minusSet:$set2]; [realm beginWriteTransaction]; %man [$set minusSet:$set2]; [realm commitWriteTransaction]; %unman %r %maxtwovalues uncheckedAssertEqual($set.count, 0U); %unman %r %maxtwovalues uncheckedAssertEqualObjects($set.allObjects, (@[])); %unman %r %nomaxvalues uncheckedAssertEqual($set.count, 1U); %unman %r %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v1])); %unman %o %maxtwovalues uncheckedAssertEqual($set.count, 0U); %unman %o %maxtwovalues uncheckedAssertEqualObjects($set.allObjects, (@[])); %unman %o %nomaxvalues uncheckedAssertEqual($set.count, 1U); %unman %o %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v1])); %man %r %maxtwovalues uncheckedAssertEqual($set.count, 0U); %man %r %maxtwovalues uncheckedAssertEqualObjects($set.allObjects, (@[])); %man %r %nomaxvalues uncheckedAssertEqual($set.count, 1U); %man %r %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v0])); %man %o %maxtwovalues uncheckedAssertEqual($set.count, 0U); %man %o %maxtwovalues uncheckedAssertEqualObjects($set.allObjects, (@[])); %man %o %nomaxvalues uncheckedAssertEqual($set.count, 1U); %man %o %nomaxvalues uncheckedAssertEqualObjects($set.allObjects, (@[$v1])); } - (void)testIsSubsetOfSet { %man %r [$set addObjects:@[$v0, $v1]]; %man %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %man %r [$set2 addObjects:@[$v0, $v1, $v3, $v4]]; %man %o [$set2 addObjects:@[$v0, $v1, $v3, $v4, NSNull.null]]; [realm commitWriteTransaction]; %unman %r [$set addObjects:@[$v0, $v1]]; %unman %o [$set addObjects:@[$v0, $v1, NSNull.null]]; %unman %r [$set2 addObjects:@[$v0, $v1, $v3, $v4]]; %unman %o [$set2 addObjects:@[$v0, $v1, $v3, $v4, NSNull.null]]; %maxtwovalues %r %man uncheckedAssertTrue([$set2 isSubsetOfSet:$set]); %maxtwovalues %r %unman uncheckedAssertTrue([$set2 isSubsetOfSet:$set]); %maxtwovalues %o %man uncheckedAssertFalse([$set2 isSubsetOfSet:$set]); %maxtwovalues %o %unman uncheckedAssertFalse([$set2 isSubsetOfSet:$set]); %maxtwovalues %r %man uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %maxtwovalues %r %unman uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %maxtwovalues %o %man uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %maxtwovalues %o %unman uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %nomaxvalues %man uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %nomaxvalues %unman uncheckedAssertTrue([$set isSubsetOfSet:$set2]); %nomaxvalues %man uncheckedAssertFalse([$set2 isSubsetOfSet:$set]); %nomaxvalues %unman uncheckedAssertFalse([$set2 isSubsetOfSet:$set]); } - (void)testMin { %noany %nominmax %unman RLMAssertThrowsWithReason([$set minOfProperty:@"self"], ^n @"minOfProperty: is not supported for $type set"); %noany %nominmax %man RLMAssertThrowsWithReason([$set minOfProperty:@"self"], ^n @"Operation 'min' not supported for $type set '$class.$prop'"); %minmax uncheckedAssertNil([$set minOfProperty:@"self"]); [self addObjects]; %minmax %unman %r uncheckedAssertEqualObjects([$set minOfProperty:@"self"], $v0); %minmax %unman %o uncheckedAssertEqualObjects([$set minOfProperty:@"self"], $v1); %minmax %man %r uncheckedAssertEqualObjects([$set minOfProperty:@"self"], $v0); %minmax %man %o uncheckedAssertEqualObjects([$set minOfProperty:@"self"], $v1); } - (void)testMax { %noany %nominmax %unman RLMAssertThrowsWithReason([$set maxOfProperty:@"self"], ^n @"maxOfProperty: is not supported for $type set"); %noany %nominmax %man RLMAssertThrowsWithReason([$set maxOfProperty:@"self"], ^n @"Operation 'max' not supported for $type set '$class.$prop'"); %minmax uncheckedAssertNil([$set maxOfProperty:@"self"]); [self addObjects]; %minmax %unman %r uncheckedAssertEqualObjects([$set maxOfProperty:@"self"], $v1); %minmax %unman %o uncheckedAssertEqualObjects([$set maxOfProperty:@"self"], $v2); %minmax %man %r uncheckedAssertEqualObjects([$set maxOfProperty:@"self"], $v1); %minmax %man %o uncheckedAssertEqualObjects([$set maxOfProperty:@"self"], $v2); } - (void)testSum { %noany %nosum %unman RLMAssertThrowsWithReason([$set sumOfProperty:@"self"], ^n @"sumOfProperty: is not supported for $type set"); %noany %nosum %man RLMAssertThrowsWithReason([$set sumOfProperty:@"self"], ^n @"Operation 'sum' not supported for $type set '$class.$prop'"); %sum uncheckedAssertEqualObjects([$set sumOfProperty:@"self"], @0); [self addObjects]; %sum XCTAssertEqualWithAccuracy([$set sumOfProperty:@"self"].doubleValue, sum($values), .001); } - (void)testAverage { %noany %noavg %unman RLMAssertThrowsWithReason([$set averageOfProperty:@"self"], ^n @"averageOfProperty: is not supported for $type set"); %noany %noavg %man RLMAssertThrowsWithReason([$set averageOfProperty:@"self"], ^n @"Operation 'average' not supported for $type set '$class.$prop'"); %avg uncheckedAssertNil([$set averageOfProperty:@"self"]); [self addObjects]; %avg XCTAssertEqualWithAccuracy([$set averageOfProperty:@"self"].doubleValue, average($values), .001); } - (void)testFastEnumeration { for (int i = 0; i < 10; ++i) { [self addObjects]; } // This is wrapped in a block to work around a compiler bug in Xcode 12.5: // in release builds, reads on `values` will read the wrong local variable, // resulting in a crash when it tries to send a message to some unitialized // stack space. Putting them in separate obj-c blocks prevents this // incorrect optimization. ^{ ^nl NSArray *values = $values; ^nl for (id value in $set) { ^nl uncheckedAssertTrue([[NSSet setWithArray:values] containsObject:value]); ^nl } ^nl }(); ^nl } - (void)testValueForKeySelf { uncheckedAssertEqualObjects([[$allSets valueForKey:@"self"] allObjects], @[]); [self addObjects]; uncheckedAssertEqualObjects([NSSet setWithArray:[[$set valueForKey:@"self"] allObjects]], ([NSSet setWithArray:$values])); } - (void)testValueForKeyNumericAggregates { %minmax uncheckedAssertNil([$set valueForKeyPath:@"@min.self"]); %minmax uncheckedAssertNil([$set valueForKeyPath:@"@max.self"]); %sum uncheckedAssertEqualObjects([$set valueForKeyPath:@"@sum.self"], @0); %avg uncheckedAssertNil([$set valueForKeyPath:@"@avg.self"]); [self addObjects]; %minmax %unman %r uncheckedAssertEqualObjects([$set valueForKeyPath:@"@min.self"], $v0); %minmax %unman %o uncheckedAssertEqualObjects([$set valueForKeyPath:@"@max.self"], $v2); %minmax %man %r uncheckedAssertEqualObjects([$set valueForKeyPath:@"@min.self"], $v0); %minmax %man %o uncheckedAssertEqualObjects([$set valueForKeyPath:@"@max.self"], $v2); %sum XCTAssertEqualWithAccuracy([[$set valueForKeyPath:@"@sum.self"] doubleValue], sum($values), .001); %avg XCTAssertEqualWithAccuracy([[$set valueForKeyPath:@"@avg.self"] doubleValue], average($values), .001); } - (void)testValueForKeyLength { uncheckedAssertEqualObjects([[$allSets valueForKey:@"length"] allObjects], @[]); [self addObjects]; %string uncheckedAssertEqualObjects([$set valueForKey:@"length"], ([[NSSet setWithArray:$values] valueForKey:@"length"])); } - (void)testSetValueForKey { RLMAssertThrowsWithReason([$allSets setValue:@0 forKey:@"not self"], ^n @"this class is not key value coding-compliant for the key not self."); %noany RLMAssertThrowsWithReason([$set setValue:$wrong forKey:@"self"], ^n @"Invalid value '$wdesc' of type '" $wtype "' for expected type '$type'"); %noany %r RLMAssertThrowsWithReason([$set setValue:NSNull.null forKey:@"self"], ^n @"Invalid value '' of type 'NSNull' for expected type '$type'"); [self addObjects]; // setValue overrides all existing values [$set setValue:$v0 forKey:@"self"]; RLMAssertThrowsWithReason($set.allObjects[1], @"index 1 beyond bounds [0 .. 0]"); uncheckedAssertEqualObjects($set.allObjects[0], $v0); %o uncheckedAssertEqualObjects($set.allObjects[0], $v0); %o [$set setValue:NSNull.null forKey:@"self"]; %o uncheckedAssertEqualObjects($set.allObjects[0], NSNull.null); } - (void)testAssignment { $set = (id)@[$v1]; ^nl uncheckedAssertEqualObjects($set.allObjects[0], $v1); // Should replace and not append $set = (id)$values; ^nl uncheckedAssertEqualObjects([NSSet setWithArray:[[$set valueForKey:@"self"] allObjects]], ([NSSet setWithArray:$values])); ^nl // Should not clear the set $set = $set; ^nl uncheckedAssertEqualObjects([NSSet setWithArray:[[$set valueForKey:@"self"] allObjects]], ([NSSet setWithArray:$values])); ^nl [unmanaged.intObj removeAllObjects]; unmanaged.intObj = managed.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); [managed.intObj removeAllObjects]; managed.intObj = unmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed.intObj valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); } - (void)testDynamicAssignment { $obj[@"$prop"] = (id)@[$v1]; ^nl uncheckedAssertEqualObjects(((RLMSet *)$obj[@"$prop"]).allObjects[0], $v1); // Should replace and not append $obj[@"$prop"] = (id)$values; ^nl uncheckedAssertEqualObjects([NSSet setWithArray:[[$obj[@"$prop"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:$values])); ^nl // Should not clear the set $obj[@"$prop"] = $obj[@"$prop"]; ^nl uncheckedAssertEqualObjects([NSSet setWithArray:[[$obj[@"$prop"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:$values])); ^nl [unmanaged[@"intObj"] removeAllObjects]; unmanaged[@"intObj"] = managed.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[unmanaged[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); [managed[@"intObj"] removeAllObjects]; managed[@"intObj"] = unmanaged.intObj; uncheckedAssertEqualObjects([NSSet setWithArray:[[managed[@"intObj"] valueForKey:@"self"] allObjects]], ([NSSet setWithArray:@[@2, @3]])); } - (void)testInvalidAssignment { RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)unmanaged.floatObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged.intObj = (id)optUnmanaged.intObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = unmanaged[@"floatObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(unmanaged[@"intObj"] = optUnmanaged[@"intObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)@[@"a"], @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)(@[@1, @"a"]), @"Invalid value 'a' of type '__NSCFConstantString' for 'int' set property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)managed.floatObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed.intObj = (id)optManaged.intObj, @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)managed[@"floatObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); RLMAssertThrowsWithReason(managed[@"intObj"] = (id)optManaged[@"intObj"], @"RLMSet does not match expected type 'int' for property 'AllPrimitiveSets.intObj'."); } - (void)testAllMethodsCheckThread { RLMSet *set = managed.intObj; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([set count], @"thread"); RLMAssertThrowsWithReason([set addObject:@0], @"thread"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"thread"); RLMAssertThrowsWithReason([set removeAllObjects], @"thread"); RLMAssertThrowsWithReason([set sortedResultsUsingKeyPath:@"self" ascending:YES], @"thread"); RLMAssertThrowsWithReason([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"thread"); RLMAssertThrowsWithReason(set.allObjects[0], @"thread"); RLMAssertThrowsWithReason([set valueForKey:@"self"], @"thread"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"thread"); RLMAssertThrowsWithReason(({for (__unused id obj in set);}), @"thread"); }]; } - (void)testAllMethodsCheckForInvalidation { RLMSet *set = managed.intObj; [realm cancelWriteTransaction]; [realm invalidate]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); RLMAssertThrowsWithReason([set count], @"invalidated"); RLMAssertThrowsWithReason([set addObject:@0], @"invalidated"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"invalidated"); RLMAssertThrowsWithReason([set removeAllObjects], @"invalidated"); RLMAssertThrowsWithReason([set sortedResultsUsingKeyPath:@"self" ascending:YES], @"invalidated"); RLMAssertThrowsWithReason([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReason(set.allObjects[0], @"invalidated"); RLMAssertThrowsWithReason([set valueForKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"invalidated"); RLMAssertThrowsWithReason(({for (__unused id obj in set);}), @"invalidated"); [realm beginWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMSet *set = managed.intObj; [set addObject:@0]; [realm commitWriteTransaction]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); XCTAssertNoThrow([set count]); XCTAssertNoThrow([set sortedResultsUsingKeyPath:@"self" ascending:YES]); XCTAssertNoThrow([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"self" ascending:YES]]]); XCTAssertNoThrow(set.allObjects[0]); XCTAssertNoThrow([set valueForKey:@"self"]); XCTAssertNoThrow(({for (__unused id obj in set);})); RLMAssertThrowsWithReason([set addObject:@0], @"write transaction"); RLMAssertThrowsWithReason([set addObjects:@[@0]], @"write transaction"); RLMAssertThrowsWithReason([set removeAllObjects], @"write transaction"); RLMAssertThrowsWithReason([set setValue:@1 forKey:@"self"], @"write transaction"); } - (void)testDeleteOwningObject { RLMSet *set = managed.intObj; uncheckedAssertFalse(set.isInvalidated); [realm deleteObject:managed]; uncheckedAssertTrue(set.isInvalidated); } #pragma mark - Queries #define RLMAssertCount(cls, expectedCount, ...) \ uncheckedAssertEqual(expectedCount, ([cls objectsInRealm:realm where:__VA_ARGS__].count)) - (void)createObjectWithValueIndex:(NSUInteger)index { NSRange range = {index, 1}; id obj = [AllPrimitiveSets createInRealm:realm withValue:@{ %r %man @"$prop": [$values subarrayWithRange:range], }]; [LinkToAllPrimitiveSets createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %o %man @"$prop": [$values subarrayWithRange:range], }]; [LinkToAllOptionalPrimitiveSets createInRealm:realm withValue:@[obj]]; } - (void)testQueryBasicOperators { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop <= %@", $v0); [self createObjectWithValueIndex:0]; %man RLMAssertCount($class, 0, @"ANY $prop = %@", $v1); %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 0, @"ANY $prop != %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v1); %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %man %comp RLMAssertCount($class, 1, @"ANY $prop >= %@", $v0); %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %r %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v1); %o %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v1); %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); [self createObjectWithValueIndex:1]; %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop = %@", $v1); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v0); %man RLMAssertCount($class, 1, @"ANY $prop != %@", $v1); %r %man %comp RLMAssertCount($class, 1, @"ANY $prop > %@", $v0); %r %man %comp RLMAssertCount($class, 2, @"ANY $prop >= %@", $v0); %r %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v0); %r %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v1); %r %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); %r %man %comp RLMAssertCount($class, 2, @"ANY $prop <= %@", $v1); %o %man %comp RLMAssertCount($class, 0, @"ANY $prop > %@", $v0); %o %man %comp RLMAssertCount($class, 1, @"ANY $prop >= %@", $v1); %o %man %comp RLMAssertCount($class, 0, @"ANY $prop < %@", $v1); %o %man %comp RLMAssertCount($class, 1, @"ANY $prop < %@", $v2); %o %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v0); %o %man %comp RLMAssertCount($class, 1, @"ANY $prop <= %@", $v1); %noany %man %nocomp RLMAssertThrows(([$class objectsInRealm:realm where:@"ANY $prop > %@", $v0])); } - (void)testQueryBetween { [realm deleteAllObjects]; %noany %man %nominmax RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"ANY $prop BETWEEN %@", @[$v0, $v1]]), ^n @"Operator 'BETWEEN' not supported for type '$basetype'"); %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v0, $v1]); [self createObjectWithValueIndex:1]; %r %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v1, $v1]); %r %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v0, $v1]); %r %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v0, $v0]); %o %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v1, $v1]); %o %man %minmax RLMAssertCount($class, 1, @"ANY $prop BETWEEN %@", @[$v1, $v2]); %o %man %minmax RLMAssertCount($class, 0, @"ANY $prop BETWEEN %@", @[$v3, $v3]); } - (void)testQueryIn { [realm deleteAllObjects]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v0, $v1]); [self createObjectWithValueIndex:0]; %man RLMAssertCount($class, 0, @"ANY $prop IN %@", @[$v1]); %man RLMAssertCount($class, 1, @"ANY $prop IN %@", @[$v0, $v1]); } - (void)testQueryCount { [realm deleteAllObjects]; [AllPrimitiveSets createInRealm:realm withValue:@{ %r %man @"$prop": @[$v0, $v1], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %o %man @"$prop": @[$v0, $v1], }]; %man RLMAssertCount($class, 1U, @"$prop.@count == %@", @(2)); %man RLMAssertCount($class, 0U, @"$prop.@count != %@", @(2)); %man RLMAssertCount($class, 0, @"$prop.@count > %@", @(2)); %man RLMAssertCount($class, 1, @"$prop.@count >= %@", @(2)); %man RLMAssertCount($class, 0, @"$prop.@count < %@", @(2)); %man RLMAssertCount($class, 1, @"$prop.@count <= %@", @(2)); } - (void)testQuerySum { [realm deleteAllObjects]; %noany %nodate %nosum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Invalid keypath '$prop.@sum': @sum can only be applied to a collection of numeric values."); %r %noany %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", $wrong]), ^n @"@sum on a property of type $basetype cannot be compared with '$wdesc'"); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@sum.prop': @sum on a collection of values must appear at the end of a keypath."); %noany %sum %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@sum = %@", NSNull.null]), ^n @"@sum on a property of type $basetype cannot be compared with ''"); [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v0], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v1], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v0, $v0], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v1, $v2], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %sum @"$prop": @[$v1, $v1, $v1], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %sum @"$prop": @[$v1, $v1, $v1], }]; %r %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", @0); %r %sum %man RLMAssertCount($class, 2U, @"$prop.@sum == %@", $v0); %r %sum %man RLMAssertCount($class, 3U, @"$prop.@sum != %@", $v1); %r %sum %man RLMAssertCount($class, 3U, @"$prop.@sum >= %@", $v0); %r %sum %man RLMAssertCount($class, 1U, @"$prop.@sum > %@", $v0); %r %sum %man RLMAssertCount($class, 3U, @"$prop.@sum < %@", $v1); %r %sum %man RLMAssertCount($class, 4U, @"$prop.@sum <= %@", $v1); %o %sum %man RLMAssertCount($class, 1U, @"$prop.@sum == %@", @0); %o %sum %man RLMAssertCount($class, 2U, @"$prop.@sum == %@", $v1); %o %sum %man RLMAssertCount($class, 2U, @"$prop.@sum != %@", $v1); %o %sum %man RLMAssertCount($class, 3U, @"$prop.@sum >= %@", $v1); %o %sum %man RLMAssertCount($class, 1U, @"$prop.@sum > %@", $v1); %o %sum %man RLMAssertCount($class, 3U, @"$prop.@sum < %@", $v2); %o %sum %man RLMAssertCount($class, 3U, @"$prop.@sum <= %@", $v1); } - (void)testQueryAverage { [realm deleteAllObjects]; %noany %nodate %noavg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Invalid keypath '$prop.@avg': @avg can only be applied to a collection of numeric values."); %noany %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %any %date %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $v0]), ^n @"Cannot sum or average date properties"); %noany %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg = %@", $wrong]), ^n @"@avg on a property of type $basetype cannot be compared with '$wdesc'"); %noany %avg %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@avg.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@avg.prop': @avg on a collection of values must appear at the end of a keypath."); [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v1], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v2], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v0, $v1], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v1, $v2], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %man %r %avg @"$prop": @[$v1], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %man %o %avg @"$prop": @[$v2], }]; %avg %man RLMAssertCount($class, 1U, @"$prop.@avg == %@", NSNull.null); %r %avg %man RLMAssertCount($class, 2U, @"$prop.@avg == %@", $v1); %o %avg %man RLMAssertCount($class, 2U, @"$prop.@avg == %@", $v2); %r %avg %man RLMAssertCount($class, 2U, @"$prop.@avg != %@", $v1); %o %avg %man RLMAssertCount($class, 2U, @"$prop.@avg != %@", $v2); %r %avg %man RLMAssertCount($class, 2U, @"$prop.@avg >= %@", $v1); %o %avg %man RLMAssertCount($class, 2U, @"$prop.@avg >= %@", $v2); %r %avg %man RLMAssertCount($class, 3U, @"$prop.@avg > %@", $v0); %o %avg %man RLMAssertCount($class, 3U, @"$prop.@avg > %@", $v1); %r %avg %man RLMAssertCount($class, 1U, @"$prop.@avg < %@", $v1); %o %avg %man RLMAssertCount($class, 1U, @"$prop.@avg < %@", $v2); %r %avg %man RLMAssertCount($class, 3U, @"$prop.@avg <= %@", $v1); %o %avg %man RLMAssertCount($class, 3U, @"$prop.@avg <= %@", $v2); } - (void)testQueryMin { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $v0]), ^n @"Invalid keypath '$prop.@min': @min can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min = %@", $wrong]), ^n @"@min on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@min.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@min.prop': @min on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); [AllPrimitiveSets createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v2); [self createObjectWithValueIndex:1]; %r %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v1); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v0); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@min == %@", $v2); [AllPrimitiveSets createInRealm:realm withValue:@{ %minmax %r %man @"$prop": @[$v1, $v0], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %minmax %o %man @"$prop": @[$v2, $v1], }]; %r %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v0); %o %minmax %man RLMAssertCount($class, 2U, @"$prop.@min == %@", $v1); %r %minmax %man RLMAssertCount($class, 1U, @"$prop.@min == %@", $v0); %o %minmax %man RLMAssertCount($class, 2U, @"$prop.@min == %@", $v1); } - (void)testQueryMax { [realm deleteAllObjects]; %noany %nominmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $v0]), ^n @"Invalid keypath '$prop.@max': @max can only be applied to a collection of numeric values."); %noany %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max = %@", $wrong]), ^n @"@max on a property of type $basetype cannot be compared with '$wdesc'"); %minmax %man RLMAssertThrowsWithReason(([$class objectsInRealm:realm where:@"$prop.@max.prop = %@", $wrong]), ^n @"Invalid keypath '$prop.@max.prop': @max on a collection of values must appear at the end of a keypath."); // No objects, so count is zero %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); [AllPrimitiveSets createInRealm:realm withValue:@{}]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{}]; // Only empty arrays, so count is zero %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v2); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == nil"); %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", NSNull.null); [self createObjectWithValueIndex:1]; %r %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v1); %r %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v0); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 0U, @"$prop.@max == %@", $v2); [AllPrimitiveSets createInRealm:realm withValue:@{ %minmax %r %man @"$prop": @[$v0], }]; [AllPrimitiveSets createInRealm:realm withValue:@{ %minmax %r %man @"$prop": @[$v1, $v0], }]; [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ %minmax %o %man @"$prop": @[$v1, $v0], }]; %noany %r %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %noany %r %minmax %man RLMAssertCount($class, 2U, @"$prop.@max == %@", $v1); %o %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %o %minmax %man RLMAssertCount($class, 2U, @"$prop.@max == %@", $v1); %any %minmax %man RLMAssertCount($class, 1U, @"$prop.@max == %@", $v0); %any %minmax %man RLMAssertCount($class, 2U, @"$prop.@max == %@", $v1); } - (void)testQueryBasicOperatorsOverLink { [realm deleteAllObjects]; %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop = %@", $v0); %man RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop != %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop >= %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop <= %@", $v0); [self createObjectWithValueIndex:1]; %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop = %@", $v1); %man RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop != %@", $v0); %r %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop > %@", $v0); %o %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop > %@", $v1); %r %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop >= %@", $v0); %o %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop >= %@", $v1); %man %comp RLMAssertCount(LinkTo$class, 0, @"ANY link.$prop < %@", $v0); %r %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop < %@", $v4); %o %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop < %@", $v2); %man %comp RLMAssertCount(LinkTo$class, 1, @"ANY link.$prop <= %@", $v1); %man %nocomp %noany RLMAssertThrowsWithReason(([LinkTo$class objectsInRealm:realm where:@"ANY link.$prop > %@", $v0]), ^n @"Operator '>' not supported for type '$basetype'"); } - (void)testSubstringQueries { NSArray *values = @[ @"", @"á", @"ó", @"ú", @"áá", @"áó", @"áú", @"óá", @"óó", @"óú", @"úá", @"úó", @"úú", @"ááá", @"ááó", @"ááú", @"áóá", @"áóó", @"áóú", @"áúá", @"áúó", @"áúú", @"óáá", @"óáó", @"óáú", @"óóá", @"óóó", @"óóú", @"óúá", @"óúó", @"óúú", @"úáá", @"úáó", @"úáú", @"úóá", @"úóó", @"úóú", @"úúá", @"úúó", @"úúú", ]; void (^create)(NSString *) = ^(NSString *value) { id obj = [AllPrimitiveSets createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllPrimitiveSets createInRealm:realm withValue:@[obj]]; obj = [AllOptionalPrimitiveSets createInRealm:realm withValue:@{ @"stringObj": @[value], @"dataObj": @[[value dataUsingEncoding:NSUTF8StringEncoding]] }]; [LinkToAllOptionalPrimitiveSets createInRealm:realm withValue:@[obj]]; }; for (NSString *value in values) { create(value); create(value.uppercaseString); create([value stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); create([value.uppercaseString stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO]); } void (^test)(NSString *, id, NSUInteger) = ^(NSString *operator, NSString *value, NSUInteger count) { NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, value); query = [NSString stringWithFormat:@"ANY link.stringObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, value); query = [NSString stringWithFormat:@"ANY dataObj %@ %%@", operator]; %man %string RLMAssertCount($class, count, query, data); query = [NSString stringWithFormat:@"ANY link.dataObj %@ %%@", operator]; %man %string RLMAssertCount(LinkTo$class, count, query, data); }; void (^testNull)(NSString *, NSUInteger) = ^(NSString *operator, NSUInteger count) { NSString *query = [NSString stringWithFormat:@"ANY stringObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(AllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.stringObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'stringObj' of type 'string'"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY dataObj %@ nil", operator]; RLMAssertThrowsWithReason([AllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(AllOptionalPrimitiveSets, count, query, NSNull.null); query = [NSString stringWithFormat:@"ANY link.dataObj %@ nil", operator]; RLMAssertThrowsWithReason([LinkToAllPrimitiveSets objectsInRealm:realm where:query], @"Cannot compare value '(null)' of type '(null)' to property 'dataObj' of type 'data'"); RLMAssertCount(LinkToAllOptionalPrimitiveSets, count, query, NSNull.null); }; testNull(@"==", 0); test(@"==", @"", 4); test(@"==", @"a", 1); test(@"==", @"á", 1); test(@"==[c]", @"a", 2); test(@"==[c]", @"á", 2); test(@"==", @"A", 1); test(@"==", @"Á", 1); test(@"==[c]", @"A", 2); test(@"==[c]", @"Á", 2); test(@"==[d]", @"a", 2); test(@"==[d]", @"á", 2); test(@"==[cd]", @"a", 4); test(@"==[cd]", @"á", 4); test(@"==[d]", @"A", 2); test(@"==[d]", @"Á", 2); test(@"==[cd]", @"A", 4); test(@"==[cd]", @"Á", 4); testNull(@"!=", 160); test(@"!=", @"", 156); test(@"!=", @"a", 159); test(@"!=", @"á", 159); test(@"!=[c]", @"a", 158); test(@"!=[c]", @"á", 158); test(@"!=", @"A", 159); test(@"!=", @"Á", 159); test(@"!=[c]", @"A", 158); test(@"!=[c]", @"Á", 158); test(@"!=[d]", @"a", 158); test(@"!=[d]", @"á", 158); test(@"!=[cd]", @"a", 156); test(@"!=[cd]", @"á", 156); test(@"!=[d]", @"A", 158); test(@"!=[d]", @"Á", 158); test(@"!=[cd]", @"A", 156); test(@"!=[cd]", @"Á", 156); testNull(@"CONTAINS", 0); testNull(@"CONTAINS[c]", 0); testNull(@"CONTAINS[d]", 0); testNull(@"CONTAINS[cd]", 0); test(@"CONTAINS", @"a", 25); test(@"CONTAINS", @"á", 25); test(@"CONTAINS[c]", @"a", 50); test(@"CONTAINS[c]", @"á", 50); test(@"CONTAINS", @"A", 25); test(@"CONTAINS", @"Á", 25); test(@"CONTAINS[c]", @"A", 50); test(@"CONTAINS[c]", @"Á", 50); test(@"CONTAINS[d]", @"a", 50); test(@"CONTAINS[d]", @"á", 50); test(@"CONTAINS[cd]", @"a", 100); test(@"CONTAINS[cd]", @"á", 100); test(@"CONTAINS[d]", @"A", 50); test(@"CONTAINS[d]", @"Á", 50); test(@"CONTAINS[cd]", @"A", 100); test(@"CONTAINS[cd]", @"Á", 100); test(@"BEGINSWITH", @"a", 13); test(@"BEGINSWITH", @"á", 13); test(@"BEGINSWITH[c]", @"a", 26); test(@"BEGINSWITH[c]", @"á", 26); test(@"BEGINSWITH", @"A", 13); test(@"BEGINSWITH", @"Á", 13); test(@"BEGINSWITH[c]", @"A", 26); test(@"BEGINSWITH[c]", @"Á", 26); test(@"BEGINSWITH[d]", @"a", 26); test(@"BEGINSWITH[d]", @"á", 26); test(@"BEGINSWITH[cd]", @"a", 52); test(@"BEGINSWITH[cd]", @"á", 52); test(@"BEGINSWITH[d]", @"A", 26); test(@"BEGINSWITH[d]", @"Á", 26); test(@"BEGINSWITH[cd]", @"A", 52); test(@"BEGINSWITH[cd]", @"Á", 52); test(@"ENDSWITH", @"a", 13); test(@"ENDSWITH", @"á", 13); test(@"ENDSWITH[c]", @"a", 26); test(@"ENDSWITH[c]", @"á", 26); test(@"ENDSWITH", @"A", 13); test(@"ENDSWITH", @"Á", 13); test(@"ENDSWITH[c]", @"A", 26); test(@"ENDSWITH[c]", @"Á", 26); test(@"ENDSWITH[d]", @"a", 26); test(@"ENDSWITH[d]", @"á", 26); test(@"ENDSWITH[cd]", @"a", 52); test(@"ENDSWITH[cd]", @"á", 52); test(@"ENDSWITH[d]", @"A", 26); test(@"ENDSWITH[d]", @"Á", 26); test(@"ENDSWITH[cd]", @"A", 52); test(@"ENDSWITH[cd]", @"Á", 52); } #pragma clang diagnostic ignored "-Warc-retain-cycles" - (void)testNotificationSentInitially { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(change); uncheckedAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); } else { uncheckedAssertEqualObjects(change.insertions, @[@0]); } first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMSet *set = [(AllPrimitiveSets *)[AllPrimitiveSets allObjectsInRealm:r].firstObject intObj]; [set addObject:@0]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(__unused RLMSet *set, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ [AllPrimitiveSets createInRealm:r withValue:@[]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *r = [RLMRealm defaultRealm]; [r transactionWithBlock:^{ RLMSet *set = [(AllPrimitiveSets *)[AllPrimitiveSets allObjectsInRealm:r].firstObject intObj]; [set addObject:@0]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { [managed.intObj addObjects:@[@10, @20]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [managed.intObj addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); uncheckedAssertNil(error); if (first) { uncheckedAssertNil(change); first = false; } else { uncheckedAssertEqualObjects(change.deletions, (@[@0, @1])); } [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:managed]; [realm commitWriteTransaction]; expectation = [self expectationWithDescription:@""]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } @end ================================================ FILE: Realm/Tests/PropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #import "RLMProperty_Private.h" #import "RLMRealm_Dynamic.h" #import @interface PropertyTests : RLMTestCase @end @implementation PropertyTests - (void)testDescription { AllTypesObject *object = [[AllTypesObject alloc] init]; RLMProperty *property = object.objectSchema[@"objectCol"]; XCTAssertEqualObjects(property.description, @"objectCol {\n" @"\ttype = object;\n" @"\tobjectClassName = StringObject;\n" @"\tlinkOriginPropertyName = (null);\n" @"\tcolumnName = objectCol;\n" @"\tindexed = NO;\n" @"\tisPrimary = NO;\n" @"\tarray = NO;\n" @"\tset = NO;\n" @"\tdictionary = NO;\n" @"\toptional = YES;\n" @"}"); } static RLMProperty *makeProperty(NSString *name, RLMPropertyType type, NSString *objectClassName, BOOL optional) { return [[RLMProperty alloc] initWithName:name type:type objectClassName:objectClassName linkOriginPropertyName:nil indexed:NO optional:optional]; } - (void)testEqualityFromObjectSchema { { // Test non-optional property types RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[AllTypesObject class]]; NSDictionary *expectedProperties = @{ @"boolCol": makeProperty(@"boolCol", RLMPropertyTypeBool, nil, NO), @"intCol": makeProperty(@"intCol", RLMPropertyTypeInt, nil, NO), @"floatCol": makeProperty(@"floatCol", RLMPropertyTypeFloat, nil, NO), @"doubleCol": makeProperty(@"doubleCol", RLMPropertyTypeDouble, nil, NO), @"stringCol": makeProperty(@"stringCol", RLMPropertyTypeString, nil, NO), @"binaryCol": makeProperty(@"binaryCol", RLMPropertyTypeData, nil, NO), @"dateCol": makeProperty(@"dateCol", RLMPropertyTypeDate, nil, NO), @"cBoolCol": makeProperty(@"cBoolCol", RLMPropertyTypeBool, nil, NO), @"longCol": makeProperty(@"longCol", RLMPropertyTypeInt, nil, NO), @"objectIdCol": makeProperty(@"objectIdCol", RLMPropertyTypeObjectId, nil, NO), @"decimalCol": makeProperty(@"decimalCol", RLMPropertyTypeDecimal128, nil, NO), @"objectCol": makeProperty(@"objectCol", RLMPropertyTypeObject, @"StringObject", YES), @"uuidCol": makeProperty(@"uuidCol", RLMPropertyTypeUUID, nil, NO), @"anyCol": makeProperty(@"anyCol", RLMPropertyTypeAny, nil, NO), @"mixedObjectCol": makeProperty(@"mixedObjectCol", RLMPropertyTypeObject, @"MixedObject", YES), }; XCTAssertEqual(objectSchema.properties.count, expectedProperties.allKeys.count); for (NSString *propertyName in expectedProperties) { XCTAssertEqualObjects(objectSchema[propertyName], expectedProperties[propertyName]); } } { // Test optional property types RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[AllOptionalTypes class]]; NSDictionary *expectedProperties = @{ @"intObj": makeProperty(@"intObj", RLMPropertyTypeInt, nil, YES), @"floatObj": makeProperty(@"floatObj", RLMPropertyTypeFloat, nil, YES), @"doubleObj": makeProperty(@"doubleObj", RLMPropertyTypeDouble, nil, YES), @"boolObj": makeProperty(@"boolObj", RLMPropertyTypeBool, nil, YES), @"string": makeProperty(@"string", RLMPropertyTypeString, nil, YES), @"data": makeProperty(@"data", RLMPropertyTypeData, nil, YES), @"date": makeProperty(@"date", RLMPropertyTypeDate, nil, YES), @"objectId": makeProperty(@"objectId", RLMPropertyTypeObjectId, nil, YES), @"decimal": makeProperty(@"decimal", RLMPropertyTypeDecimal128, nil, YES), @"uuidCol": makeProperty(@"uuidCol", RLMPropertyTypeUUID, nil, YES), }; XCTAssertEqual(objectSchema.properties.count, expectedProperties.allKeys.count); for (NSString *propertyName in expectedProperties) { XCTAssertEqualObjects(objectSchema[propertyName], expectedProperties[propertyName]); } } { // Test indexed property RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[IndexedStringObject class]]; RLMProperty *stringProperty = objectSchema[@"stringCol"]; RLMProperty *expectedProperty = [[RLMProperty alloc] initWithName:@"stringCol" type:RLMPropertyTypeString objectClassName:nil linkOriginPropertyName:nil indexed:YES optional:YES]; XCTAssertEqualObjects(stringProperty, expectedProperty); } { // Test primary key property RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:[PrimaryStringObject class]]; RLMProperty *stringProperty = objectSchema[@"stringCol"]; RLMProperty *expectedProperty = [[RLMProperty alloc] initWithName:@"stringCol" type:RLMPropertyTypeString objectClassName:nil linkOriginPropertyName:nil indexed:YES optional:NO]; expectedProperty.isPrimary = YES; XCTAssertEqualObjects(stringProperty, expectedProperty); } } - (void)testTwoPropertiesAreEqual { const char *name = "intCol"; objc_property_t objcProperty1 = class_getProperty(AllTypesObject.class, name); RLMProperty *property1 = [[RLMProperty alloc] initWithName:@(name) indexed:YES linkPropertyDescriptor:nil property:objcProperty1]; objc_property_t objcProperty2 = class_getProperty(IntObject.class, name); RLMProperty *property2 = [[RLMProperty alloc] initWithName:@(name) indexed:YES linkPropertyDescriptor:nil property:objcProperty2]; XCTAssertEqualObjects(property1, property2); } - (void)testTwoPropertiesAreUnequal { const char *name = "stringCol"; objc_property_t objcProperty1 = class_getProperty(AllTypesObject.class, name); RLMProperty *property1 = [[RLMProperty alloc] initWithName:@(name) indexed:YES linkPropertyDescriptor:nil property:objcProperty1]; name = "intCol"; objc_property_t objcProperty2 = class_getProperty(IntObject.class, name); RLMProperty *property2 = [[RLMProperty alloc] initWithName:@(name) indexed:YES linkPropertyDescriptor:nil property:objcProperty2]; XCTAssertNotEqualObjects(property1, property2); } - (void)testSwiftPropertyNameValidation { RLMAssertThrows(RLMValidateSwiftPropertyName(@"alloc")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"_alloc")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"allocOject")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"_allocOject")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"alloc_object")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"_alloc_object")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"new")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"copy")); RLMAssertThrows(RLMValidateSwiftPropertyName(@"mutableCopy")); // Swift doesn't infer family from `init` XCTAssertNoThrow(RLMValidateSwiftPropertyName(@"init")); XCTAssertNoThrow(RLMValidateSwiftPropertyName(@"_init")); XCTAssertNoThrow(RLMValidateSwiftPropertyName(@"initWithValue")); // Lowercase letter after family name XCTAssertNoThrow(RLMValidateSwiftPropertyName(@"allocate")); XCTAssertNoThrow(RLMValidateSwiftPropertyName(@"__alloc")); } - (void)testTypeToString { XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeString), @"string"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeInt), @"int"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeBool), @"bool"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeDate), @"date"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeData), @"data"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeDouble), @"double"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeFloat), @"float"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeObject), @"object"); XCTAssertEqualObjects(RLMTypeToString(RLMPropertyTypeLinkingObjects), @"linking objects"); XCTAssertEqualObjects(RLMTypeToString((RLMPropertyType)-1), @"Unknown"); } @end ================================================ FILE: Realm/Tests/QueryTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #import "RLMRealmConfiguration_Private.h" #import "RLMRealm_Dynamic.h" #import "RLMSchema_Private.h" #pragma mark - Test Objects @class LinkChain2, LinkChain3; @interface LinkChain1 : RLMObject @property int value; @property LinkChain2 *next; @end @interface LinkChain2 : RLMObject @property LinkChain3 *next; @property (readonly) RLMLinkingObjects *prev; @end @interface LinkChain3 : RLMObject @property (readonly) RLMLinkingObjects *prev; @end @implementation LinkChain1 @end @implementation LinkChain2 + (NSDictionary *)linkingObjectsProperties { return @{@"prev": [RLMPropertyDescriptor descriptorWithClass:LinkChain1.class propertyName:@"next"]}; } @end @implementation LinkChain3 + (NSDictionary *)linkingObjectsProperties { return @{@"prev": [RLMPropertyDescriptor descriptorWithClass:LinkChain2.class propertyName:@"next"]}; } @end #pragma mark NonRealmEmployeeObject @interface NonRealmEmployeeObject : NSObject @property NSString *name; @property NSInteger age; @end @implementation NonRealmEmployeeObject @end @interface PersonLinkObject : RLMObject @property PersonObject *person; @end @implementation PersonLinkObject @end #pragma mark QueryObject @interface QueryObject : RLMObject @property (nonatomic, assign) BOOL bool1; @property (nonatomic, assign) BOOL bool2; @property (nonatomic, assign) NSInteger int1; @property (nonatomic, assign) NSInteger int2; @property (nonatomic, assign) float float1; @property (nonatomic, assign) float float2; @property (nonatomic, assign) double double1; @property (nonatomic, assign) double double2; @property (nonatomic, copy) NSString *string1; @property (nonatomic, copy) NSString *string2; @property (nonatomic, copy) NSData *data1; @property (nonatomic, copy) NSData *data2; @property (nonatomic, copy) RLMDecimal128 *decimal1; @property (nonatomic, copy) RLMDecimal128 *decimal2; @property (nonatomic, copy) RLMObjectId *objectId1; @property (nonatomic, copy) RLMObjectId *objectId2; @property (nonatomic, copy) id any1; @property (nonatomic, copy) id any2; @property (nonatomic, copy) QueryObject *object1; @property (nonatomic, copy) QueryObject *object2; @end @implementation QueryObject + (NSArray *)requiredProperties { return @[@"string1", @"string2", @"data1", @"data2", @"objectId1", @"objectId2", @"decimal1", @"decimal2"]; } @end @interface NullQueryObject : RLMObject @property (nonatomic, copy) NSNumber *bool1; @property (nonatomic, copy) NSNumber *bool2; @property (nonatomic, copy) NSNumber *int1; @property (nonatomic, copy) NSNumber *int2; @property (nonatomic, copy) NSNumber *float1; @property (nonatomic, copy) NSNumber *float2; @property (nonatomic, copy) NSNumber *double1; @property (nonatomic, copy) NSNumber *double2; @property (nonatomic, copy) NSString *string1; @property (nonatomic, copy) NSString *string2; @property (nonatomic, copy) NSData *data1; @property (nonatomic, copy) NSData *data2; @property (nonatomic, copy) RLMDecimal128 *decimal1; @property (nonatomic, copy) RLMDecimal128 *decimal2; @property (nonatomic, copy) RLMObjectId *objectId1; @property (nonatomic, copy) RLMObjectId *objectId2; @property (nonatomic, copy) id any1; @property (nonatomic, copy) id any2; @property (nonatomic, copy) NullQueryObject *object1; @property (nonatomic, copy) NullQueryObject *object2; @end @implementation NullQueryObject @end @interface DictionaryParentObject : RLMObject @property (nonatomic, copy) AllDictionariesObject *objectCol; @end @implementation DictionaryParentObject @end #pragma mark - Tests #define RLMAssertCount(cls, expectedCount, ...) \ XCTAssertEqual(expectedCount, ([self evaluate:[cls objectsWhere:__VA_ARGS__]].count)) @interface QueryConstructionTests : RLMTestCase @end @implementation QueryConstructionTests - (RLMResults *)evaluate:(RLMResults *)results { return results; } - (void)testQueryingNilRealmThrows { XCTAssertThrows([PersonObject allObjectsInRealm:self.nonLiteralNil]); } - (void)testDynamicQueryInvalidClass { RLMRealm *realm = [RLMRealm defaultRealm]; // class not derived from RLMObject XCTAssertThrows([realm objects:@"NonRealmPersonObject" where:@"age > 25"], @"invalid object type"); XCTAssertThrows([[realm objects:@"NonRealmPersonObject" where:@"age > 25"] sortedResultsUsingKeyPath:@"age" ascending:YES], @"invalid object type"); // empty string for class name XCTAssertThrows([realm objects:@"" where:@"age > 25"], @"missing class name"); XCTAssertThrows([[realm objects:@"" where:@"age > 25"] sortedResultsUsingKeyPath:@"age" ascending:YES], @"missing class name"); // nil class name XCTAssertThrows([realm objects:self.nonLiteralNil where:@"age > 25"], @"nil class name"); XCTAssertThrows([[realm objects:self.nonLiteralNil where:@"age > 25"] sortedResultsUsingKeyPath:@"age" ascending:YES], @"nil class name"); } - (void)testPredicateValidUse { RLMRealm *realm = [RLMRealm defaultRealm]; // boolean false XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == no"], @"== no"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == No"], @"== No"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == NO"], @"== NO"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == false"], @"== false"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == False"], @"== False"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == FALSE"], @"== FALSE"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 0"], @"== 0"); // boolean true XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == yes"], @"== yes"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == Yes"], @"== Yes"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == YES"], @"== YES"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == true"], @"== true"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == True"], @"== True"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == TRUE"], @"== TRUE"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 1"], @"== 1"); // inequality XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol != YES"], @"!= YES"); XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol <> YES"], @"<> YES"); } - (void)testPredicateNotSupported { // These are things which are valid predicates, but which we do not support // Aggregate operators on non-arrays RLMAssertThrowsWithReasonMatching([PersonObject objectsWhere:@"ANY age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReasonMatching([PersonObject objectsWhere:@"ALL age > 5"], @"ALL modifier not supported"); RLMAssertThrowsWithReasonMatching([PersonObject objectsWhere:@"SOME age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReasonMatching([PersonObject objectsWhere:@"NONE age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReasonMatching([PersonLinkObject objectsWhere:@"ANY person.age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReasonMatching([PersonLinkObject objectsWhere:@"ALL person.age > 5"], @"ALL modifier not supported"); RLMAssertThrowsWithReasonMatching([PersonLinkObject objectsWhere:@"SOME person.age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReasonMatching([PersonLinkObject objectsWhere:@"NONE person.age > 5"], @"Aggregate operations can only.*collection property"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age.@count > 5"], @"Invalid keypath 'age.@count': Property 'PersonObject.age' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age.@sum > 5"], @"Invalid keypath 'age.@sum': Property 'PersonObject.age' is not a link or collection and can only appear at the end of a keypath."); // comparing two constants RLMAssertThrowsWithReason([PersonObject objectsWhere:@"5 = 5"], @"Predicate expressions must compare a keypath and another keypath or a constant value"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"nil = nil"], @"Predicate expressions must compare a keypath and another keypath or a constant value"); // substring operations with constant on LHS RLMAssertThrowsWithReason(([AllOptionalTypes objectsWhere:@"%@ CONTAINS data", [NSData data]]), @"Operator 'CONTAINS' requires a keypath on the left side"); // LinkList equality is unsupport since the semantics are unclear XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"ANY array = array"])); // Unsupported variants of subqueries. RLMAssertThrowsWithReasonMatching(([ArrayOfAllTypesObject objectsWhere:@"SUBQUERY(array, $obj, $obj.intCol = 5).@count == array.@count"]), @"SUBQUERY.*compared with a constant number"); RLMAssertThrowsWithReasonMatching(([ArrayOfAllTypesObject objectsWhere:@"SUBQUERY(array, $obj, $obj.intCol = 5) == 0"]), @"SUBQUERY.*immediately followed by .@count"); RLMAssertThrowsWithReasonMatching(([ArrayOfAllTypesObject objectsWhere:@"SELF IN SUBQUERY(array, $obj, $obj.intCol = 5)"]), @"Predicate with IN operator must compare.*aggregate$"); // Nonexistent aggregate operators RLMAssertThrowsWithReason([PersonObject objectsWhere:@"children.@average.age == 5"], @"Unsupported collection operation '@average'"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"name <[c] 'name'"], @"Lexicographical comparisons must be case-sensitive"); // block-based predicate NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL (__unused id obj, __unused NSDictionary *bindings) { return true; }]; XCTAssertThrows([IntObject objectsWithPredicate:pred]); } - (void)testPredicateMisuse { RLMRealm *realm = [RLMRealm defaultRealm]; // invalid column/property name RLMAssertThrowsWithReason([PersonObject objectsWhere:@"height > 72"], @"Property 'height' not found in object of type 'PersonObject'"); // wrong/invalid data types RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age != xyz"], @"Invalid keypath 'xyz': Property 'xyz' not found in object of type 'PersonObject'"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"name == 3"], @"Cannot compare value '3' of type '__NSCFNumber' to property 'name' of type 'string'"); RLMAssertThrowsWithReasonMatching([PersonObject objectsWhere:@"age IN {'xyz'}"], @"Cannot compare value 'xyz' of type '.*String.*' to property 'age' of type 'int'"); XCTAssertThrows([PersonObject objectsWhere:@"name IN {3}"], @"asdf"); // compare columns to incorrect type of constant value RLMAssertThrowsWithReasonMatching([AllTypesObject objectsWhere:@"boolCol == 'Foo'"], @"Cannot compare value 'Foo' of type '.*String.*' to property 'boolCol' of type 'bool'"); RLMAssertThrowsWithReason([AllTypesObject objectsWhere:@"boolCol == 2"], @"Cannot compare value '2' of type '__NSCFNumber' to property 'boolCol' of type 'bool'"); RLMAssertThrowsWithReason([AllTypesObject objectsWhere:@"dateCol == 7"], @"Cannot compare value '7' of type '__NSCFNumber' to property 'dateCol' of type 'date'"); RLMAssertThrowsWithReasonMatching([AllTypesObject objectsWhere:@"doubleCol == 'The'"], @"Cannot compare value 'The' of type '.*String.*' to property 'doubleCol' of type 'double'"); RLMAssertThrowsWithReasonMatching([AllTypesObject objectsWhere:@"floatCol == 'Bar'"], @"Cannot compare value 'Bar' of type '.*String.*' to property 'floatCol' of type 'float'"); RLMAssertThrowsWithReasonMatching([AllTypesObject objectsWhere:@"intCol == 'Baz'"], @"Cannot compare value 'Baz' of type '.*String.*' to property 'intCol' of type 'int'"); // compare two constants XCTAssertThrows([PersonObject objectsWhere:@"3 == 3"], @"comparing 2 constants"); // invalid strings RLMAssertThrowsWithReason([PersonObject objectsWhere:@""], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age"], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"sdlfjasdflj"], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age * 25"], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age === 25"], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@","], @"Unable to parse the format string"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"()"], @"Unable to parse the format string"); // Misspelled keypath (should be %K) RLMAssertThrowsWithReason([PersonObject objectsWhere:@"@K == YES"], @"Unsupported collection operation '@K'"); NSPredicate *(^predicateWithKeyPath)(NSString *) = ^(NSString *keyPath) { return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:keyPath] rightExpression:[NSExpression expressionForConstantValue:@0] modifier:0 type:NSEqualToPredicateOperatorType options:0]; }; // malformed keypath operators RLMAssertThrowsWithReason([PersonObject objectsWhere:@"@count == 0"], @"Invalid keypath '@count': collection operation '@count' must be applied to a collection"); [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"name.@"] rightExpression:[NSExpression expressionForConstantValue:@0] modifier:0 type:NSEqualToPredicateOperatorType options:0]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:predicateWithKeyPath(@"children.@")], @"Unsupported collection operation '@'"); RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:predicateWithKeyPath(@"children@")], @"Invalid keypath 'children@': Property 'children@' not found in object of type 'PersonObject'"); RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:predicateWithKeyPath(@"name@length")], @"Invalid keypath 'name@length': Property 'name@length' not found in object of type 'PersonObject'"); RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:predicateWithKeyPath(@".name")], @"Invalid keypath '.name': no key name before '.'"); RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:predicateWithKeyPath(@"children.")], @"Invalid keypath 'children.': no key name after last '.'"); // not a link column RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age.age == 25"], @"Invalid keypath 'age.age': Property 'PersonObject.age' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age.age.age == 25"], @"Invalid keypath 'age.age.age': Property 'PersonObject.age' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason([AllPrimitiveArrays objectsWhere:@"intObj.foo == 25"], @"Invalid keypath 'intObj.foo': RLMArray property intObj can only be followed by a collection operation"); RLMAssertThrowsWithReason([AllPrimitiveSets objectsWhere:@"intObj.foo == 25"], @"Invalid keypath 'intObj.foo': RLMSet property intObj can only be followed by a collection operation"); RLMAssertThrowsWithReason([AllPrimitiveDictionaries objectsWhere:@"intObj.foo == 25"], @"Invalid keypath 'intObj.foo': RLMDictionary property intObj can only be followed by a collection operation"); // abuse of BETWEEN RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN 25"], @"type NSArray for BETWEEN"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN Foo"], @"BETWEEN operator must compare a KeyPath with an aggregate"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN {age, age}"], @"must be constant values"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN {age, 0}"], @"must be constant values"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN {0, age}"], @"must be constant values"); RLMAssertThrowsWithReason([PersonObject objectsWhere:@"age BETWEEN {0, {1, 10}}"], @"must be constant values"); NSPredicate *pred; pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"exactly two objects"); pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1, @2, @3]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"exactly two objects"); pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@"Foo", @"Bar"]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"type int for BETWEEN"); pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1.5, @2.5]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"type int for BETWEEN"); pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1, @[@2, @3]]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"type int for BETWEEN"); pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @{@25: @35}]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"type NSArray for BETWEEN"); pred = [NSPredicate predicateWithFormat:@"height BETWEEN %@", @[@25, @35]]; RLMAssertThrowsWithReason([PersonObject objectsWithPredicate:pred], @"Property 'height' not found in object of type 'PersonObject'"); // bad type in link IN RLMAssertThrowsWithReasonMatching([PersonLinkObject objectsInRealm:realm where:@"person.age IN {'Tim'}"], @"Cannot compare value 'Tim' of type '.*String.*' to property 'age' of type 'int'"); } - (void)testStringUnsupportedOperations { XCTAssertThrows([StringObject objectsWhere:@"stringCol MATCHES 'abc'"]); XCTAssertThrows([StringObject objectsWhere:@"stringCol BETWEEN {'a', 'b'}"]); XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol MATCHES 'abc'"]); XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol BETWEEN {'a', 'b'}"]); } - (void)testBinaryComparisonInPredicate { NSData *data = [NSData data]; RLMAssertCount(BinaryObject, 0U, @"binaryCol BEGINSWITH %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol ENDSWITH %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol CONTAINS %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol BEGINSWITH NULL"); RLMAssertCount(BinaryObject, 0U, @"binaryCol ENDSWITH NULL"); RLMAssertCount(BinaryObject, 0U, @"binaryCol CONTAINS NULL"); RLMAssertCount(BinaryObject, 0U, @"binaryCol = %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol != %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol > %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol >= %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol < %@", data); RLMAssertCount(BinaryObject, 0U, @"binaryCol <= %@", data); XCTAssertThrows(([BinaryObject objectsWhere:@"binaryCol MATCHES %@", data])); } - (void)testLinkQueryInvalid { XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.binaryCol = 'a'"], @"Binary data not supported"); XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.invalidCol = 'a'"], @"Invalid column name should throw"); XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.longCol = 'a'"], @"Wrong data type should throw"); RLMAssertThrowsWithReasonMatching([ArrayPropertyObject objectsWhere:@"intArray.intCol > 5"], @"Key paths that include a collection property must use aggregate operations"); RLMAssertThrowsWithReasonMatching([SetPropertyObject objectsWhere:@"intSet.intCol > 5"], @"Key paths that include a collection property must use aggregate operations"); RLMAssertThrowsWithReasonMatching([LinkToCompanyObject objectsWhere:@"company.employees.age > 5"], @"Key paths that include a collection property must use aggregate operations"); RLMAssertThrowsWithReasonMatching([LinkToAllTypesObject objectsWhere:@"allTypesCol.intCol = allTypesCol.doubleCol"], @"Property type mismatch"); } - (void)testNumericOperatorsOnClass:(Class)class property:(NSString *)property value:(id)value { NSArray *operators = @[@"<", @"<=", @">", @">=", @"==", @"!="]; for (NSString *operator in operators) { NSString *fmt = [@[property, operator, @"%@"] componentsJoinedByString:@" "]; RLMAssertCount(class, 0U, fmt, value); } } - (void)testValidOperatorsInNumericComparison { [self testNumericOperatorsOnClass:[IntObject class] property:@"intCol" value:@0]; [self testNumericOperatorsOnClass:[FloatObject class] property:@"floatCol" value:@0]; [self testNumericOperatorsOnClass:[DoubleObject class] property:@"doubleCol" value:@0]; [self testNumericOperatorsOnClass:[DateObject class] property:@"dateCol" value:NSDate.date]; [self testNumericOperatorsOnClass:[DecimalObject class] property:@"decimalCol" value:[RLMDecimal128 decimalWithNumber:@0]]; [self testNumericOperatorsOnClass:[MixedObject class] property:@"anyCol" value:@0]; } - (void)testStringOperatorsOnClass:(Class)class property:(NSString *)property value:(id)value { NSArray *operators = @[@"BEGINSWITH", @"ENDSWITH", @"CONTAINS", @"LIKE", @"MATCHES"]; for (NSString *operator in operators) { NSString *fmt = [@[property, operator, @"%@"] componentsJoinedByString:@" "]; RLMAssertThrowsWithReasonMatching(([class objectsWhere:fmt, value]), @"not supported for type"); } } - (void)testInvalidOperatorsInNumericComparison { [self testStringOperatorsOnClass:[IntObject class] property:@"intCol" value:@0]; [self testStringOperatorsOnClass:[FloatObject class] property:@"floatCol" value:@0]; [self testStringOperatorsOnClass:[DoubleObject class] property:@"doubleCol" value:@0]; [self testStringOperatorsOnClass:[DateObject class] property:@"dateCol" value:NSDate.date]; [self testStringOperatorsOnClass:[DecimalObject class] property:@"decimalCol" value:[RLMDecimal128 decimalWithNumber:@0]]; } @end @interface QueryTests : RLMTestCase - (Class)queryObjectClass; @end @implementation QueryTests - (Class)queryObjectClass { return [QueryObject class]; } - (RLMResults *)evaluate:(RLMResults *)results { return results; } - (RLMRealm *)realm { return [RLMRealm defaultRealm]; } - (void)testBasicQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [realm commitWriteTransaction]; // query on realm RLMAssertCount(PersonObject, 2U, @"age > 28"); // query on realm with order RLMResults *results = [[PersonObject objectsInRealm:realm where:@"age > 28"] sortedResultsUsingKeyPath:@"age" ascending:YES]; XCTAssertEqualObjects([results[0] name], @"Tim", @"Tim should be first results"); // query on sorted results results = [[[PersonObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"age" ascending:YES] objectsWhere:@"age > 28"]; XCTAssertEqualObjects([results[0] name], @"Tim", @"Tim should be first results"); } - (void)testQueryBetween { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; AllTypesObject *a = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:nil]]; AllTypesObject *b = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:nil]]; AllTypesObject *c = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:nil]]; AllTypesObject *d = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:4 stringObject:nil]]; [ArrayOfAllTypesObject createInRealm:realm withValue:@[@[a, c]]]; [ArrayOfAllTypesObject createInRealm:realm withValue:@[@[b, d]]]; [DictionaryOfAllTypesObject createInRealm:realm withValue:@[@{@"1": a, @"3": c}]]; [DictionaryOfAllTypesObject createInRealm:realm withValue:@[@{@"2": b, @"4": d}]]; [SetOfAllTypesObject createInRealm:realm withValue:@[@[a, c]]]; [SetOfAllTypesObject createInRealm:realm withValue:@[@[b, d]]]; [realm commitWriteTransaction]; RLMAssertCount(AllTypesObject, 4U, @"intCol BETWEEN %@", @[@0, @5]); RLMAssertCount(AllTypesObject, 2U, @"intCol BETWEEN %@", @[@2, @3]); RLMAssertCount(AllTypesObject, 2U, @"intCol BETWEEN {2, 3}"); RLMAssertCount(ArrayOfAllTypesObject, 1U, @"ANY array.intCol BETWEEN %@", @[@4, @5]); RLMAssertCount(SetOfAllTypesObject, 1U, @"ANY set.intCol BETWEEN %@", @[@4, @5]); RLMAssertCount(DictionaryOfAllTypesObject, 1U, @"ANY dictionary.intCol BETWEEN %@", @[@4, @5]); RLMAssertCount(AllTypesObject, 4U, @"floatCol BETWEEN %@", @[@1.0f, @5.0f]); RLMAssertCount(AllTypesObject, 2U, @"floatCol BETWEEN %@", @[@2.0f, @4.0f]); RLMAssertCount(AllTypesObject, 2U, @"floatCol BETWEEN {2, 4}"); RLMAssertCount(ArrayOfAllTypesObject, 0U, @"ANY array.floatCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(SetOfAllTypesObject, 0U, @"ANY set.floatCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(DictionaryOfAllTypesObject, 0U, @"ANY dictionary.floatCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(AllTypesObject, 4U, @"doubleCol BETWEEN %@", @[@1.0, @5.0]); RLMAssertCount(AllTypesObject, 2U, @"doubleCol BETWEEN %@", @[@2.0, @4.0]); RLMAssertCount(AllTypesObject, 2U, @"doubleCol BETWEEN {3.0, 7.0}"); RLMAssertCount(ArrayOfAllTypesObject, 0U, @"ANY array.doubleCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(SetOfAllTypesObject, 0U, @"ANY set.doubleCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(DictionaryOfAllTypesObject, 0U, @"ANY dictionary.doubleCol BETWEEN %@", @[@3.1, @3.2]); RLMAssertCount(AllTypesObject, 2U, @"dateCol BETWEEN %@", @[[b dateCol], [c dateCol]]); RLMAssertCount(ArrayOfAllTypesObject, 2U, @"ANY array.dateCol BETWEEN %@", @[[b dateCol], [c dateCol]]); RLMAssertCount(SetOfAllTypesObject, 2U, @"ANY set.dateCol BETWEEN %@", @[[b dateCol], [c dateCol]]); RLMAssertCount(DictionaryOfAllTypesObject, 2U, @"ANY dictionary.dateCol BETWEEN %@", @[[b dateCol], [c dateCol]]); RLMAssertCount(AllTypesObject, 1U, @"longCol BETWEEN %@", @[@5000000000LL, @7000000000LL]); RLMAssertCount(AllTypesObject, 1U, @"longCol BETWEEN {5000000000, 7000000000}"); RLMAssertCount(ArrayOfAllTypesObject, 1U, @"ANY array.longCol BETWEEN %@", @[@5000000000LL, @7000000000LL]); RLMAssertCount(SetOfAllTypesObject, 1U, @"ANY set.longCol BETWEEN %@", @[@5000000000LL, @7000000000LL]); RLMAssertCount(DictionaryOfAllTypesObject, 1U, @"ANY dictionary.longCol BETWEEN %@", @[@5000000000LL, @7000000000LL]); RLMAssertCount(AllTypesObject, 4U, @"decimalCol BETWEEN %@", @[@0, @5]); RLMAssertCount(AllTypesObject, 4U, @"decimalCol BETWEEN %@", @[[[RLMDecimal128 alloc] initWithNumber:@0], [[RLMDecimal128 alloc] initWithNumber:@5]]); RLMAssertCount(AllTypesObject, 4U, @"decimalCol BETWEEN {0, 5}"); RLMAssertCount(AllTypesObject, 3U, @"decimalCol BETWEEN {'0e1', '30e-1'}"); RLMAssertCount(AllTypesObject, 4U, @"anyCol BETWEEN %@", @[@0, @5]); RLMAssertCount(AllTypesObject, 2U, @"anyCol BETWEEN %@", @[@2, @3]); RLMAssertCount(AllTypesObject, 2U, @"anyCol BETWEEN {2, 3}"); RLMAssertCount(ArrayOfAllTypesObject, 2U, @"ANY array.anyCol BETWEEN %@", @[@4, @5]); RLMAssertCount(SetOfAllTypesObject, 2U, @"ANY set.anyCol BETWEEN %@", @[@4, @5]); RLMAssertCount(DictionaryOfAllTypesObject, 2U, @"ANY dictionary.anyCol BETWEEN %@", @[@4, @5]); RLMResults *allObjects = [AllTypesObject allObjectsInRealm:realm]; RLMAssertThrowsWithReason([allObjects objectsWhere:@"boolCol BETWEEN {true, false}"], @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason([allObjects objectsWhere:@"stringCol BETWEEN {'', ''}"], @"Operator 'BETWEEN' not supported for type 'string'"); RLMAssertThrowsWithReason(([allObjects objectsWhere:@"binaryCol BETWEEN %@", @[NSData.data, NSData.data]]), @"Operator 'BETWEEN' not supported for type 'data'"); RLMAssertThrowsWithReason([allObjects objectsWhere:@"cBoolCol BETWEEN {true, false}"], @"Operator 'BETWEEN' not supported for type 'bool'"); RLMAssertThrowsWithReason(([allObjects objectsWhere:@"objectIdCol BETWEEN %@", @[[RLMObjectId objectId], [RLMObjectId objectId]]]), @"Operator 'BETWEEN' not supported for type 'object id'"); } - (void)testQueryWithDates { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; AllTypesObject *all0 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:nil]]; AllTypesObject *all1 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:nil]]; AllTypesObject *all2 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:nil]]; all0.anyCol = all0.dateCol; all1.anyCol = all1.dateCol; all2.anyCol = all2.dateCol; [realm commitWriteTransaction]; NSArray *dates = [[AllTypesObject allObjectsInRealm:realm] valueForKey:@"dateCol"]; RLMAssertCount(AllTypesObject, 2U, @"dateCol < %@", dates[2]); RLMAssertCount(AllTypesObject, 3U, @"dateCol <= %@", dates[2]); RLMAssertCount(AllTypesObject, 2U, @"dateCol > %@", dates[0]); RLMAssertCount(AllTypesObject, 3U, @"dateCol >= %@", dates[0]); RLMAssertCount(AllTypesObject, 1U, @"dateCol == %@", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"dateCol != %@", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ < dateCol", dates[0]); RLMAssertCount(AllTypesObject, 3U, @"%@ <= dateCol", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ > dateCol", dates[2]); RLMAssertCount(AllTypesObject, 3U, @"%@ >= dateCol", dates[2]); RLMAssertCount(AllTypesObject, 1U, @"%@ == dateCol", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ != dateCol", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"anyCol < %@", dates[2]); RLMAssertCount(AllTypesObject, 3U, @"anyCol <= %@", dates[2]); RLMAssertCount(AllTypesObject, 2U, @"anyCol > %@", dates[0]); RLMAssertCount(AllTypesObject, 3U, @"anyCol >= %@", dates[0]); RLMAssertCount(AllTypesObject, 1U, @"anyCol == %@", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"anyCol != %@", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ < anyCol", dates[0]); RLMAssertCount(AllTypesObject, 3U, @"%@ <= anyCol", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ > anyCol", dates[2]); RLMAssertCount(AllTypesObject, 3U, @"%@ >= anyCol", dates[2]); RLMAssertCount(AllTypesObject, 1U, @"%@ == anyCol", dates[0]); RLMAssertCount(AllTypesObject, 2U, @"%@ != anyCol", dates[0]); } - (void)testDefaultRealmQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; // query on class XCTAssertEqual([PersonObject allObjects].count, 3U); RLMAssertCount(PersonObject, 1U, @"age == 27"); // with order RLMResults *results = [[PersonObject objectsWhere:@"age > 28"] sortedResultsUsingKeyPath:@"age" ascending:YES]; PersonObject *tim = results[0]; XCTAssertEqualObjects(tim.name, @"Tim", @"Tim should be first results"); } - (void)testUuidRealmQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [UuidObject createInRealm:realm withValue:@[[[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]]]; [UuidObject createInRealm:realm withValue:@[[[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]]]; [MixedObject createInRealm:realm withValue:@[[[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]]]; [MixedObject createInRealm:realm withValue:@[[[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]]]; [realm commitWriteTransaction]; // query on class XCTAssertEqual([UuidObject allObjects].count, 2U); RLMAssertCount(UuidObject, 1U, @"uuidCol == %@", [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]); XCTAssertEqual([MixedObject allObjects].count, 2U); RLMAssertCount(MixedObject, 1U, @"anyCol == %@", [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]); } - (void)testRLMValueQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *allValues = @[@YES, @NO, @true, @false, @TRUE, @FALSE, @"0", @"1", @0, @1, @0.0, @1.0, @0.0f, @1.0f, [[RLMDecimal128 alloc] initWithNumber:@(0)], [[RLMDecimal128 alloc] initWithNumber:@(1)], [NSData dataWithBytes:"0" length:1], [NSData dataWithBytes:"1" length:1], [NSDate dateWithTimeIntervalSince1970:0], [NSDate dateWithTimeIntervalSince1970:1], [RLMObjectId objectId], [[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"], NSNull.null, ]; StringObject *stringObj = [StringObject new]; stringObj.stringCol = @"required-string"; ArrayOfAllTypesObject *arrayOfAll = [ArrayOfAllTypesObject createInRealm:realm withValue:@{}]; for (int i = 0; i < (int)allValues.count; i++) { AllTypesObject *obj = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:i stringObject:stringObj]]; obj.anyCol = allValues[i]; [arrayOfAll.array addObject:obj]; } [realm commitWriteTransaction]; // Numeric based comparability RLMAssertCount(AllTypesObject, 6U, @"anyCol BETWEEN %@", @[@1, @2]); RLMAssertCount(AllTypesObject, 6U, @"anyCol BETWEEN {1, 2}"); RLMAssertCount(AllTypesObject, 1U, @"anyCol == FALSE"); RLMAssertCount(AllTypesObject, 6U, @"anyCol == 0"); RLMAssertCount(AllTypesObject, 22, @"anyCol != false"); RLMAssertCount(AllTypesObject, 22, @"anyCol != FALSE"); RLMAssertCount(AllTypesObject, 22, @"anyCol != NO"); RLMAssertCount(AllTypesObject, 17, @"anyCol != 0"); RLMAssertCount(AllTypesObject, 6U, @"anyCol < 1"); RLMAssertCount(AllTypesObject, 0U, @"anyCol > 1"); RLMAssertCount(AllTypesObject, 6U, @"anyCol >= 1"); RLMAssertCount(AllTypesObject, 12U, @"anyCol <= 1"); XCTAssertThrowsSpecificNamed([AllTypesObject objectsWhere:@"anyCol BETWEEN TRUE"], NSException, @"Invalid value", @"object must be of type NSArray for BETWEEN operations"); // Binary based comparability RLMAssertCount(AllTypesObject, 1U, @"anyCol == '0'"); RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != '0'"); RLMAssertCount(AllTypesObject, 1U, @"anyCol BEGINSWITH '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol BEGINSWITH 'a'"); RLMAssertCount(AllTypesObject, 1U, @"anyCol ENDSWITH '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol ENDSWITH 'a'"); RLMAssertCount(AllTypesObject, 1U, @"anyCol CONTAINS '1'"); RLMAssertCount(AllTypesObject, 0U, @"anyCol CONTAINS 'a'"); RLMAssertCount(AllTypesObject, 1U, @"anyCol == %@", [@"0" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != %@", [@"0" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 1U, @"anyCol BEGINSWITH %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 0U, @"anyCol BEGINSWITH %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 1U, @"anyCol ENDSWITH %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 0U, @"anyCol ENDSWITH %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 1U, @"anyCol CONTAINS %@", [@"1" dataUsingEncoding:NSUTF8StringEncoding]); RLMAssertCount(AllTypesObject, 0U, @"anyCol CONTAINS %@", [@"a" dataUsingEncoding:NSUTF8StringEncoding]); XCTAssertThrowsSpecificNamed([AllTypesObject objectsWhere:@"anyCol CONATINS 0"], NSException, NSInvalidArgumentException, @"Unable to parse the format string \"anyCol CONATINS 0\""); RLMAssertCount(AllTypesObject, 0U, @"anyCol BEGINSWITH '%@'", @0); RLMAssertCount(AllTypesObject, 0U, @"anyCol ENDSWITH '%@'", @0); RLMAssertCount(AllTypesObject, 1U, @"anyCol == %@", [NSDate dateWithTimeIntervalSince1970:0]); RLMAssertCount(AllTypesObject, allValues.count, @"anyCol != %@", [NSDate dateWithTimeIntervalSince1970:123]); RLMAssertCount(AllTypesObject, 1U, @"anyCol == %@", [[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"]); RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != %@", [[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"]); XCTAssertThrowsSpecificNamed([AllTypesObject objectsWhere:@"anyCol BETWEEN '85d4fbee-6ec6-47df-bfa1-615931903d7e'"], NSException, @"Invalid value", @"object must be of type NSArray for BETWEEN operations"); RLMAssertCount(AllTypesObject, 1U, @"anyCol == NULL"); RLMAssertCount(AllTypesObject, allValues.count-1, @"anyCol != NULL"); } - (void)testArrayQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; // query on class RLMResults *all = [PersonObject allObjects]; XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); RLMResults *some = [[PersonObject objectsWhere:@"age > 28"] sortedResultsUsingKeyPath:@"age" ascending:YES]; // query/order on array RLMAssertCount(all, 1U, @"age == 27"); RLMAssertCount(all, 0U, @"age == 28"); some = [some sortedResultsUsingKeyPath:@"age" ascending:NO]; XCTAssertEqualObjects([some[0] name], @"Ari", @"Ari should be first results"); } - (void)verifySort:(RLMRealm *)realm column:(NSString *)column ascending:(BOOL)ascending expected:(id)val { RLMResults *results = [[AllTypesObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:column ascending:ascending]; AllTypesObject *obj = results[0]; XCTAssertEqualObjects(obj[column], val, @"%@", column); RLMArray *ar = [(ArrayPropertyObject *)[[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject] array]; results = [ar sortedResultsUsingKeyPath:column ascending:ascending]; obj = results[0]; XCTAssertEqualObjects(obj[column], val, @"%@", column); } - (void)testEmbeddedObjectQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; EmbeddedIntParentObject *obj0 = [EmbeddedIntParentObject createInRealm:realm withValue:@[@1, @[@2], @[@[@3]]]]; EmbeddedIntParentObject *obj1 = [EmbeddedIntParentObject createInRealm:realm withValue:@[@4, @[@5], @[@[@6]]]]; EmbeddedIntParentObject *obj2 = [EmbeddedIntParentObject createInRealm:realm withValue:@[@7, @[@8], @[@[@9]]]]; [realm commitWriteTransaction]; // Query parent objects based on property of embedded object RLMResults *r0 = [EmbeddedIntParentObject objectsWhere:@"object.intCol = 2"]; XCTAssertEqualObjects(r0[0], obj0); XCTAssert(r0.count == 1); // Query parent objects based on array of embedded objects RLMResults *r1 = [EmbeddedIntParentObject objectsWhere:@"ANY array.intCol > 4"]; XCTAssertEqualObjects(r1[0], obj1); XCTAssertEqualObjects(r1[1], obj2); XCTAssert(r1.count == 2); // Compound query using two different embedded object properties RLMResults *r2 = [EmbeddedIntParentObject objectsWhere:@"ANY array.intCol > 4 and object.intCol = 5"]; XCTAssertEqualObjects(r2[0], obj1); XCTAssert(r2.count == 1); // Aggregate query on embedded object array, sort using embedded object key path RLMResults *r3 = [[EmbeddedIntParentObject objectsWhere:@"array.@max.intCol < 9"] sortedResultsUsingKeyPath:@"object.intCol" ascending:NO]; XCTAssertEqualObjects(r3[0], obj1); XCTAssertEqualObjects(r3[1], obj0); XCTAssert(r3.count == 2); } - (void)testQuerySorting { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *stringObj = [StringObject new]; stringObj.stringCol = @"string"; ArrayOfAllTypesObject *arrayOfAll = [ArrayOfAllTypesObject createInRealm:realm withValue:@{}]; DictionaryOfAllTypesObject *dictionaryOfAll = [DictionaryOfAllTypesObject createInRealm:realm withValue: @{@"dictionary": @{ @"1": [AllTypesObject values:1 stringObject:stringObj], @"2": [AllTypesObject values:2 stringObject:stringObj], @"3": [AllTypesObject values:3 stringObject:stringObj], @"4": [AllTypesObject values:4 stringObject:stringObj], }}]; SetOfAllTypesObject *setOfAll = [SetOfAllTypesObject createInRealm:realm withValue:@{}]; [arrayOfAll.array addObjects:@[ [AllTypesObject values:1 stringObject:stringObj], [AllTypesObject values:2 stringObject:stringObj], [AllTypesObject values:3 stringObject:stringObj], [AllTypesObject values:4 stringObject:stringObj], ]]; [setOfAll.set addObjects:@[ [AllTypesObject values:1 stringObject:stringObj], [AllTypesObject values:2 stringObject:stringObj], [AllTypesObject values:3 stringObject:stringObj], [AllTypesObject values:4 stringObject:stringObj], ]]; arrayOfAll.array[0].anyCol = @NO; arrayOfAll.array[1].anyCol = [NSNull null]; arrayOfAll.array[2].anyCol = @1; arrayOfAll.array[3].anyCol = [[NSUUID alloc] initWithUUIDString:@"B9D325B0-3058-4838-8473-8F1AAAE410DB"]; [realm commitWriteTransaction]; //////////// sort by boolCol [self verifySort:realm column:@"boolCol" ascending:YES expected:@NO]; [self verifySort:realm column:@"boolCol" ascending:NO expected:@YES]; //////////// sort by intCol [self verifySort:realm column:@"intCol" ascending:YES expected:@1]; [self verifySort:realm column:@"intCol" ascending:NO expected:@4]; //////////// sort by dateCol [self verifySort:realm column:@"dateCol" ascending:YES expected:arrayOfAll.array[0].dateCol]; [self verifySort:realm column:@"dateCol" ascending:NO expected:arrayOfAll.array[3].dateCol]; //////////// sort by doubleCol [self verifySort:realm column:@"doubleCol" ascending:YES expected:@1.11]; [self verifySort:realm column:@"doubleCol" ascending:NO expected:@4.44]; //////////// sort by floatCol [self verifySort:realm column:@"floatCol" ascending:YES expected:@1.1f]; [self verifySort:realm column:@"floatCol" ascending:NO expected:@4.4f]; //////////// sort by stringCol [self verifySort:realm column:@"stringCol" ascending:YES expected:@"a"]; [self verifySort:realm column:@"stringCol" ascending:NO expected:@"d"]; //////////// sort by decimalCol [self verifySort:realm column:@"decimalCol" ascending:YES expected:[RLMDecimal128 decimalWithNumber:@1]]; [self verifySort:realm column:@"decimalCol" ascending:NO expected:[RLMDecimal128 decimalWithNumber:@4]]; //////////// sort by uuidCol [self verifySort:realm column:@"uuidCol" ascending:YES expected:[[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]]; [self verifySort:realm column:@"uuidCol" ascending:NO expected:[[NSUUID alloc] initWithUUIDString:@"B9D325B0-3058-4838-8473-8F1AAAE410DB"]]; //////////// sort by anyCol //////////// nulls < strings, binaries < numerics < timestamps < objectId < uuid. [self verifySort:realm column:@"anyCol" ascending:YES expected:nil]; [self verifySort:realm column:@"anyCol" ascending:NO expected:[[NSUUID alloc] initWithUUIDString:@"B9D325B0-3058-4838-8473-8F1AAAE410DB"]]; // sort invalid name RLMAssertThrowsWithReason([[AllTypesObject allObjects] sortedResultsUsingKeyPath:@"invalidCol" ascending:YES], @"Cannot sort on key path 'invalidCol': property 'AllTypesObject.invalidCol' does not exist."); RLMAssertThrowsWithReason([arrayOfAll.array sortedResultsUsingKeyPath:@"invalidCol" ascending:NO], @"Cannot sort on key path 'invalidCol': property 'AllTypesObject.invalidCol' does not exist."); RLMAssertThrowsWithReason([setOfAll.set sortedResultsUsingKeyPath:@"invalidCol" ascending:NO], @"Cannot sort on key path 'invalidCol': property 'AllTypesObject.invalidCol' does not exist."); RLMAssertThrowsWithReason([dictionaryOfAll.dictionary sortedResultsUsingKeyPath:@"invalidCol" ascending:NO], @"Cannot sort on key path 'invalidCol': property 'AllTypesObject.invalidCol' does not exist."); } - (void)testSortByNoColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; [realm commitWriteTransaction]; RLMResults *notActuallySorted = [DogObject.allObjects sortedResultsUsingDescriptors:@[]]; XCTAssertTrue([a2 isEqualToObject:notActuallySorted[0]]); XCTAssertTrue([b1 isEqualToObject:notActuallySorted[1]]); XCTAssertTrue([a1 isEqualToObject:notActuallySorted[2]]); XCTAssertTrue([b2 isEqualToObject:notActuallySorted[3]]); } - (void)testSortByMultipleColumns { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; [realm commitWriteTransaction]; bool (^checkOrder)(NSArray *, NSArray *, NSArray *) = ^bool(NSArray *properties, NSArray *ascending, NSArray *dogs) { NSArray *sort = @[[RLMSortDescriptor sortDescriptorWithKeyPath:properties[0] ascending:[ascending[0] boolValue]], [RLMSortDescriptor sortDescriptorWithKeyPath:properties[1] ascending:[ascending[1] boolValue]]]; RLMResults *actual = [DogObject.allObjects sortedResultsUsingDescriptors:sort]; return [actual[0] isEqualToObject:dogs[0]] && [actual[1] isEqualToObject:dogs[1]] && [actual[2] isEqualToObject:dogs[2]] && [actual[3] isEqualToObject:dogs[3]]; }; // Check each valid sort XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @YES], @[a1, a2, b1, b2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @NO], @[a2, a1, b2, b1])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @YES], @[b1, b2, a1, a2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @NO], @[b2, b1, a2, a1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @YES], @[a1, b1, a2, b2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @NO], @[b1, a1, b2, a2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @YES], @[a2, b2, a1, b1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @NO], @[b2, a2, b1, a1])); } - (void)testSortByKeyPath { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; DogObject *lucy = [DogObject createInDefaultRealmWithValue:@[@"Lucy", @7]]; DogObject *freyja = [DogObject createInDefaultRealmWithValue:@[@"Freyja", @6]]; DogObject *ziggy = [DogObject createInDefaultRealmWithValue:@[@"Ziggy", @9]]; OwnerObject *mark = [OwnerObject createInDefaultRealmWithValue:@[@"Mark", freyja]]; OwnerObject *diane = [OwnerObject createInDefaultRealmWithValue:@[@"Diane", lucy]]; OwnerObject *hannah = [OwnerObject createInDefaultRealmWithValue:@[@"Hannah"]]; OwnerObject *don = [OwnerObject createInDefaultRealmWithValue:@[@"Don", ziggy]]; OwnerObject *diane_sr = [OwnerObject createInDefaultRealmWithValue:@[@"Diane Sr", ziggy]]; [realm commitWriteTransaction]; NSArray *(^asArray)(RLMResults *) = ^(RLMResults *results) { return [[self evaluate:results] valueForKeyPath:@"self"]; }; RLMResults *r1 = [OwnerObject.allObjects sortedResultsUsingKeyPath:@"dog.age" ascending:YES]; XCTAssertEqualObjects(asArray(r1), (@[ mark, diane, don, diane_sr, hannah ])); RLMResults *r2 = [OwnerObject.allObjects sortedResultsUsingKeyPath:@"dog.age" ascending:NO]; XCTAssertEqualObjects(asArray(r2), (@[ hannah, don, diane_sr, diane, mark ])); RLMResults *r3 = [OwnerObject.allObjects sortedResultsUsingDescriptors:@[ [RLMSortDescriptor sortDescriptorWithKeyPath:@"dog.age" ascending:YES], [RLMSortDescriptor sortDescriptorWithKeyPath:@"name" ascending:YES] ]]; XCTAssertEqualObjects(asArray(r3), (@[ mark, diane, diane_sr, don, hannah ])); RLMResults *r4 = [OwnerObject.allObjects sortedResultsUsingDescriptors:@[ [RLMSortDescriptor sortDescriptorWithKeyPath:@"dog.age" ascending:NO], [RLMSortDescriptor sortDescriptorWithKeyPath:@"name" ascending:YES] ]]; XCTAssertEqualObjects(asArray(r4), (@[ hannah, diane_sr, don, diane, mark ])); } - (void)testSortByUnspportedKeyPath { // Array property RLMAssertThrowsWithReason([DogArrayObject.allObjects sortedResultsUsingKeyPath:@"dogs.age" ascending:YES], @"Cannot sort on key path 'dogs.age': property 'DogArrayObject.dogs' is of unsupported type 'array'."); // Backlinks property RLMAssertThrowsWithReason([DogObject.allObjects sortedResultsUsingKeyPath:@"owners.name" ascending:YES], @"Cannot sort on key path 'owners.name': property 'DogObject.owners' is of unsupported type 'linking objects'."); // Collection operator RLMAssertThrowsWithReason([DogArrayObject.allObjects sortedResultsUsingKeyPath:@"dogs.@count" ascending:YES], @"Cannot sort on key path 'dogs.@count': KVC collection operators are not supported."); } - (void)testSortedLinkViewWithDeletion { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *stringObj = [StringObject new]; stringObj.stringCol = @"string"; ArrayOfAllTypesObject *arrayOfAll = [ArrayOfAllTypesObject createInRealm:realm withValue:@{}]; SetOfAllTypesObject *setOfAll = [SetOfAllTypesObject createInRealm:realm withValue:@{}]; DictionaryOfAllTypesObject *dictOfAll = [DictionaryOfAllTypesObject createInRealm:realm withValue: @{@"dictionary": @{ @"1": [AllTypesObject values:1 stringObject:stringObj], @"2": [AllTypesObject values:2 stringObject:stringObj], @"3": [AllTypesObject values:3 stringObject:stringObj], @"4": [AllTypesObject values:4 stringObject:stringObj], }}]; [arrayOfAll.array addObjects:@[ [AllTypesObject values:1 stringObject:stringObj], [AllTypesObject values:2 stringObject:stringObj], [AllTypesObject values:3 stringObject:stringObj], [AllTypesObject values:4 stringObject:stringObj], ]]; [setOfAll.set addObjects:@[ [AllTypesObject values:1 stringObject:stringObj], [AllTypesObject values:2 stringObject:stringObj], [AllTypesObject values:3 stringObject:stringObj], [AllTypesObject values:4 stringObject:stringObj], ]]; [realm commitWriteTransaction]; RLMResults *results = [arrayOfAll.array sortedResultsUsingKeyPath:@"stringCol" ascending:NO]; XCTAssertEqualObjects([results[0] stringCol], @"d"); RLMResults *results2 = [setOfAll.set sortedResultsUsingKeyPath:@"stringCol" ascending:NO]; XCTAssertEqualObjects([results2[0] stringCol], @"d"); RLMResults *results3 = [dictOfAll.dictionary sortedResultsUsingKeyPath:@"stringCol" ascending:NO]; XCTAssertEqualObjects([results3[0] stringCol], @"d"); // delete d, add e results should update [realm transactionWithBlock:^{ [arrayOfAll.array removeObjectAtIndex:3]; [setOfAll.set removeObject:setOfAll.set.allObjects[3]]; [dictOfAll.dictionary removeObjectForKey:@"4"]; [arrayOfAll.array addObject:(id)[AllTypesObject values:5 stringObject:stringObj]]; [setOfAll.set addObject:(id)[AllTypesObject values:5 stringObject:stringObj]]; dictOfAll.dictionary[@"5"] = [[AllTypesObject alloc] initWithValue:[AllTypesObject values:5 stringObject:stringObj]]; }]; XCTAssertEqualObjects([results[0] stringCol], @"e"); XCTAssertEqualObjects([results[1] stringCol], @"c"); XCTAssertEqualObjects([results2[0] stringCol], @"e"); XCTAssertEqualObjects([results2[1] stringCol], @"c"); XCTAssertEqualObjects([results3[0] stringCol], @"e"); XCTAssertEqualObjects([results3[1] stringCol], @"c"); // delete from realm should be removed from results [realm transactionWithBlock:^{ [realm deleteObject:arrayOfAll.array.lastObject]; [realm deleteObject:setOfAll.set.allObjects.lastObject]; }]; XCTAssertEqualObjects([results[0] stringCol], @"c"); XCTAssertEqualObjects([results2[0] stringCol], @"c"); } - (void)testQueryingSortedQueryPreservesOrder { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; for (int i = 0; i < 5; ++i) { [IntObject createInRealm:realm withValue:@[@(i)]]; } ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", @[], [IntObject allObjects]]]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"name", @[], [IntObject allObjects]]]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@{}]; for (IntObject *io in [IntObject allObjects]) { dict.intObjDictionary[[NSUUID UUID].UUIDString] = io; } [realm commitWriteTransaction]; RLMResults *asc = [IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:YES]; RLMResults *desc = [IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO]; // sanity check; would work even without sort order being preserved XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); // check query on allObjects and query on query XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); // same thing but on an linkview asc = [array.intArray sortedResultsUsingKeyPath:@"intCol" ascending:YES]; desc = [array.intArray sortedResultsUsingKeyPath:@"intCol" ascending:NO]; XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); asc = [set.intSet sortedResultsUsingKeyPath:@"intCol" ascending:YES]; desc = [set.intSet sortedResultsUsingKeyPath:@"intCol" ascending:NO]; XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); asc = [dict.intObjDictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES]; desc = [dict.intObjDictionary sortedResultsUsingKeyPath:@"intCol" ascending:NO]; XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); } - (void)testTwoColumnComparison { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *values = [self queryObjectClassValues]; for (id value in values) { [self.queryObjectClass createInRealm:realm withValue:value]; } QueryObject *first = [[self.queryObjectClass allObjectsInRealm:realm] firstObject]; first.object1 = first; [realm commitWriteTransaction]; RLMAssertCount(self.queryObjectClass, 7U, @"bool1 == bool1"); RLMAssertCount(self.queryObjectClass, 3U, @"bool1 == bool2"); RLMAssertCount(self.queryObjectClass, 4U, @"bool1 != bool2"); RLMAssertCount(self.queryObjectClass, 7U, @"int1 == int1"); RLMAssertCount(self.queryObjectClass, 2U, @"int1 == int2"); RLMAssertCount(self.queryObjectClass, 5U, @"int1 != int2"); RLMAssertCount(self.queryObjectClass, 1U, @"int1 > int2"); RLMAssertCount(self.queryObjectClass, 4U, @"int1 < int2"); RLMAssertCount(self.queryObjectClass, 3U, @"int1 >= int2"); RLMAssertCount(self.queryObjectClass, 6U, @"int1 <= int2"); RLMAssertCount(self.queryObjectClass, 7U, @"float1 == float1"); RLMAssertCount(self.queryObjectClass, 1U, @"float1 == float2"); RLMAssertCount(self.queryObjectClass, 6U, @"float1 != float2"); RLMAssertCount(self.queryObjectClass, 2U, @"float1 > float2"); RLMAssertCount(self.queryObjectClass, 4U, @"float1 < float2"); RLMAssertCount(self.queryObjectClass, 3U, @"float1 >= float2"); RLMAssertCount(self.queryObjectClass, 5U, @"float1 <= float2"); RLMAssertCount(self.queryObjectClass, 7U, @"double1 == double1"); RLMAssertCount(self.queryObjectClass, 0U, @"double1 == double2"); RLMAssertCount(self.queryObjectClass, 7U, @"double1 != double2"); RLMAssertCount(self.queryObjectClass, 1U, @"double1 > double2"); RLMAssertCount(self.queryObjectClass, 6U, @"double1 < double2"); RLMAssertCount(self.queryObjectClass, 1U, @"double1 >= double2"); RLMAssertCount(self.queryObjectClass, 6U, @"double1 <= double2"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 == string1"); RLMAssertCount(self.queryObjectClass, 1U, @"string1 == string2"); RLMAssertCount(self.queryObjectClass, 6U, @"string1 != string2"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 CONTAINS string1"); RLMAssertCount(self.queryObjectClass, 1U, @"string1 CONTAINS string2"); RLMAssertCount(self.queryObjectClass, 3U, @"string2 CONTAINS string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 BEGINSWITH string1"); RLMAssertCount(self.queryObjectClass, 1U, @"string1 BEGINSWITH string2"); RLMAssertCount(self.queryObjectClass, 2U, @"string2 BEGINSWITH string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 ENDSWITH string1"); RLMAssertCount(self.queryObjectClass, 1U, @"string1 ENDSWITH string2"); RLMAssertCount(self.queryObjectClass, 2U, @"string2 ENDSWITH string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 LIKE string1"); RLMAssertCount(self.queryObjectClass, 1U, @"string1 LIKE string2"); RLMAssertCount(self.queryObjectClass, 1U, @"string2 LIKE string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 ==[c] string1"); RLMAssertCount(self.queryObjectClass, 2U, @"string1 ==[c] string2"); RLMAssertCount(self.queryObjectClass, 5U, @"string1 !=[c] string2"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 CONTAINS[c] string1"); RLMAssertCount(self.queryObjectClass, 2U, @"string1 CONTAINS[c] string2"); RLMAssertCount(self.queryObjectClass, 6U, @"string2 CONTAINS[c] string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 BEGINSWITH[c] string1"); RLMAssertCount(self.queryObjectClass, 2U, @"string1 BEGINSWITH[c] string2"); RLMAssertCount(self.queryObjectClass, 4U, @"string2 BEGINSWITH[c] string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 ENDSWITH[c] string1"); RLMAssertCount(self.queryObjectClass, 2U, @"string1 ENDSWITH[c] string2"); RLMAssertCount(self.queryObjectClass, 4U, @"string2 ENDSWITH[c] string1"); RLMAssertCount(self.queryObjectClass, 7U, @"string1 LIKE[c] string1"); RLMAssertCount(self.queryObjectClass, 2U, @"string1 LIKE[c] string2"); RLMAssertCount(self.queryObjectClass, 2U, @"string2 LIKE[c] string1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 == data1"); RLMAssertCount(self.queryObjectClass, 1U, @"data1 == data2"); RLMAssertCount(self.queryObjectClass, 6U, @"data1 != data2"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 CONTAINS data1"); RLMAssertCount(self.queryObjectClass, 1U, @"data1 CONTAINS data2"); RLMAssertCount(self.queryObjectClass, 3U, @"data2 CONTAINS data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 BEGINSWITH data1"); RLMAssertCount(self.queryObjectClass, 1U, @"data1 BEGINSWITH data2"); RLMAssertCount(self.queryObjectClass, 2U, @"data2 BEGINSWITH data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 ENDSWITH data1"); RLMAssertCount(self.queryObjectClass, 1U, @"data1 ENDSWITH data2"); RLMAssertCount(self.queryObjectClass, 2U, @"data2 ENDSWITH data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 LIKE data1"); RLMAssertCount(self.queryObjectClass, 1U, @"data1 LIKE data2"); RLMAssertCount(self.queryObjectClass, 1U, @"data2 LIKE data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 ==[c] data1"); RLMAssertCount(self.queryObjectClass, 2U, @"data1 ==[c] data2"); RLMAssertCount(self.queryObjectClass, 5U, @"data1 !=[c] data2"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 CONTAINS[c] data1"); RLMAssertCount(self.queryObjectClass, 2U, @"data1 CONTAINS[c] data2"); RLMAssertCount(self.queryObjectClass, 6U, @"data2 CONTAINS[c] data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 BEGINSWITH[c] data1"); RLMAssertCount(self.queryObjectClass, 2U, @"data1 BEGINSWITH[c] data2"); RLMAssertCount(self.queryObjectClass, 4U, @"data2 BEGINSWITH[c] data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 ENDSWITH[c] data1"); RLMAssertCount(self.queryObjectClass, 2U, @"data1 ENDSWITH[c] data2"); RLMAssertCount(self.queryObjectClass, 4U, @"data2 ENDSWITH[c] data1"); RLMAssertCount(self.queryObjectClass, 7U, @"data1 LIKE[c] data1"); RLMAssertCount(self.queryObjectClass, 2U, @"data1 LIKE[c] data2"); RLMAssertCount(self.queryObjectClass, 2U, @"data2 LIKE[c] data1"); RLMAssertCount(self.queryObjectClass, 7U, @"decimal1 == decimal1"); RLMAssertCount(self.queryObjectClass, 2U, @"decimal1 == decimal2"); RLMAssertCount(self.queryObjectClass, 5U, @"decimal1 != decimal2"); RLMAssertCount(self.queryObjectClass, 1U, @"decimal1 > decimal2"); RLMAssertCount(self.queryObjectClass, 4U, @"decimal1 < decimal2"); RLMAssertCount(self.queryObjectClass, 3U, @"decimal1 >= decimal2"); RLMAssertCount(self.queryObjectClass, 6U, @"decimal1 <= decimal2"); RLMAssertCount(self.queryObjectClass, 7U, @"objectId1 == objectId1"); RLMAssertCount(self.queryObjectClass, 3U, @"objectId1 == objectId2"); RLMAssertCount(self.queryObjectClass, 4U, @"objectId1 != objectId2"); RLMAssertCount(self.queryObjectClass, 7U, @"object1 == object1"); RLMAssertCount(self.queryObjectClass, 6U, @"object1 == object2"); RLMAssertCount(self.queryObjectClass, 1U, @"object1 != object2"); RLMAssertCount(self.queryObjectClass, 7U, @"any1 == any1"); RLMAssertCount(self.queryObjectClass, 0U, @"any1 == any2"); RLMAssertCount(self.queryObjectClass, 7U, @"any1 != any2"); RLMAssertCount(self.queryObjectClass, 7U, @"any1 ==[c] any1"); RLMAssertCount(self.queryObjectClass, 0U, @"any1 ==[c] any2"); RLMAssertCount(self.queryObjectClass, 0U, @"any1 !=[c] any1"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"int1 == float1"], @"Property type mismatch between int and float"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"float2 >= double1"], @"Property type mismatch between float and double"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"double2 <= int2"], @"Property type mismatch between double and int"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"int2 != string1"], @"Property type mismatch between int and string"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"float1 > string1"], @"Property type mismatch between float and string"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"double1 < string1"], @"Property type mismatch between double and string"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"double1 LIKE string1"], @"Property type mismatch between double and string"); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"string1 LIKE double1"], @"Property type mismatch between string and double"); } - (void)testBooleanPredicate { RLMAssertCount(BoolObject, 0U, @"boolCol == TRUE"); RLMAssertCount(BoolObject, 0U, @"boolCol != TRUE"); XCTAssertThrows([BoolObject objectsWhere:@"boolCol == NULL"]); XCTAssertThrows([BoolObject objectsWhere:@"boolCol != NULL"]); XCTAssertThrowsSpecificNamed([BoolObject objectsWhere:@"boolCol >= TRUE"], NSException, @"Invalid operator type", @"Invalid operator in bool predicate."); } - (void)testStringBeginsWith { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:@[@"abc"]]; [StringObject createInRealm:realm withValue:@[@"üvw"]]; [StringObject createInRealm:realm withValue:@[@"ûvw"]]; [StringObject createInRealm:realm withValue:@[@"uvw"]]; [StringObject createInRealm:realm withValue:@[@"stü"]]; AllTypesObject *ato = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; ato.anyCol = @"abc"; // overwrite int ato.mixedObjectCol = [MixedObject createInRealm:realm withValue:@[@"abc"]]; [MixedObject createInRealm:realm withValue:@[@"üvw"]]; [MixedObject createInRealm:realm withValue:@[@"ûvw"]]; [MixedObject createInRealm:realm withValue:@[@"uvw"]]; [MixedObject createInRealm:realm withValue:@[@"stü"]]; [realm commitWriteTransaction]; void (^testBlock)(NSString *, NSString *, Class) = ^(NSString * objectCol, NSString *colName, Class cls) { RLMAssertCount(cls, 1U, @"%K BEGINSWITH 'a'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH 'ab'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH 'abc'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH 'abcd'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH 'abd'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH 'c'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH 'A'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH ''", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH[c] 'a'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH[c] 'A'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[c] ''", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[d] ''", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[cd] ''", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH 'u'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH[c] 'U'", colName); RLMAssertCount(cls, 3U, @"%K BEGINSWITH[d] 'u'", colName); RLMAssertCount(cls, 3U, @"%K BEGINSWITH[cd] 'U'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH 'ü'", colName); RLMAssertCount(cls, 1U, @"%K BEGINSWITH[c] 'Ü'", colName); RLMAssertCount(cls, 3U, @"%K BEGINSWITH[d] 'ü'", colName); RLMAssertCount(cls, 3U, @"%K BEGINSWITH[cd] 'Ü'", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH NULL", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[c] NULL", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[d] NULL", colName); RLMAssertCount(cls, 0U, @"%K BEGINSWITH[cd] NULL", colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K BEGINSWITH 'a'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH 'c'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH 'A'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH ''", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K BEGINSWITH[c] 'a'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K BEGINSWITH[c] 'A'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[c] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[d] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[cd] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[c] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[d] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K BEGINSWITH[cd] NULL", objectCol, colName); }; testBlock(@"objectCol", @"stringCol", [StringObject class]); testBlock(@"mixedObjectCol", @"anyCol", [MixedObject class]); } - (void)testStringEndsWith { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:@[@"abc"]]; [StringObject createInRealm:realm withValue:@[@"üvw"]]; [StringObject createInRealm:realm withValue:@[@"ûvw"]]; [StringObject createInRealm:realm withValue:@[@"uvü"]]; [StringObject createInRealm:realm withValue:@[@"stu"]]; AllTypesObject *ato = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; ato.anyCol = @"abc"; // overwrite int ato.mixedObjectCol = [MixedObject createInRealm:realm withValue:@[@"abc"]]; [MixedObject createInRealm:realm withValue:@[@"üvw"]]; [MixedObject createInRealm:realm withValue:@[@"ûvw"]]; [MixedObject createInRealm:realm withValue:@[@"uvü"]]; [MixedObject createInRealm:realm withValue:@[@"stu"]]; [realm commitWriteTransaction]; void (^testBlock)(NSString *, NSString *, Class) = ^(NSString *objectCol, NSString *colName, Class cls) { RLMAssertCount(cls, 1U, @"%K ENDSWITH 'c'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH 'bc'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH 'abc'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH 'aabc'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH 'bbc'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH 'a'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH 'C'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH ''", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH[c] 'c'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH[c] 'C'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[c] ''", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[d] ''", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[cd] ''", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH 'u'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH[c] 'U'", colName); RLMAssertCount(cls, 2U, @"%K ENDSWITH[d] 'u'", colName); RLMAssertCount(cls, 2U, @"%K ENDSWITH[cd] 'U'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH 'ü'", colName); RLMAssertCount(cls, 1U, @"%K ENDSWITH[c] 'Ü'", colName); RLMAssertCount(cls, 2U, @"%K ENDSWITH[d] 'ü'", colName); RLMAssertCount(cls, 2U, @"%K ENDSWITH[cd] 'Ü'", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH NULL", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[c] NULL", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[d] NULL", colName); RLMAssertCount(cls, 0U, @"%K ENDSWITH[cd] NULL", colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K ENDSWITH 'c'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH 'a'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH 'C'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH ''", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K ENDSWITH[c] 'c'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K ENDSWITH[c] 'C'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[c] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[d] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[cd] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[c] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[d] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K ENDSWITH[cd] NULL", objectCol, colName); }; testBlock(@"objectCol", @"stringCol", [StringObject class]); testBlock(@"mixedObjectCol", @"anyCol", [MixedObject class]); } - (void)testStringContains { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:@[@"abc"]]; [StringObject createInRealm:realm withValue:@[@"tüv"]]; [StringObject createInRealm:realm withValue:@[@"tûv"]]; [StringObject createInRealm:realm withValue:@[@"tuv"]]; AllTypesObject *ato = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; ato.anyCol = @"abc"; // overwrite int ato.mixedObjectCol = [MixedObject createInRealm:realm withValue:@[@"abc"]]; [MixedObject createInRealm:realm withValue:@[@"tüv"]]; [MixedObject createInRealm:realm withValue:@[@"tûv"]]; [MixedObject createInRealm:realm withValue:@[@"tuv"]]; [realm commitWriteTransaction]; void (^testBlock)(NSString *, NSString *, Class) = ^(NSString *objectCol, NSString *colName, Class cls) { RLMAssertCount(cls, 1U, @"%K CONTAINS 'a'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'b'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'c'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'ab'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'bc'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'abc'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS 'd'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS 'aabc'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS 'bbc'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS ''", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS 'C'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS[c] 'c'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS[c] 'C'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[c] ''", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'u'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS[c] 'U'", colName); RLMAssertCount(cls, 3U, @"%K CONTAINS[d] 'u'", colName); RLMAssertCount(cls, 3U, @"%K CONTAINS[cd] 'U'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[d] ''", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[cd] ''", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS 'ü'", colName); RLMAssertCount(cls, 1U, @"%K CONTAINS[c] 'Ü'", colName); RLMAssertCount(cls, 3U, @"%K CONTAINS[d] 'ü'", colName); RLMAssertCount(cls, 3U, @"%K CONTAINS[cd] 'Ü'", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS NULL", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[c] NULL", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[d] NULL", colName); RLMAssertCount(cls, 0U, @"%K CONTAINS[cd] NULL", colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS 'd'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K CONTAINS 'c'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS 'C'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS ''", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K CONTAINS[c] 'c'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K CONTAINS[c] 'C'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[c] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[d] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[cd] ''", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[c] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[d] NULL", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K CONTAINS[cd] NULL", objectCol, colName); }; testBlock(@"objectCol", @"stringCol", [StringObject class]); testBlock(@"mixedObjectCol", @"anyCol", [MixedObject class]); } - (void)testStringLike { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; AllTypesObject *ato = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; ato.mixedObjectCol = [MixedObject createInRealm:realm withValue:@[@"abc"]]; [realm commitWriteTransaction]; void (^testBlock)(NSString *, NSString *, Class) = ^(NSString *objectCol, NSString *colName, Class cls) { RLMAssertCount(cls, 1U, @"%K LIKE '*a*'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '*b*'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '*c'", colName); RLMAssertCount(cls, 1U, @"%K LIKE 'ab*'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '*bc'", colName); RLMAssertCount(cls, 1U, @"%K LIKE 'a*bc'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '*abc*'", colName); RLMAssertCount(cls, 0U, @"%K LIKE '*d*'", colName); RLMAssertCount(cls, 0U, @"%K LIKE 'aabc'", colName); RLMAssertCount(cls, 0U, @"%K LIKE 'b*bc'", colName); RLMAssertCount(cls, 1U, @"%K LIKE 'a?" "?'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '?b?'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '*?c'", colName); RLMAssertCount(cls, 1U, @"%K LIKE 'ab?'", colName); RLMAssertCount(cls, 1U, @"%K LIKE '?bc'", colName); RLMAssertCount(cls, 0U, @"%K LIKE '?d?'", colName); RLMAssertCount(cls, 0U, @"%K LIKE '?abc'", colName); RLMAssertCount(cls, 0U, @"%K LIKE 'b?bc'", colName); RLMAssertCount(cls, 0U, @"%K LIKE '*C*'", colName); RLMAssertCount(cls, 1U, @"%K LIKE[c] '*c*'", colName); RLMAssertCount(cls, 1U, @"%K LIKE[c] '*C*'", colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K LIKE '*d*'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K LIKE '*c*'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K LIKE '*C*'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K LIKE[c] '*c*'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K LIKE[c] '*C*'", objectCol, colName); RLMAssertThrowsWithReasonMatching(([cls objectsWhere:@"%K LIKE[d] '*'", colName]), @"'LIKE' not supported .* diacritic-insensitive"); RLMAssertThrowsWithReasonMatching(([cls objectsWhere:@"%K LIKE[cd] '*'", colName]), @"'LIKE' not supported .* diacritic-insensitive"); }; testBlock(@"objectCol", @"stringCol", [StringObject class]); testBlock(@"mixedObjectCol", @"anyCol", [MixedObject class]); } - (void)testStringEquality { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:@[@"abc"]]; [StringObject createInRealm:realm withValue:@[@"tüv"]]; [StringObject createInRealm:realm withValue:@[@"tûv"]]; [StringObject createInRealm:realm withValue:@[@"tuv"]]; AllTypesObject *ato = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; ato.anyCol = @"abc"; // overwrite int ato.mixedObjectCol = [MixedObject createInRealm:realm withValue:@[@"abc"]]; [MixedObject createInRealm:realm withValue:@[@"tüv"]]; [MixedObject createInRealm:realm withValue:@[@"tûv"]]; [MixedObject createInRealm:realm withValue:@[@"tuv"]]; [realm commitWriteTransaction]; void (^testBlock)(NSString *, NSString *, Class) = ^(NSString *objectCol, NSString *colName, Class cls) { RLMAssertCount(cls, 1U, @"%K == 'abc'", colName); RLMAssertCount(cls, 4U, @"%K != 'def'", colName); RLMAssertCount(cls, 1U, @"%K ==[c] 'abc'", colName); RLMAssertCount(cls, 1U, @"%K ==[c] 'ABC'", colName); RLMAssertCount(cls, 3U, @"%K != 'abc'", colName); RLMAssertCount(cls, 0U, @"%K == 'def'", colName); RLMAssertCount(cls, 0U, @"%K == 'ABC'", colName); RLMAssertCount(cls, 1U, @"%K == 'tuv'", colName); RLMAssertCount(cls, 1U, @"%K ==[c] 'TUV'", colName); RLMAssertCount(cls, 3U, @"%K ==[d] 'tuv'", colName); RLMAssertCount(cls, 3U, @"%K ==[cd] 'TUV'", colName); RLMAssertCount(cls, 3U, @"%K != 'tuv'", colName); RLMAssertCount(cls, 3U, @"%K !=[c] 'TUV'", colName); RLMAssertCount(cls, 1U, @"%K !=[d] 'tuv'", colName); RLMAssertCount(cls, 1U, @"%K !=[cd] 'TUV'", colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K == 'abc'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K != 'def'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K ==[c] 'abc'", objectCol, colName); RLMAssertCount(AllTypesObject, 1U, @"%K.%K ==[c] 'ABC'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K != 'abc'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K == 'def'", objectCol, colName); RLMAssertCount(AllTypesObject, 0U, @"%K.%K == 'ABC'", objectCol, colName); }; testBlock(@"objectCol", @"stringCol", [StringObject class]); testBlock(@"mixedObjectCol", @"anyCol", [MixedObject class]); } - (void)testFloatQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [FloatObject createInRealm:realm withValue:@[@1.7f]]; [MixedObject createInRealm:realm withValue:@[@1.7f]]; [realm commitWriteTransaction]; RLMAssertCount(FloatObject, 1U, @"floatCol > 1"); RLMAssertCount(FloatObject, 1U, @"floatCol > %d", 1); RLMAssertCount(FloatObject, 1U, @"floatCol = 1.7"); RLMAssertCount(FloatObject, 1U, @"floatCol = %f", 1.7f); RLMAssertCount(FloatObject, 1U, @"floatCol > 1.0"); RLMAssertCount(FloatObject, 1U, @"floatCol >= 1.0"); RLMAssertCount(FloatObject, 0U, @"floatCol < 1.0"); RLMAssertCount(FloatObject, 0U, @"floatCol <= 1.0"); RLMAssertCount(FloatObject, 1U, @"floatCol = %e", 1.7); RLMAssertCount(FloatObject, 0U, @"floatCol == %f", FLT_MAX); RLMAssertCount(FloatObject, 1U, @"floatCol BETWEEN %@", @[@1.0, @2.0]); // Mixed requires you to specify floats explicitly. RLMAssertCount(MixedObject, 1U, @"anyCol > 1"); RLMAssertCount(MixedObject, 1U, @"anyCol > %lf", 1.0f); RLMAssertCount(MixedObject, 1U, @"anyCol = %@", @1.7f); RLMAssertCount(MixedObject, 1U, @"anyCol = %f", 1.7f); RLMAssertCount(MixedObject, 1U, @"anyCol > 1.0"); RLMAssertCount(MixedObject, 1U, @"anyCol >= 1.0"); RLMAssertCount(MixedObject, 0U, @"anyCol < 1.0"); RLMAssertCount(MixedObject, 0U, @"anyCol <= 1.0"); RLMAssertCount(MixedObject, 1U, @"anyCol = %e", 1.7f); RLMAssertCount(MixedObject, 0U, @"anyCol == %f", FLT_MAX); RLMAssertCount(MixedObject, 1U, @"anyCol BETWEEN %@", @[@1.0, @2.0]); XCTAssertThrows([FloatObject objectsInRealm:realm where:@"floatCol = 3.5e+38"], @"Too large to be a float"); XCTAssertThrows([FloatObject objectsInRealm:realm where:@"floatCol = -3.5e+38"], @"Too small to be a float"); } - (void)testDecimalQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [DecimalObject createInRealm:realm withValue:@[@"-Inf"]]; [DecimalObject createInRealm:realm withValue:@[@"Inf"]]; [DecimalObject createInRealm:realm withValue:@[@"123456789.123456789e1234"]]; [realm commitWriteTransaction]; RLMAssertCount(DecimalObject, 0U, @"decimalCol > 'Inf'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol >= 'Inf'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol == 'Inf'"); RLMAssertCount(DecimalObject, 3U, @"decimalCol <= 'Inf'"); RLMAssertCount(DecimalObject, 2U, @"decimalCol < 'Inf'"); RLMAssertCount(DecimalObject, 2U, @"decimalCol > '-Inf'"); RLMAssertCount(DecimalObject, 3U, @"decimalCol >= '-Inf'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol == '-Inf'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol <= '-Inf'"); RLMAssertCount(DecimalObject, 0U, @"decimalCol < '-Inf'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol > '123456789.123456789e1234'"); RLMAssertCount(DecimalObject, 2U, @"decimalCol >= '123456789.123456789e1234'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol == '123456789.123456789e1234'"); RLMAssertCount(DecimalObject, 2U, @"decimalCol <= '123456789.123456789e1234'"); RLMAssertCount(DecimalObject, 1U, @"decimalCol < '123456789.123456789e1234'"); } - (void)testLiveQueriesInsideTransaction { RLMRealm *realm = [self realm]; NSMutableArray *values = [@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @"", data(""), data("")] mutableCopy]; [realm beginWriteTransaction]; [self.queryObjectClass createInRealm:realm withValue:values]; RLMResults *resultsQuery = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; RLMResults *resultsTableView = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; // Force resultsTableView to form the TableView to verify that it syncs // correctly, and don't call anything but count on resultsQuery so that // it always reruns the query count method (void)[resultsTableView firstObject]; XCTAssertEqual(resultsQuery.count, 1U); XCTAssertEqual(resultsTableView.count, 1U); // Delete the (only) object in result set [realm deleteObject:[resultsTableView lastObject]]; XCTAssertEqual(resultsQuery.count, 0U); XCTAssertEqual(resultsTableView.count, 0U); // Add an object that does not match query values[0] = @NO; QueryObject *q1 = [self.queryObjectClass createInRealm:realm withValue:values]; XCTAssertEqual(resultsQuery.count, 0U); XCTAssertEqual(resultsTableView.count, 0U); // Change object to match query q1[@"bool1"] = @YES; XCTAssertEqual(resultsQuery.count, 1U); XCTAssertEqual(resultsTableView.count, 1U); // Add another object that matches values[0] = @YES; [self.queryObjectClass createInRealm:realm withValue:values]; XCTAssertEqual(resultsQuery.count, 2U); XCTAssertEqual(resultsTableView.count, 2U); [realm commitWriteTransaction]; } - (void)testLiveQueriesBetweenTransactions { RLMRealm *realm = [self realm]; NSMutableArray *values = [@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @"", data(""), data("")] mutableCopy]; [realm beginWriteTransaction]; [self.queryObjectClass createInRealm:realm withValue:values]; [realm commitWriteTransaction]; RLMResults *resultsQuery = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; RLMResults *resultsTableView = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; // Force resultsTableView to form the TableView to verify that it syncs // correctly, and don't call anything but count on resultsQuery so that // it always reruns the query count method (void)[resultsTableView firstObject]; XCTAssertEqual(resultsQuery.count, 1U); XCTAssertEqual(resultsTableView.count, 1U); // Delete the (only) object in result set [realm beginWriteTransaction]; [realm deleteObject:[resultsTableView lastObject]]; [realm commitWriteTransaction]; XCTAssertEqual(resultsQuery.count, 0U); XCTAssertEqual(resultsTableView.count, 0U); // Add an object that does not match query [realm beginWriteTransaction]; values[0] = @NO; QueryObject *q1 = [self.queryObjectClass createInRealm:realm withValue:values]; [realm commitWriteTransaction]; XCTAssertEqual(resultsQuery.count, 0U); XCTAssertEqual(resultsTableView.count, 0U); // Change object to match query [realm beginWriteTransaction]; q1[@"bool1"] = @YES; [realm commitWriteTransaction]; XCTAssertEqual(resultsQuery.count, 1U); XCTAssertEqual(resultsTableView.count, 1U); // Add another object that matches [realm beginWriteTransaction]; values[0] = @YES; [self.queryObjectClass createInRealm:realm withValue:values]; [realm commitWriteTransaction]; XCTAssertEqual(resultsQuery.count, 2U); XCTAssertEqual(resultsTableView.count, 2U); } - (void)makeDogWithName:(NSString *)name owner:(NSString *)ownerName { RLMRealm *realm = [self realm]; OwnerObject *owner = [[OwnerObject alloc] init]; owner.name = ownerName; owner.dog = [[DogObject alloc] init]; owner.dog.dogName = name; [realm beginWriteTransaction]; [realm addObject:owner]; [realm commitWriteTransaction]; } - (void)makeDogWithAge:(int)age owner:(NSString *)ownerName { RLMRealm *realm = [self realm]; OwnerObject *owner = [[OwnerObject alloc] init]; owner.name = ownerName; owner.dog = [[DogObject alloc] init]; owner.dog.dogName = @""; owner.dog.age = age; [realm beginWriteTransaction]; [realm addObject:owner]; [realm commitWriteTransaction]; } - (void)testLinkQueryNewObjectCausesEmptyResults { [self makeDogWithName:@"Harvie" owner:@"Tim"]; DogObject *newDogObject = [[DogObject alloc] init]; RLMAssertCount(OwnerObject, 0U, @"dog = %@", newDogObject); RLMAssertCount(OwnerObject, 1U, @"dog != %@", newDogObject); } - (void)testLinkQueryDifferentRealmsThrows { RLMRealm *testRealm = [self realmWithTestPath]; [self makeDogWithName:@"Harvie" owner:@"Tim"]; RLMRealm *defaultRealm = [self realm]; DogObject *dog = [[DogObject alloc] init]; dog.dogName = @"Fido"; [defaultRealm beginWriteTransaction]; [defaultRealm addObject:dog]; [defaultRealm commitWriteTransaction]; XCTAssertThrows(([OwnerObject objectsInRealm:testRealm where:@"dog = %@", dog])); } - (void)testLinkQueryString { [self makeDogWithName:@"Harvie" owner:@"Tim"]; RLMAssertCount(OwnerObject, 1U, @"dog.dogName = 'Harvie'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName != 'Harvie'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'eivraH'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'Fido'"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName IN {'Fido', 'Harvie'}"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName IN {'Fido', 'eivraH'}"); [self makeDogWithName:@"Harvie" owner:@"Joe"]; RLMAssertCount(OwnerObject, 2U, @"dog.dogName = 'Harvie'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName != 'Harvie'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'eivraH'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'Fido'"); RLMAssertCount(OwnerObject, 2U, @"dog.dogName IN {'Fido', 'Harvie'}"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName IN {'Fido', 'eivraH'}"); [self makeDogWithName:@"Fido" owner:@"Jim"]; RLMAssertCount(OwnerObject, 2U, @"dog.dogName = 'Harvie'"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName != 'Harvie'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'eivraH'"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName = 'Fido'"); RLMAssertCount(OwnerObject, 3U, @"dog.dogName IN {'Fido', 'Harvie'}"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName IN {'Fido', 'eivraH'}"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName = 'Harvie' and name = 'Tim'"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName = 'Harvie' and name = 'Jim'"); [self makeDogWithName:@"Rex" owner:@"Rex"]; RLMAssertCount(OwnerObject, 1U, @"dog.dogName = name"); RLMAssertCount(OwnerObject, 1U, @"name = dog.dogName"); RLMAssertCount(OwnerObject, 3U, @"dog.dogName != name"); RLMAssertCount(OwnerObject, 3U, @"name != dog.dogName"); RLMAssertCount(OwnerObject, 4U, @"dog.dogName == dog.dogName"); RLMAssertCount(OwnerObject, 0U, @"dog.dogName != dog.dogName"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName > 'Harvie'"); RLMAssertCount(OwnerObject, 3U, @"dog.dogName >= 'Harvie'"); RLMAssertCount(OwnerObject, 1U, @"dog.dogName < 'Harvie'"); RLMAssertCount(OwnerObject, 3U, @"dog.dogName <= 'Harvie'"); } - (void)testLinkQueryInt { [self makeDogWithAge:5 owner:@"Tim"]; RLMAssertCount(OwnerObject, 1U, @"dog.age = 5"); RLMAssertCount(OwnerObject, 0U, @"dog.age != 5"); RLMAssertCount(OwnerObject, 0U, @"dog.age = 10"); RLMAssertCount(OwnerObject, 0U, @"dog.age = 8"); RLMAssertCount(OwnerObject, 1U, @"dog.age IN {5, 8}"); RLMAssertCount(OwnerObject, 0U, @"dog.age IN {8, 10}"); RLMAssertCount(OwnerObject, 1U, @"dog.age BETWEEN {0, 10}"); RLMAssertCount(OwnerObject, 1U, @"dog.age BETWEEN {0, 7}"); [self makeDogWithAge:5 owner:@"Joe"]; RLMAssertCount(OwnerObject, 2U, @"dog.age = 5"); RLMAssertCount(OwnerObject, 0U, @"dog.age != 5"); RLMAssertCount(OwnerObject, 0U, @"dog.age = 10"); RLMAssertCount(OwnerObject, 0U, @"dog.age = 8"); RLMAssertCount(OwnerObject, 2U, @"dog.age IN {5, 8}"); RLMAssertCount(OwnerObject, 0U, @"dog.age IN {8, 10}"); RLMAssertCount(OwnerObject, 2U, @"dog.age BETWEEN {0, 10}"); RLMAssertCount(OwnerObject, 2U, @"dog.age BETWEEN {0, 7}"); [self makeDogWithAge:8 owner:@"Jim"]; RLMAssertCount(OwnerObject, 2U, @"dog.age = 5"); RLMAssertCount(OwnerObject, 1U, @"dog.age != 5"); RLMAssertCount(OwnerObject, 0U, @"dog.age = 10"); RLMAssertCount(OwnerObject, 1U, @"dog.age = 8"); RLMAssertCount(OwnerObject, 3U, @"dog.age IN {5, 8}"); RLMAssertCount(OwnerObject, 1U, @"dog.age IN {8, 10}"); RLMAssertCount(OwnerObject, 3U, @"dog.age BETWEEN {0, 10}"); RLMAssertCount(OwnerObject, 2U, @"dog.age BETWEEN {0, 7}"); } - (void)testLinkQueryAllTypes { RLMRealm *realm = [self realm]; LinkToAllTypesObject *linkToAllTypes = [[LinkToAllTypesObject alloc] init]; linkToAllTypes.allTypesCol = [[AllTypesObject alloc] initWithValue:[AllTypesObject values:1 stringObject:nil]]; StringObject *obj = [[StringObject alloc] initWithValue:@[@"string"]]; linkToAllTypes.allTypesCol.objectCol = obj; [realm beginWriteTransaction]; [realm addObject:linkToAllTypes]; [realm commitWriteTransaction]; RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.boolCol = YES"); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.boolCol = NO"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.intCol = 1"); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.intCol != 1"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.intCol > 0"); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.intCol > 1"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.floatCol = %f", 1.1); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.floatCol <= %f", 1.1); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.floatCol < %f", 1.1); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.doubleCol = 1.11"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.doubleCol >= 1.11"); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.doubleCol > 1.11"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.longCol = 2147483648"); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.longCol != 2147483648"); RLMAssertCount(LinkToAllTypesObject, 1U, @"allTypesCol.dateCol = %@", linkToAllTypes.allTypesCol.dateCol); RLMAssertCount(LinkToAllTypesObject, 0U, @"allTypesCol.dateCol != %@", linkToAllTypes.allTypesCol.dateCol); } - (void)testLinkQueryManyArray { RLMRealm *realm = [self realm]; ArrayPropertyObject *arrPropObj1 = [[ArrayPropertyObject alloc] init]; arrPropObj1.name = @"Test"; for (NSUInteger i=0; i<10; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; [arrPropObj1.array addObject:sobj]; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; [arrPropObj1.intArray addObject:iobj]; } [realm beginWriteTransaction]; [realm addObject:arrPropObj1]; [realm commitWriteTransaction]; RLMAssertCount(ArrayPropertyObject, 0U, @"ANY intArray.intCol > 10"); RLMAssertCount(ArrayPropertyObject, 1U, @"ANY intArray.intCol > 5"); RLMAssertCount(ArrayPropertyObject, 1U, @"ANY array.stringCol = '1'"); RLMAssertCount(ArrayPropertyObject, 0U, @"NONE intArray.intCol == 5"); RLMAssertCount(ArrayPropertyObject, 1U, @"NONE intArray.intCol > 10"); ArrayPropertyObject *arrPropObj2 = [[ArrayPropertyObject alloc] init]; arrPropObj2.name = @"Test"; for (NSUInteger i=0; i<4; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; [arrPropObj2.array addObject:sobj]; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; [arrPropObj2.intArray addObject:iobj]; } [realm beginWriteTransaction]; [realm addObject:arrPropObj2]; [realm commitWriteTransaction]; RLMAssertCount(ArrayPropertyObject, 0U, @"ANY intArray.intCol > 10"); RLMAssertCount(ArrayPropertyObject, 1U, @"ANY intArray.intCol > 5"); RLMAssertCount(ArrayPropertyObject, 2U, @"ANY intArray.intCol > 2"); RLMAssertCount(ArrayPropertyObject, 1U, @"NONE intArray.intCol == 5"); RLMAssertCount(ArrayPropertyObject, 2U, @"NONE intArray.intCol > 10"); } - (void)testLinkQueryManySet { RLMRealm *realm = [self realm]; SetPropertyObject *setPropObj1 = [[SetPropertyObject alloc] init]; setPropObj1.name = @"Test"; for (NSUInteger i=0; i<10; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; [setPropObj1.set addObject:sobj]; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; [setPropObj1.intSet addObject:iobj]; } [realm beginWriteTransaction]; [realm addObject:setPropObj1]; [realm commitWriteTransaction]; RLMAssertCount(SetPropertyObject, 0U, @"ANY intSet.intCol > 10"); RLMAssertCount(SetPropertyObject, 0U, @"ANY intSet.intCol > 10"); RLMAssertCount(SetPropertyObject, 1U, @"ANY intSet.intCol > 5"); RLMAssertCount(SetPropertyObject, 1U, @"ANY set.stringCol = '1'"); RLMAssertCount(SetPropertyObject, 0U, @"NONE intSet.intCol == 5"); RLMAssertCount(SetPropertyObject, 1U, @"NONE intSet.intCol > 10"); SetPropertyObject *setPropObj2 = [[SetPropertyObject alloc] init]; setPropObj2.name = @"Test"; for (NSUInteger i=0; i<4; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; [setPropObj2.set addObject:sobj]; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; [setPropObj2.intSet addObject:iobj]; } [realm beginWriteTransaction]; [realm addObject:setPropObj2]; [realm commitWriteTransaction]; RLMAssertCount(SetPropertyObject, 0U, @"ANY intSet.intCol > 10"); RLMAssertCount(SetPropertyObject, 1U, @"ANY intSet.intCol > 5"); RLMAssertCount(SetPropertyObject, 2U, @"ANY intSet.intCol > 2"); RLMAssertCount(SetPropertyObject, 1U, @"NONE intSet.intCol == 5"); RLMAssertCount(SetPropertyObject, 2U, @"NONE intSet.intCol > 10"); } - (void)testLinkQueryManyDictionaries { RLMRealm *realm = [self realm]; DictionaryPropertyObject *dpo1 = [[DictionaryPropertyObject alloc] init]; for (NSUInteger i=0; i<10; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; dpo1.stringDictionary[sobj.stringCol] = sobj; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; dpo1.intObjDictionary[sobj.stringCol] = iobj; } [realm beginWriteTransaction]; [realm addObject:dpo1]; [realm commitWriteTransaction]; RLMAssertCount(DictionaryPropertyObject, 0U, @"ANY intObjDictionary.intCol > 10"); RLMAssertCount(DictionaryPropertyObject, 1U, @"ANY intObjDictionary.intCol > 5"); RLMAssertCount(DictionaryPropertyObject, 1U, @"ANY stringDictionary.stringCol = '1'"); RLMAssertCount(DictionaryPropertyObject, 0U, @"NONE intObjDictionary.intCol == 5"); RLMAssertCount(DictionaryPropertyObject, 1U, @"NONE intObjDictionary.intCol > 10"); DictionaryPropertyObject *dpo2 = [[DictionaryPropertyObject alloc] init]; for (NSUInteger i=0; i<4; i++) { StringObject *sobj = [[StringObject alloc] init]; sobj.stringCol = @(i).stringValue; dpo2.stringDictionary[sobj.stringCol] = sobj; IntObject *iobj = [[IntObject alloc] init]; iobj.intCol = (int)i; dpo2.intObjDictionary[sobj.stringCol] = iobj; } [realm beginWriteTransaction]; [realm addObject:dpo2]; [realm commitWriteTransaction]; RLMAssertCount(DictionaryPropertyObject, 0U, @"ANY intObjDictionary.intCol > 10"); RLMAssertCount(DictionaryPropertyObject, 1U, @"ANY intObjDictionary.intCol > 5"); RLMAssertCount(DictionaryPropertyObject, 2U, @"ANY intObjDictionary.intCol > 2"); RLMAssertCount(DictionaryPropertyObject, 1U, @"NONE intObjDictionary.intCol == 5"); RLMAssertCount(DictionaryPropertyObject, 2U, @"NONE intObjDictionary.intCol > 10"); } - (void)testMultiLevelLinkQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; CircleObject *circle = nil; for (int i = 0; i < 5; ++i) { circle = [CircleObject createInRealm:realm withValue:@{@"data": @(i).stringValue, @"next": circle ?: NSNull.null}]; } [realm commitWriteTransaction]; XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"data = '4'"].firstObject]); XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.data = '3'"].firstObject]); XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.data = '2'"].firstObject]); XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.data = '1'"].firstObject]); XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.next.data = '0'"].firstObject]); XCTAssertTrue([circle.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.data = '0'"].firstObject]); XCTAssertTrue([circle.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.data = '0'"].firstObject]); XCTAssertNoThrow(([CircleObject objectsInRealm:realm where:@"next = %@", circle])); XCTAssertNoThrow(([CircleObject objectsInRealm:realm where:@"next.next = %@", circle])); XCTAssertTrue([circle.next.next.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next = nil"].firstObject]); XCTAssertTrue([circle.next.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next != nil AND next.next = nil"].firstObject]); XCTAssertTrue([circle.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next != nil AND next.next.next = nil"].firstObject]); } - (void)testArrayMultiLevelLinkQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; CircleObject *circle = nil; for (int i = 0; i < 5; ++i) { circle = [CircleObject createInRealm:realm withValue:@{@"data": @(i).stringValue, @"next": circle ?: NSNull.null}]; } [CircleArrayObject createInRealm:realm withValue:@[[CircleObject allObjectsInRealm:realm]]]; [realm commitWriteTransaction]; RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.data = '4'"); RLMAssertCount(CircleArrayObject, 0U, @"ANY circles.next.data = '4'"); RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.next.data = '3'"); RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.data = '3'"); RLMAssertCount(CircleArrayObject, 1U, @"NONE circles.next.data = '4'"); RLMAssertCount(CircleArrayObject, 0U, @"ANY circles.next.next.data = '3'"); RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.next.next.data = '2'"); RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.next.data = '2'"); RLMAssertCount(CircleArrayObject, 1U, @"ANY circles.data = '2'"); RLMAssertCount(CircleArrayObject, 1U, @"NONE circles.next.next.data = '3'"); XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY data = '2'"]); XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY circles.next = '2'"]); XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY data.circles = '2'"]); XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"circles.data = '2'"]); XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"NONE data.circles = '2'"]); } - (void)testSetMultiLevelLinkQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; CircleObject *circle = nil; for (int i = 0; i < 5; ++i) { circle = [CircleObject createInRealm:realm withValue:@{@"data": @(i).stringValue, @"next": circle ?: NSNull.null}]; } [CircleSetObject createInRealm:realm withValue:@[[CircleObject allObjectsInRealm:realm]]]; [realm commitWriteTransaction]; RLMAssertCount(CircleSetObject, 1U, @"ANY circles.data = '4'"); RLMAssertCount(CircleSetObject, 0U, @"ANY circles.next.data = '4'"); RLMAssertCount(CircleSetObject, 1U, @"ANY circles.next.data = '3'"); RLMAssertCount(CircleSetObject, 1U, @"ANY circles.data = '3'"); RLMAssertCount(CircleSetObject, 1U, @"NONE circles.next.data = '4'"); RLMAssertCount(CircleSetObject, 0U, @"ANY circles.next.next.data = '3'"); RLMAssertCount(CircleSetObject, 1U, @"ANY circles.next.next.data = '2'"); RLMAssertCount(CircleSetObject, 1U, @"ANY circles.next.data = '2'"); RLMAssertCount(CircleSetObject, 1U, @"ANY circles.data = '2'"); RLMAssertCount(CircleSetObject, 1U, @"NONE circles.next.next.data = '3'"); XCTAssertThrows([CircleSetObject objectsInRealm:realm where:@"ANY data = '2'"]); XCTAssertThrows([CircleSetObject objectsInRealm:realm where:@"ANY circles.next = '2'"]); XCTAssertThrows([CircleSetObject objectsInRealm:realm where:@"ANY data.circles = '2'"]); XCTAssertThrows([CircleSetObject objectsInRealm:realm where:@"circles.data = '2'"]); XCTAssertThrows([CircleSetObject objectsInRealm:realm where:@"NONE data.circles = '2'"]); } - (void)testDictionaryMultiLevelLinkQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; CircleObject *circle = nil; for (int i = 0; i < 5; ++i) { circle = [CircleObject createInRealm:realm withValue:@{@"data": @(i).stringValue, @"next": circle ?: NSNull.null}]; } CircleDictionaryObject *cdo = [CircleDictionaryObject createInRealm:realm withValue:@{}]; for (CircleObject *co in [CircleObject allObjectsInRealm:realm]) { cdo.circles[co.data] = co; } [realm commitWriteTransaction]; RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.data = '4'"); RLMAssertCount(CircleDictionaryObject, 0U, @"ANY circles.next.data = '4'"); RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.next.data = '3'"); RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.data = '3'"); RLMAssertCount(CircleDictionaryObject, 1U, @"NONE circles.next.data = '4'"); RLMAssertCount(CircleDictionaryObject, 0U, @"ANY circles.next.next.data = '3'"); RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.next.next.data = '2'"); RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.next.data = '2'"); RLMAssertCount(CircleDictionaryObject, 1U, @"ANY circles.data = '2'"); RLMAssertCount(CircleDictionaryObject, 1U, @"NONE circles.next.next.data = '3'"); XCTAssertThrows([CircleDictionaryObject objectsInRealm:realm where:@"ANY data = '2'"]); XCTAssertThrows([CircleDictionaryObject objectsInRealm:realm where:@"ANY circles.next = '2'"]); XCTAssertThrows([CircleDictionaryObject objectsInRealm:realm where:@"ANY data.circles = '2'"]); XCTAssertThrows([CircleDictionaryObject objectsInRealm:realm where:@"NONE data.circles = '2'"]); XCTAssertThrows([CircleDictionaryObject objectsInRealm:realm where:@"circles.data = '2'"]); } - (void)testMultiLevelBackLinkQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; LinkChain1 *root1 = [LinkChain1 createInRealm:realm withValue:@{@"value": @1, @"next": @[@[]]}]; LinkChain1 *root2 = [LinkChain1 createInRealm:realm withValue:@{@"value": @2, @"next": @[@[]]}]; [realm commitWriteTransaction]; RLMResults *results = [LinkChain3 objectsInRealm:realm where:@"ANY prev.prev.value = 1"]; XCTAssertEqual(1U, results.count); XCTAssertTrue([root1.next.next isEqualToObject:results.firstObject]); results = [LinkChain3 objectsInRealm:realm where:@"ANY prev.prev.value = 2"]; XCTAssertEqual(1U, results.count); XCTAssertTrue([root2.next.next isEqualToObject:results.firstObject]); results = [LinkChain3 objectsInRealm:realm where:@"ANY prev.prev.value = 3"]; XCTAssertEqual(0U, results.count); } - (void)testQueryWithObjects { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *stringObj0 = [StringObject createInRealm:realm withValue:@[@"string0"]]; StringObject *stringObj1 = [StringObject createInRealm:realm withValue:@[@"string1"]]; StringObject *stringObj2 = [StringObject createInRealm:realm withValue:@[@"string2"]]; AllTypesObject *obj0 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:stringObj0]]; AllTypesObject *obj1 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:stringObj1]]; AllTypesObject *obj2 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:stringObj0]]; AllTypesObject *obj3 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:4 stringObject:stringObj2]]; AllTypesObject *obj4 = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:5 stringObject:nil]]; [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj1]]]; [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj1]]]; [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj2, obj3]]]; [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj4]]]; [SetOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj1]]]; [SetOfAllTypesObject createInDefaultRealmWithValue:@[@[obj1]]]; [SetOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj2, obj3]]]; [SetOfAllTypesObject createInDefaultRealmWithValue:@[@[obj4]]]; [DictionaryOfAllTypesObject createInDefaultRealmWithValue:@[@{@"0": obj0, @"1": obj1}]]; [DictionaryOfAllTypesObject createInDefaultRealmWithValue:@[@{@"1": obj1}]]; [DictionaryOfAllTypesObject createInDefaultRealmWithValue:@[@{@"0": obj0, @"2": obj2, @"3": obj3}]]; [DictionaryOfAllTypesObject createInDefaultRealmWithValue:@[@{@"4": obj4}]]; [realm commitWriteTransaction]; // simple queries RLMAssertCount(AllTypesObject, 2U, @"objectCol = %@", stringObj0); RLMAssertCount(AllTypesObject, 1U, @"objectCol = %@", stringObj1); RLMAssertCount(AllTypesObject, 1U, @"objectCol = nil"); RLMAssertCount(AllTypesObject, 4U, @"objectCol != nil"); RLMAssertCount(AllTypesObject, 3U, @"objectCol != %@", stringObj0); // check for ANY object in array RLMAssertCount(ArrayOfAllTypesObject, 2U, @"ANY array = %@", obj0); RLMAssertCount(ArrayOfAllTypesObject, 3U, @"ANY array != %@", obj1); RLMAssertCount(ArrayOfAllTypesObject, 2U, @"NONE array = %@", obj0); RLMAssertCount(ArrayOfAllTypesObject, 1U, @"NONE array != %@", obj1); XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array = %@", obj0].count)); XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array != %@", obj0].count)); // check for ANY object in set RLMAssertCount(SetOfAllTypesObject, 2U, @"ANY set = %@", obj0); RLMAssertCount(SetOfAllTypesObject, 3U, @"ANY set != %@", obj1); RLMAssertCount(SetOfAllTypesObject, 2U, @"NONE set = %@", obj0); RLMAssertCount(SetOfAllTypesObject, 1U, @"NONE set != %@", obj1); XCTAssertThrows(([SetOfAllTypesObject objectsWhere:@"set = %@", obj0].count)); XCTAssertThrows(([SetOfAllTypesObject objectsWhere:@"set != %@", obj0].count)); // check for ANY object in dictionary RLMAssertCount(DictionaryOfAllTypesObject, 2U, @"ANY dictionary = %@", obj0); RLMAssertCount(DictionaryOfAllTypesObject, 3U, @"ANY dictionary != %@", obj1); RLMAssertCount(DictionaryOfAllTypesObject, 2U, @"NONE dictionary = %@", obj0); RLMAssertCount(DictionaryOfAllTypesObject, 1U, @"NONE dictionary != %@", obj1); XCTAssertThrows(([DictionaryOfAllTypesObject objectsWhere:@"dictionary = %@", obj0].count)); XCTAssertThrows(([DictionaryOfAllTypesObject objectsWhere:@"dictionary != %@", obj0].count)); } - (void)testCompoundOrQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; RLMAssertCount(PersonObject, 2U, @"name == 'Ari' or age < 30"); RLMAssertCount(PersonObject, 1U, @"name == 'Ari' or age > 40"); } - (void)testCompoundAndQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; RLMAssertCount(PersonObject, 1U, @"name == 'Ari' and age > 30"); RLMAssertCount(PersonObject, 0U, @"name == 'Ari' and age > 40"); } - (void)testClass:(Class)class withNormalCount:(NSUInteger)normalCount notCount:(NSUInteger)notCount where:(NSString *)predicateFormat, ... { va_list args; va_start(args, predicateFormat); NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat arguments:args]; va_end(args); XCTAssertEqual(normalCount, [[self evaluate:[class objectsWithPredicate:predicate]] count], @"%@", predicateFormat); predicate = [NSCompoundPredicate notPredicateWithSubpredicate:predicate]; XCTAssertEqual(notCount, [[self evaluate:[class objectsWithPredicate:predicate]] count], @"%@", predicateFormat); } - (void)testINPredicate { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; AllTypesObject *obj = [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:so]]; [realm commitWriteTransaction]; // Tests for each type always follow: none, some, more //////////////////////// // Literal Predicates //////////////////////// // BOOL [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"boolCol IN {NO}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"boolCol IN {YES}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"boolCol IN {NO, YES}"]; // int [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"intCol IN {0, 2, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"intCol IN {1}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"intCol IN {1, 2}"]; // float [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"floatCol IN {0, 2, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"floatCol IN {1.1}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"floatCol IN {1.1, 2.2}"]; // double [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"doubleCol IN {0, 2, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"doubleCol IN {1.11}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"doubleCol IN {1.11, 2.22}"]; // NSString [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN {'a'}"]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"stringCol IN {'b'}"]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"stringCol IN {'A'}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN[c] {'a'}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN[c] {'A'}"]; // NSData // Can't represent NSData with NSPredicate literal. See format predicates below // NSDate // Can't represent NSDate with NSPredicate literal. See format predicates below // bool [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"cBoolCol IN {NO}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"cBoolCol IN {YES}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"cBoolCol IN {NO, YES}"]; // int64_t [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"longCol IN {0, 2, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"longCol IN {2147483648}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"longCol IN {100, 2147483648}"]; // string subobject [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN {'abc'}"]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"objectCol.stringCol IN {'def'}"]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"objectCol.stringCol IN {'ABC'}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN[c] {'abc'}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN[c] {'ABC'}"]; // RLMDecimal128 [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"decimalCol IN {0, 2, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN {1}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN {1, 2}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN {'1', '2'}"]; // RLMValue [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"anyCol IN {0, 1, 3}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"anyCol IN {2}"]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"anyCol IN {1, 2}"]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"anyCol IN {'1', '2'}"]; // RLMObjectId // Can't represent RLMObjectId with NSPredicate literal. See format predicates below //////////////////////// // Format Predicates //////////////////////// // BOOL [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"boolCol IN %@", @[@NO]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"boolCol IN %@", @[@YES]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"boolCol IN %@", @[@NO, @YES]]; // int [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"intCol IN %@", @[@0, @2, @3]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"intCol IN %@", @[@1]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"intCol IN %@", @[@1, @2]]; // float [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"floatCol IN %@", @[@0, @2, @3]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"floatCol IN %@", @[@1.1f]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"floatCol IN %@", @[@1.1f, @2]]; // double [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"doubleCol IN %@", @[@0, @2, @3]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"doubleCol IN %@", @[@1.11]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"doubleCol IN %@", @[@1.11, @2]]; // NSString [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN %@", @[@"a"]]; [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"stringCol IN %@", @[@"b"]]; [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"stringCol IN %@", @[@"A"]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN[c] %@", @[@"a"]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN[c] %@", @[@"A"]]; // NSData [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"binaryCol IN %@", @[[@"" dataUsingEncoding:NSUTF8StringEncoding]]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"binaryCol IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"binaryCol IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding], [@"b" dataUsingEncoding:NSUTF8StringEncoding]]]; // NSDate [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:0]]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:0], [NSDate dateWithTimeIntervalSince1970:1]]]; // bool [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"cBoolCol IN %@", @[@NO]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"cBoolCol IN %@", @[@YES]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"cBoolCol IN %@", @[@NO, @YES]]; // int64_t [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"longCol IN %@", @[@0, @2, @3]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"longCol IN %@", @[@(INT_MAX + 1LL)]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"longCol IN %@", @[@(INT_MAX + 1LL), @2]]; // string subobject [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN %@", @[@"abc"]]; [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"objectCol.stringCol IN %@", @[@"def"]]; [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"objectCol.stringCol IN %@", @[@"ABC"]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN[c] %@", @[@"abc"]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN[c] %@", @[@"ABC"]]; // RLMDecimal128 [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"decimalCol IN %@", @[@0, @2, @3]]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"decimalCol IN %@", @[@"0", @"2", @"3"]]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN %@", @[@1]]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN %@", @[@1, @2]]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"decimalCol IN %@", @[@"1", @"2"]]; // RLMObjectId RLMObjectId *objectId = obj.objectIdCol; RLMObjectId *otherId = [RLMObjectId objectId]; [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"objectIdCol IN %@", @[otherId]]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectIdCol IN %@", @[objectId]]; [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectIdCol IN %@", @[objectId, otherId]]; // RLMValue [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"anyCol IN %@", @[@0, @1, @3]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"anyCol IN %@", @[@2]]; [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"anyCol IN %@", @[@1, @2]]; } - (void)testArrayIn { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; ArrayPropertyObject *arr = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", @[], @[]]]; [arr.array addObject:[StringObject createInRealm:realm withValue:@[@"value"]]]; StringObject *otherStringObject = [StringObject createInRealm:realm withValue:@[@"some other value"]]; [realm commitWriteTransaction]; RLMAssertCount(ArrayPropertyObject, 0U, @"ANY array.stringCol IN %@", @[@"missing"]); RLMAssertCount(ArrayPropertyObject, 1U, @"ANY array.stringCol IN %@", @[@"value"]); RLMAssertCount(ArrayPropertyObject, 1U, @"NONE array.stringCol IN %@", @[@"missing"]); RLMAssertCount(ArrayPropertyObject, 0U, @"NONE array.stringCol IN %@", @[@"value"]); RLMAssertCount(ArrayPropertyObject, 0U, @"ANY array IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(ArrayPropertyObject, 1U, @"ANY array IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); RLMAssertCount(ArrayPropertyObject, 1U, @"NONE array IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(ArrayPropertyObject, 0U, @"NONE array IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); StringObject *stringObject = [[StringObject allObjectsInRealm:realm] firstObject]; RLMAssertCount(ArrayPropertyObject, 1U, @"%@ IN array", stringObject); RLMAssertCount(ArrayPropertyObject, 0U, @"%@ IN array", otherStringObject); } - (void)testSetIn { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; SetPropertyObject *s = [SetPropertyObject createInRealm:realm withValue:@[@"name", @[], @[]]]; [s.set addObject:[StringObject createInRealm:realm withValue:@[@"value"]]]; StringObject *otherStringObject = [StringObject createInRealm:realm withValue:@[@"some other value"]]; [realm commitWriteTransaction]; RLMAssertCount(SetPropertyObject, 0U, @"ANY set.stringCol IN %@", @[@"missing"]); RLMAssertCount(SetPropertyObject, 1U, @"ANY set.stringCol IN %@", @[@"value"]); RLMAssertCount(SetPropertyObject, 1U, @"NONE set.stringCol IN %@", @[@"missing"]); RLMAssertCount(SetPropertyObject, 0U, @"NONE set.stringCol IN %@", @[@"value"]); RLMAssertCount(SetPropertyObject, 0U, @"ANY set IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(SetPropertyObject, 1U, @"ANY set IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); RLMAssertCount(SetPropertyObject, 1U, @"NONE set IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(SetPropertyObject, 0U, @"NONE set IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); StringObject *stringObject = [[StringObject allObjectsInRealm:realm] firstObject]; RLMAssertCount(SetPropertyObject, 1U, @"%@ IN set", stringObject); RLMAssertCount(SetPropertyObject, 0U, @"%@ IN set", otherStringObject); } - (void)testDictionaryIn { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; DictionaryPropertyObject *dict = [DictionaryPropertyObject createInRealm:realm withValue:@[]]; dict.stringDictionary[@"value"] = [StringObject createInRealm:realm withValue:@[@"value"]]; StringObject *otherStringObject = [StringObject createInRealm:realm withValue:@[@"some other value"]]; [realm commitWriteTransaction]; RLMAssertCount(DictionaryPropertyObject, 0U, @"ANY stringDictionary.stringCol IN %@", @[@"missing"]); RLMAssertCount(DictionaryPropertyObject, 1U, @"ANY stringDictionary.stringCol IN %@", @[@"value"]); RLMAssertCount(DictionaryPropertyObject, 1U, @"NONE stringDictionary.stringCol IN %@", @[@"missing"]); RLMAssertCount(DictionaryPropertyObject, 0U, @"NONE stringDictionary.stringCol IN %@", @[@"value"]); RLMAssertCount(DictionaryPropertyObject, 0U, @"ANY stringDictionary IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(DictionaryPropertyObject, 1U, @"ANY stringDictionary IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); RLMAssertCount(DictionaryPropertyObject, 1U, @"NONE stringDictionary IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]); RLMAssertCount(DictionaryPropertyObject, 0U, @"NONE stringDictionary IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]); StringObject *stringObject = [[StringObject allObjectsInRealm:realm] firstObject]; RLMAssertCount(DictionaryPropertyObject, 1U, @"%@ IN stringDictionary", stringObject); RLMAssertCount(DictionaryPropertyObject, 0U, @"%@ IN stringDictionary", otherStringObject); } - (void)testQueryChaining { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; RLMAssertCount(PersonObject, 1U, @"name == 'Ari'"); RLMAssertCount(PersonObject, 0U, @"name == 'Ari' and age == 29"); XCTAssertEqual(0U, [[[PersonObject objectsWhere:@"name == 'Ari'"] objectsWhere:@"age == 29"] count]); } - (void)testLinkViewQuery { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *employees = @[@{@"name": @"John", @"age": @30, @"hired": @NO}, @{@"name": @"Joe", @"age": @40, @"hired": @YES}, @{@"name": @"Jill", @"age": @50, @"hired": @YES}]; CompanyObject *company = [CompanyObject createInRealm:realm withValue:@[@"company name", employees, employees, @{}]]; for (NSDictionary *eData in employees) { company.employeeDict[eData[@"name"]] = [[EmployeeObject alloc] initWithValue:eData]; } [realm commitWriteTransaction]; CompanyObject *co = [CompanyObject allObjects][0]; RLMAssertCount(co.employees, 1U, @"hired = NO"); RLMAssertCount(co.employees, 2U, @"hired = YES"); RLMAssertCount(co.employees, 1U, @"hired = YES AND age = 40"); RLMAssertCount(co.employees, 0U, @"hired = YES AND age = 30"); RLMAssertCount(co.employees, 3U, @"hired = YES OR age = 30"); RLMAssertCount([co.employees, 1U, @"hired = YES"] objectsWhere:@"name = 'Joe'"); RLMAssertCount(co.employeeSet, 1U, @"hired = NO"); RLMAssertCount(co.employeeSet, 2U, @"hired = YES"); RLMAssertCount(co.employeeSet, 1U, @"hired = YES AND age = 40"); RLMAssertCount(co.employeeSet, 0U, @"hired = YES AND age = 30"); RLMAssertCount(co.employeeSet, 3U, @"hired = YES OR age = 30"); RLMAssertCount([co.employeeSet, 1U, @"hired = YES"] objectsWhere:@"name = 'Joe'"); RLMAssertCount(co.employeeDict, 1U, @"hired = NO"); RLMAssertCount(co.employeeDict, 2U, @"hired = YES"); RLMAssertCount(co.employeeDict, 1U, @"hired = YES AND age = 40"); RLMAssertCount(co.employeeDict, 0U, @"hired = YES AND age = 30"); RLMAssertCount(co.employeeDict, 3U, @"hired = YES OR age = 30"); RLMAssertCount([co.employeeDict, 1U, @"hired = YES"] objectsWhere:@"name = 'Joe'"); } - (void)testLinkViewQueryLifetime { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *employees = @[@{@"name": @"John", @"age": @30, @"hired": @NO}, @{@"name": @"Jill", @"age": @50, @"hired": @YES}]; CompanyObject *company = [CompanyObject createInRealm:realm withValue:@[@"company name", employees, employees]]; for (NSDictionary *eData in employees) { company.employeeDict[eData[@"name"]] = [[EmployeeObject alloc] initWithValue:eData]; } [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; RLMResults *subarray = nil; RLMResults *subarray2 = nil; RLMResults *subarray3 = nil; @autoreleasepool { __attribute((objc_precise_lifetime)) CompanyObject *co = [CompanyObject allObjects][0]; subarray = [co.employees objectsWhere:@"age = 40"]; subarray2 = [co.employeeSet objectsWhere:@"age = 40"]; subarray3 = [co.employeeDict objectsWhere:@"age = 40"]; XCTAssertEqual(0U, subarray.count); XCTAssertEqual(0U, subarray2.count); XCTAssertEqual(0U, subarray3.count); } [realm beginWriteTransaction]; @autoreleasepool { __attribute((objc_precise_lifetime)) CompanyObject *co = [CompanyObject allObjects][0]; [co.employees addObject:[EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]]; [co.employeeSet addObject:[EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]]; co.employeeDict[@"Joe"] = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; } [realm commitWriteTransaction]; XCTAssertEqual(1U, subarray.count); XCTAssertEqualObjects(@"Joe", subarray[0][@"name"]); XCTAssertEqual(1U, subarray2.count); XCTAssertEqualObjects(@"Joe", subarray2[0][@"name"]); XCTAssertEqual(1U, subarray3.count); XCTAssertEqualObjects(@"Joe", subarray3[0][@"name"]); } - (void)testLinkViewQueryLiveUpdate { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *employees = @[@{@"name": @"John", @"age": @30, @"hired": @NO}, @{@"name": @"Jill", @"age": @40, @"hired": @YES}]; CompanyObject *company = [CompanyObject createInRealm:realm withValue:@[@"company name", employees, employees]]; for (NSDictionary *eData in employees) { company.employeeDict[eData[@"name"]] = [[EmployeeObject alloc] initWithValue:eData]; } EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; CompanyObject *co = CompanyObject.allObjects.firstObject; RLMResults *basic = [co.employees objectsWhere:@"age = 40"]; RLMResults *sort = [co.employees sortedResultsUsingKeyPath:@"name" ascending:YES]; RLMResults *sortQuery = [[co.employees sortedResultsUsingKeyPath:@"name" ascending:YES] objectsWhere:@"age = 40"]; RLMResults *querySort = [[co.employees objectsWhere:@"age = 40"] sortedResultsUsingKeyPath:@"name" ascending:YES]; RLMResults *basic2 = [co.employeeSet objectsWhere:@"age = 40"]; RLMResults *sort2 = [co.employeeSet sortedResultsUsingKeyPath:@"name" ascending:YES]; RLMResults *sortQuery2 = [[co.employeeSet sortedResultsUsingKeyPath:@"name" ascending:YES] objectsWhere:@"age = 40"]; RLMResults *querySort2 = [[co.employeeSet objectsWhere:@"age = 40"] sortedResultsUsingKeyPath:@"name" ascending:YES]; RLMResults *basic3 = [co.employeeDict objectsWhere:@"age = 40"]; RLMResults *sort3 = [co.employeeDict sortedResultsUsingKeyPath:@"name" ascending:YES]; RLMResults *sortQuery3 = [[co.employeeDict sortedResultsUsingKeyPath:@"name" ascending:YES] objectsWhere:@"age = 40"]; RLMResults *querySort3 = [[co.employeeDict objectsWhere:@"age = 40"] sortedResultsUsingKeyPath:@"name" ascending:YES]; XCTAssertEqual(1U, basic.count); XCTAssertEqual(2U, sort.count); XCTAssertEqual(1U, sortQuery.count); XCTAssertEqual(1U, querySort.count); XCTAssertEqual(1U, basic2.count); XCTAssertEqual(2U, sort2.count); XCTAssertEqual(1U, sortQuery2.count); XCTAssertEqual(1U, querySort2.count); XCTAssertEqual(1U, basic3.count); XCTAssertEqual(2U, sort3.count); XCTAssertEqual(1U, sortQuery3.count); XCTAssertEqual(1U, querySort3.count); XCTAssertEqualObjects(@"Jill", [[basic lastObject] name]); XCTAssertEqualObjects(@"Jill", [[sortQuery lastObject] name]); XCTAssertEqualObjects(@"Jill", [[querySort lastObject] name]); XCTAssertEqualObjects(@"Jill", [[basic2 lastObject] name]); XCTAssertEqualObjects(@"Jill", [[sortQuery2 lastObject] name]); XCTAssertEqualObjects(@"Jill", [[querySort2 lastObject] name]); XCTAssertEqualObjects(@"Jill", [[basic3 lastObject] name]); XCTAssertEqualObjects(@"Jill", [[sortQuery3 lastObject] name]); XCTAssertEqualObjects(@"Jill", [[querySort3 lastObject] name]); [realm beginWriteTransaction]; [co.employees addObject:eo]; [co.employeeSet addObject:eo]; co.employeeDict[eo.name] = eo; [realm commitWriteTransaction]; XCTAssertEqual(2U, basic.count); XCTAssertEqual(3U, sort.count); XCTAssertEqual(2U, sortQuery.count); XCTAssertEqual(2U, querySort.count); XCTAssertEqual(2U, basic2.count); XCTAssertEqual(3U, sort2.count); XCTAssertEqual(2U, sortQuery2.count); XCTAssertEqual(2U, querySort2.count); XCTAssertEqual(2U, basic3.count); XCTAssertEqual(3U, sort3.count); XCTAssertEqual(2U, sortQuery3.count); XCTAssertEqual(2U, querySort3.count); XCTAssertEqualObjects(@"Joe", [[basic lastObject] name]); XCTAssertEqualObjects(@"Joe", [[sortQuery lastObject] name]); XCTAssertEqualObjects(@"Joe", [[querySort lastObject] name]); XCTAssertEqualObjects(@"Joe", [[basic2 lastObject] name]); XCTAssertEqualObjects(@"Joe", [[sortQuery2 lastObject] name]); XCTAssertEqualObjects(@"Joe", [[querySort2 lastObject] name]); XCTAssertEqualObjects(@"Joe", [[basic3 lastObject] name]); XCTAssertEqualObjects(@"Joe", [[sortQuery3 lastObject] name]); XCTAssertEqualObjects(@"Joe", [[querySort3 lastObject] name]); } - (void)testConstantPredicates { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; RLMResults *all = [PersonObject objectsWithPredicate:[NSPredicate predicateWithValue:YES]]; XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); RLMResults *none = [PersonObject objectsWithPredicate:[NSPredicate predicateWithValue:NO]]; XCTAssertEqual(none.count, 0U, @"Expecting 0 results"); } - (void)testEmptyCompoundPredicates { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; [realm commitWriteTransaction]; RLMResults *all = [PersonObject objectsWithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:@[]]]; XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); RLMResults *none = [PersonObject objectsWithPredicate:[NSCompoundPredicate orPredicateWithSubpredicates:@[]]]; XCTAssertEqual(none.count, 0U, @"Expecting 0 results"); } static NSData *data(const char *str) { return [NSData dataWithBytes:str length:strlen(str)]; } - (NSArray *)queryObjectClassValues { RLMObjectId *oid1 = [RLMObjectId objectId]; RLMObjectId *oid2 = [RLMObjectId objectId]; return @[ @[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"a", @"a", data("a"), data("a"), @1, @2, oid1, oid1, @YES, @NO], @[@YES, @NO, @1, @3, @-5.3f, @4.21f, @1.0, @4.44, @"a", @"A", data("a"), data("A"), @1, @3, oid1, oid2, @1, @2], @[@NO, @NO, @2, @2, @1.0f, @3.55f, @99.9, @6.66, @"a", @"ab", data("a"), data("ab"), @2, @2, oid2, oid2, @1.0f, @2.0f], @[@NO, @YES, @3, @6, @4.21f, @1.0f, @1.0, @7.77, @"a", @"AB", data("a"), data("AB"), @3, @6, oid2, oid1, @"one", @"two"], @[@YES, @YES, @4, @5, @23.0f, @23.0f, @7.4, @8.88, @"a", @"b", data("a"), data("b"), @4, @5, oid1, oid1, @"two", @"three"], @[@YES, @NO, @15, @8, @1.0f, @66.0f, @1.01, @9.99, @"a", @"ba", data("a"), data("ba"), @15, @8, oid1, oid2, data("a"), data("b")], @[@NO, @YES, @15, @15, @1.0f, @66.0f, @1.01, @9.99, @"a", @"BA", data("a"), data("BA"), @15, @15, oid2, oid1, oid1, oid2], ]; } - (void)testComparisonsWithKeyPathOnRHS { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *values = [self queryObjectClassValues]; RLMObjectId *oid1 = values[0][14]; for (id value in values) { [self.queryObjectClass createInRealm:realm withValue:value]; } [realm commitWriteTransaction]; RLMAssertCount(self.queryObjectClass, 4U, @"TRUE == bool1"); RLMAssertCount(self.queryObjectClass, 3U, @"TRUE != bool2"); RLMAssertCount(self.queryObjectClass, 2U, @"1 == int1"); RLMAssertCount(self.queryObjectClass, 5U, @"2 != int2"); RLMAssertCount(self.queryObjectClass, 2U, @"2 > int1"); RLMAssertCount(self.queryObjectClass, 4U, @"2 < int1"); RLMAssertCount(self.queryObjectClass, 3U, @"2 >= int1"); RLMAssertCount(self.queryObjectClass, 5U, @"2 <= int1"); RLMAssertCount(self.queryObjectClass, 3U, @"1.0 == float1"); RLMAssertCount(self.queryObjectClass, 6U, @"1.0 != float2"); RLMAssertCount(self.queryObjectClass, 1U, @"1.0 > float1"); RLMAssertCount(self.queryObjectClass, 6U, @"1.0 < float2"); RLMAssertCount(self.queryObjectClass, 4U, @"1.0 >= float1"); RLMAssertCount(self.queryObjectClass, 7U, @"1.0 <= float2"); RLMAssertCount(self.queryObjectClass, 2U, @"1.0 == double1"); RLMAssertCount(self.queryObjectClass, 5U, @"1.0 != double1"); RLMAssertCount(self.queryObjectClass, 1U, @"5.0 > double2"); RLMAssertCount(self.queryObjectClass, 6U, @"5.0 < double2"); RLMAssertCount(self.queryObjectClass, 2U, @"5.55 >= double2"); RLMAssertCount(self.queryObjectClass, 6U, @"5.55 <= double2"); RLMAssertCount(self.queryObjectClass, 1U, @"'a' == string2"); RLMAssertCount(self.queryObjectClass, 6U, @"'a' != string2"); RLMAssertCount(self.queryObjectClass, 1U, @"%@ == data2", data("a")); RLMAssertCount(self.queryObjectClass, 6U, @"%@ != data2", data("a")); RLMAssertCount(self.queryObjectClass, 2U, @"1 == decimal1"); RLMAssertCount(self.queryObjectClass, 5U, @"2 != decimal2"); RLMAssertCount(self.queryObjectClass, 2U, @"2 > decimal1"); RLMAssertCount(self.queryObjectClass, 4U, @"2 < decimal1"); RLMAssertCount(self.queryObjectClass, 3U, @"2 >= decimal1"); RLMAssertCount(self.queryObjectClass, 5U, @"2 <= decimal1"); RLMAssertCount(self.queryObjectClass, 2U, @"1 == any1"); RLMAssertCount(self.queryObjectClass, 2U, @"1.0 == any1"); RLMAssertCount(self.queryObjectClass, 1U, @"'one' == any1"); RLMAssertCount(self.queryObjectClass, 6U, @"'one' != any1"); RLMAssertCount(self.queryObjectClass, 1U, @"TRUE == any1"); RLMAssertCount(self.queryObjectClass, 6U, @"TRUE != any1"); RLMAssertCount(self.queryObjectClass, 1U, @"%@ == any1", oid1); RLMAssertCount(self.queryObjectClass, 6U, @"%@ != any1", oid1); RLMAssertCount(self.queryObjectClass, 5U, @"2 != any2"); RLMAssertCount(self.queryObjectClass, 2U, @"2 > any1"); RLMAssertCount(self.queryObjectClass, 0U, @"2 < any1"); RLMAssertCount(self.queryObjectClass, 2U, @"2 >= any1"); RLMAssertCount(self.queryObjectClass, 0U, @"2 <= any1"); RLMAssertCount(self.queryObjectClass, 4U, @"%@ == objectId1", oid1); RLMAssertCount(self.queryObjectClass, 3U, @"%@ != objectId2", oid1); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Realm' CONTAINS string1"].count, @"Operator 'CONTAINS' requires a keypath on the left side."); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Amazon' BEGINSWITH string2"].count, @"Operator 'BEGINSWITH' requires a keypath on the left side."); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Tuba' ENDSWITH string1"].count, @"Operator 'ENDSWITH' requires a keypath on the left side."); RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Tuba' LIKE string1"].count, @"Operator 'LIKE' requires a keypath on the left side."); } - (void)testLinksToDeletedOrMovedObject { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; DogObject *fido = [DogObject createInRealm:realm withValue:@[ @"Fido", @3 ]]; [OwnerObject createInRealm:realm withValue:@[ @"Fido's owner", fido ]]; DogObject *rex = [DogObject createInRealm:realm withValue:@[ @"Rex", @2 ]]; [OwnerObject createInRealm:realm withValue:@[ @"Rex's owner", rex ]]; DogObject *spot = [DogObject createInRealm:realm withValue:@[ @"Spot", @2 ]]; [OwnerObject createInRealm:realm withValue:@[ @"Spot's owner", spot ]]; [realm commitWriteTransaction]; RLMResults *fidoQuery = [OwnerObject objectsInRealm:realm where:@"dog == %@", fido]; RLMResults *rexQuery = [OwnerObject objectsInRealm:realm where:@"dog == %@", rex]; RLMResults *spotQuery = [OwnerObject objectsInRealm:realm where:@"dog == %@", spot]; [realm beginWriteTransaction]; [realm deleteObject:fido]; [realm commitWriteTransaction]; // Fido was removed, so we should not find his owner. XCTAssertEqual(0u, fidoQuery.count); // Rex's owner should be found as the row was not touched. XCTAssertEqual(1u, rexQuery.count); XCTAssertEqualObjects(@"Rex's owner", [rexQuery.firstObject name]); // Spot's owner should be found, despite Spot's row having moved. XCTAssertEqual(1u, spotQuery.count); XCTAssertEqualObjects(@"Spot's owner", [spotQuery.firstObject name]); } - (void)testQueryOnDeletedArrayProperty { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntObject *io = [IntObject createInRealm:realm withValue:@[@0]]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]]; [realm commitWriteTransaction]; RLMResults *results = [array.intArray objectsWhere:@"TRUEPREDICATE"]; XCTAssertEqual(1U, results.count); [realm beginWriteTransaction]; [realm deleteObject:array]; [realm commitWriteTransaction]; XCTAssertEqual(0U, results.count); XCTAssertNil(results.firstObject); } - (void)testQueryOnDeletedSetProperty { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntObject *io = [IntObject createInRealm:realm withValue:@[@0]]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]]; [realm commitWriteTransaction]; RLMResults *results = [set.intSet objectsWhere:@"TRUEPREDICATE"]; XCTAssertEqual(1U, results.count); [realm beginWriteTransaction]; [realm deleteObject:set]; [realm commitWriteTransaction]; XCTAssertEqual(0U, results.count); XCTAssertNil(results.firstObject); } - (void)testQueryOnDeletedDictionaryProperty { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntObject *io = [IntObject createInRealm:realm withValue:@[@0]]; DictionaryPropertyObject *dpo = [DictionaryPropertyObject createInRealm:realm withValue:@{@"intObjDictionary": @{@"0": io}}]; [realm commitWriteTransaction]; RLMResults *results = [dpo.intObjDictionary objectsWhere:@"TRUEPREDICATE"]; XCTAssertEqual(1U, results.count); [realm beginWriteTransaction]; [realm deleteObject:dpo]; [realm commitWriteTransaction]; XCTAssertEqual(0U, results.count); XCTAssertNil(results.firstObject); } - (void)testSubqueries { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; NSArray *employees = @[@{@"name": @"John", @"age": @30, @"hired": @NO}, @{@"name": @"Jill", @"age": @40, @"hired": @YES}, @{@"name": @"Joe", @"age": @40, @"hired": @YES}]; NSArray *employees2 = @[@{@"name": @"Bill", @"age": @35, @"hired": @YES}, @{@"name": @"Don", @"age": @45, @"hired": @NO}, @{@"name": @"Tim", @"age": @60, @"hired": @NO}]; CompanyObject *first = [CompanyObject createInRealm:realm withValue:@[@"first company", employees, employees]]; for (NSDictionary *eData in employees) { first.employeeDict[eData[@"name"]] = [[EmployeeObject alloc] initWithValue:eData]; } CompanyObject *second = [CompanyObject createInRealm:realm withValue:@[@"second company", employees2, employees2]]; for (NSDictionary *eData in employees2) { second.employeeDict[eData[@"name"]] = [[EmployeeObject alloc] initWithValue:eData]; } [LinkToCompanyObject createInRealm:realm withValue:@[ first ]]; [LinkToCompanyObject createInRealm:realm withValue:@[ second ]]; [realm commitWriteTransaction]; RLMAssertCount(CompanyObject, 1U, @"SUBQUERY(employees, $employee, $employee.age > 30 AND $employee.hired = FALSE).@count > 0"); RLMAssertCount(CompanyObject, 2U, @"SUBQUERY(employees, $employee, $employee.age < 30 AND $employee.hired = TRUE).@count == 0"); RLMAssertCount(CompanyObject, 1U, @"SUBQUERY(employeeSet, $employee, $employee.age > 30 AND $employee.hired = FALSE).@count > 0"); RLMAssertCount(CompanyObject, 2U, @"SUBQUERY(employeeSet, $employee, $employee.age < 30 AND $employee.hired = TRUE).@count == 0"); RLMAssertCount(LinkToCompanyObject, 1U, @"SUBQUERY(company.employees, $employee, $employee.age > 30 AND $employee.hired = FALSE).@count > 0"); RLMAssertCount(LinkToCompanyObject, 2U, @"SUBQUERY(company.employees, $employee, $employee.age < 30 AND $employee.hired = TRUE).@count == 0"); RLMAssertCount(LinkToCompanyObject, 1U, @"SUBQUERY(company.employeeSet, $employee, $employee.age > 30 AND $employee.hired = FALSE).@count > 0"); RLMAssertCount(LinkToCompanyObject, 2U, @"SUBQUERY(company.employeeSet, $employee, $employee.age < 30 AND $employee.hired = TRUE).@count == 0"); RLMAssertCount(LinkToCompanyObject, 1U, @"SUBQUERY(company.employeeDict, $employee, $employee.age > 30 AND $employee.hired = FALSE).@count > 0"); RLMAssertCount(LinkToCompanyObject, 2U, @"SUBQUERY(company.employeeDict, $employee, $employee.age < 30 AND $employee.hired = TRUE).@count == 0"); } - (void)testLinkingObjects { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]]; PersonObject *elijah = [PersonObject createInRealm:realm withValue:@[ @"Elijah", @3 ]]; PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]]; PersonObject *jason = [PersonObject createInRealm:realm withValue:@[ @"Jason", @31, @[ elijah ]]]; PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]]; PersonObject *carol = [PersonObject createInRealm:realm withValue:@[ @"Carol", @31 ]]; PersonObject *michael = [PersonObject createInRealm:realm withValue:@[ @"Michael", @57, @[ jason, mark ]]]; PersonObject *raewynne = [PersonObject createInRealm:realm withValue:@[ @"Raewynne", @57, @[ jason, mark ]]]; PersonObject *don = [PersonObject createInRealm:realm withValue:@[ @"Don", @64, @[ carol, diane ]]]; PersonObject *diane_sr = [PersonObject createInRealm:realm withValue:@[ @"Diane", @60, @[ carol, diane ]]]; [realm commitWriteTransaction]; NSArray *(^asArray)(RLMResults *) = ^(RLMResults *results) { return [[self evaluate:results] valueForKeyPath:@"self"]; }; // People that have a parent with a name that starts with 'M'. RLMResults *r1 = [PersonObject objectsWhere:@"ANY parents.name BEGINSWITH 'M'"]; XCTAssertEqualObjects(asArray(r1), (@[ hannah, mark, jason ])); // People that have a grandparent with a name that starts with 'M'. RLMResults *r2 = [PersonObject objectsWhere:@"ANY parents.parents.name BEGINSWITH 'M'"]; XCTAssertEqualObjects(asArray(r2), (@[ hannah, elijah ])); // People that have children that have a parent named Diane. RLMResults *r3 = [PersonObject objectsWhere:@"ANY children.parents.name == 'Diane'"]; XCTAssertEqualObjects(asArray(r3), (@[ mark, diane, don, diane_sr ])); // People that have children that have a grandparent named Don. RLMResults *r4 = [PersonObject objectsWhere:@"ANY children.parents.parents.name == 'Don'"]; XCTAssertEqualObjects(asArray(r4), (@[ mark, diane ])); // People whose parents have an average age of < 60. RLMResults *r5 = [PersonObject objectsWhere:@"parents.@avg.age < 60"]; XCTAssertEqualObjects(asArray(r5), (@[ hannah, elijah, mark, jason ])); // People that have at least one sibling. RLMResults *r6 = [PersonObject objectsWhere:@"SUBQUERY(parents, $parent, $parent.children.@count > 1).@count > 0"]; XCTAssertEqualObjects(asArray(r6), (@[ mark, jason, diane, carol ])); // People that have Raewynne as a parent. RLMResults *r7 = [PersonObject objectsWhere:@"ANY parents == %@", raewynne]; XCTAssertEqualObjects(asArray(r7), (@[ mark, jason ])); // People that have Mark as a child. RLMResults *r8 = [PersonObject objectsWhere:@"ANY children == %@", mark]; XCTAssertEqualObjects(asArray(r8), (@[ michael, raewynne ])); // People that have Michael as a grandparent. RLMResults *r9 = [PersonObject objectsWhere:@"ANY parents.parents == %@", michael]; XCTAssertEqualObjects(asArray(r9), (@[ hannah, elijah ])); // People that have Hannah as a grandchild. RLMResults *r10 = [PersonObject objectsWhere:@"ANY children.children == %@", hannah]; XCTAssertEqualObjects(asArray(r10), (@[ michael, raewynne, don, diane_sr ])); // People that have no listed parents. RLMResults *r11 = [PersonObject objectsWhere:@"parents.@count == 0"]; XCTAssertEqualObjects(asArray(r11), (@[ michael, raewynne, don, diane_sr ])); // No links are equal to a detached row accessor. RLMResults *r12 = [PersonObject objectsWhere:@"ANY parents == %@", [PersonObject new]]; XCTAssertEqualObjects(asArray(r12), (@[ ])); // All links are not equal to a detached row accessor so this will match all rows that are linked to. RLMResults *r13 = [PersonObject objectsWhere:@"ANY parents != %@", [PersonObject new]]; XCTAssertEqualObjects(asArray(r13), (@[ hannah, elijah, mark, jason, diane, carol ])); // Linking objects cannot contain null so their members cannot be compared with null. XCTAssertThrows([PersonObject objectsWhere:@"ANY parents == NULL"]); // People that have a parent under the age of 31 where that parent has a parent over the age of 35 whose name is Michael. RLMResults *r14 = [PersonObject objectsWhere:@"SUBQUERY(parents, $p1, $p1.age < 31 AND SUBQUERY($p1.parents, $p2, $p2.age > 35 AND $p2.name == 'Michael').@count > 0).@count > 0"]; XCTAssertEqualObjects(asArray(r14), (@[ hannah ])); // Add a new link and verify that the existing results update as expected. __block PersonObject *mackenzie; [realm transactionWithBlock:^{ mackenzie = [PersonObject createInRealm:realm withValue:@[ @"Mackenzie", @0 ]]; [jason.children addObject:mackenzie]; }]; // People that have a parent with a name that starts with 'M'. XCTAssertEqualObjects(asArray(r1), (@[ hannah, mark, jason ])); // People that have a grandparent with a name that starts with 'M'. XCTAssertEqualObjects(asArray(r2), (@[ hannah, elijah, mackenzie ])); // People that have children that have a parent named Diane. XCTAssertEqualObjects(asArray(r3), (@[ mark, diane, don, diane_sr ])); // People that have children that have a grandparent named Don. XCTAssertEqualObjects(asArray(r4), (@[ mark, diane ])); // People whose parents have an average age of < 60. XCTAssertEqualObjects(asArray(r5), (@[ hannah, elijah, mark, jason, mackenzie ])); // People that have at least one sibling. XCTAssertEqualObjects(asArray(r6), (@[ elijah, mark, jason, diane, carol, mackenzie ])); // People that have Raewynne as a parent. XCTAssertEqualObjects(asArray(r7), (@[ mark, jason ])); // People that have Mark as a child. XCTAssertEqualObjects(asArray(r8), (@[ michael, raewynne ])); // People that have Michael as a grandparent. XCTAssertEqualObjects(asArray(r9), (@[ hannah, elijah, mackenzie ])); // People that have Hannah as a grandchild. XCTAssertEqualObjects(asArray(r10), (@[ michael, raewynne, don, diane_sr ])); // People that have no listed parents. XCTAssertEqualObjects(asArray(r11), (@[ michael, raewynne, don, diane_sr ])); // No links are equal to a detached row accessor. XCTAssertEqualObjects(asArray(r12), (@[ ])); // All links are not equal to a detached row accessor so this will match all rows that are linked to. XCTAssertEqualObjects(asArray(r13), (@[ hannah, elijah, mark, jason, diane, carol, mackenzie ])); // People that have a parent under the age of 31 where that parent has a parent over the age of 35 whose name is Michael. XCTAssertEqualObjects(asArray(r14), (@[ hannah ])); } - (void)testCountOnArrayCollection { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerArrayPropertyObject *arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @1, @[]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @456 ]]]; arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @2, @[]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @1 ]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @3 ]]]; [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @0, @[]]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@count > 0"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@count == 3"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@count < 1"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"0 < array.@count"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"3 == array.@count"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"1 > array.@count"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@count == number"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@count > number"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"number < array.@count"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@count == array.@count"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@count.foo.bar != 0"]), @"Invalid keypath 'array.@count.foo.bar': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@count.intCol > 0"]), @"Invalid keypath 'array.@count.intCol': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@count != 'Hello'"]), @"@count can only be compared with a numeric value"); } - (void)testCountOnSetCollection { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerSetPropertyObject *set = [IntegerSetPropertyObject createInRealm:realm withValue:@[ @1, @[]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @456 ]]]; set = [IntegerSetPropertyObject createInRealm:realm withValue:@[ @2, @[]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @1 ]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @3 ]]]; [IntegerSetPropertyObject createInRealm:realm withValue:@[ @0, @[]]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@count > 0"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@count == 3"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@count < 1"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"0 < set.@count"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"3 == set.@count"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"1 > set.@count"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@count == number"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@count > number"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"number < set.@count"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@count == set.@count"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@count.foo.bar != 0"]), @"Invalid keypath 'set.@count.foo.bar': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@count.intCol > 0"]), @"Invalid keypath 'set.@count.intCol': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@count != 'Hello'"]), @"@count can only be compared with a numeric value"); } - (void)testCountOnDictionaryCollection { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerDictionaryPropertyObject *idpo = [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @1, @[]]]; idpo.dictionary[@"456"] = [IntObject createInRealm:realm withValue:@[ @456 ]]; idpo = [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @2, @[]]]; idpo.dictionary[@"1"] = [IntObject createInRealm:realm withValue:@[ @1 ]]; idpo.dictionary[@"2"] = [IntObject createInRealm:realm withValue:@[ @2 ]]; idpo.dictionary[@"3"] = [IntObject createInRealm:realm withValue:@[ @3 ]]; [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @0, @[]]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@count > 0"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@count == 3"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@count < 1"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"0 < dictionary.@count"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"3 == dictionary.@count"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"1 > dictionary.@count"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@count == number"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@count > number"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"number < dictionary.@count"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@count == dictionary.@count"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@count.foo.bar != 0"]), @"Invalid keypath 'dictionary.@count.foo.bar': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@count.intCol > 0"]), @"Invalid keypath 'dictionary.@count.intCol': @count must appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@count != 'Hello'"]), @"@count can only be compared with a numeric value"); } - (void)testAggregateArrayCollectionOperators { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerArrayPropertyObject *arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @1111, @[] ]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @1234 ]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @-12345 ]]]; arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @2222, @[] ]]; [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @100 ]]]; [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @3333, @[] ]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@min.intCol == -12345"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@min.intCol == 100"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@min.intCol < 1000"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@min.intCol > -1000"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@max.intCol == 1234"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@max.intCol == 100"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@max.intCol > -1000"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@max.intCol > 1000"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@sum.intCol == 100"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@sum.intCol == -11109"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@sum.intCol == 0"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@sum.intCol > -50"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@sum.intCol < 50"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@avg.intCol == 100"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@avg.intCol == -3703"); RLMAssertCount(IntegerArrayPropertyObject, 0U, @"array.@avg.intCol == 0"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@avg.intCol < -50"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@avg.intCol > 50"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@min.intCol < number"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"number > array.@min.intCol"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"array.@max.intCol < number"); RLMAssertCount(IntegerArrayPropertyObject, 1U, @"number > array.@max.intCol"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"array.@avg.intCol < number"); RLMAssertCount(IntegerArrayPropertyObject, 2U, @"number > array.@avg.intCol"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == array.@min.intCol"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol.foo.bar == 1.23"]), @"Invalid keypath 'array.@min.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol.foo.bar == 1.23"]), @"Invalid keypath 'array.@max.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol.foo.bar == 1.23"]), @"Invalid keypath 'array.@sum.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol.foo.bar == 1.23"]), @"Invalid keypath 'array.@avg.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); // Average is omitted from this test as its result is always a double. RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == 1.23"]), @"@min.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol == 1.23"]), @"@max.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol == 1.23"]), @"@sum.*type int cannot be compared"); } - (void)testAggregateSetCollectionOperators { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerSetPropertyObject *set = [IntegerSetPropertyObject createInRealm:realm withValue:@[ @1111, @[] ]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @1234 ]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @-12345 ]]]; set = [IntegerSetPropertyObject createInRealm:realm withValue:@[ @2222, @[] ]]; [set.set addObject:[IntObject createInRealm:realm withValue:@[ @100 ]]]; [IntegerSetPropertyObject createInRealm:realm withValue:@[ @3333, @[] ]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@min.intCol == -12345"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@min.intCol == 100"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@min.intCol < 1000"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@min.intCol > -1000"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@max.intCol == 1234"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@max.intCol == 100"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@max.intCol > -1000"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@max.intCol > 1000"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@sum.intCol == 100"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@sum.intCol == -11109"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@sum.intCol == 0"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@sum.intCol > -50"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@sum.intCol < 50"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@avg.intCol == 100"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@avg.intCol == -3703"); RLMAssertCount(IntegerSetPropertyObject, 0U, @"set.@avg.intCol == 0"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@avg.intCol < -50"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@avg.intCol > 50"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@min.intCol < number"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"number > set.@min.intCol"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"set.@max.intCol < number"); RLMAssertCount(IntegerSetPropertyObject, 1U, @"number > set.@max.intCol"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"set.@avg.intCol < number"); RLMAssertCount(IntegerSetPropertyObject, 2U, @"number > set.@avg.intCol"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@min.intCol == set.@min.intCol"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@min.intCol.foo.bar == 1.23"]), @"Invalid keypath 'set.@min.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@max.intCol.foo.bar == 1.23"]), @"Invalid keypath 'set.@max.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@sum.intCol.foo.bar == 1.23"]), @"Invalid keypath 'set.@sum.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerSetPropertyObject objectsWhere:@"set.@avg.intCol.foo.bar == 1.23"]), @"Invalid keypath 'set.@avg.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); // Average is omitted from this test as its result is always a double. RLMAssertThrowsWithReasonMatching(([IntegerSetPropertyObject objectsWhere:@"set.@min.intCol == 1.23"]), @"@min.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerSetPropertyObject objectsWhere:@"set.@max.intCol == 1.23"]), @"@max.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerSetPropertyObject objectsWhere:@"set.@sum.intCol == 1.23"]), @"@sum.*type int cannot be compared"); } - (void)testAggregateDictionaryCollectionOperators { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; IntegerDictionaryPropertyObject *idpo = [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @1111, @[] ]]; idpo.dictionary[@"0"] = [IntObject createInRealm:realm withValue:@[ @1234 ]]; idpo.dictionary[@"1"] = [IntObject createInRealm:realm withValue:@[ @2 ]]; idpo.dictionary[@"2"] = [IntObject createInRealm:realm withValue:@[ @-12345 ]]; idpo = [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @2222, @[] ]]; idpo.dictionary[@"3"] = [IntObject createInRealm:realm withValue:@[ @100 ]]; [IntegerDictionaryPropertyObject createInRealm:realm withValue:@[ @3333, @[] ]]; [realm commitWriteTransaction]; RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@min.intCol == -12345"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@min.intCol == 100"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@min.intCol < 1000"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@min.intCol > -1000"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@max.intCol == 1234"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@max.intCol == 100"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@max.intCol > -1000"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@max.intCol > 1000"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@sum.intCol == 100"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@sum.intCol == -11109"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@sum.intCol == 0"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@sum.intCol > -50"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@sum.intCol < 50"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@avg.intCol == 100"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@avg.intCol == -3703"); RLMAssertCount(IntegerDictionaryPropertyObject, 0U, @"dictionary.@avg.intCol == 0"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@avg.intCol < -50"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@avg.intCol > 50"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@min.intCol < number"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"number > dictionary.@min.intCol"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"dictionary.@max.intCol < number"); RLMAssertCount(IntegerDictionaryPropertyObject, 1U, @"number > dictionary.@max.intCol"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"dictionary.@avg.intCol < number"); RLMAssertCount(IntegerDictionaryPropertyObject, 2U, @"number > dictionary.@avg.intCol"); // We do not yet handle collection operations on both sides of the comparison. RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@min.intCol == dictionary.@min.intCol"]), @"aggregate operations cannot be compared with other aggregate operations"); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@min.intCol.foo.bar == 1.23"]), @"Invalid keypath 'dictionary.@min.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@max.intCol.foo.bar == 1.23"]), @"Invalid keypath 'dictionary.@max.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@sum.intCol.foo.bar == 1.23"]), @"Invalid keypath 'dictionary.@sum.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@avg.intCol.foo.bar == 1.23"]), @"Invalid keypath 'dictionary.@avg.intCol.foo.bar': Property 'IntObject.intCol' is not a link or collection and can only appear at the end of a keypath."); // Average is omitted from this test as its result is always a double. RLMAssertThrowsWithReasonMatching(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@min.intCol == 1.23"]), @"@min.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@max.intCol == 1.23"]), @"@max.*type int cannot be compared"); RLMAssertThrowsWithReasonMatching(([IntegerDictionaryPropertyObject objectsWhere:@"dictionary.@sum.intCol == 1.23"]), @"@sum.*type int cannot be compared"); } - (void)testDictionaryQueryAllKeys { void (^test)(NSString *, id) = ^(NSString *property, id value) { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"key1": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"key2": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"key3": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"key1": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"KEY3": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"kêÿ2": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"KEY2": value}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"lock1": value}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allKeys = 'key'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allKeys = 'key1'", property); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allKeys = 'key3'", property); RLMAssertCount(AllDictionariesObject, 7U, @"ANY %K.@allKeys != 'key3'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allKeys =[c] 'key3'", property); RLMAssertCount(AllDictionariesObject, 6U, @"ANY %K.@allKeys !=[c] 'key3'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allKeys =[cd] 'key3'", property); RLMAssertCount(AllDictionariesObject, 6U, @"ANY %K.@allKeys !=[cd] 'key3'", property); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allKeys !=[cd] 'key3'", property); // BEGINSWITH RLMAssertCount(AllDictionariesObject, 4U, @"ANY %K.@allKeys BEGINSWITH 'ke'", property); RLMAssertCount(AllDictionariesObject, 4U, @"NOT ANY %K.@allKeys BEGINSWITH 'ke'", property); RLMAssertCount(AllDictionariesObject, 6U, @"ANY %K.@allKeys BEGINSWITH[c] 'ke'", property); RLMAssertCount(AllDictionariesObject, 7U, @"ANY %K.@allKeys BEGINSWITH[cd] 'ke'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allKeys BEGINSWITH NULL", property); // CONTAINS RLMAssertCount(AllDictionariesObject, 4U, @"ANY %K.@allKeys CONTAINS 'ey'", property); RLMAssertCount(AllDictionariesObject, 6U, @"ANY %K.@allKeys CONTAINS[c] 'ey'", property); RLMAssertCount(AllDictionariesObject, 7U, @"ANY %K.@allKeys CONTAINS[cd] 'ey'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allKeys CONTAINS NULL", property); // ENDSWITH RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allKeys ENDSWITH 'y2'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allKeys ENDSWITH[c] 'y2'", property); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allKeys ENDSWITH[cd] 'y2'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allKeys ENDSWITH NULL", property); // LIKE RLMAssertCount(AllDictionariesObject, 4U, @"ANY %K.@allKeys LIKE 'key*'", property); RLMAssertCount(AllDictionariesObject, 6U, @"ANY %K.@allKeys LIKE[c] 'key*'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allKeys LIKE NULL", property); RLMAssertCount(AllDictionariesObject, 4U, @"NOT ANY %K.@allKeys LIKE 'key*'", property); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allKeys LIKE[c] 'key*'", property); RLMAssertCount(AllDictionariesObject, 8U, @"NOT ANY %K.@allKeys LIKE NULL", property); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allKeys LIKE[cd] 'key*'", property]), @"Operator 'LIKE' not supported with diacritic-insensitive modifier."); [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; }; test(@"intDict", @123); test(@"floatDict", @789.123); test(@"doubleDict", @789.123); test(@"boolDict", @YES); test(@"stringDict", @"Hello"); test(@"dataDict", [@"123" dataUsingEncoding:NSUTF8StringEncoding]); test(@"dateDict", [NSDate dateWithTimeIntervalSince1970:100]); test(@"decimalDict", [RLMDecimal128 decimalWithNumber:@123.456]); test(@"objectIdDict", [RLMObjectId objectId]); test(@"uuidDict", [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]); test(@"stringObjDict", [[StringObject alloc] initWithValue:@[@"hi"]]); } - (void)testDictionaryQueryAllValues_RLMObject { NSString *property = @"stringObjDict"; NSArray *values = @[[[StringObject alloc] initWithValue:@[@"hello"]], [[StringObject alloc] initWithValue:@[@"Héllo"]], [[StringObject alloc] initWithValue:@[@"HELLO"]]]; RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; StringObject *obj = [[StringObject objectsInRealm:realm where:@"stringCol = %@", [values[0] stringCol]] firstObject]; RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, obj); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, obj); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues IN %@", property, @[obj]); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues IN %@", property, @[]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.stringCol = %@", property, obj.stringCol); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.stringCol != %@", property, obj.stringCol); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.stringCol BEGINSWITH %@", property, @"h"); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.stringCol BEGINSWITH[c] %@", property, @"h"); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.stringCol BEGINSWITH[c] %@", property, @"he"); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.stringCol BEGINSWITH[cd] %@", property, @"he"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues BETWEEN %@", property, @[obj, obj]]), @"Operator 'BETWEEN' not supported for type 'object'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues BEGINSWITH %@", property, obj]), @"Operator 'BEGINSWITH' not supported for type 'object'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues CONTAINS %@", property, obj]), @"Operator 'CONTAINS' not supported for type 'object'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues ENDSWITH %@", property, obj]), @"Operator 'ENDSWITH' not supported for type 'object'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues LIKE %@", property, obj]), @"Operator 'LIKE' not supported for type 'object'"); } - (void)testDictionaryQueryAllValues_NSString { NSString *property = @"stringDict"; NSArray *values = @[@"hello", @"Héllo", @"HELLO"]; RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allValues =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues !=[cd] %@", property, values[0]); // BEGINSWITH RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues BEGINSWITH 'he'", property); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allValues BEGINSWITH 'he'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues BEGINSWITH[c] 'he'", property); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allValues BEGINSWITH[cd] 'he'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues BEGINSWITH NULL", property); // CONTAINS RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues CONTAINS 'el'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues CONTAINS[c] 'el'", property); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allValues CONTAINS[cd] 'el'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues CONTAINS NULL", property); // ENDSWITH RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues ENDSWITH 'lo'", property); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allValues ENDSWITH[c] 'lo'", property); RLMAssertCount(AllDictionariesObject, 3U, @"ANY %K.@allValues ENDSWITH[cd] 'lo'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues ENDSWITH NULL", property); // LIKE RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues LIKE 'hel*'", property); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues LIKE[c] 'hel*'", property); RLMAssertCount(AllDictionariesObject, 0U, @"ANY %K.@allValues LIKE NULL", property); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allValues LIKE 'hel*'", property); RLMAssertCount(AllDictionariesObject, 1U, @"NOT ANY %K.@allValues LIKE[c] 'hel*'", property); RLMAssertCount(AllDictionariesObject, 3U, @"NOT ANY %K.@allValues LIKE NULL", property); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues LIKE[cd] 'hel*'", property]), @"Operator 'LIKE' not supported with diacritic-insensitive modifier."); } - (void)testDictionaryQueryAllValues_ObjectId { NSString *property = @"objectIdDict"; NSArray *values = @[[[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1b" error:nil], [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1a" error:nil], [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1c" error:nil]]; RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[cd] %@", property, values[0]); // Unsupported RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues > %@", property, values[0]]), @"Operator '>' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues < %@", property, values[0]]), @"Operator '<' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues LIKE %@", property, values[0]]), @"Operator 'LIKE' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues BEGINSWITH %@", property, values[0]]), @"Operator 'BEGINSWITH' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues CONTAINS %@", property, values[0]]), @"Operator 'CONTAINS' not supported for type 'object id'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues ENDSWITH %@", property, values[0]]), @"Operator 'ENDSWITH' not supported for type 'object id'"); } - (void)testDictionaryQueryAllValues_UUID { NSString *property = @"uuidDict"; NSArray *values = @[[[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD88"], [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD87"], [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]]; RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[cd] %@", property, values[0]); // Unsupported RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues > %@", property, values[0]]), @"Operator '>' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues < %@", property, values[0]]), @"Operator '<' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues LIKE %@", property, values[0]]), @"Operator 'LIKE' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues BEGINSWITH %@", property, values[0]]), @"Operator 'BEGINSWITH' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues CONTAINS %@", property, values[0]]), @"Operator 'CONTAINS' not supported for type 'uuid'"); RLMAssertThrowsWithReason(([AllDictionariesObject objectsInRealm:realm where:@"ANY %K.@allValues ENDSWITH %@", property, values[0]]), @"Operator 'ENDSWITH' not supported for type 'uuid'"); } - (void)testDictionaryQueryAllValues_Data { NSString *property = @"dataDict"; NSArray *values = @[[NSData dataWithBytes:"hey" length:3], [NSData dataWithBytes:"hi" length:2], [NSData dataWithBytes:"hello" length:5]]; RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues >= %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues < %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues <= %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues LIKE %@", property, [NSData dataWithBytes:"hello" length:5]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues LIKE %@", property, [NSData dataWithBytes:"he*" length:3]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues BEGINSWITH %@", property, [NSData dataWithBytes:"he" length:2]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues CONTAINS %@", property, [NSData dataWithBytes:"ell" length:3]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues ENDSWITH %@", property, [NSData dataWithBytes:"lo" length:2]); } - (void)testDictionaryQueryAllValues { void (^test)(NSString *, NSArray *) = ^(NSString *property, NSArray *values) { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; if ([property isEqualToString:@"boolDict"]) { RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues !=[cd] %@", property, values[0]); } else { RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"ANY %K.@allValues !=[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allValues > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues >[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues >[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues < %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"NOT ANY %K.@allValues < %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues <[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"ANY %K.@allValues <[cd] %@", property, values[0]); } [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; }; test(@"intDict", @[@456, @123, @789]); test(@"doubleDict", @[@456.123, @123.123, @789.123]); test(@"boolDict", @[@NO, @NO, @YES]); test(@"decimalDict", @[[RLMDecimal128 decimalWithNumber:@456.123], [RLMDecimal128 decimalWithNumber:@123.123], [RLMDecimal128 decimalWithNumber:@789.123]]); test(@"dateDict", @[[NSDate dateWithTimeIntervalSince1970:4000], [NSDate dateWithTimeIntervalSince1970:2000], [NSDate dateWithTimeIntervalSince1970:8000]]); } - (void)testDictionaryQueryKeySubscript { void (^test)(NSString *, NSArray *, id (^)(NSString *)) = ^(NSString *property, NSArray *values, id (^string)(NSString *)) { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey": values[0]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey2": values[1]}}]; [AllDictionariesObject createInRealm:realm withValue:@{property: @{@"aKey3": values[2]}}]; [realm commitWriteTransaction]; RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[cd] %@", property, values[0]); if (!string) { RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 3U, @"NOT %K['aKey'] > %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] >[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] >[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] < %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 3U, @"NOT %K['aKey'] < %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] <[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] <[cd] %@", property, values[0]); } else { RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] = %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] != %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] !=[c] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] =[cd] %@", property, values[0]); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] !=[cd] %@", property, values[0]); // BEGINSWITH RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH %@", property, string(@"he")); RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] BEGINSWITH %@", property, string(@"he")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[c] %@", property, string(@"he")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] BEGINSWITH[cd] %@", property, string(@"he")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] BEGINSWITH NULL", property); // CONTAINS RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS %@", property, string(@"el")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[c] %@", property, string(@"el")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] CONTAINS[cd] %@", property, string(@"el")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] CONTAINS NULL", property); // ENDSWITH RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH %@", property, string(@"lo")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[c] %@", property, string(@"lo")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] ENDSWITH[cd] %@", property, string(@"lo")); RLMAssertCount(AllDictionariesObject, 0U, @"%K['aKey'] ENDSWITH NULL", property); // LIKE RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 1U, @"%K['aKey'] LIKE[c] %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 2U, @"%K['aKey'] LIKE NULL", property); RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 2U, @"NOT %K['aKey'] LIKE[c] %@", property, string(@"hel*")); RLMAssertCount(AllDictionariesObject, 1U, @"NOT %K['aKey'] LIKE NULL", property); RLMAssertThrowsWithReasonMatching(([AllDictionariesObject objectsInRealm:realm where:@"%K['aKey'] LIKE[cd] %@", property, string(@"hel*")]), @"not supported"); } [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; }; test(@"intDict", @[@456, @123, @789], nil); test(@"doubleDict", @[@456.123, @123.123, @789.123], nil); test(@"boolDict", @[@NO, @NO, @YES], nil); test(@"decimalDict", @[[RLMDecimal128 decimalWithNumber:@456.123], [RLMDecimal128 decimalWithNumber:@123.123], [RLMDecimal128 decimalWithNumber:@789.123]], nil); test(@"dateDict", @[[NSDate dateWithTimeIntervalSince1970:4000], [NSDate dateWithTimeIntervalSince1970:2000], [NSDate dateWithTimeIntervalSince1970:8000]], nil); test(@"dataDict", @[[NSData dataWithBytes:"hello" length:5], [NSData dataWithBytes:"Héllo" length:5], [NSData dataWithBytes:"HELLO" length:5]], ^(NSString *str) { return [str dataUsingEncoding:NSUTF8StringEncoding]; }); test(@"objectIdDict", @[[[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1b" error:nil], [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1a" error:nil], [[RLMObjectId alloc] initWithString:@"60425fff91d7a195d5ddac1c" error:nil]], nil); test(@"stringDict", @[@"hello", @"Héllo", @"HELLO"], ^(NSString *str) { return str; }); } - (void)testDictionaryQueryKeySubscriptWithObjectCol { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [DictionaryParentObject createInRealm:realm withValue:@{@"objectCol": @{@"stringDict": @{@"aKey": @"blah"}}}]; [realm commitWriteTransaction]; // This test checks that we can use a link col keypath to the dictionary and subscript it. RLMAssertCount(DictionaryParentObject, 1U, @"%K['aKey'] = %@", @"objectCol.stringDict", @"blah"); } - (void)testDictionarySubscriptThrowsException { RLMRealm *realm = [self realm]; RLMAssertThrowsWithReason(([realm objects:@"ArrayPropertyObject" where:@"array['invalid'] = NULL"]), @"Invalid keypath 'array[\"invalid\"]': only dictionaries and realm `Any` support subscript predicates."); RLMAssertThrowsWithReason(([realm objects:@"SetPropertyObject" where:@"set['invalid'] = NULL"]), @"Invalid keypath 'set[\"invalid\"]': only dictionaries and realm `Any` support subscript predicates."); RLMAssertThrowsWithReason(([realm objects:@"OwnerObject" where:@"dog['dogName'] = NULL"]), @"Aggregate operations can only be used on key paths that include an collection property"); RLMAssertThrows(([realm objects:@"DictionaryPropertyObject" where:@"stringDictionary[%@] = NULL", [RLMObjectId objectId]]), @"Invalid subscript type 'anyCol[[a-z0-9]+]': Only `Strings` or index are allowed subscripts"); RLMAssertThrowsWithReason(([realm objects:@"DictionaryPropertyObject" where:@"stringDictionary['aKey']['bKey'] = NULL"]), @"Invalid subscript size 'stringDictionary[\"aKey\"][\"bKey\"]': nested dictionaries queries are only allowed in mixed properties."); RLMAssertThrowsWithReason(([realm objects:@"DictionaryPropertyObject" where:@"stringDictionary[0] = NULL"]), @"Invalid subscript type 'stringDictionary[0]'; only string keys are allowed as subscripts in dictionary queries."); } - (void)testMixedSubscriptsThrowsException { RLMRealm *realm = [self realm]; RLMAssertThrows(([realm objects:@"AllTypesObject" where:@"anyCol[%@] = NULL", [RLMObjectId objectId]]), @"Invalid subscript type 'anyCol[[a-z0-9]+]': Only `Strings` or index are allowed subscripts"); } - (void)testCollectionsQueryAllValuesAllKeys { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; StringObject *so1 = [StringObject createInRealm:realm withValue:@[@"value1"]]; RLMAssertThrowsWithReason(([realm objects:@"ArrayPropertyObject" where:@"ANY array.@allValues = %@", so1]), @"Invalid keypath 'array.@allValues': @allValues must follow a dictionary property."); RLMAssertThrowsWithReason(([realm objects:@"ArrayPropertyObject" where:@"ANY array.@allKeys = %@", so1]), @"Invalid keypath 'array.@allKeys': @allKeys must follow a dictionary property."); RLMAssertThrowsWithReason(([realm objects:@"SetPropertyObject" where:@"ANY set.@allValues = %@", so1]), @"Invalid keypath 'set.@allValues': @allValues must follow a dictionary property."); RLMAssertThrowsWithReason(([realm objects:@"SetPropertyObject" where:@"ANY set.@allKeys = %@", so1]), @"Invalid keypath 'set.@allKeys': @allKeys must follow a dictionary property."); [realm cancelWriteTransaction]; } - (void)testDictionaryKeyComparedToColumn { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; EmployeeObject *eo = [[EmployeeObject alloc] init]; [CompanyObject createInRealm:realm withValue:@{@"name": @"a", @"employeeDict": @{@"a": eo}}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"a", @"employeeDict": @{@"aa": eo}}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"a", @"employeeDict": @{@"A": eo}}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"a", @"employeeDict": @{@"AA": eo}}]; [realm commitWriteTransaction]; RLMAssertCount(CompanyObject, 1U, @"ANY employeeDict.@allKeys = name"); RLMAssertCount(CompanyObject, 2U, @"ANY employeeDict.@allKeys =[c] name"); RLMAssertCount(CompanyObject, 3U, @"ANY employeeDict.@allKeys != name"); RLMAssertCount(CompanyObject, 2U, @"ANY employeeDict.@allKeys !=[c] name"); RLMAssertCount(CompanyObject, 2U, @"ANY employeeDict.@allKeys BEGINSWITH name"); RLMAssertCount(CompanyObject, 4U, @"ANY employeeDict.@allKeys BEGINSWITH[c] name"); RLMAssertCount(CompanyObject, 2U, @"ANY employeeDict.@allKeys ENDSWITH name"); RLMAssertCount(CompanyObject, 4U, @"ANY employeeDict.@allKeys ENDSWITH[c] name"); RLMAssertCount(CompanyObject, 2U, @"ANY employeeDict.@allKeys CONTAINS name"); RLMAssertCount(CompanyObject, 4U, @"ANY employeeDict.@allKeys CONTAINS[c] name"); RLMAssertThrowsWithReason(([CompanyObject objectsInRealm:realm where:@"ANY employeeDict.@allKeys = 1"]), @"@allKeys can only be compared with a string value."); RLMAssertThrowsWithReason(([IntegerDictionaryPropertyObject objectsInRealm:realm where:@"ANY dictionary.@allKeys = number"]), @"@allKeys can only be compared with a string value."); } @end @interface NullQueryTests : QueryTests @end @implementation NullQueryTests - (Class)queryObjectClass { return [NullQueryObject class]; } - (void)testQueryOnNullableStringColumn { void (^testWithStringClass)(Class) = ^(Class stringObjectClass) { RLMRealm *realm = [self realm]; [realm transactionWithBlock:^{ [stringObjectClass createInRealm:realm withValue:@[@"a"]]; [stringObjectClass createInRealm:realm withValue:@[NSNull.null]]; [stringObjectClass createInRealm:realm withValue:@[@"b"]]; [stringObjectClass createInRealm:realm withValue:@[NSNull.null]]; [stringObjectClass createInRealm:realm withValue:@[@""]]; }]; RLMResults *allObjects = [stringObjectClass allObjectsInRealm:realm]; XCTAssertEqual(5U, allObjects.count); RLMResults *nilStrings = [stringObjectClass objectsInRealm:realm where:@"stringCol = NULL"]; XCTAssertEqual(2U, nilStrings.count); XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilStrings valueForKey:@"stringCol"]); RLMResults *nilLikeStrings = [stringObjectClass objectsInRealm:realm where:@"stringCol LIKE NULL"]; XCTAssertEqual(2U, nilLikeStrings.count); XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilLikeStrings valueForKey:@"stringCol"]); RLMResults *nonNilStrings = [stringObjectClass objectsInRealm:realm where:@"stringCol != NULL"]; XCTAssertEqual(3U, nonNilStrings.count); XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilStrings valueForKey:@"stringCol"]); RLMResults *nonNilLikeStrings = [stringObjectClass objectsInRealm:realm where:@"NOT stringCol LIKE NULL"]; XCTAssertEqual(3U, nonNilLikeStrings.count); XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilLikeStrings valueForKey:@"stringCol"]); RLMAssertCount(stringObjectClass, 3U, @"stringCol IN {NULL, 'a'}"); RLMAssertCount(stringObjectClass, 1U, @"stringCol CONTAINS 'a'"); RLMAssertCount(stringObjectClass, 1U, @"stringCol BEGINSWITH 'a'"); RLMAssertCount(stringObjectClass, 1U, @"stringCol ENDSWITH 'a'"); RLMAssertCount(stringObjectClass, 1U, @"stringCol LIKE 'a'"); RLMAssertCount(stringObjectClass, 0U, @"stringCol CONTAINS 'z'"); RLMAssertCount(stringObjectClass, 0U, @"stringCol LIKE 'z'"); RLMAssertCount(stringObjectClass, 1U, @"stringCol = ''"); RLMResults *sorted = [[stringObjectClass allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"stringCol" ascending:YES]; XCTAssertEqualObjects((@[NSNull.null, NSNull.null, @"", @"a", @"b"]), [sorted valueForKey:@"stringCol"]); XCTAssertEqualObjects((@[@"b", @"a", @"", NSNull.null, NSNull.null]), [[sorted sortedResultsUsingKeyPath:@"stringCol" ascending:NO] valueForKey:@"stringCol"]); [realm transactionWithBlock:^{ [realm deleteObject:[stringObjectClass allObjectsInRealm:realm].firstObject]; }]; XCTAssertEqual(2U, nilStrings.count); XCTAssertEqual(2U, nonNilStrings.count); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol LIKE '*'"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[c] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH[c] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH[c] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol LIKE[c] '*'"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[d] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH[d] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH[d] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[cd] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH[cd] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH[cd] ''"] valueForKey:@"stringCol"]); XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS %@", @"\0"] valueForKey:@"self"])); XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS NULL"] valueForKey:@"stringCol"])); XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[c] NULL"] valueForKey:@"stringCol"])); XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[d] NULL"] valueForKey:@"stringCol"])); XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[cd] NULL"] valueForKey:@"stringCol"])); }; testWithStringClass([StringObject class]); testWithStringClass([IndexedStringObject class]); } - (void)testQueryingOnLinkToNullableStringColumn { void (^testWithStringClass)(Class, Class) = ^(Class stringLinkClass, Class stringObjectClass) { RLMRealm *realm = [self realm]; [realm transactionWithBlock:^{ [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@"a"]]]]; [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[NSNull.null]]]]; [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@"b"]]]]; [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[NSNull.null]]]]; [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@""]]]]; }]; RLMResults *nilStrings = [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol = NULL"]; XCTAssertEqual(2U, nilStrings.count); XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilStrings valueForKeyPath:@"objectCol.stringCol"]); RLMResults *nilLikeStrings = [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol LIKE NULL"]; XCTAssertEqual(2U, nilLikeStrings.count); XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilLikeStrings valueForKeyPath:@"objectCol.stringCol"]); RLMResults *nonNilStrings = [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol != NULL"]; XCTAssertEqual(3U, nonNilStrings.count); XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilStrings valueForKeyPath:@"objectCol.stringCol"]); RLMResults *nonNilLikeStrings = [stringLinkClass objectsInRealm:realm where:@"NOT objectCol.stringCol LIKE NULL"]; XCTAssertEqual(3U, nonNilLikeStrings.count); XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilLikeStrings valueForKeyPath:@"objectCol.stringCol"]); RLMAssertCount(stringLinkClass, 3U, @"objectCol.stringCol IN {NULL, 'a'}"); RLMAssertCount(stringLinkClass, 1U, @"objectCol.stringCol CONTAINS 'a'"); RLMAssertCount(stringLinkClass, 1U, @"objectCol.stringCol BEGINSWITH 'a'"); RLMAssertCount(stringLinkClass, 1U, @"objectCol.stringCol ENDSWITH 'a'"); RLMAssertCount(stringLinkClass, 1U, @"objectCol.stringCol LIKE 'a'"); RLMAssertCount(stringLinkClass, 0U, @"objectCol.stringCol LIKE 'c'"); RLMAssertCount(stringLinkClass, 0U, @"objectCol.stringCol CONTAINS 'z'"); RLMAssertCount(stringLinkClass, 1U, @"objectCol.stringCol = ''"); }; testWithStringClass([LinkStringObject class], [StringObject class]); testWithStringClass([LinkIndexedStringObject class], [IndexedStringObject class]); } - (void)testSortingColumnsWithNull { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; { NumberObject *no1 = [NumberObject createInRealm:realm withValue:@[@1, @1.1f, @1.1, @YES]]; NumberObject *noNull = [NumberObject createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; NumberObject *no0 = [NumberObject createInRealm:realm withValue:@[@0, @0.0f, @0.0, @NO]]; for (RLMProperty *property in [[NumberObject alloc] init].objectSchema.properties) { NSString *name = property.name; RLMResults *ascending = [[NumberObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:name ascending:YES]; XCTAssertEqualObjects([ascending valueForKey:name], ([@[noNull, no0, no1] valueForKey:name])); RLMResults *descending = [[NumberObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:name ascending:NO]; XCTAssertEqualObjects([descending valueForKey:name], ([@[no1, no0, noNull] valueForKey:name])); } } { DateObject *doPositive = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:100]]]; DateObject *doNegative = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:-100]]]; DateObject *doZero = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:0]]]; DateObject *doNull = [DateObject createInRealm:realm withValue:@[NSNull.null]]; RLMResults *ascending = [[DateObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"dateCol" ascending:YES]; XCTAssertEqualObjects([ascending valueForKey:@"dateCol"], ([@[doNull, doNegative, doZero, doPositive] valueForKey:@"dateCol"])); RLMResults *descending = [[DateObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"dateCol" ascending:NO]; XCTAssertEqualObjects([descending valueForKey:@"dateCol"], ([@[doPositive, doZero, doNegative, doNull] valueForKey:@"dateCol"])); } { StringObject *soA = [StringObject createInRealm:realm withValue:@[@"A"]]; StringObject *soEmpty = [StringObject createInRealm:realm withValue:@[@""]]; StringObject *soB = [StringObject createInRealm:realm withValue:@[@"B"]]; StringObject *soNull = [StringObject createInRealm:realm withValue:@[NSNull.null]]; StringObject *soAB = [StringObject createInRealm:realm withValue:@[@"AB"]]; RLMResults *ascending = [[StringObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"stringCol" ascending:YES]; XCTAssertEqualObjects([ascending valueForKey:@"stringCol"], ([@[soNull, soEmpty, soA, soAB, soB] valueForKey:@"stringCol"])); RLMResults *descending = [[StringObject allObjectsInRealm:realm] sortedResultsUsingKeyPath:@"stringCol" ascending:NO]; XCTAssertEqualObjects([descending valueForKey:@"stringCol"], ([@[soB, soAB, soA, soEmpty, soNull] valueForKey:@"stringCol"])); } [realm cancelWriteTransaction]; } struct NullTestData { __unsafe_unretained NSString *propertyName; __unsafe_unretained NSString *nonMatchingStr; __unsafe_unretained NSString *matchingStr; __unsafe_unretained id nonMatchingValue; __unsafe_unretained id matchingValue; bool orderable; bool substringOperations; }; - (void)testPrimitiveOperatorsOnAllNullablePropertyTypes { RLMRealm *realm = [self realm]; // These need to be stored in variables because the struct does not retain them NSData *matchingData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; NSData *notMatchingData = [@"a" dataUsingEncoding:NSUTF8StringEncoding]; NSDate *matchingDate = [NSDate dateWithTimeIntervalSince1970:1]; NSDate *notMatchingDate = [NSDate dateWithTimeIntervalSince1970:2]; RLMDecimal128 *matchingDecimal = [RLMDecimal128 decimalWithNumber:@1]; RLMDecimal128 *notMatchingDecimal = [RLMDecimal128 decimalWithNumber:@2]; RLMObjectId *matchingObjectId = [RLMObjectId objectId]; RLMObjectId *notMatchingObjectId = [RLMObjectId objectId]; struct NullTestData data[] = { {@"boolObj", @"YES", @"NO", @YES, @NO}, {@"intObj", @"1", @"0", @1, @0, true}, {@"floatObj", @"1", @"0", @1, @0, true}, {@"doubleObj", @"1", @"0", @1, @0, true}, {@"string", @"'a'", @"''", @"a", @"", false, true}, {@"data", nil, nil, notMatchingData, matchingData, false, true}, {@"date", nil, nil, notMatchingDate, matchingDate, true}, {@"decimal", nil, nil, notMatchingDecimal, matchingDecimal, true}, {@"objectId", nil, nil, notMatchingObjectId, matchingObjectId, false}, }; // Assert that the query "prop op value" gives expectedCount results when // assembled via string formatting #define RLMAssertCountWithString(expectedCount, op, prop, value) \ do { \ NSString *queryStr = [NSString stringWithFormat:@"%@ " #op " %@", prop, value]; \ NSUInteger actual = [AllOptionalTypes objectsWhere:queryStr].count; \ XCTAssertEqual(expectedCount, actual, @"%@: expected %@, got %@", queryStr, @(expectedCount), @(actual)); \ } while (0) // Assert that the query "prop op value" gives expectedCount results when // assembled via predicateWithFormat #define RLMAssertCountWithPredicate(expectedCount, op, prop, value) \ do { \ NSPredicate *query = [NSPredicate predicateWithFormat:@"%K " #op " %@", prop, value]; \ NSUInteger actual = [AllOptionalTypes objectsWithPredicate:query].count; \ XCTAssertEqual(expectedCount, actual, @"%@ " #op " %@: expected %@, got %@", prop, value, @(expectedCount), @(actual)); \ } while (0) // Assert that the given operator gives the expected count for each of the // stored value, a different value, and nil #define RLMAssertOperator(op, matchingCount, notMatchingCount, nilCount) \ do { \ if (d.matchingStr) { \ RLMAssertCountWithString(matchingCount, op, d.propertyName, d.matchingStr); \ RLMAssertCountWithString(notMatchingCount, op, d.propertyName, d.nonMatchingStr); \ } \ RLMAssertCountWithString(nilCount, op, d.propertyName, nil); \ \ RLMAssertCountWithPredicate(matchingCount, op, d.propertyName, d.matchingValue); \ RLMAssertCountWithPredicate(notMatchingCount, op, d.propertyName, d.nonMatchingValue); \ RLMAssertCountWithPredicate(nilCount, op, d.propertyName, nil); \ } while (0) // First test with the `matchingValue` stored in each property [realm beginWriteTransaction]; [AllOptionalTypes createInRealm:realm withValue:@[@NO, @0, @0, @0, @"", matchingData, matchingDate, matchingDecimal, matchingObjectId]]; [realm commitWriteTransaction]; for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { struct NullTestData d = data[i]; RLMAssertOperator(=, 1U, 0U, 0U); RLMAssertOperator(!=, 0U, 1U, 1U); if (d.orderable) { RLMAssertOperator(<, 0U, 1U, 0U); RLMAssertOperator(<=, 1U, 1U, 0U); RLMAssertOperator(>, 0U, 0U, 0U); RLMAssertOperator(>=, 1U, 0U, 0U); } if (d.substringOperations) { RLMAssertOperator(BEGINSWITH, 0U, 0U, 0U); RLMAssertOperator(ENDSWITH, 0U, 0U, 0U); RLMAssertOperator(CONTAINS, 0U, 0U, 0U); } } // Retest with all properties nil [realm beginWriteTransaction]; [realm deleteAllObjects]; [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; [realm commitWriteTransaction]; for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { struct NullTestData d = data[i]; RLMAssertOperator(=, 0U, 0U, 1U); RLMAssertOperator(!=, 1U, 1U, 0U); if (d.orderable) { RLMAssertOperator(<, 0U, 0U, 0U); RLMAssertOperator(<=, 0U, 0U, 1U); RLMAssertOperator(>, 0U, 0U, 0U); RLMAssertOperator(>=, 0U, 0U, 1U); } if (d.substringOperations) { RLMAssertOperator(BEGINSWITH, 0U, 0U, 0U); RLMAssertOperator(ENDSWITH, 0U, 0U, 0U); RLMAssertOperator(CONTAINS, 0U, 0U, 0U); } } #undef RLMAssertCountWithString #undef RLMAssertCountWithPredicate #undef RLMAssertOperator } - (void)testPrimitiveOperatorsOnAllNullablePropertyTypesKeypathOnRHS { RLMRealm *realm = [self realm]; // These need to be stored in variables because the struct does not retain them NSData *matchingData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; NSData *notMatchingData = [@"a" dataUsingEncoding:NSUTF8StringEncoding]; NSDate *matchingDate = [NSDate dateWithTimeIntervalSince1970:1]; NSDate *notMatchingDate = [NSDate dateWithTimeIntervalSince1970:2]; RLMDecimal128 *matchingDecimal = [RLMDecimal128 decimalWithNumber:@1]; RLMDecimal128 *notMatchingDecimal = [RLMDecimal128 decimalWithNumber:@2]; RLMObjectId *matchingObjectId = [RLMObjectId objectId]; RLMObjectId *notMatchingObjectId = [RLMObjectId objectId]; struct NullTestData data[] = { {@"boolObj", @"YES", @"NO", @YES, @NO}, {@"intObj", @"1", @"0", @1, @0, true}, {@"floatObj", @"1", @"0", @1, @0, true}, {@"doubleObj", @"1", @"0", @1, @0, true}, {@"string", @"'a'", @"''", @"a", @"", false, true}, {@"data", nil, nil, notMatchingData, matchingData, false, true}, {@"date", nil, nil, notMatchingDate, matchingDate, true}, {@"decimal", nil, nil, notMatchingDecimal, matchingDecimal, true}, {@"objectId", nil, nil, notMatchingObjectId, matchingObjectId, false}, }; // Assert that the query "prop op value" gives expectedCount results when // assembled via string formatting #define RLMAssertCountWithString(expectedCount, op, prop, value) \ do { \ NSString *queryStr = [NSString stringWithFormat:@"%@ " #op " %@", value, prop]; \ NSUInteger actual = [AllOptionalTypes objectsWhere:queryStr].count; \ XCTAssertEqual(expectedCount, actual, @"%@: expected %@, got %@", queryStr, @(expectedCount), @(actual)); \ queryStr = [NSString stringWithFormat:@"%@ " #op " %@", value, prop]; \ actual = [AllOptionalTypes objectsWhere:queryStr].count; \ XCTAssertEqual(expectedCount, actual, @"%@: expected %@, got %@", queryStr, @(expectedCount), @(actual)); \ } while (0) // Assert that the query "prop op value" gives expectedCount results when // assembled via predicateWithFormat #define RLMAssertCountWithPredicate(expectedCount, op, prop, value) \ do { \ NSPredicate *query = [NSPredicate predicateWithFormat:@ "%@ " #op " %K", value, prop]; \ NSUInteger actual = [AllOptionalTypes objectsWithPredicate:query].count; \ XCTAssertEqual(expectedCount, actual, @"%@ " #op " %@: expected %@, got %@", prop, value, @(expectedCount), @(actual)); \ } while (0) // Assert that the given operator gives the expected count for each of the // stored value, a different value, and nil #define RLMAssertOperator(op, matchingCount, notMatchingCount, nilCount) \ do { \ if (d.matchingStr) { \ RLMAssertCountWithString(matchingCount, op, d.propertyName, d.matchingStr); \ RLMAssertCountWithString(notMatchingCount, op, d.propertyName, d.nonMatchingStr); \ } \ RLMAssertCountWithString(nilCount, op, d.propertyName, nil); \ \ RLMAssertCountWithPredicate(matchingCount, op, d.propertyName, d.matchingValue); \ RLMAssertCountWithPredicate(notMatchingCount, op, d.propertyName, d.nonMatchingValue); \ RLMAssertCountWithPredicate(nilCount, op, d.propertyName, nil); \ } while (0) // First test with the `matchingValue` stored in each property [realm beginWriteTransaction]; [AllOptionalTypes createInRealm:realm withValue:@[@NO, @0, @0, @0, @"", matchingData, matchingDate, matchingDecimal, matchingObjectId]]; [realm commitWriteTransaction]; for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { struct NullTestData d = data[i]; RLMAssertOperator(=, 1U, 0U, 0U); RLMAssertOperator(!=, 0U, 1U, 1U); if (d.orderable) { RLMAssertOperator(>, 0U, 1U, 0U); RLMAssertOperator(>=, 1U, 1U, 0U); RLMAssertOperator(<, 0U, 0U, 0U); RLMAssertOperator(<=, 1U, 0U, 0U); } } // Retest with all properties nil [realm beginWriteTransaction]; [realm deleteAllObjects]; [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; [realm commitWriteTransaction]; for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { struct NullTestData d = data[i]; RLMAssertOperator(=, 0U, 0U, 1U); RLMAssertOperator(!=, 1U, 1U, 0U); if (d.orderable) { RLMAssertOperator(>, 0U, 0U, 0U); RLMAssertOperator(>=, 0U, 0U, 1U); RLMAssertOperator(<, 0U, 0U, 0U); RLMAssertOperator(<=, 0U, 0U, 1U); } } #undef RLMAssertCountWithString #undef RLMAssertCountWithPredicate #undef RLMAssertOperator } - (void)testINPredicateOnNullWithNonNullValues { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; RLMObjectId *objectId = [RLMObjectId objectId]; [AllOptionalTypes createInRealm:realm withValue:@[@YES, @1, @1, @1, @"abc", [@"a" dataUsingEncoding:NSUTF8StringEncoding], [NSDate dateWithTimeIntervalSince1970:1], @1, objectId]]; [realm commitWriteTransaction]; //////////////////////// // Literal Predicates //////////////////////// // BOOL [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"boolObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"boolObj IN {YES}"]; // int [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"intObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"intObj IN {1}"]; // float [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"floatObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"floatObj IN {1}"]; // double [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"doubleObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"doubleObj IN {1}"]; // NSString [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"string IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"string IN {'abc'}"]; // RLMDecimal128 [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"decimal IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"decimal IN {'1'}"]; // NSData // Can't represent NSData with NSPredicate literal. See format predicates below // NSDate // Can't represent NSDate with NSPredicate literal. See format predicates below //////////////////////// // Format Predicates //////////////////////// // BOOL [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"boolObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"boolObj IN %@", @[@YES]]; // int [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"intObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"intObj IN %@", @[@1]]; // float [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"floatObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"floatObj IN %@", @[@1]]; // double [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"doubleObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"doubleObj IN %@", @[@1]]; // NSString [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"string IN %@", @[@"abc"]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"string IN %@", @[NSNull.null]]; // NSData [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"data IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"data IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; // NSDate [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"date IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"date IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; // RLMDecimal128 [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"decimal IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"decimal IN %@", @[@1]]; // RLMObjectId [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"objectId IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"objectId IN %@", @[objectId]]; } - (void)testINPredicateOnNullWithNullValues { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; [realm commitWriteTransaction]; //////////////////////// // Literal Predicates //////////////////////// // BOOL [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"boolObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"boolObj IN {YES}"]; // int [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"intObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"intObj IN {1}"]; // float [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"floatObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"floatObj IN {1}"]; // double [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"doubleObj IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"doubleObj IN {1}"]; // NSString [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"string IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"string IN {'abc'}"]; // RLMDecimal128 [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"decimal IN {NULL}"]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"decimal IN {'1'}"]; // NSData // Can't represent NSData with NSPredicate literal. See format predicates below // NSDate // Can't represent NSDate with NSPredicate literal. See format predicates below //////////////////////// // Format Predicates //////////////////////// // BOOL [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"boolObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"boolObj IN %@", @[@YES]]; // int [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"intObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"intObj IN %@", @[@1]]; // float [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"floatObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"floatObj IN %@", @[@1]]; // double [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"doubleObj IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"doubleObj IN %@", @[@1]]; // NSString [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"string IN %@", @[@"abc"]]; [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"string IN %@", @[NSNull.null]]; // NSData [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"data IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"data IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; // NSDate [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"date IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"date IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; // RLMDecimal128 [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"decimal IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"decimal IN %@", @[@1]]; // RLMObjectId [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"objectId IN %@", @[NSNull.null]]; [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"objectId IN %@", @[RLMObjectId.objectId]]; } - (void)testQueryOnRenamedProperties { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]]; [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]]; [realm commitWriteTransaction]; [self testClass:[RenamedProperties1 class] withNormalCount:2 notCount:0 where:@"propA != 0"]; [self testClass:[RenamedProperties1 class] withNormalCount:1 notCount:1 where:@"propA = 1"]; [self testClass:[RenamedProperties1 class] withNormalCount:1 notCount:1 where:@"propA = 2"]; [self testClass:[RenamedProperties1 class] withNormalCount:0 notCount:2 where:@"propA = 3"]; [self testClass:[RenamedProperties2 class] withNormalCount:2 notCount:0 where:@"propC != 0"]; [self testClass:[RenamedProperties2 class] withNormalCount:1 notCount:1 where:@"propC = 1"]; [self testClass:[RenamedProperties2 class] withNormalCount:1 notCount:1 where:@"propC = 2"]; [self testClass:[RenamedProperties2 class] withNormalCount:0 notCount:2 where:@"propC = 3"]; } - (void)testQueryOverRenamedLinks { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; id obj1 = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]]; id obj2 = [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]]; [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, NSNull.null, @[obj1], @[obj1]]]; [LinkToRenamedProperties2 createInRealm:realm withValue:@[obj2, NSNull.null, @[obj2], @[obj2]]]; [realm commitWriteTransaction]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:2 notCount:0 where:@"linkA.propA != 0"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"linkA.propA = 1"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"linkA.propA = 2"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:0 notCount:2 where:@"linkA.propA = 3"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:2 notCount:0 where:@"linkC.propC != 0"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"linkC.propC = 1"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"linkC.propC = 2"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:0 notCount:2 where:@"linkC.propC = 3"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:2 notCount:0 where:@"ANY array.propA != 0"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY array.propA = 1"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY array.propA = 2"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:0 notCount:2 where:@"ANY array.propA = 3"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:2 notCount:0 where:@"ANY array.propC != 0"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"ANY array.propC = 1"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"ANY array.propC = 2"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:0 notCount:2 where:@"ANY array.propC = 3"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:2 notCount:0 where:@"ANY set.propA != 0"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY set.propA = 1"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY set.propA = 2"]; [self testClass:[LinkToRenamedProperties1 class] withNormalCount:0 notCount:2 where:@"ANY set.propA = 3"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:2 notCount:0 where:@"ANY set.propC != 0"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"ANY set.propC = 1"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:1 notCount:1 where:@"ANY set.propC = 2"]; [self testClass:[LinkToRenamedProperties2 class] withNormalCount:0 notCount:2 where:@"ANY set.propC = 3"]; } - (void)testQueryOverRenamedBacklinks { RLMRealm *realm = [self realm]; [realm beginWriteTransaction]; id obj1 = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]]; id obj2 = [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]]; [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, NSNull.null, @[obj1]]]; [LinkToRenamedProperties2 createInRealm:realm withValue:@[obj2, NSNull.null, @[obj2]]]; [realm commitWriteTransaction]; [self testClass:[RenamedProperties1 class] withNormalCount:2 notCount:0 where:@"ANY linking1.linkA.propA != 0"]; [self testClass:[RenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY linking1.linkA.propA = 1"]; [self testClass:[RenamedProperties1 class] withNormalCount:1 notCount:1 where:@"ANY linking1.linkA.propA = 2"]; [self testClass:[RenamedProperties1 class] withNormalCount:0 notCount:2 where:@"ANY linking1.linkA.propA = 3"]; } @end @interface AsyncQueryTests : QueryTests @end @implementation AsyncQueryTests - (RLMResults *)evaluate:(RLMResults *)results { id token = [results addNotificationBlock:^(RLMResults *r, __unused RLMCollectionChange *changed, NSError *e) { XCTAssertNil(e); XCTAssertNotNil(r); CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [(RLMNotificationToken *)token invalidate]; return results; } @end @interface QueryWithReversedColumnOrderTests : QueryTests @end @implementation QueryWithReversedColumnOrderTests - (RLMRealm *)realm { @autoreleasepool { NSSet *classNames = [NSSet setWithArray:@[@"AllTypesObject", @"QueryObject", @"PersonObject", @"DogObject", @"EmployeeObject", @"CompanyObject", @"OwnerObject"]]; RLMSchema *schema = [RLMSchema.sharedSchema copy]; NSMutableArray *objectSchemas = [schema.objectSchema mutableCopy]; for (NSUInteger i = 0; i < objectSchemas.count; i++) { RLMObjectSchema *objectSchema = objectSchemas[i]; if ([classNames member:objectSchema.className]) { objectSchemas[i] = [self reverseProperties:objectSchema]; } } schema.objectSchema = objectSchemas; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = schema; [RLMRealm realmWithConfiguration:config error:nil]; } return RLMRealm.defaultRealm; } - (RLMObjectSchema *)reverseProperties:(RLMObjectSchema *)source { RLMObjectSchema *objectSchema = [source copy]; objectSchema.properties = objectSchema.properties.reverseObjectEnumerator.allObjects; return objectSchema; } @end ================================================ FILE: Realm/Tests/RLMValueTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "RLMTestCase.h" #import @interface RLMValueTests : RLMTestCase @end @implementation RLMValueTests #pragma mark - Type Checking - (void)testIntType { id v = @123; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeInt); } - (void)testFloatType { id v = @123.456f; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeFloat); } - (void)testStringType { id v = @"hello"; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeString); } - (void)testDataType { id v = [NSData dataWithBytes:"hey" length:3]; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeData); } - (void)testDateType { id v = [NSDate date]; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testObjectType { id v = [[StringObject alloc] init]; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeObject); } - (void)testObjectIdType { id v = [RLMObjectId objectId]; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeObjectId); } - (void)testDecimal128Type { id v = [RLMDecimal128 decimalWithNumber:@123.456]; XCTAssertEqual(v.rlm_anyValueType, RLMAnyValueTypeDecimal128); } - (void)testDictionaryType { NSDictionary *dictionary = @{ @"key1" : @"hello" }; XCTAssertEqual(dictionary.rlm_anyValueType, RLMAnyValueTypeDictionary); } - (void)testArrayType { NSArray *array = @[ @"hello", @123456 ]; XCTAssertEqual(array.rlm_anyValueType, RLMAnyValueTypeList); } #pragma mark - Comparison - (void)testNumberEquals { id v1 = @123; id v2 = @123; XCTAssertEqualObjects(v1, v2); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeInt); XCTAssertNotEqual(v2, @456); } - (void)testStringEquals { id v1 = @"hello"; id v2 = @"hello"; XCTAssertEqual(v1, v2); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeString); XCTAssertNotEqual(v2, @"there"); } - (void)testDataEquals { NSData *d = [NSData dataWithBytes:"hey" length:3]; id v1 = [d copy]; id v2 = [d copy]; XCTAssertEqual(v1, v2); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeData); XCTAssertNotEqual(v1, [NSData dataWithBytes:"there" length:5]); } - (void)testDateEquals { NSDate *d = [NSDate date]; id v1 = [d copy]; id v2 = [d copy]; XCTAssertEqual(v1, v2); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeDate); XCTAssertNotEqual(v1, [NSDate dateWithTimeIntervalSince1970:0]); } - (void)testObjectEquals { StringObject *so = [[StringObject alloc] init]; id v1 = so; id v2 = so; XCTAssertEqual(v1, so); XCTAssertEqual(v2, so); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertEqual(v1, v2); XCTAssertNotEqual(v1, [[StringObject alloc] init]); } - (void)testObjectIdEquals { RLMObjectId *oid = [RLMObjectId objectId]; id v1 = oid; id v2 = oid; XCTAssertEqual(v1, oid); XCTAssertEqual(v2, oid); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeObjectId); XCTAssertEqual(v1, v2); XCTAssertNotEqual(v1, [RLMObjectId objectId]); } - (void)testDecimal128Equals { RLMDecimal128 *d = [RLMDecimal128 decimalWithNumber:@123.456]; id v1 = d; id v2 = d; XCTAssertEqual(v1, d); XCTAssertEqual(v2, d); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeDecimal128); XCTAssertEqual(v1, v2); XCTAssertNotEqual(v1, [RLMDecimal128 decimalWithNumber:@456.123]); } - (void)testDictionaryAnyEquals { NSDictionary *dictionary = @{ @"key2" : @"hello2", @"key3" : @YES, @"key4" : @123, @"key5" : @456.789, @"key6" : [NSData dataWithBytes:"hey" length:3], @"key7" : [NSDate date], @"key8" : [[MixedObject alloc] init], @"key9" : [RLMObjectId objectId], @"key10" : [RLMDecimal128 decimalWithNumber:@123.456] }; id v1 = dictionary; id v2 = dictionary; XCTAssertEqual(v1, dictionary); XCTAssertEqual(v2, dictionary); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeDictionary); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeDictionary); XCTAssertEqual(v1, v2); } - (void)testArrayAnyEquals { NSArray *array = @[ @"hello2", @YES, @123, @456.789, [NSData dataWithBytes:"hey" length:3], [NSDate date], [[MixedObject alloc] init], [RLMObjectId objectId], [RLMDecimal128 decimalWithNumber:@123.456] ]; id v1 = array; id v2 = array; XCTAssertEqual(v1, array); XCTAssertEqual(v2, array); XCTAssertEqual(v1.rlm_anyValueType, RLMAnyValueTypeList); XCTAssertEqual(v2.rlm_anyValueType, RLMAnyValueTypeList); XCTAssertEqual(v1, v2); } #pragma mark - Managed Values - (void)testCreateManagedObjectManagedChild { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:so]; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[so, @[so]]]; MixedObject *mo1 = [[MixedObject alloc] init]; mo1.anyCol = so; [mo1.anyArray addObject:so]; [r commitWriteTransaction]; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)mo0.anyCol).stringCol isEqualToString:so.stringCol]); XCTAssertEqual(mo0.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertTrue([((StringObject *)mo0.anyArray.firstObject).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)mo1.anyCol).stringCol isEqualToString:so.stringCol]); XCTAssertEqual(mo1.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertTrue([((StringObject *)mo1.anyArray.firstObject).stringCol isEqualToString:so.stringCol]); XCTAssertEqual([StringObject allObjectsInRealm:r].count, 1U); } - (void)testCreateManagedObjectInAnyDictionary { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:so]; NSDictionary *dictionary = @{ @"key1" : so }; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[dictionary, @[dictionary]]]; MixedObject *mo1 = [[MixedObject alloc] init]; mo1.anyCol = dictionary; [mo1.anyArray addObject:dictionary]; [r commitWriteTransaction]; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)((NSDictionary *)mo0.anyCol)[@"key1"]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSDictionary *)mo0.anyArray.firstObject)[@"key1"]).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)((NSDictionary *)mo1.anyCol)[@"key1"]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSDictionary *)mo1.anyArray[0])[@"key1"]).stringCol isEqualToString:so.stringCol]); } - (void)testCreateManagedObjectInAnyArray { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:so]; NSArray *array = @[so]; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[array, @[array]]]; MixedObject *mo1 = [[MixedObject alloc] init]; mo1.anyCol = array; [mo1.anyArray addObject:array]; [r commitWriteTransaction]; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)((NSArray *)mo0.anyCol)[0]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSArray *)mo0.anyArray.firstObject)[0]).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)((NSArray *)mo1.anyCol)[0]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSArray *)mo1.anyArray[0])[0]).stringCol isEqualToString:so.stringCol]); } - (void)testCreateManagedObjectUnmanagedChild { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; StringObject *so1 = [[StringObject alloc] init]; so1.stringCol = @"hello2"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[so, @[so]]]; MixedObject *mo1 = [[MixedObject alloc] init]; [mo1.anyArray addObject:so]; [r addObject:mo1]; [r commitWriteTransaction]; XCTAssertThrows(mo1.anyCol = so1); [r beginWriteTransaction]; mo1.anyCol = so1; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)mo0.anyCol).stringCol isEqualToString:so.stringCol]); XCTAssertEqual(mo0.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertTrue([((StringObject *)mo0.anyArray[0]).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)mo1.anyCol).stringCol isEqualToString:so1.stringCol]); XCTAssertEqual(mo1.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); XCTAssertTrue([((StringObject *)mo1.anyArray[0]).stringCol isEqualToString:so.stringCol]); } - (void)testCreateManagedObjectUnmanagedChildInAnyDictionary { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; StringObject *so1 = [[StringObject alloc] init]; so1.stringCol = @"hello2"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; NSDictionary *dictionary = @{ @"key1" : so }; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[dictionary, @[dictionary]]]; MixedObject *mo1 = [[MixedObject alloc] init]; [mo1.anyArray addObject:dictionary]; [r addObject:mo1]; [r commitWriteTransaction]; XCTAssertThrows(mo1.anyCol = so1); [r beginWriteTransaction]; mo1.anyCol = @{ @"key2" : so1 }; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)((NSDictionary *)mo0.anyCol)[@"key1"]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSDictionary *)mo0.anyArray.firstObject)[@"key1"]).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)((NSDictionary *)mo1.anyCol)[@"key2"]).stringCol isEqualToString:so1.stringCol]); XCTAssertTrue([((StringObject *)((NSDictionary *)mo1.anyArray[0])[@"key1"]).stringCol isEqualToString:so.stringCol]); } - (void)testCreateManagedObjectUnmanagedChildInAnyArray { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; StringObject *so1 = [[StringObject alloc] init]; so1.stringCol = @"hello2"; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; NSArray *array = @[so]; MixedObject *mo0 = [MixedObject createInRealm:r withValue:@[array, @[array]]]; MixedObject *mo1 = [[MixedObject alloc] init]; [mo1.anyArray addObject:array]; [r addObject:mo1]; [r commitWriteTransaction]; XCTAssertThrows(mo1.anyCol = so1); [r beginWriteTransaction]; mo1.anyCol = @[so1]; XCTAssertNotNil(mo0.anyCol); XCTAssertTrue([((StringObject *)((NSArray *)mo0.anyCol)[0]).stringCol isEqualToString:so.stringCol]); XCTAssertTrue([((StringObject *)((NSArray *)mo0.anyArray.firstObject)[0]).stringCol isEqualToString:so.stringCol]); XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)((NSArray *)mo1.anyCol)[0]).stringCol isEqualToString:so1.stringCol]); XCTAssertTrue([((StringObject *)((NSArray *)mo1.anyArray[0])[0]).stringCol isEqualToString:so.stringCol]); } // difference between adding object and not! - (void)testCreateManagedObject { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; StringObject *so = [StringObject createInRealm:r withValue:@[@"hello"]]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[so, @[]]]; [r commitWriteTransaction]; XCTAssertTrue([((StringObject *)mo.anyCol).stringCol isEqualToString:so.stringCol]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); } - (void)testCreateManagedInt { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[@123456789, @[@123456, @67890]]]; [r commitWriteTransaction]; XCTAssertTrue([(NSNumber *)mo.anyCol isEqualToNumber:@123456789]); XCTAssertTrue([mo.anyArray[0] isEqualToNumber:@123456]); XCTAssertTrue([mo.anyArray[1] isEqualToNumber:@67890]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeInt); } - (void)testCreateManagedFloat { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[@1234.5f, @[@12345.6f, @678.9f]]]; [r commitWriteTransaction]; XCTAssertTrue([(NSNumber *)mo.anyCol isEqualToNumber:@1234.5f]); XCTAssertTrue([mo.anyArray[0] isEqualToNumber:[NSNumber numberWithFloat:12345.6f]]); XCTAssertTrue([mo.anyArray[1] isEqualToNumber:[NSNumber numberWithFloat:678.9f]]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeFloat); } - (void)testCreateManagedDouble { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[@1234.5, @[@12345.6, @678.9]]]; [r commitWriteTransaction]; XCTAssertTrue([(NSNumber *)mo.anyCol isEqualToNumber:@1234.5]); XCTAssertTrue([mo.anyArray[0] isEqualToNumber:[NSNumber numberWithDouble:12345.6]]); XCTAssertTrue([mo.anyArray[1] isEqualToNumber:[NSNumber numberWithDouble:678.9]]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeDouble); } - (void)testCreateManagedString { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[@"hello", @[@"over", @"there"]]]; [r commitWriteTransaction]; XCTAssertTrue([(NSString *)mo.anyCol isEqualToString:@"hello"]); XCTAssertTrue([mo.anyArray[0] isEqualToString:@"over"]); XCTAssertTrue([mo.anyArray[1] isEqualToString:@"there"]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeString); } - (void)testCreateManagedData { RLMRealm *r = [self realmWithTestPath]; NSData *d1 = [NSData dataWithBytes:"hey" length:3]; NSData *d2 = [NSData dataWithBytes:"you" length:3]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[d1, @[d1, d2]]]; [r commitWriteTransaction]; XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)mo.anyCol encoding:NSUTF8StringEncoding] isEqualToString:@"hey"]); XCTAssertTrue([[[NSString alloc] initWithData:mo.anyArray[0] encoding:NSUTF8StringEncoding] isEqualToString:@"hey"]); XCTAssertTrue([[[NSString alloc] initWithData:mo.anyArray[1] encoding:NSUTF8StringEncoding] isEqualToString:@"you"]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeData); } - (void)testCreateManagedDate { RLMRealm *r = [self realmWithTestPath]; NSDate *d1 = [NSDate date]; NSDate *d2 = [NSDate date]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[d1, @[d1, d2]]]; [r commitWriteTransaction]; // handle lossy margin of error. XCTAssertEqualWithAccuracy(d1.timeIntervalSince1970, ((NSDate *)mo.anyCol).timeIntervalSince1970, 1); XCTAssertEqualWithAccuracy(d1.timeIntervalSince1970, ((NSDate *)mo.anyArray[0]).timeIntervalSince1970, 1); XCTAssertEqualWithAccuracy(d2.timeIntervalSince1970, ((NSDate *)mo.anyArray[1]).timeIntervalSince1970, 1); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testCreateManagedObjectId { RLMRealm *r = [self realmWithTestPath]; RLMObjectId *oid1 = [RLMObjectId objectId]; RLMObjectId *oid2 = [RLMObjectId objectId]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[oid1, @[oid1, oid2]]]; [r commitWriteTransaction]; XCTAssertTrue([(RLMObjectId *)mo.anyCol isEqual:oid1]); XCTAssertTrue([(RLMObjectId *)mo.anyArray[0] isEqual:oid1]); XCTAssertTrue([(RLMObjectId *)mo.anyArray[1] isEqual:oid2]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeObjectId); } - (void)testCreateManagedDecimal128 { RLMRealm *r = [self realmWithTestPath]; RLMDecimal128 *d1 = [RLMDecimal128 decimalWithNumber:@123.456]; RLMDecimal128 *d2 = [RLMDecimal128 decimalWithNumber:@890.456]; [r beginWriteTransaction]; MixedObject *mo = [MixedObject createInRealm:r withValue:@[d1, @[d1, d2]]]; [r commitWriteTransaction]; XCTAssertTrue([(RLMDecimal128 *)mo.anyCol isEqual:d1]); XCTAssertTrue([(RLMDecimal128 *)mo.anyArray[0] isEqual:d1]); XCTAssertTrue([(RLMDecimal128 *)mo.anyArray[1] isEqual:d2]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeDecimal128); } - (void)testCreateManagedDictionary { RLMRealm *r = [self realmWithTestPath]; StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMObjectId *oid = [RLMObjectId objectId]; NSDate *d = [NSDate date]; NSDictionary *d1 = @{ @"key2" : @"hello2", @"key3" : @YES, @"key4" : @123, @"key5" : @456.789, @"key6" : [NSData dataWithBytes:"hey" length:3], @"key7" : d, @"key8" : so, @"key9" : oid, @"key10" : [RLMDecimal128 decimalWithNumber:@123.456] }; NSDictionary *d2 = @{ @"key1" : @"hello" }; [r beginWriteTransaction]; [MixedObject createInRealm:r withValue:@[d1, @[d1, d2]]]; [r commitWriteTransaction]; XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *result = [[MixedObject allObjectsInRealm:r] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)result.anyCol; XCTAssertTrue([dictionary[@"key2"] isEqual:@"hello2"]); XCTAssertTrue([dictionary[@"key3"] isEqual:@YES]); XCTAssertTrue([dictionary[@"key4"] isEqual:@123]); XCTAssertTrue([dictionary[@"key5"] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)dictionary[@"key6"] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)dictionary[@"key7"]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)dictionary[@"key8"]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)dictionary[@"key9"]) isEqual:oid]); XCTAssertTrue([dictionary[@"key10"] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMDictionary *dictionary1 = (RLMDictionary *)result.anyArray.firstObject; XCTAssertTrue([dictionary1[@"key2"] isEqual:@"hello2"]); XCTAssertTrue([dictionary1[@"key3"] isEqual:@YES]); XCTAssertTrue([dictionary1[@"key4"] isEqual:@123]); XCTAssertTrue([dictionary1[@"key5"] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)dictionary1[@"key6"] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)dictionary1[@"key7"]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)dictionary1[@"key8"]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)dictionary1[@"key9"]) isEqual:oid]); XCTAssertTrue([dictionary1[@"key10"] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMDictionary *dictionary2 = (RLMDictionary *)result.anyArray.lastObject; XCTAssertTrue([dictionary2[@"key1"] isEqual:@"hello"]); } - (void)testCreateManagedArray { RLMRealm *r = [self realmWithTestPath]; StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMObjectId *oid = [RLMObjectId objectId]; NSDate *d = [NSDate date]; NSArray *d1 = @[ @"hello2", @YES, @123, @456.789, [NSData dataWithBytes:"hey" length:3], d, so, oid, [RLMDecimal128 decimalWithNumber:@123.456] ]; NSArray *d2 = @[@"hello"]; [r beginWriteTransaction]; [MixedObject createInRealm:r withValue:@[d1, @[d1, d2]]]; [r commitWriteTransaction]; XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *result = [[MixedObject allObjectsInRealm:r] firstObject]; RLMArray *array = ((RLMArray *)result.anyCol); XCTAssertTrue([array[0] isEqual:@"hello2"]); XCTAssertTrue([array[1] isEqual:@YES]); XCTAssertTrue([array[2] isEqual:@123]); XCTAssertTrue([array[3] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)array[4] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)array[5]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)array[6]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)array[7]) isEqual:oid]); XCTAssertTrue([array[8] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMArray *array1 = (RLMArray *)result.anyArray.firstObject; XCTAssertTrue([array1[0] isEqual:@"hello2"]); XCTAssertTrue([array1[1] isEqual:@YES]); XCTAssertTrue([array1[2] isEqual:@123]); XCTAssertTrue([array1[3] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)array1[4] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)array1[5]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)array1[6]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)array1[7]) isEqual:oid]); XCTAssertTrue([array1[8] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMArray *array2 = (RLMArray *)result.anyArray.lastObject; XCTAssertTrue([array2[0] isEqual:@"hello"]); } #pragma mark - Add Managed Values - (void)testAddManagedObject { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; MixedObject *mo1 = [[MixedObject alloc] init]; mo1.anyCol = so; [mo1.anyArray addObject:so]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:so]; [r addObject:mo1]; [r commitWriteTransaction]; XCTAssertNotNil(mo1.anyCol); XCTAssertTrue([((StringObject *)mo1.anyCol).stringCol isEqualToString:so.stringCol]); XCTAssertEqual(mo1.anyCol.rlm_anyValueType, RLMAnyValueTypeObject); } - (void)testAddManagedInt { MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = @123456789; [mo.anyArray addObjects:@[@123456, @67890]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([(NSNumber *)mo.anyCol isEqualToNumber:@123456789]); XCTAssertTrue([mo.anyArray[0] isEqualToNumber:@123456]); XCTAssertTrue([mo.anyArray[1] isEqualToNumber:@67890]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeInt); } - (void)testAddManagedFloat { MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = @1234.5f; [mo.anyArray addObjects:@[@12345.6f, @678.9f]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([(NSNumber *)mo.anyCol isEqualToNumber:@1234.5f]); XCTAssertTrue([mo.anyArray[0] isEqualToNumber:[NSNumber numberWithFloat:12345.6f]]); XCTAssertTrue([mo.anyArray[1] isEqualToNumber:[NSNumber numberWithFloat:678.9f]]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeFloat); } - (void)testAddManagedString { MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = @"hello"; [mo.anyArray addObjects:@[@"over", @"there"]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([(NSString *)mo.anyCol isEqualToString:@"hello"]); XCTAssertTrue([mo.anyArray[0] isEqualToString:@"over"]); XCTAssertTrue([mo.anyArray[1] isEqualToString:@"there"]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeString); } - (void)testAddManagedData { NSData *d1 = [NSData dataWithBytes:"hey" length:3]; NSData *d2 = [NSData dataWithBytes:"you" length:3]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)mo.anyCol encoding:NSUTF8StringEncoding] isEqualToString:@"hey"]); XCTAssertTrue([[[NSString alloc] initWithData:mo.anyArray[0] encoding:NSUTF8StringEncoding] isEqualToString:@"hey"]); XCTAssertTrue([[[NSString alloc] initWithData:mo.anyArray[1] encoding:NSUTF8StringEncoding] isEqualToString:@"you"]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeData); } - (void)testAddManagedDate { NSDate *d1 = [NSDate date]; NSDate *d2 = [NSDate date]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; // handle lossy margin of error. XCTAssertEqualWithAccuracy(d1.timeIntervalSince1970, ((NSDate *)mo.anyCol).timeIntervalSince1970, 1.0); XCTAssertEqualWithAccuracy(d1.timeIntervalSince1970, ((NSDate *)mo.anyArray[0]).timeIntervalSince1970, 1.0); XCTAssertEqualWithAccuracy(d2.timeIntervalSince1970, ((NSDate *)mo.anyArray[1]).timeIntervalSince1970, 1.0); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeDate); } - (void)testAddManagedObjectId { RLMObjectId *oid1 = [RLMObjectId objectId]; RLMObjectId *oid2 = [RLMObjectId objectId]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = oid1; [mo.anyArray addObjects:@[oid1, oid2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([(RLMObjectId *)mo.anyCol isEqual:oid1]); XCTAssertTrue([(RLMObjectId *)mo.anyArray[0] isEqual:oid1]); XCTAssertTrue([(RLMObjectId *)mo.anyArray[1] isEqual:oid2]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeObjectId); } - (void)testAddManagedDecimal128 { RLMDecimal128 *d1 = [RLMDecimal128 decimalWithNumber:@123.456]; RLMDecimal128 *d2 = [RLMDecimal128 decimalWithNumber:@890.456]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertTrue([(RLMDecimal128 *)mo.anyCol isEqual:d1]); XCTAssertTrue([(RLMDecimal128 *)mo.anyArray[0] isEqual:d1]); XCTAssertTrue([(RLMDecimal128 *)mo.anyArray[1] isEqual:d2]); XCTAssertEqual(mo.anyCol.rlm_anyValueType, RLMAnyValueTypeDecimal128); } - (void)testAddManagedDictionary { StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMObjectId *oid = [RLMObjectId objectId]; NSDate *d = [NSDate date]; NSDictionary *d1 = @{ @"key2" : @"hello2", @"key3" : @YES, @"key4" : @123, @"key5" : @456.789, @"key6" : [NSData dataWithBytes:"hey" length:3], @"key7" : d, @"key8" : so, @"key9" : oid, @"key10" : [RLMDecimal128 decimalWithNumber:@123.456] }; NSDictionary *d2 = @{ @"key1" : @"hello" }; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *result = [[MixedObject allObjectsInRealm:r] firstObject]; RLMDictionary *dictionary = (RLMDictionary *)result.anyCol; XCTAssertTrue([dictionary[@"key2"] isEqual:@"hello2"]); XCTAssertTrue([dictionary[@"key3"] isEqual:@YES]); XCTAssertTrue([dictionary[@"key4"] isEqual:@123]); XCTAssertTrue([dictionary[@"key5"] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)dictionary[@"key6"] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)dictionary[@"key7"]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)dictionary[@"key8"]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)dictionary[@"key9"]) isEqual:oid]); XCTAssertTrue([dictionary[@"key10"] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMDictionary *dictionary1 = (RLMDictionary *)result.anyArray.firstObject; XCTAssertTrue([dictionary1[@"key2"] isEqual:@"hello2"]); XCTAssertTrue([dictionary1[@"key3"] isEqual:@YES]); XCTAssertTrue([dictionary1[@"key4"] isEqual:@123]); XCTAssertTrue([dictionary1[@"key5"] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)dictionary1[@"key6"] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)dictionary1[@"key7"]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)dictionary1[@"key8"]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)dictionary1[@"key9"]) isEqual:oid]); XCTAssertTrue([dictionary1[@"key10"] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMDictionary *dictionary2 = (RLMDictionary *)result.anyArray.lastObject; XCTAssertTrue([dictionary2[@"key1"] isEqual:@"hello"]); } - (void)testAddManagedArray { RLMRealm *r = [self realmWithTestPath]; StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; RLMObjectId *oid = [RLMObjectId objectId]; NSDate *d = [NSDate date]; NSArray *d1 = @[ @"hello2", @YES, @123, @456.789, [NSData dataWithBytes:"hey" length:3], d, so, oid, [RLMDecimal128 decimalWithNumber:@123.456] ]; NSArray *d2 = @[@"hello"]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *result = [[MixedObject allObjectsInRealm:r] firstObject]; RLMArray *array = ((RLMArray *)result.anyCol); XCTAssertTrue([array[0] isEqual:@"hello2"]); XCTAssertTrue([array[1] isEqual:@YES]); XCTAssertTrue([array[2] isEqual:@123]); XCTAssertTrue([array[3] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)array[4] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)array[5]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)array[6]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)array[7]) isEqual:oid]); XCTAssertTrue([array[8] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMArray *array1 = (RLMArray *)result.anyArray.firstObject; XCTAssertTrue([array1[0] isEqual:@"hello2"]); XCTAssertTrue([array1[1] isEqual:@YES]); XCTAssertTrue([array1[2] isEqual:@123]); XCTAssertTrue([array1[3] isEqual:@456.789]); XCTAssertTrue([[[NSString alloc] initWithData:(NSData *)array1[4] encoding:NSUTF8StringEncoding] isEqual:@"hey"]); XCTAssertEqualWithAccuracy(((NSDate *)array1[5]).timeIntervalSince1970, d.timeIntervalSince1970, 1.0); XCTAssertTrue([((StringObject *)array1[6]).stringCol isEqual:@"hello"]); XCTAssertTrue([((RLMObjectId *)array1[7]) isEqual:oid]); XCTAssertTrue([array1[8] isEqual:[RLMDecimal128 decimalWithNumber:@123.456]]); RLMArray *array2 = (RLMArray *)result.anyArray.lastObject; XCTAssertTrue([array2[0] isEqual:@"hello"]); } - (void)testDeleteAndUpdateValuesInManagedDictionary { RLMRealm *r = [self realmWithTestPath]; StringObject *so = [[StringObject alloc] init]; so.stringCol = @"hello"; NSDictionary *d1 = @{ @"key2" : @"hello2", @"key3" : @YES}; NSDictionary *d2 = @{ @"key1" : @"hello" }; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; RLMDictionary *dictionary = (RLMDictionary *)mo.anyCol; XCTAssertTrue([dictionary[@"key2"] isEqual:@"hello2"]); XCTAssertTrue([dictionary[@"key3"] isEqual:@YES]); RLMDictionary *dictionary2 = (RLMDictionary *)mo.anyArray.lastObject; XCTAssertTrue([dictionary2[@"key1"] isEqual:@"hello"]); [r beginWriteTransaction]; dictionary[@"key2"] = @1234; dictionary2[@"key1"] = @123.456; [r commitWriteTransaction]; XCTAssertTrue([dictionary[@"key2"] isEqual:@1234]); XCTAssertTrue([dictionary2[@"key1"] isEqual:@123.456]); [r beginWriteTransaction]; [dictionary removeObjectForKey:@"key3"]; [dictionary2 removeObjectForKey:@"key1"]; [r commitWriteTransaction]; XCTAssertNil(dictionary[@"key3"]); XCTAssertNil(dictionary2[@"key1"]); } - (void)testDeleteAndUpdateValuesInArray { NSArray *d1 = @[ @"hello2", @YES]; NSArray *d2 = @[@"hello"]; MixedObject *mo = [[MixedObject alloc] init]; mo.anyCol = d1; [mo.anyArray addObjects:@[d1, d2]]; RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; [r addObject:mo]; [r commitWriteTransaction]; XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *result = [[MixedObject allObjectsInRealm:r] firstObject]; RLMArray *array = (RLMArray *)result.anyCol; XCTAssertTrue([array[0] isEqual:@"hello2"]); XCTAssertTrue([array[1] isEqual:@YES]); XCTAssertEqual(array.count, 2U); RLMArray *array2 = (RLMArray *)mo.anyArray.lastObject; XCTAssertTrue([array2[0] isEqual:@"hello"]); [r beginWriteTransaction]; array[0] = @1234; array2[0] = @123.456; [r commitWriteTransaction]; XCTAssertTrue([array[0] isEqual:@1234]); XCTAssertTrue([array2[0] isEqual:@123.456]); XCTAssertEqual(array.count, 2U); XCTAssertEqual(array2.count, 1U); [r beginWriteTransaction]; [array removeObjectAtIndex:0]; [array2 removeObjectAtIndex:0]; [r commitWriteTransaction]; XCTAssertFalse([array[0] isEqual:@1234]); XCTAssertEqual(array.count, 1U); XCTAssertEqual(array2.count, 0U); } #pragma mark - Dynamic Object Accessor - (void)testDynamicObjectAccessor { @autoreleasepool { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[MixedObject.class, StringObject.class]; configuration.fileURL = RLMTestRealmURL(); RLMRealm *r = [RLMRealm realmWithConfiguration:configuration error:nil]; [r transactionWithBlock:^{ StringObject *so = [StringObject new]; so.stringCol = @"some string..."; [MixedObject createInRealm:r withValue:@{@"anyCol": so}]; }]; XCTAssertEqual([StringObject allObjectsInRealm:r].count, 1U); } RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[MixedObject.class]; configuration.fileURL = RLMTestRealmURL(); RLMRealm *r = [RLMRealm realmWithConfiguration:configuration error:nil]; for (RLMObjectSchema *os in r.schema.objectSchema) { XCTAssertFalse([os.className isEqualToString:@"StringObject"]); } XCTAssertEqual([MixedObject allObjectsInRealm:r].count, 1U); MixedObject *o = [MixedObject allObjectsInRealm:r][0]; XCTAssertTrue([o[@"anyCol"][@"stringCol"] isEqualToString:@"some string..."]); } // Tests that the `RLMSchemaInfo::clone` and `RLMSchemaInfo::operator[]` correctly // skip class names that are not present in the source schema. If such an event were // to occur an OOB exception would be thrown. - (void)testDynamicSchema { @autoreleasepool { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.objectClasses = @[MixedObject.class, IntObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm beginWriteTransaction]; [MixedObject createInRealm:realm withValue:@[[IntObject new]]]; [realm commitWriteTransaction]; } RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.objectClasses = @[MixedObject.class]; XCTestExpectation *ex = [self expectationWithDescription:@""]; __block RLMRealm *realm2; dispatch_async(dispatch_get_global_queue(0, 0), ^{ @autoreleasepool { RLMRealm *realm1 = [RLMRealm realmWithConfiguration:config error:nil]; (void)[[MixedObject allObjectsInRealm:realm1].firstObject anyCol]; dispatch_sync(dispatch_get_main_queue(), ^{ realm2 = [RLMRealm realmWithConfiguration:config error:nil]; }); } [ex fulfill]; }); [self waitForExpectations:@[ex] timeout:1.0]; (void)[[MixedObject allObjectsInRealm:realm2].firstObject anyCol]; } @end ================================================ FILE: Realm/Tests/RealmConfigurationTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "TestUtils.h" #import "RLMRealmConfiguration_Private.hpp" #import "RLMTestObjects.h" #import "RLMUtil.hpp" @interface RealmConfigurationTests : RLMTestCase @end @implementation RealmConfigurationTests #pragma mark - Setter Validation - (void)testSetPathAndInMemoryIdentifierAreMutuallyExclusive { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.inMemoryIdentifier = @"identifier"; XCTAssertNil(configuration.fileURL); XCTAssertEqualObjects(configuration.inMemoryIdentifier, @"identifier"); configuration.fileURL = [NSURL fileURLWithPath:@"/dev/null"]; XCTAssertNil(configuration.inMemoryIdentifier); XCTAssertEqualObjects(configuration.fileURL.path, @"/dev/null"); } - (void)testFileURLValidation { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; XCTAssertThrows(configuration.fileURL = nil); } - (void)testEncryptionKeyValidation { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; XCTAssertNoThrow(configuration.encryptionKey = nil); RLMAssertThrowsWithReasonMatching(configuration.encryptionKey = [NSData data], @"Encryption key must be exactly 64 bytes long"); NSData *key = RLMGenerateKey(); configuration.encryptionKey = key; XCTAssertEqualObjects(configuration.encryptionKey, key); } - (void)testSchemaVersionValidation { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; RLMAssertThrowsWithReasonMatching(configuration.schemaVersion = RLMNotVersioned, @"schema version.*RLMNotVersioned"); configuration.schemaVersion = 1; XCTAssertEqual(configuration.schemaVersion, 1U); configuration.schemaVersion = std::numeric_limits::max() - 1; XCTAssertEqual(configuration.schemaVersion, std::numeric_limits::max() - 1); } - (void)testClassSubsetsValidateLinks { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; XCTAssertThrows(configuration.objectClasses = @[LinkStringObject.class]); XCTAssertNoThrow(configuration.objectClasses = (@[LinkStringObject.class, StringObject.class])); XCTAssertThrows(configuration.objectClasses = @[CompanyObject.class]); XCTAssertNoThrow(configuration.objectClasses = (@[CompanyObject.class, EmployeeObject.class])); } - (void)testCannotSetMutuallyExclusiveProperties { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; XCTAssertNoThrow(configuration.readOnly = YES); XCTAssertNoThrow(configuration.deleteRealmIfMigrationNeeded = NO); XCTAssertThrows(configuration.deleteRealmIfMigrationNeeded = YES); XCTAssertNoThrow(configuration.readOnly = NO); XCTAssertNoThrow(configuration.deleteRealmIfMigrationNeeded = YES); XCTAssertNoThrow(configuration.readOnly = NO); XCTAssertThrows(configuration.readOnly = YES); } - (void)testSchemaModeTransitions { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::Automatic); configuration.readOnly = true; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::Immutable); configuration.readOnly = false; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::Automatic); configuration.deleteRealmIfMigrationNeeded = true; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::SoftResetFile); configuration.deleteRealmIfMigrationNeeded = false; XCTAssertEqual(configuration.schemaMode, realm::SchemaMode::Automatic); } #pragma mark - Default Configuration - (void)testDefaultConfiguration { RLMRealmConfiguration *defaultConfiguration = [RLMRealmConfiguration defaultConfiguration]; XCTAssertEqualObjects(defaultConfiguration.fileURL, RLMDefaultRealmURL()); XCTAssertNil(defaultConfiguration.inMemoryIdentifier); XCTAssertEqual(!!defaultConfiguration.encryptionKey, self.encryptTests); XCTAssertFalse(defaultConfiguration.readOnly); XCTAssertEqual(defaultConfiguration.schemaVersion, 0U); XCTAssertNil(defaultConfiguration.migrationBlock); // private properties XCTAssertFalse(defaultConfiguration.dynamic); XCTAssertNil(defaultConfiguration.customSchema); } - (void)testSetDefaultConfiguration { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.fileURL = [NSURL fileURLWithPath:@"/dev/null"]; [RLMRealmConfiguration setDefaultConfiguration:configuration]; XCTAssertEqualObjects(RLMRealmConfiguration.defaultConfiguration.fileURL.path, @"/dev/null"); } - (void)testDefaultConfigurationUsesValueSemantics { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.fileURL = [NSURL fileURLWithPath:@"/dev/null"]; XCTAssertNotEqualObjects(config.fileURL, RLMRealmConfiguration.defaultConfiguration.fileURL); [RLMRealmConfiguration setDefaultConfiguration:config]; XCTAssertEqualObjects(config.fileURL, RLMRealmConfiguration.defaultConfiguration.fileURL); config.fileURL = [NSURL fileURLWithPath:@"/dev/null/foo"]; XCTAssertNotEqualObjects(config.fileURL, RLMRealmConfiguration.defaultConfiguration.fileURL); } - (void)testDefaultRealmUsesDefaultConfiguration { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; @autoreleasepool { XCTAssertEqualObjects(RLMRealm.defaultRealm.configuration.fileURL, config.fileURL); } config.fileURL = RLMTestRealmURL(); @autoreleasepool { XCTAssertNotEqualObjects(RLMRealm.defaultRealm.configuration.fileURL, config.fileURL); } RLMRealmConfiguration.defaultConfiguration = config; @autoreleasepool { XCTAssertEqualObjects(RLMRealm.defaultRealm.configuration.fileURL, config.fileURL); } config.inMemoryIdentifier = NSUUID.UUID.UUIDString; config.encryptionKey = nil; RLMRealmConfiguration.defaultConfiguration = config; @autoreleasepool { RLMRealm *realm = RLMRealm.defaultRealm; NSString *realmPath = @(realm.configuration.path.c_str()); XCTAssertTrue([realmPath hasSuffix:config.inMemoryIdentifier]); XCTAssertTrue([realmPath hasPrefix:NSTemporaryDirectory()]); } config.schemaVersion = 1; RLMRealmConfiguration.defaultConfiguration = config; @autoreleasepool { RLMRealm *realm = RLMRealm.defaultRealm; NSString *realmPath = @(realm.configuration.path.c_str()); XCTAssertEqual(1U, [RLMRealm schemaVersionAtURL:[NSURL fileURLWithPath:realmPath] encryptionKey:nil error:nil]); } config.fileURL = RLMDefaultRealmURL(); RLMRealmConfiguration.defaultConfiguration = config; config.encryptionKey = RLMGenerateKey(); RLMRealmConfiguration.defaultConfiguration = config; @autoreleasepool { // Realm with no encryption key already exists from above XCTAssertThrows([RLMRealm defaultRealm]); } [self deleteRealmFileAtURL:config.fileURL]; // Create and then re-open with same key @autoreleasepool { XCTAssertNoThrow([RLMRealm defaultRealm]); } @autoreleasepool { XCTAssertNoThrow([RLMRealm defaultRealm]); } // Fail to re-open with a different key config.encryptionKey = RLMGenerateKey(); RLMRealmConfiguration.defaultConfiguration = config; @autoreleasepool { XCTAssertThrows([RLMRealm defaultRealm]); } // Verify that the default realm's migration block is used implicitly // when needed [self deleteRealmFileAtURL:config.fileURL]; @autoreleasepool { XCTAssertNoThrow([RLMRealm defaultRealm]); } config.schemaVersion = 2; __block bool migrationCalled = false; config.migrationBlock = ^(RLMMigration *, uint64_t) { migrationCalled = true; }; RLMRealmConfiguration.defaultConfiguration = config; XCTAssertFalse(migrationCalled); @autoreleasepool { XCTAssertNoThrow([RLMRealm defaultRealm]); } XCTAssertTrue(migrationCalled); } -(void)testCopyConfiguration { RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.fileURL = [NSURL fileURLWithPath:@"/dev/null"]; configuration.seedFilePath = [NSURL fileURLWithPath:@"/dev/null/seed"]; RLMRealmConfiguration *copy = [configuration copy]; XCTAssertEqualObjects(copy.fileURL, [NSURL fileURLWithPath:@"/dev/null"]); XCTAssertEqualObjects(copy.seedFilePath, [NSURL fileURLWithPath:@"/dev/null/seed"]); } @end ================================================ FILE: Realm/Tests/RealmTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Realm/Tests/RealmTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.hpp" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealmUtil.hpp" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.h" #import "RLMLogger_Private.h" #import #import #import #import #import #import #import #if !defined(REALM_COCOA_VERSION) #import "RLMVersion.h" #endif @interface RLMObjectSchema (Private) + (instancetype)schemaForObjectClass:(Class)objectClass; @property (nonatomic, readwrite, assign) Class objectClass; @end @interface RLMSchema (Private) @property (nonatomic, readwrite, copy) NSArray *objectSchema; @end @interface RealmTests : RLMTestCase @end @implementation RealmTests - (void)deleteFiles { [super deleteFiles]; for (NSString *realmPath in self.pathsFor100Realms) { [self deleteRealmFileAtURL:[NSURL fileURLWithPath:realmPath]]; } } #pragma mark - Opening Realms - (void)testOpeningInvalidPathThrows { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.fileURL = [NSURL fileURLWithPath:@"/dev/null/foo"]; RLMAssertRealmException([RLMRealm realmWithConfiguration:config error:nil], RLMErrorFileOperationFailed, @"Failed to open file at path '%@': parent path is not a directory", [config.fileURL.path stringByAppendingString:@".lock"]); } - (void)testPathCannotBeBothInMemoryAndRegularDurability { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.inMemoryIdentifier = @"identifier"; config.encryptionKey = nil; RLMRealm *inMemoryRealm = [RLMRealm realmWithConfiguration:config error:nil]; // make sure we can't open disk-realm at same path config.fileURL = [NSURL fileURLWithPath:@(inMemoryRealm.configuration.path.c_str())]; NSError *error; // passing in a reference to assert that this error can't be caught! RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:&error], ([NSString stringWithFormat:@"Realm at path '%@' already opened with different inMemory settings", config.fileURL.path])); } - (void)testRealmWithPathUsesDefaultConfiguration { RLMRealmConfiguration *originalDefaultConfiguration = [RLMRealmConfiguration defaultConfiguration]; RLMRealmConfiguration *newDefaultConfiguration = [originalDefaultConfiguration copy]; newDefaultConfiguration.objectClasses = @[]; [RLMRealmConfiguration setDefaultConfiguration:newDefaultConfiguration]; XCTAssertEqual([[[[RLMRealm realmWithURL:RLMTestRealmURL()] configuration] objectClasses] count], 0U); [RLMRealmConfiguration setDefaultConfiguration:originalDefaultConfiguration]; } - (void)testReadOnlyFile { @autoreleasepool { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; } [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @YES} ofItemAtPath:RLMTestRealmURL().path error:nil]; // Should not be able to open read-write RLMAssertRealmException([self realmWithTestPath], RLMErrorFilePermissionDenied, @"Failed to open Realm file at path '%@': Operation not permitted. Please use a path where your app has read-write permissions.", RLMTestRealmURL().path); RLMRealm *realm; XCTAssertNoThrow(realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @NO} ofItemAtPath:RLMTestRealmURL().path error:nil]; } - (void)testReadOnlyFileInImmutableDirectory { @autoreleasepool { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; } // Delete '*.lock' and '.note' files to simulate opening Realm in an app bundle [[NSFileManager defaultManager] removeItemAtURL:[RLMTestRealmURL() URLByAppendingPathExtension:@"lock"] error:nil]; [[NSFileManager defaultManager] removeItemAtURL:[RLMTestRealmURL() URLByAppendingPathExtension:@"note"] error:nil]; // Make parent directory immutable to simulate opening Realm in an app bundle NSURL *parentDirectoryOfTestRealmURL = [RLMTestRealmURL() URLByDeletingLastPathComponent]; [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @YES} ofItemAtPath:parentDirectoryOfTestRealmURL.path error:nil]; RLMRealm *realm; // Read-only Realm should be opened even in immutable directory XCTAssertNoThrow(realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]); [self dispatchAsyncAndWait:^{ XCTAssertNoThrow([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]); }]; [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @NO} ofItemAtPath:parentDirectoryOfTestRealmURL.path error:nil]; } - (void)testReadOnlyRealmMustExist { RLMAssertRealmException([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil], RLMErrorFileNotFound, @"Failed to open Realm file at path '%@': No such file or directory", RLMTestRealmURL().path); } - (void)testCannotHaveReadOnlyAndReadWriteRealmsAtSamePathAtSameTime { NSString *exceptionReason = @"Realm at path '.*' already opened with different read permissions"; @autoreleasepool { RLMRealm *realm = self.realmWithTestPath; RLMAssertThrowsWithReasonMatching([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil], exceptionReason); } @autoreleasepool { RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; RLMAssertThrowsWithReasonMatching([self realmWithTestPath], exceptionReason); } [self dispatchAsyncAndWait:^{ RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; RLMAssertThrowsWithReasonMatching([self realmWithTestPath], exceptionReason); }]; } - (void)testCanOpenReadOnlyOnMulitpleThreadsAtOnce { @autoreleasepool { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; } RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); [self dispatchAsyncAndWait:^{ RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); }]; // Verify that closing the other RLMRealm didn't manage to break anything XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testFilePermissionDenied { @autoreleasepool { XCTAssertNoThrow([self realmWithTestPath]); } // Make Realm at test path temporarily unreadable NSError *error; NSNumber *permissions = [NSFileManager.defaultManager attributesOfItemAtPath:RLMTestRealmURL().path error:&error][NSFilePosixPermissions]; assert(!error); [NSFileManager.defaultManager setAttributes:@{NSFilePosixPermissions: @(0000)} ofItemAtPath:RLMTestRealmURL().path error:&error]; assert(!error); RLMAssertRealmException([self realmWithTestPath], RLMErrorFilePermissionDenied, @"Failed to open Realm file at path '%@': Permission denied. Please use a path where your app has read-write permissions.", RLMTestRealmURL().path); RLMAssertRealmException([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil], RLMErrorFilePermissionDenied, @"Failed to open Realm file at path '%@': Permission denied. Please use a path where your app has read permissions.", RLMTestRealmURL().path); [NSFileManager.defaultManager setAttributes:@{NSFilePosixPermissions: permissions} ofItemAtPath:RLMTestRealmURL().path error:&error]; assert(!error); } #ifndef SWIFT_PACKAGE // Check that the data for file was left unchanged when opened with upgrading // disabled, but allow expanding the file to the page size #define AssertFileUnmodified(oldURL, newURL) do { \ NSData *oldData = [NSData dataWithContentsOfURL:oldURL]; \ NSData *newData = [NSData dataWithContentsOfURL:newURL]; \ if (oldData.length != newData.length && oldData.length < realm::util::page_size()) { \ XCTAssertEqual(newData.length, realm::util::page_size()); \ XCTAssertEqualObjects(oldData, ([newData subdataWithRange:{0, oldData.length}])); \ } \ else \ XCTAssertEqualObjects(oldData, newData); \ } while (0) - (void)testFileFormatUpgradeRequiredDeleteRealmIfNeeded { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.deleteRealmIfMigrationNeeded = YES; NSURL *bundledRealmURL = [[NSBundle bundleForClass:[RealmTests class]] URLForResource:@"file-format-version-21" withExtension:@"realm"]; [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil]; @autoreleasepool { XCTAssertTrue([[RLMRealm realmWithConfiguration:config error:nil] isEmpty]); } } - (void)testFileFormatUpgradeRequiredButDisabled { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.disableFormatUpgrade = true; NSURL *bundledRealmURL = [[NSBundle bundleForClass:[RealmTests class]] URLForResource:@"file-format-version-21" withExtension:@"realm"]; [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil]; RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorFileFormatUpgradeRequired); AssertFileUnmodified(bundledRealmURL, config.fileURL); } - (void)testFileFormatUpgradeRequiredButReadOnly { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; config.readOnly = true; NSURL *bundledRealmURL = [[NSBundle bundleForClass:[RealmTests class]] URLForResource:@"file-format-version-10" withExtension:@"realm"]; [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil]; RLMAssertRealmException([RLMRealm realmWithConfiguration:config error:nil], RLMErrorFileFormatUpgradeRequired, @"Realm file at path '%@' cannot be opened in read-only mode because it has a file format version (10) which requires an upgrade", config.fileURL.path); XCTAssertEqualObjects([NSData dataWithContentsOfURL:bundledRealmURL], [NSData dataWithContentsOfURL:config.fileURL]); } - (void)testUnsupportedFileFormatVersion { RLMRealmConfiguration *config = [RLMRealmConfiguration new]; NSURL *bundledRealmURL = [[NSBundle bundleForClass:[RealmTests class]] URLForResource:@"fileformat-pre-null" withExtension:@"realm"]; [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil]; RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorUnsupportedFileFormatVersion); AssertFileUnmodified(bundledRealmURL, config.fileURL); } #endif // SWIFT_PACKAGE #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST && (!TARGET_OS_SIMULATOR || !TARGET_RT_64_BIT) - (void)testExceedingVirtualAddressSpace { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; const NSUInteger stringLength = 1024 * 1024; void *mem = calloc(stringLength, '1'); NSString *largeString = [[NSString alloc] initWithBytesNoCopy:mem length:stringLength encoding:NSUTF8StringEncoding freeWhenDone:YES]; @autoreleasepool { RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm beginWriteTransaction]; StringObject *stringObj = [StringObject new]; stringObj.stringCol = largeString; [realm addObject:stringObj]; [realm commitWriteTransaction]; } struct VirtualMemoryChunk { vm_address_t address; vm_size_t size; }; std::vector allocatedChunks; NSUInteger size = 1024 * 1024 * 1024; while (size >= stringLength) { VirtualMemoryChunk chunk { .size = size }; kern_return_t ret = vm_allocate(mach_task_self(), &chunk.address, chunk.size, VM_FLAGS_ANYWHERE); if (ret == KERN_NO_SPACE) { size /= 2; } else { allocatedChunks.push_back(chunk); } } @autoreleasepool { RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorAddressSpaceExhausted); } for (auto chunk : allocatedChunks) { kern_return_t ret = vm_deallocate(mach_task_self(), chunk.address, chunk.size); assert(ret == KERN_SUCCESS); } @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } } #endif - (void)testOpenAsync { // Locals RLMRealmConfiguration *c = [RLMRealmConfiguration defaultConfiguration]; XCTestExpectation *ex = [self expectationWithDescription:@"open-async"]; // Helpers auto assertNoCachedRealm = ^{ XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String)); }; auto fileExists = ^BOOL() { return [[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]; }; // Unsuccessful open c.readOnly = true; [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue() callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) { XCTAssertEqual(error.code, RLMErrorFileNotFound); XCTAssertNil(realm); [ex fulfill]; }]; XCTAssertFalse(fileExists()); assertNoCachedRealm(); [self waitForExpectationsWithTimeout:5 handler:nil]; XCTAssertFalse(fileExists()); assertNoCachedRealm(); // Successful open c.readOnly = false; ex = [self expectationWithDescription:@"open-async"]; // Hold exclusive lock on lock file to prevent Realm from being created // if the dispatch_async happens too quickly NSString *lockFilePath = [c.pathOnDisk stringByAppendingString:@".lock"]; [[NSFileManager defaultManager] createFileAtPath:lockFilePath contents:[NSData data] attributes:nil]; int fd = open(lockFilePath.UTF8String, O_RDWR); XCTAssertNotEqual(-1, fd); int ret = flock(fd, LOCK_SH); XCTAssertEqual(0, ret); [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue() callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) { XCTAssertNil(error); XCTAssertNotNil(realm); [ex fulfill]; }]; XCTAssertFalse(fileExists()); flock(fd, LOCK_UN); close(fd); assertNoCachedRealm(); [self waitForExpectationsWithTimeout:1 handler:nil]; XCTAssertTrue(fileExists()); assertNoCachedRealm(); } #pragma mark - Adding and Removing Objects - (void)testRealmAddAndRemoveObjects { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [StringObject createInRealm:realm withValue:@[@"b"]]; [StringObject createInRealm:realm withValue:@[@"c"]]; XCTAssertEqual([StringObject objectsInRealm:realm withPredicate:nil].count, 3U, @"Expecting 3 objects"); [realm commitWriteTransaction]; // test again after write transaction RLMResults *objects = [StringObject allObjectsInRealm:realm]; XCTAssertEqual(objects.count, 3U, @"Expecting 3 objects"); XCTAssertEqualObjects([objects.firstObject stringCol], @"a", @"Expecting column to be 'a'"); [realm beginWriteTransaction]; [realm deleteObject:objects[2]]; [realm deleteObject:objects[0]]; XCTAssertEqual([StringObject objectsInRealm:realm withPredicate:nil].count, 1U, @"Expecting 1 object"); [realm commitWriteTransaction]; objects = [StringObject allObjectsInRealm:realm]; XCTAssertEqual(objects.count, 1U, @"Expecting 1 object"); XCTAssertEqualObjects([objects.firstObject stringCol], @"b", @"Expecting column to be 'b'"); } - (void)testRemoveUnmanagedObject { RLMRealm *realm = [self realmWithTestPath]; StringObject *obj = [[StringObject alloc] initWithValue:@[@"a"]]; [realm beginWriteTransaction]; XCTAssertThrows([realm deleteObject:obj]); obj = [StringObject createInRealm:realm withValue:@[@"b"]]; [realm commitWriteTransaction]; [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [self realmWithTestPath]; RLMObject *obj = [[StringObject allObjectsInRealm:realm] firstObject]; [realm beginWriteTransaction]; [realm deleteObject:obj]; XCTAssertThrows([realm deleteObject:obj]); [realm commitWriteTransaction]; }]; [realm beginWriteTransaction]; [realm deleteObject:obj]; [realm commitWriteTransaction]; } - (void)testRealmBatchRemoveObjects { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; StringObject *strObj = [StringObject createInRealm:realm withValue:@[@"a"]]; [StringObject createInRealm:realm withValue:@[@"b"]]; [StringObject createInRealm:realm withValue:@[@"c"]]; [realm commitWriteTransaction]; // delete objects RLMResults *objects = [StringObject allObjectsInRealm:realm]; XCTAssertEqual(objects.count, 3U); [realm beginWriteTransaction]; [realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol != 'a'"]]; XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], 1U); [realm deleteObjects:objects]; XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], 0U); [realm commitWriteTransaction]; XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], 0U); RLMAssertThrowsWithReason(strObj.stringCol, @"invalidated"); // add objects to collections [realm beginWriteTransaction]; ArrayPropertyObject *arrayObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", @[@[@"a"], @[@"b"], @[@"c"]], @[]]]; SetPropertyObject *setObj = [SetPropertyObject createInRealm:realm withValue:@[@"name", @[@[@"d"], @[@"e"], @[@"f"]], @[]]]; DictionaryPropertyObject *dictObj = [DictionaryPropertyObject createInRealm:realm withValue:@{@"stringDictionary": @{@"a": @[@"b"]}}]; [StringObject createInRealm:realm withValue:@[@"g"]]; [realm commitWriteTransaction]; XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], 8U); // delete via collections [realm beginWriteTransaction]; [realm deleteObjects:arrayObj.array]; [realm deleteObjects:setObj.set]; [realm deleteObjects:dictObj.stringDictionary]; [realm commitWriteTransaction]; XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], 1U); XCTAssertEqual(arrayObj.array.count, 0U); XCTAssertEqual(setObj.set.count, 0U); XCTAssertEqual(dictObj.stringDictionary.count, 0U); // remove NSArray NSArray *arrayOfLastObject = @[[[StringObject allObjectsInRealm:realm] lastObject]]; [realm beginWriteTransaction]; [realm deleteObjects:arrayOfLastObject]; [realm commitWriteTransaction]; XCTAssertEqual(objects.count, 0U); // add objects to collections [realm beginWriteTransaction]; [arrayObj.array addObject:[StringObject createInRealm:realm withValue:@[@"a"]]]; [arrayObj.array addObject:[[StringObject alloc] initWithValue:@[@"b"]]]; [setObj.set addObject:[StringObject createInRealm:realm withValue:@[@"c"]]]; [setObj.set addObject:[[StringObject alloc] initWithValue:@[@"d"]]]; dictObj.stringDictionary[@"b"] = [[StringObject alloc] initWithValue:@[@"e"]]; [realm commitWriteTransaction]; // remove objects from realm XCTAssertEqual(arrayObj.array.count, 2U); XCTAssertEqual(setObj.set.count, 2U); XCTAssertEqual(dictObj.stringDictionary.count, 1U); [realm beginWriteTransaction]; [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; [realm commitWriteTransaction]; XCTAssertEqual(arrayObj.array.count, 0U); XCTAssertEqual(setObj.set.count, 0U); // deleting the target objects in a dictionary sets them to null rather than // removing the entries XCTAssertEqual(dictObj.stringDictionary.count, 1U); XCTAssertEqual((id)dictObj.stringDictionary[@"b"], NSNull.null); } - (void)testAddManagedObjectToOtherRealm { RLMRealm *realm1 = [self realmWithTestPath]; RLMRealm *realm2 = [RLMRealm defaultRealm]; CircleObject *co1 = [[CircleObject alloc] init]; co1.data = @"1"; CircleObject *co2 = [[CircleObject alloc] init]; co2.data = @"2"; co2.next = co1; CircleArrayObject *cao = [[CircleArrayObject alloc] init]; [cao.circles addObject:co1]; [realm1 transactionWithBlock:^{ [realm1 addObject:co1]; }]; [realm2 beginWriteTransaction]; XCTAssertThrows([realm2 addObject:co1], @"should reject already-managed object"); XCTAssertThrows([realm2 addObject:co2], @"should reject linked managed object"); XCTAssertThrows([realm2 addObject:cao], @"should reject array containing managed object"); [realm2 commitWriteTransaction]; // The objects are left in an odd state if validation fails (since the // exception isn't supposed to be recoverable), so make new objects co2 = [[CircleObject alloc] init]; co2.data = @"2"; co2.next = co1; cao = [[CircleArrayObject alloc] init]; [cao.circles addObject:co1]; [realm1 beginWriteTransaction]; XCTAssertNoThrow([realm1 addObject:co2], @"should be able to add object which links to object managed by target Realm"); XCTAssertNoThrow([realm1 addObject:cao], @"should be able to add object with an array containing an object managed by target Realm"); [realm1 commitWriteTransaction]; } - (void)testCopyObjectsBetweenRealms { RLMRealm *realm1 = [self realmWithTestPath]; RLMRealm *realm2 = [RLMRealm defaultRealm]; StringObject *so = [[StringObject alloc] init]; so.stringCol = @"value"; [realm1 beginWriteTransaction]; [realm1 addObject:so]; [realm1 commitWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm1].count); XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm2].count); XCTAssertEqualObjects(so.stringCol, @"value"); [realm2 beginWriteTransaction]; StringObject *so2 = [StringObject createInRealm:realm2 withValue:so]; [realm2 commitWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm1].count); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm2].count); XCTAssertEqualObjects(so2.stringCol, @"value"); } - (void)testCopyArrayPropertyBetweenRealms { RLMRealm *realm1 = [self realmWithTestPath]; RLMRealm *realm2 = [RLMRealm defaultRealm]; EmployeeObject *eo = [[EmployeeObject alloc] init]; eo.name = @"name"; eo.age = 50; eo.hired = YES; CompanyObject *co = [[CompanyObject alloc] init]; co.name = @"company name"; [co.employees addObject:eo]; [realm1 beginWriteTransaction]; [realm1 addObject:co]; [realm1 commitWriteTransaction]; XCTAssertEqual(1U, [EmployeeObject allObjectsInRealm:realm1].count); XCTAssertEqual(1U, [CompanyObject allObjectsInRealm:realm1].count); [realm2 beginWriteTransaction]; CompanyObject *co2 = [CompanyObject createInRealm:realm2 withValue:co]; [realm2 commitWriteTransaction]; XCTAssertEqual(1U, [EmployeeObject allObjectsInRealm:realm1].count); XCTAssertEqual(1U, [CompanyObject allObjectsInRealm:realm1].count); XCTAssertEqual(1U, [EmployeeObject allObjectsInRealm:realm2].count); XCTAssertEqual(1U, [CompanyObject allObjectsInRealm:realm2].count); XCTAssertEqualObjects(@"name", [co2.employees.firstObject name]); } - (void)testCopyLinksBetweenRealms { RLMRealm *realm1 = [self realmWithTestPath]; RLMRealm *realm2 = [RLMRealm defaultRealm]; CircleObject *c = [[CircleObject alloc] init]; c.data = @"1"; c.next = [[CircleObject alloc] init]; c.next.data = @"2"; [realm1 beginWriteTransaction]; [realm1 addObject:c]; [realm1 commitWriteTransaction]; XCTAssertEqual(realm1, c.realm); XCTAssertEqual(realm1, c.next.realm); XCTAssertEqual(2U, [CircleObject allObjectsInRealm:realm1].count); [realm2 beginWriteTransaction]; CircleObject *c2 = [CircleObject createInRealm:realm2 withValue:c]; [realm2 commitWriteTransaction]; XCTAssertEqualObjects(c2.data, @"1"); XCTAssertEqualObjects(c2.next.data, @"2"); XCTAssertEqual(2U, [CircleObject allObjectsInRealm:realm1].count); XCTAssertEqual(2U, [CircleObject allObjectsInRealm:realm2].count); } - (void)testCopyObjectsInArrayLiteral { RLMRealm *realm1 = [self realmWithTestPath]; RLMRealm *realm2 = [RLMRealm defaultRealm]; CircleObject *c = [[CircleObject alloc] init]; c.data = @"1"; [realm1 beginWriteTransaction]; [realm1 addObject:c]; [realm1 commitWriteTransaction]; [realm2 beginWriteTransaction]; CircleObject *c2 = [CircleObject createInRealm:realm2 withValue:@[@"3", @[@"2", c]]]; [realm2 commitWriteTransaction]; XCTAssertEqual(1U, [CircleObject allObjectsInRealm:realm1].count); XCTAssertEqual(3U, [CircleObject allObjectsInRealm:realm2].count); XCTAssertEqual(realm1, c.realm); XCTAssertEqual(realm2, c2.realm); XCTAssertEqualObjects(@"1", c.data); XCTAssertEqualObjects(@"3", c2.data); XCTAssertEqualObjects(@"2", c2.next.data); XCTAssertEqualObjects(@"1", c2.next.next.data); } - (void)testAddOrUpdate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; PrimaryStringObject *obj = [[PrimaryStringObject alloc] initWithValue:@[@"string", @1]]; [realm addOrUpdateObject:obj]; RLMResults *objects = [PrimaryStringObject allObjects]; XCTAssertEqual([objects count], 1U, @"Should have 1 object"); XCTAssertEqual([(PrimaryStringObject *)objects[0] intCol], 1, @"Value should be 1"); PrimaryStringObject *obj2 = [[PrimaryStringObject alloc] initWithValue:@[@"string2", @2]]; [realm addOrUpdateObject:obj2]; XCTAssertEqual([objects count], 2U, @"Should have 2 objects"); // upsert with new secondary property PrimaryStringObject *obj3 = [[PrimaryStringObject alloc] initWithValue:@[@"string", @3]]; [realm addOrUpdateObject:obj3]; XCTAssertEqual([objects count], 2U, @"Should have 2 objects"); XCTAssertEqual([(PrimaryStringObject *)objects[0] intCol], 3, @"Value should be 3"); // upsert on non-primary key object should throw XCTAssertThrows([realm addOrUpdateObject:[[StringObject alloc] initWithValue:@[@"string"]]]); [realm commitWriteTransaction]; } - (void)testAddOrUpdateObjectsFromArray { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; PrimaryStringObject *obj = [[PrimaryStringObject alloc] initWithValue:@[@"string1", @1]]; [realm addObject:obj]; PrimaryStringObject *obj2 = [[PrimaryStringObject alloc] initWithValue:@[@"string2", @2]]; [realm addObject:obj2]; PrimaryStringObject *obj3 = [[PrimaryStringObject alloc] initWithValue:@[@"string3", @3]]; [realm addObject:obj3]; RLMResults *objects = [PrimaryStringObject allObjects]; XCTAssertEqual([objects count], 3U); XCTAssertEqual(obj.intCol, 1); XCTAssertEqual(obj2.intCol, 2); XCTAssertEqual(obj3.intCol, 3); // upsert with array of 2 objects. One is to update the existing value, another is added NSArray *array = @[[[PrimaryStringObject alloc] initWithValue:@[@"string2", @4]], [[PrimaryStringObject alloc] initWithValue:@[@"string4", @5]]]; [realm addOrUpdateObjects:array]; XCTAssertEqual([objects count], 4U, @"Should have 4 objects"); XCTAssertEqual(obj.intCol, 1); XCTAssertEqual(obj2.intCol, 4); XCTAssertEqual(obj3.intCol, 3); XCTAssertEqual([array[1] intCol], 5); [realm commitWriteTransaction]; } - (void)testDelete { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; OwnerObject *obj = [OwnerObject createInDefaultRealmWithValue:@[@"deeter", @[@"barney", @2]]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, OwnerObject.allObjects.count); XCTAssertEqual(NO, obj.invalidated); XCTAssertThrows([realm deleteObject:obj]); RLMRealm *testRealm = [self realmWithTestPath]; [testRealm transactionWithBlock:^{ XCTAssertThrows([testRealm deleteObject:[[OwnerObject alloc] init]]); [realm transactionWithBlock:^{ XCTAssertThrows([testRealm deleteObject:obj]); }]; }]; [realm transactionWithBlock:^{ [realm deleteObject:obj]; XCTAssertEqual(YES, obj.invalidated); }]; XCTAssertEqual(0U, OwnerObject.allObjects.count); } - (void)testDeleteObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *obj = [CompanyObject createInDefaultRealmWithValue:@[@"deeter", @[@[@"barney", @2, @YES]]]]; NSArray *objects = @[obj]; [realm commitWriteTransaction]; XCTAssertEqual(1U, CompanyObject.allObjects.count); XCTAssertThrows([realm deleteObjects:objects]); XCTAssertThrows([realm deleteObjects:[CompanyObject allObjectsInRealm:realm]]); XCTAssertThrows([realm deleteObjects:obj.employees]); RLMRealm *testRealm = [self realmWithTestPath]; [testRealm transactionWithBlock:^{ [realm transactionWithBlock:^{ XCTAssertThrows([testRealm deleteObjects:objects]); XCTAssertThrows([testRealm deleteObjects:[CompanyObject allObjectsInRealm:realm]]); XCTAssertThrows([testRealm deleteObjects:obj.employees]); }]; }]; XCTAssertEqual(1U, CompanyObject.allObjects.count); } - (void)testDeleteAllObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; OwnerObject *obj = [OwnerObject createInDefaultRealmWithValue:@[@"deeter", @[@"barney", @2]]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, OwnerObject.allObjects.count); XCTAssertEqual(1U, DogObject.allObjects.count); XCTAssertEqual(NO, obj.invalidated); XCTAssertThrows([realm deleteAllObjects]); [realm transactionWithBlock:^{ [realm deleteAllObjects]; XCTAssertEqual(YES, obj.invalidated); }]; XCTAssertEqual(0U, OwnerObject.allObjects.count); XCTAssertEqual(0U, DogObject.allObjects.count); } - (void)testAddObjectsFromArray { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; XCTAssertThrows(([realm addObjects:@[@[@"Rex", @10]]]), @"should reject non-RLMObject in array"); DogObject *dog = [DogObject new]; dog.dogName = @"Rex"; dog.age = 10; XCTAssertNoThrow([realm addObjects:@[dog]], @"should allow RLMObject in array"); XCTAssertEqual(1U, [[DogObject allObjectsInRealm:realm] count]); [realm cancelWriteTransaction]; } #pragma mark - Transactions - (void)testRealmTransactionBlock { RLMRealm *realm = [self realmWithTestPath]; [realm transactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"b"]]; }]; RLMResults *objects = [StringObject allObjectsInRealm:realm]; XCTAssertEqual(objects.count, 1U, @"Expecting 1 object"); XCTAssertEqualObjects([objects.firstObject stringCol], @"b", @"Expecting column to be 'b'"); } - (void)testInWriteTransaction { RLMRealm *realm = [self realmWithTestPath]; XCTAssertFalse(realm.inWriteTransaction); [realm beginWriteTransaction]; XCTAssertTrue(realm.inWriteTransaction); [realm cancelWriteTransaction]; [realm transactionWithBlock:^{ XCTAssertTrue(realm.inWriteTransaction); [realm cancelWriteTransaction]; XCTAssertFalse(realm.inWriteTransaction); }]; [realm beginWriteTransaction]; [realm invalidate]; XCTAssertFalse(realm.inWriteTransaction); } - (void)testAutorefreshAfterBackgroundUpdate { RLMRealm *realm = [self realmWithTestPath]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testBackgroundUpdateWithoutAutorefresh { RLMRealm *realm = [self realmWithTestPath]; realm.autorefresh = NO; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); }]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm refresh]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testBeginWriteTransactionsNotifiesWithUpdatedObjects { RLMRealm *realm = [self realmWithTestPath]; realm.autorefresh = NO; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); // Create an object in a background thread and wait for that to complete, // without refreshing the main thread realm [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); }]; // Verify that the main thread realm still doesn't have any objects XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); // Verify that the local notification sent by the beginWriteTransaction // below when it advances the realm to the latest version occurs *after* // the advance __block bool notificationFired = false; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, RLMRealm *realm) { XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); notificationFired = true; }]; [realm beginWriteTransaction]; [realm commitWriteTransaction]; [token invalidate]; XCTAssertTrue(notificationFired); } - (void)testBeginWriteTransactionsRefreshesRealm { // auto refresh on by default RLMRealm *realm = [self realmWithTestPath]; // Set up notification which will be triggered when calling beginWriteTransaction __block bool notificationFired = false; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, RLMRealm *realm) { XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); XCTAssertThrows([realm beginWriteTransaction], @"We should already be in a write transaction"); notificationFired = true; }]; // dispatch to background syncronously [self dispatchAsyncAndWait:^{ RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; // notification shouldnt have fired XCTAssertFalse(notificationFired); [realm beginWriteTransaction]; // notification should have fired XCTAssertTrue(notificationFired); [realm cancelWriteTransaction]; [token invalidate]; } - (void)testBeginWriteTransactionFromWithinRefreshRequiredNotification { RLMRealm *realm = [RLMRealm defaultRealm]; realm.autorefresh = NO; auto expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertEqual(RLMRealmRefreshRequiredNotification, note); XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; [expectation fulfill]; // note that this will throw if the notification is incorrectly called twice }]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; } - (void)testBeginWriteTransactionFromWithinRealmChangedNotification { RLMRealm *realm = [RLMRealm defaultRealm]; auto createObject = ^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; }; // Test with the triggering transaction on a different thread auto expectation = [self expectationWithDescription:@""]; RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertEqual(RLMRealmDidChangeNotification, note); // We're in DidChange, so the first object is already present XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); createObject(); // Haven't refreshed yet, so still one XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); // Refreshes without sending notifications since we're within a notification [realm beginWriteTransaction]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); [realm cancelWriteTransaction]; [expectation fulfill]; // note that this will throw if the notification is incorrectly called twice }]; createObject(); [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; // Test with the triggering transaction on the same thread __block bool first = true; token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertTrue(first); XCTAssertEqual(RLMRealmDidChangeNotification, note); XCTAssertEqual(3U, [StringObject allObjectsInRealm:realm].count); first = false; [realm beginWriteTransaction]; // should not trigger a notification [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; // also should not trigger a notification }]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; XCTAssertFalse(first); [token invalidate]; } - (void)testBeginWriteTransactionFromWithinCollectionChangedNotification { RLMRealm *realm = [RLMRealm defaultRealm]; auto createObject = ^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; }; __block auto expectation = [self expectationWithDescription:@""]; __block RLMNotificationToken *token; auto block = ^(RLMResults *results, RLMCollectionChange *changes, NSError *) { if (!changes) { [expectation fulfill]; return; } [token invalidate]; XCTAssertEqual(1U, results.count); createObject(); XCTAssertEqual(1U, results.count); [realm beginWriteTransaction]; XCTAssertEqual(2U, results.count); [realm cancelWriteTransaction]; [expectation fulfill]; }; token = [StringObject.allObjects addNotificationBlock:block]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; createObject(); expectation = [self expectationWithDescription:@""]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } - (void)testReadOnlyRealmIsImmutable { @autoreleasepool { [self realmWithTestPath]; } RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; XCTAssertThrows([realm beginWriteTransaction]); XCTAssertThrows([realm refresh]); } - (void)testRollbackInsert { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; IntObject *createdObject = [IntObject createInRealm:realm withValue:@[@0]]; [realm cancelWriteTransaction]; XCTAssertTrue(createdObject.isInvalidated); XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); } - (void)testRollbackDelete { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; IntObject *objectToDelete = [IntObject createInRealm:realm withValue:@[@5]]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; [realm deleteObject:objectToDelete]; [realm cancelWriteTransaction]; XCTAssertFalse(objectToDelete.isInvalidated); XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count); XCTAssertEqual(5, objectToDelete.intCol); } - (void)testRollbackModify { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; IntObject *objectToModify = [IntObject createInRealm:realm withValue:@[@0]]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; objectToModify.intCol = 1; [realm cancelWriteTransaction]; XCTAssertEqual(0, objectToModify.intCol); } - (void)testRollbackLink { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; CircleObject *obj1 = [CircleObject createInRealm:realm withValue:@[@"1", NSNull.null]]; CircleObject *obj2 = [CircleObject createInRealm:realm withValue:@[@"2", NSNull.null]]; [realm commitWriteTransaction]; // Link to existing managed [realm beginWriteTransaction]; obj1.next = obj2; [realm cancelWriteTransaction]; XCTAssertNil(obj1.next); // Link to unmanaged [realm beginWriteTransaction]; CircleObject *obj3 = [[CircleObject alloc] init]; obj3.data = @"3"; obj1.next = obj3; [realm cancelWriteTransaction]; XCTAssertNil(obj1.next); XCTAssertEqual(2U, [CircleObject allObjectsInRealm:realm].count); // Remove link [realm beginWriteTransaction]; obj1.next = obj2; [realm commitWriteTransaction]; [realm beginWriteTransaction]; obj1.next = nil; [realm cancelWriteTransaction]; XCTAssertTrue([obj1.next isEqualToObject:obj2]); // Modify link [realm beginWriteTransaction]; CircleObject *obj4 = [CircleObject createInRealm:realm withValue:@[@"4", NSNull.null]]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; obj1.next = obj4; [realm cancelWriteTransaction]; XCTAssertTrue([obj1.next isEqualToObject:obj2]); } - (void)testRollbackLinkList { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; IntObject *obj1 = [IntObject createInRealm:realm withValue:@[@0]]; IntObject *obj2 = [IntObject createInRealm:realm withValue:@[@1]]; ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[obj1]]]; [realm commitWriteTransaction]; // Add existing managed object [realm beginWriteTransaction]; [array.intArray addObject:obj2]; [realm cancelWriteTransaction]; XCTAssertEqual(1U, array.intArray.count); // Add unmanaged object [realm beginWriteTransaction]; [array.intArray addObject:[[IntObject alloc] init]]; [realm cancelWriteTransaction]; XCTAssertEqual(1U, array.intArray.count); XCTAssertEqual(2U, [IntObject allObjectsInRealm:realm].count); // Remove [realm beginWriteTransaction]; [array.intArray removeObjectAtIndex:0]; [realm cancelWriteTransaction]; XCTAssertEqual(1U, array.intArray.count); // Modify [realm beginWriteTransaction]; array.intArray[0] = obj2; [realm cancelWriteTransaction]; XCTAssertEqual(1U, array.intArray.count); XCTAssertTrue([array.intArray[0] isEqualToObject:obj1]); } - (void)testRollbackTransactionWithBlock { RLMRealm *realm = [self realmWithTestPath]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; [realm cancelWriteTransaction]; }]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); } - (void)testRollbackTransactionWithoutExplicitCommitOrCancel { @autoreleasepool { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; } XCTAssertEqual(0U, [IntObject allObjectsInRealm:[self realmWithTestPath]].count); } - (void)testCanRestartReadTransactionAfterInvalidate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; [realm invalidate]; IntObject *obj = [IntObject allObjectsInRealm:realm].firstObject; XCTAssertEqual(obj.intCol, 1); } - (void)testInvalidateDetachesAccessors { RLMRealm *realm = [RLMRealm defaultRealm]; __block IntObject *obj; [realm transactionWithBlock:^{ obj = [IntObject createInRealm:realm withValue:@[@0]]; }]; [realm invalidate]; XCTAssertTrue(obj.isInvalidated); XCTAssertThrows([obj intCol]); } - (void)testInvalidateInvalidatesResults { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@1]]; }]; RLMResults *results = [IntObject objectsInRealm:realm where:@"intCol = 1"]; XCTAssertEqual([results.firstObject intCol], 1); [realm invalidate]; XCTAssertThrows([results count]); XCTAssertThrows([results firstObject]); } - (void)testInvalidateInvalidatesArrays { RLMRealm *realm = [RLMRealm defaultRealm]; __block ArrayPropertyObject *arrayObject; [realm transactionWithBlock:^{ arrayObject = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[@[@1]]]]; }]; RLMArray *array = arrayObject.intArray; XCTAssertEqual(1U, array.count); [realm invalidate]; XCTAssertThrows([array count]); } - (void)testInvalidateOnReadOnlyRealm { @autoreleasepool { RLMRealm *realm = [self realmWithTestPath]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; } RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; IntObject *io = [[IntObject allObjectsInRealm:realm] firstObject]; [realm invalidate]; XCTAssertTrue(io.isInvalidated); // Starts a new read transaction XCTAssertFalse([[[IntObject allObjectsInRealm:realm] firstObject] isInvalidated]); } - (void)testInvalidateBeforeReadDoesNotAssert { RLMRealm *realm = [RLMRealm defaultRealm]; [realm invalidate]; } - (void)testInvalidateDuringWriteRollsBack { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; @autoreleasepool { [IntObject createInRealm:realm withValue:@[@1]]; } [realm invalidate]; XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); } - (void)testRefreshCreatesAReadTransaction { RLMRealm *realm = [RLMRealm defaultRealm]; [self dispatchAsyncAndWait:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@1]]; }]; }]; XCTAssertTrue([realm refresh]); [self dispatchAsyncAndWait:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@1]]; }]; }]; // refresh above should have created a read transaction, so realm should // still only see one object XCTAssertEqual(1U, [IntObject allObjects].count); // Just a sanity check XCTAssertTrue([realm refresh]); XCTAssertEqual(2U, [IntObject allObjects].count); } - (void)testInWriteTransactionInNotificationFromBeginWrite { RLMRealm *realm = RLMRealm.defaultRealm; realm.autorefresh = NO; __block bool called = false; RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { if (note == RLMRealmDidChangeNotification) { called = true; XCTAssertTrue(realm.inWriteTransaction); } }]; [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ }]; }]; [realm beginWriteTransaction]; XCTAssertTrue(called); [realm cancelWriteTransaction]; [token invalidate]; } - (void)testThrowingFromDidChangeNotificationFromBeginWriteCancelsTransaction { RLMRealm *realm = RLMRealm.defaultRealm; realm.autorefresh = NO; RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *) { if (note == RLMRealmDidChangeNotification) { throw 0; } }]; [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ }]; }]; try { [realm beginWriteTransaction]; XCTFail(@"should have thrown"); } catch (int) { } [token invalidate]; XCTAssertFalse(realm.inWriteTransaction); XCTAssertNoThrow([realm beginWriteTransaction]); [realm cancelWriteTransaction]; } - (void)testThrowingFromDidChangeNotificationAfterLocalCommit { RLMRealm *realm = RLMRealm.defaultRealm; realm.autorefresh = NO; RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *) { if (note == RLMRealmDidChangeNotification) { throw 0; } }]; [realm beginWriteTransaction]; try { [realm commitWriteTransaction]; XCTFail(@"should have thrown"); } catch (int) { } [token invalidate]; XCTAssertFalse(realm.inWriteTransaction); XCTAssertNoThrow([realm beginWriteTransaction]); [realm cancelWriteTransaction]; } - (void)testNotificationsFireEvenWithoutReadTransaction { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *notificationFired = [self expectationWithDescription:@"notification fired"]; __block RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *) { if (note == RLMRealmDidChangeNotification) { [notificationFired fulfill]; [token invalidate]; } }]; [realm invalidate]; [self dispatchAsync:^{ [RLMRealm.defaultRealm transactionWithBlock:^{ }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } - (void)testNotificationBlockMustNotBeNil { RLMRealm *realm = RLMRealm.defaultRealm; XCTAssertThrows([realm addNotificationBlock:self.nonLiteralNil]); } - (void)testRefreshInWriteTransactionReturnsFalse { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; XCTAssertFalse([realm refresh]); [realm cancelWriteTransaction]; } - (void)testCancelWriteWhenNotInWrite { XCTAssertThrows([RLMRealm.defaultRealm cancelWriteTransaction]); } - (void)testActiveVersionLimit { RLMRealmConfiguration *config = RLMRealmConfiguration.defaultConfiguration; config.maximumNumberOfActiveVersions = 3; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; // Create frozen Realms at four different versions so that we have too many // active versions. It's four rather than three as the implementation has // an off-by-one error and checks if we're already over the limit rather than // if a write would put us over the limit. __attribute__((objc_precise_lifetime)) NSMutableArray *pinnedVersions = [NSMutableArray new]; [pinnedVersions addObject:realm.freeze]; for (int i = 0; i < 3; ++i) { [realm transactionWithBlock:^{ }]; [pinnedVersions addObject:realm.freeze]; } XCTAssertThrows([realm beginWriteTransaction]); XCTAssertThrows([realm transactionWithBlock:^{ }]); NSError *error; [realm transactionWithBlock:^{} error:&error]; RLMValidateError(error, RLMErrorDomain, RLMErrorFail, @"Number of active versions (4) in the Realm exceeded the limit of 3"); } #pragma mark - Async Transactions - (void)testAsyncTransactionShouldWrite { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm asyncTransactionWithBlock:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; } onComplete:^(NSError *error) { StringObject *stringObject = [StringObject allObjectsInRealm:realm].firstObject; XCTAssertEqualObjects(stringObject.stringCol, @"string"); [asyncComplete fulfill]; XCTAssertNil(error); }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } - (void)testAsyncTransactionShouldWriteOnCommit { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *writeComplete = [self expectationWithDescription:@"async transaction complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [self dispatchAsync:^{ RLMRealm *realmBg = [RLMRealm defaultRealmForQueue:self.bgQueue]; [realmBg beginAsyncWriteTransaction:^{ [realmBg createObject:StringObject.className withValue:@[@"string"]]; [realmBg commitAsyncWriteTransaction:^(NSError *error) { StringObject *stringObject = [StringObject allObjectsInRealm:realmBg].firstObject; XCTAssertEqualObjects(stringObject.stringCol, @"string"); [writeComplete fulfill]; XCTAssertNil(error); }]; }]; }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; [realm refresh]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionShouldCancel { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *waitComplete = [self expectationWithDescription:@"async wait complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); RLMAsyncTransactionId asyncTransactionId = [realm beginAsyncWriteTransaction:^{ XCTFail(@"should have been cancelled"); }]; [realm beginAsyncWriteTransaction:^{ [realm cancelWriteTransaction]; [waitComplete fulfill]; }]; [realm cancelAsyncTransaction:asyncTransactionId]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionShouldNotRunTransactionOnClosedRealm { XCTestExpectation *writeComplete = [self expectationWithDescription:@"async transaction complete"]; writeComplete.inverted = YES; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:self.bgQueue]; [realm beginAsyncWriteTransaction:^{ [writeComplete fulfill]; }]; [realm invalidate]; }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } - (void)testAsyncTransactionShouldNotAutoCommitOnCanceledTransaction { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *waitComplete = [self expectationWithDescription:@"async wait complete"]; XCTestExpectation *writeComplete = [self expectationWithDescription:@"async transaction complete"]; writeComplete.inverted = YES; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:self.bgQueue]; RLMAsyncTransactionId asyncTransactionId = [realm asyncTransactionWithBlock:^{ [realm createObject:StringObject.className withValue:@[@"string 1"]]; } onComplete:^(NSError *) { [writeComplete fulfill]; }]; [realm cancelAsyncTransaction:asyncTransactionId]; [waitComplete fulfill]; }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; [realm refresh]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionShouldAutorefresh { RLMRealm *realm = [self realmWithTestPath]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); XCTestExpectation *notificationFired = [self expectationWithDescription:@"notification fired"]; __block RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertNotNil(note, @"Note should not be nil"); XCTAssertNotNil(realm, @"Realm should not be nil"); if (note == RLMRealmDidChangeNotification) { // Check pointer equality to ensure we're using the interned string constant [notificationFired fulfill]; [token invalidate]; } }]; [realm asyncTransactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionSyncCommit { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; asyncComplete.expectedFulfillmentCount = 2; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction:^(NSError *) { [asyncComplete fulfill]; } allowGrouping:true]; }]; [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction:^(NSError *) { [asyncComplete fulfill]; }]; }]; [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; }]; [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionSyncAfterAsyncWithoutCommit { RLMRealm *realm = RLMRealm.defaultRealm; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; }]; [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"string 2"]]; [realm commitWriteTransaction]; XCTAssertEqualObjects([[StringObject allObjectsInRealm:realm].firstObject stringCol], @"string 2"); } - (void)testAsyncTransactionWriteWithSync { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"string 2"]]; [realm commitAsyncWriteTransaction:^(NSError *) { [asyncComplete fulfill]; }]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionMixedWithSync { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; }]; [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string 2"]]; [realm commitAsyncWriteTransaction:^(NSError *) { [asyncComplete fulfill]; }]; }]; [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"string 3"]]; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; XCTAssertEqual(3U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionMixedWithCancelledSync { RLMRealm *realm = RLMRealm.defaultRealm; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; }]; [realm beginAsyncWriteTransaction:^{ [realm createObject:StringObject.className withValue:@[@"string 2"]]; [realm commitAsyncWriteTransaction:^(NSError *) { [asyncComplete fulfill]; }]; }]; [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"string 3"]]; [realm cancelWriteTransaction]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionChangeNotification { RLMRealm *realm = RLMRealm.defaultRealm; __block XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; auto expectInitial = [self expectationWithDescription:@"initial"]; asyncComplete.expectedFulfillmentCount = 4; RLMResults *resultsUnderTest = [StringObject allObjects]; RLMNotificationToken *token = [resultsUnderTest addNotificationBlock:^(RLMResults *, RLMCollectionChange * _Nullable change, NSError *) { if (!change) { // ignore initial [expectInitial fulfill]; return; } XCTAssertNotNil(change); [asyncComplete fulfill]; }]; [self waitForExpectations:@[expectInitial] timeout:1.0]; [realm asyncTransactionWithBlock:^{ [realm createObject:StringObject.className withValue:@[@"string 1"]]; } onComplete:^(NSError *) { [asyncComplete fulfill]; }]; [realm asyncTransactionWithBlock:^{ [realm createObject:StringObject.className withValue:@[@"string 1"]]; } onComplete:^(NSError *) { [asyncComplete fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [token invalidate]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionsRefreshesRealm { // auto refresh on by default, so turn it off RLMRealm *realm = [self realmWithTestPath]; realm.autorefresh = NO; XCTestExpectation *asyncComplete = [self expectationWithDescription:@"async transaction complete"]; __block bool notificationFired = false; RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, RLMRealm *realm) { XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); notificationFired = true; }]; [realm asyncTransactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; } onComplete:^(NSError *) { [asyncComplete fulfill]; }]; [self waitForExpectationsWithTimeout:3.0 handler:nil]; // notification shouldnt have fired XCTAssertTrue(notificationFired); [token invalidate]; } - (void)testAsyncBeginTransactionInAsyncTransaction { RLMRealm *realm = [self realmWithTestPath]; XCTestExpectation *transaction1 = [self expectationWithDescription:@"async transaction 1 complete"]; XCTestExpectation *transaction2 = [self expectationWithDescription:@"async transaction 2 complete"]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitAsyncWriteTransaction:^(NSError *error) { XCTAssertEqual(0U, [StringObject allObjects].count); [transaction1 fulfill]; XCTAssertNil(error); }]; }]; [realm commitAsyncWriteTransaction:^(NSError *error) { XCTAssertEqual(0U, [StringObject allObjects].count); [transaction2 fulfill]; XCTAssertNil(error); }]; }]; [self waitForExpectationsWithTimeout:3.0 handler:nil]; XCTAssertEqual(0U, [StringObject allObjects].count); } - (void)testAsyncTransactionFromSyncTransaction { XCTestExpectation *transaction1 = [self expectationWithDescription:@"async transaction 1 complete"]; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:self.bgQueue]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitAsyncWriteTransaction:^(NSError *error) { [transaction1 fulfill]; XCTAssertNil(error); }]; }]; [realm commitWriteTransaction]; }]; [self waitForExpectationsWithTimeout:3.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjects].count); } - (void)testAsyncNestedWrites { XCTestExpectation *transactionsComplete = [self expectationWithDescription:@"async transaction 1 complete"]; transactionsComplete.expectedFulfillmentCount = 2; [self dispatchAsync:^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:self.bgQueue]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string 1"]]; // nested in async block [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string 2"]]; [realm commitAsyncWriteTransaction:^(NSError *) { // nested in completion block XCTAssertThrows([realm beginAsyncWriteTransaction:^{ }]); [transactionsComplete fulfill]; }]; }]; [realm commitAsyncWriteTransaction:^(NSError *) { [transactionsComplete fulfill]; }]; }]; }]; [self waitForExpectationsWithTimeout:3.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjects].count); } - (void)testAsyncTransactionCancel { RLMRealm *realm = [RLMRealm defaultRealm]; auto expectation = [self expectationWithDescription:@""]; expectation.expectedFulfillmentCount = 3; XCTestExpectation *unexpectation = [self expectationWithDescription:@""]; unexpectation.inverted = YES; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [expectation fulfill]; }]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; [expectation fulfill]; }]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitAsyncWriteTransaction]; [expectation fulfill]; }]; RLMAsyncTransactionId asyncTransactionIdB = [realm beginAsyncWriteTransaction:^{ [unexpectation fulfill]; }]; [realm cancelAsyncTransaction:asyncTransactionIdB]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncTransactionCommit { RLMRealm *realm = [RLMRealm defaultRealm]; auto expectation = [self expectationWithDescription:@""]; expectation.expectedFulfillmentCount = 3; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"with commit should commit"]]; [expectation fulfill]; [realm commitAsyncWriteTransaction]; }]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"without commit/cancel should not commit"]]; [expectation fulfill]; }]; [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"cancel after commit should commit"]]; [expectation fulfill]; RLMAsyncTransactionId asyncTransactionId = [realm commitAsyncWriteTransaction:^(NSError * _Nonnull) { // no op }]; [realm cancelAsyncTransaction:asyncTransactionId]; }]; RLMAsyncTransactionId asyncTransactionIdA = [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"commit after cancel should not commit"]]; [expectation fulfill]; [realm cancelAsyncTransaction:asyncTransactionIdA]; [realm commitAsyncWriteTransaction]; }]; RLMAsyncTransactionId asyncTransactionIdB = [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"cancel should not commit"]]; [expectation fulfill]; [realm commitAsyncWriteTransaction]; }]; [realm cancelAsyncTransaction:asyncTransactionIdB]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:realm].count); } - (void)testAsyncWriteOnQueueConfinedRealm { __block RLMRealm *qRealm; auto q = dispatch_queue_create("queue 1", DISPATCH_QUEUE_SERIAL); auto expectationWrite = [self expectationWithDescription:@""]; auto expectationCommit = [self expectationWithDescription:@""]; dispatch_sync(q, ^{ qRealm = [RLMRealm defaultRealmForQueue:q]; }); dispatch_sync(q, ^{ [qRealm beginAsyncWriteTransaction:^{ [StringObject createInRealm:qRealm withValue:@[@"in queue"]]; [expectationWrite fulfill]; [qRealm commitAsyncWriteTransaction:^(NSError * _Nonnull) { [expectationCommit fulfill]; }]; }]; }); [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:[RLMRealm defaultRealm]].count); } - (void)testAsyncWriteOnReadonlyRealm { @autoreleasepool { // ensure the Realm exists __unused RLMRealm *discard = self.realmWithTestPath; } RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; XCTAssertThrows([realm beginAsyncWriteTransaction:^{ }]); } - (void)testAsyncCommitAfterTransaction { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertThrows([realm commitWriteTransaction]); [realm beginAsyncWriteTransaction:^{ }]; XCTAssertThrows([realm commitAsyncWriteTransaction]); } - (void)testAsyncCommitWithoutTransaction { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertThrows([realm commitAsyncWriteTransaction]); } - (void)testAsyncCancelWtongTransaction { RLMRealm *realm = [RLMRealm defaultRealm]; auto expectation = [self expectationWithDescription:@""]; RLMAsyncTransactionId transId = [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"prim"]]; [realm commitAsyncWriteTransaction]; [expectation fulfill]; }]; [realm cancelAsyncTransaction:transId+1]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:[RLMRealm defaultRealm]].count); } - (void)testAsyncCrossSync { RLMRealm *realm = [RLMRealm defaultRealm]; auto expectation = [self expectationWithDescription:@""]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:[RLMRealm defaultRealm]].count); [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"prim"]]; [realm commitAsyncWriteTransaction]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:[RLMRealm defaultRealm]].count); [realm beginAsyncWriteTransaction:^{ [StringObject createInRealm:realm withValue:@[@"sec"]]; [realm commitWriteTransaction]; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertEqual(2U, [StringObject allObjectsInRealm:[RLMRealm defaultRealm]].count); } - (void)testAsyncIsInTransaction { auto realm = [RLMRealm defaultRealm]; auto expectation = [self expectationWithDescription:@""]; XCTAssertFalse(realm.isPerformingAsynchronousWriteOperations); XCTAssertFalse(realm.inWriteTransaction); [realm beginWriteTransaction]; XCTAssertFalse(realm.isPerformingAsynchronousWriteOperations); XCTAssertTrue(realm.inWriteTransaction); [realm cancelWriteTransaction]; [realm beginAsyncWriteTransaction:^{ XCTAssertTrue(realm.isPerformingAsynchronousWriteOperations); XCTAssertTrue(realm.inWriteTransaction); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } #pragma mark - Threads - (void)testCrossThreadAccess { RLMRealm *realm = RLMRealm.defaultRealm; [self dispatchAsyncAndWait:^{ XCTAssertThrows([realm beginWriteTransaction]); XCTAssertThrows([IntObject allObjectsInRealm:realm]); XCTAssertThrows([IntObject objectsInRealm:realm where:@"intCol = 0"]); }]; } - (void)testHoldRealmAfterSourceThreadIsDestroyed { RLMRealm *realm; // Explicitly create a thread so that we can ensure the thread (and thus // runloop) is actually destroyed std::thread([&] { realm = [RLMRealm defaultRealm]; }).join(); [realm.configuration fileURL]; // ensure ARC releases the object after the thread has finished } - (void)testBackgroundRealmIsNotified { RLMRealm *realm = [self realmWithTestPath]; XCTestExpectation *bgReady = [self expectationWithDescription:@"background queue waiting for commit"]; __block XCTestExpectation *bgDone = nil; [self dispatchAsync:^{ RLMRealm *realm = [self realmWithTestPath]; __block bool fulfilled = false; CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ __block RLMNotificationToken *token = [realm addNotificationBlock:^(NSString *note, RLMRealm *realm) { XCTAssertNotNil(realm, @"Realm should not be nil"); XCTAssertEqual(note, RLMRealmDidChangeNotification); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); fulfilled = true; [token invalidate]; }]; // notify main thread that we're ready for it to commit [bgReady fulfill]; }); // run for two seconds or until we receive notification NSDate *end = [NSDate dateWithTimeIntervalSinceNow:5.0]; while (!fulfilled) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:end]; } XCTAssertTrue(fulfilled, @"Notification should have been received"); [bgDone fulfill]; }]; // wait for background realm to be created [self waitForExpectationsWithTimeout:2.0 handler:nil]; bgDone = [self expectationWithDescription:@"background queue done"]; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"string"]]; [realm commitWriteTransaction]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; } - (void)testAddingNotificationOutsideOfRunLoopIsAnError { [self dispatchAsyncAndWait:^{ RLMRealm *realm = RLMRealm.defaultRealm; XCTAssertThrows([realm addNotificationBlock:^(NSString *, RLMRealm *) { }]); CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{ RLMNotificationToken *token; XCTAssertNoThrow(token = [realm addNotificationBlock:^(NSString *, RLMRealm *) { }]); [token invalidate]; CFRunLoopStop(CFRunLoopGetCurrent()); }); CFRunLoopRun(); }]; } - (void)testAddingNotificationToQueueBoundThreadOutsideOfRunLoop { [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealmForQueue:self.bgQueue]; XCTAssertNoThrow([realm addNotificationBlock:^(NSString *, RLMRealm *) { }]); }]; } - (void)testQueueBoundRealmCaching { auto q1 = dispatch_queue_create("queue 1", DISPATCH_QUEUE_SERIAL); auto q2 = dispatch_queue_create("queue 2", DISPATCH_QUEUE_SERIAL); RLMRealm *mainThreadRealm1 = [RLMRealm defaultRealm]; RLMRealm *mainQueueRealm1 = [RLMRealm defaultRealmForQueue:dispatch_get_main_queue()]; __block RLMRealm *q1Realm1; __block RLMRealm *q2Realm1; dispatch_sync(q1, ^{ q1Realm1 = [RLMRealm defaultRealmForQueue:q1]; }); dispatch_sync(q2, ^{ q2Realm1 = [RLMRealm defaultRealmForQueue:q2]; }); XCTAssertEqual(mainQueueRealm1, mainThreadRealm1); XCTAssertNotEqual(mainThreadRealm1, q1Realm1); XCTAssertNotEqual(mainThreadRealm1, q2Realm1); XCTAssertNotEqual(mainQueueRealm1, q1Realm1); XCTAssertNotEqual(mainQueueRealm1, q2Realm1); XCTAssertNotEqual(q1Realm1, q2Realm1); RLMRealm *mainThreadRealm2 = [RLMRealm defaultRealm]; RLMRealm *mainQueueRealm2 = [RLMRealm defaultRealmForQueue:dispatch_get_main_queue()]; __block RLMRealm *q1Realm2; __block RLMRealm *q2Realm2; dispatch_sync(q1, ^{ q1Realm2 = [RLMRealm defaultRealmForQueue:q1]; }); dispatch_sync(q2, ^{ q2Realm2 = [RLMRealm defaultRealmForQueue:q2]; }); XCTAssertEqual(mainThreadRealm1, mainThreadRealm2); XCTAssertEqual(mainQueueRealm1, mainQueueRealm2); XCTAssertEqual(q1Realm1, q1Realm2); XCTAssertEqual(q2Realm2, q2Realm2); dispatch_async(q1, ^{ @autoreleasepool { RLMRealm *backgroundThreadRealm = [RLMRealm defaultRealm]; RLMRealm *q1Realm3 = [RLMRealm defaultRealmForQueue:q1]; XCTAssertThrows([RLMRealm defaultRealmForQueue:q2]); XCTAssertNotEqual(backgroundThreadRealm, mainThreadRealm1); XCTAssertNotEqual(backgroundThreadRealm, mainQueueRealm1); XCTAssertNotEqual(backgroundThreadRealm, q1Realm1); XCTAssertNotEqual(backgroundThreadRealm, q1Realm2); XCTAssertEqual(q1Realm1, q1Realm3); } }); dispatch_sync(q1, ^{}); dispatch_async(q2, ^{ @autoreleasepool { RLMRealm *backgroundThreadRealm = [RLMRealm defaultRealm]; XCTAssertThrows([RLMRealm defaultRealmForQueue:q1]); RLMRealm *q2Realm3 = [RLMRealm defaultRealmForQueue:q2]; XCTAssertNotEqual(backgroundThreadRealm, mainThreadRealm1); XCTAssertNotEqual(backgroundThreadRealm, mainQueueRealm1); XCTAssertNotEqual(backgroundThreadRealm, q1Realm1); XCTAssertNotEqual(backgroundThreadRealm, q1Realm2); XCTAssertEqual(q2Realm2, q2Realm3); } }); dispatch_sync(q2, ^{}); } - (void)testQueueValidation { XCTAssertNoThrow([RLMRealm defaultRealmForQueue:dispatch_get_main_queue()]); RLMAssertThrowsWithReason([RLMRealm defaultRealmForQueue:self.bgQueue], @"Realm opened from incorrect dispatch queue."); RLMAssertThrowsWithReasonMatching([RLMRealm defaultRealmForQueue:dispatch_get_global_queue(0, 0)], @"Invalid queue '.*' \\(.*\\): Realms can only be confined to serial queues or the main queue."); RLMAssertThrowsWithReason([RLMRealm defaultRealmForQueue:dispatch_queue_create("concurrent queue", DISPATCH_QUEUE_CONCURRENT)], @"Invalid queue 'concurrent queue' (OS_dispatch_queue_concurrent): Realms can only be confined to serial queues or the main queue."); dispatch_sync(self.bgQueue, ^{ XCTAssertNoThrow([RLMRealm defaultRealmForQueue:self.bgQueue]); }); } - (void)testQueueChecking { auto q1 = dispatch_queue_create("queue 1", DISPATCH_QUEUE_SERIAL); auto q2 = dispatch_queue_create("queue 2", DISPATCH_QUEUE_SERIAL); RLMRealm *mainRealm = [RLMRealm defaultRealmForQueue:dispatch_get_main_queue()]; __block RLMRealm *q1Realm; __block RLMRealm *q2Realm; dispatch_sync(q1, ^{ q1Realm = [RLMRealm defaultRealmForQueue:q1]; }); dispatch_sync(q2, ^{ q2Realm = [RLMRealm defaultRealmForQueue:q2]; }); XCTAssertNoThrow([mainRealm refresh]); RLMAssertThrowsWithReason([q1Realm refresh], @"thread"); RLMAssertThrowsWithReason([q2Realm refresh], @"thread"); dispatch_sync(q1, ^{ // dispatch_sync() doesn't change the thread and mainRealm is actually // bound to the main thread and not the main queue XCTAssertNoThrow([mainRealm refresh]); XCTAssertNoThrow([q1Realm refresh]); RLMAssertThrowsWithReason([q2Realm refresh], @"thread"); dispatch_sync(q2, ^{ XCTAssertNoThrow([mainRealm refresh]); XCTAssertNoThrow([q2Realm refresh]); RLMAssertThrowsWithReason([q1Realm refresh], @"thread"); }); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([mainRealm refresh], @"thread"); RLMAssertThrowsWithReason([q1Realm refresh], @"thread"); RLMAssertThrowsWithReason([q2Realm refresh], @"thread"); }]; }); } - (void)testReusingConfigOnMultipleQueues { auto config = [RLMRealmConfiguration defaultConfiguration]; auto q1 = dispatch_queue_create("queue 1", DISPATCH_QUEUE_SERIAL); auto q2 = dispatch_queue_create("queue 2", DISPATCH_QUEUE_SERIAL); dispatch_sync(q1, ^{ XCTAssertNoThrow([RLMRealm realmWithConfiguration:config queue:q1 error:nil]); }); dispatch_sync(q2, ^{ XCTAssertNoThrow([RLMRealm realmWithConfiguration:config queue:q2 error:nil]); }); } - (void)testConfigurationFromExistingRealmOnNewThread { auto r1 = [RLMRealm defaultRealm]; [self dispatchAsyncAndWait:^{ auto r2 = [RLMRealm realmWithConfiguration:r1.configuration error:nil]; XCTAssertNoThrow([r2 refresh]); }]; } #pragma mark - In-memory Realms - (void)testInMemoryRealm { @autoreleasepool { RLMRealm *inMemoryRealm = [self inMemoryRealmWithIdentifier:@"identifier"]; [self waitForNotification:RLMRealmDidChangeNotification realm:inMemoryRealm block:^{ RLMRealm *inMemoryRealm = [self inMemoryRealmWithIdentifier:@"identifier"]; [inMemoryRealm beginWriteTransaction]; [StringObject createInRealm:inMemoryRealm withValue:@[@"a"]]; [StringObject createInRealm:inMemoryRealm withValue:@[@"b"]]; [StringObject createInRealm:inMemoryRealm withValue:@[@"c"]]; XCTAssertEqual(3U, [StringObject allObjectsInRealm:inMemoryRealm].count); [inMemoryRealm commitWriteTransaction]; }]; XCTAssertEqual(3U, [StringObject allObjectsInRealm:inMemoryRealm].count); // make sure we can have another RLMRealm *anotherInMemoryRealm = [self inMemoryRealmWithIdentifier:@"identifier2"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:anotherInMemoryRealm].count); } // Should now be empty RLMRealm *inMemoryRealm = [self inMemoryRealmWithIdentifier:@"identifier"]; XCTAssertEqual(0U, [StringObject allObjectsInRealm:inMemoryRealm].count); } #pragma mark - Read-only Realms - (void)testReadOnlyRealmWithMissingTables { // create a realm with only a StringObject table @autoreleasepool { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; objectSchema.objectClass = RLMObject.class; RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = @[objectSchema]; RLMRealm *realm = [self realmWithTestPathAndSchema:schema]; [realm beginWriteTransaction]; [realm createObject:StringObject.className withValue:@[@"a"]]; [realm commitWriteTransaction]; } RLMRealm *realm = [self readOnlyRealmWithURL:RLMTestRealmURL() error:nil]; XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); XCTAssertNil([PrimaryIntObject objectInRealm:realm forPrimaryKey:@0]); // verify that reading a missing table gives an empty array rather than // crashing RLMResults *results = [IntObject allObjectsInRealm:realm]; XCTAssertEqual(0U, results.count); XCTAssertEqual(results, [results objectsWhere:@"intCol = 5"]); XCTAssertEqual(results, [results sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertThrows([results objectAtIndex:0]); XCTAssertEqual(NSNotFound, [results indexOfObject:self.nonLiteralNil]); XCTAssertEqual(NSNotFound, [results indexOfObjectWhere:@"intCol = 5"]); RLMAssertThrowsWithReason([realm deleteObjects:results], @"Cannot modify Results outside of a write transaction."); XCTAssertNil([results maxOfProperty:@"intCol"]); XCTAssertNil([results minOfProperty:@"intCol"]); XCTAssertNil([results averageOfProperty:@"intCol"]); XCTAssertEqualObjects(@0, [results sumOfProperty:@"intCol"]); XCTAssertNil([results firstObject]); XCTAssertNil([results lastObject]); for (__unused id obj in results) { XCTFail(@"Got an item in empty results"); } } - (void)testReadOnlyRealmWithMissingColumns { // create a realm with only a zero-column StringObject table @autoreleasepool { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class]; objectSchema.objectClass = RLMObject.class; objectSchema.properties = @[]; RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = @[objectSchema]; [self realmWithTestPathAndSchema:schema]; } XCTAssertThrows([self readOnlyRealmWithURL:RLMTestRealmURL() error:nil], @"should reject table missing column"); } #pragma mark - Write Copy to Path - (void)testWriteCopyOfRealm { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; NSError *writeError; XCTAssertTrue([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:realm.configuration.encryptionKey error:&writeError]); XCTAssertNil(writeError); RLMRealm *copy = [self realmWithTestPath]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:copy].count); RLMRealm *frozenCopy = [copy freeze]; XCTAssertTrue(frozenCopy.isFrozen); XCTAssertTrue([IntObject allObjectsInRealm:frozenCopy].isFrozen); } - (void)testCannotOverwriteWithWriteCopy { RLMRealm *realm = [self realmWithTestPath]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; NSError *writeError; // Does not throw when given a nil error out param XCTAssertFalse([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:nil]); NSString *expectedError = @"Failed to open file at path '%@': File exists"; XCTAssertFalse([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileExists, expectedError, RLMTestRealmURL().path); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); writeError = nil; XCTAssertFalse([realm writeCopyForConfiguration:configuration error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileExists, expectedError, RLMTestRealmURL().path); } - (void)testCannotWriteInNonExistentDirectory { RLMRealm *realm = [self realmWithTestPath]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; NSString *badPath = @"/tmp/RLMTestDirMayNotExist/foo"; NSString *expectedError = @"Failed to open file at path '%@': parent directory does not exist"; NSError *writeError; XCTAssertFalse([realm writeCopyToURL:[NSURL fileURLWithPath:badPath] encryptionKey:nil error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileNotFound, expectedError, badPath); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = [NSURL fileURLWithPath:badPath]; writeError = nil; XCTAssertFalse([realm writeCopyForConfiguration:configuration error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileNotFound, expectedError, badPath); } - (void)testWriteToReadOnlyDirectory { RLMRealm *realm = [RLMRealm defaultRealm]; // Make the parent directory temporarily read-only NSString *directory = RLMTestRealmURL().URLByDeletingLastPathComponent.path; NSFileManager *fm = NSFileManager.defaultManager; NSNumber *oldPermissions = [fm attributesOfItemAtPath:directory error:nil][NSFilePosixPermissions]; [fm setAttributes:@{NSFilePosixPermissions: @(0100)} ofItemAtPath:directory error:nil]; NSError *writeError; XCTAssertFalse([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFilePermissionDenied, @"Failed to open file at path '%@': Permission denied", RLMTestRealmURL().path); // Test writeCopyForConfiguration RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); writeError = nil; XCTAssertFalse([realm writeCopyForConfiguration:configuration error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFilePermissionDenied, @"Failed to open file at path '%@': Permission denied", RLMTestRealmURL().path); // Restore old permissions [fm setAttributes:@{NSFilePosixPermissions: oldPermissions} ofItemAtPath:directory error:nil]; } - (void)testWriteWithNonSpecialCasedError { // Testing an open() error which doesn't have its own exception type and // just uses the generic "something failed" error RLMRealm *realm = [RLMRealm defaultRealm]; #ifdef REALM_FILELOCK_EMULATION // Beginning a read transaction involves opening a file when using filelock // emulation, so do that before setting the open file limit. [realm refresh]; #endif // Set the max open files to zero so that opening new files will fail rlimit oldrl; getrlimit(RLIMIT_NOFILE, &oldrl); rlimit rl = oldrl; rl.rlim_cur = 0; setrlimit(RLIMIT_NOFILE, &rl); NSError *writeError; XCTAssertFalse([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileOperationFailed, @"Failed to open file at path '%@': Too many open files", RLMTestRealmURL().path); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); writeError = nil; XCTAssertFalse([realm writeCopyForConfiguration:configuration error:&writeError]); RLMValidateRealmError(writeError, RLMErrorFileOperationFailed, @"Failed to open file at path '%@': Too many open files", RLMTestRealmURL().path); // Restore the old open file limit setrlimit(RLIMIT_NOFILE, &oldrl); } - (void)testWritingCopyUsesWriteTransactionInProgress { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; NSError *writeError; XCTAssertTrue([realm writeCopyToURL:RLMTestRealmURL() encryptionKey:realm.configuration.encryptionKey error:&writeError]); XCTAssertNil(writeError); RLMRealm *copy = [self realmWithTestPath]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:copy].count); }]; } #pragma mark - Write Copy For Configuration - (void)testWriteCopyForConfiguration { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; NSError *writeError; XCTAssertTrue([realm writeCopyForConfiguration:configuration error:&writeError]); XCTAssertNil(writeError); RLMRealm *copy = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:copy].count); RLMRealm *frozenCopy = [copy freeze]; XCTAssertTrue(frozenCopy.isFrozen); XCTAssertTrue([IntObject allObjectsInRealm:frozenCopy].isFrozen); } - (void)testWritingCopyWithConfigurationUsesWriteTransactionInProgress { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = RLMTestRealmURL(); RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; NSError *writeError; XCTAssertTrue([realm writeCopyForConfiguration:configuration error:&writeError]); XCTAssertNil(writeError); RLMRealm *copy = [RLMRealm realmWithConfiguration:configuration error:nil]; XCTAssertEqual(1U, [IntObject allObjectsInRealm:copy].count); }]; } #pragma mark - Frozen Realms - (void)testIsFrozen { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertFalse(realm.frozen); RLMRealm *frozenRealm = [realm freeze]; RLMRealm *thawedRealm = [frozenRealm thaw]; XCTAssertFalse(realm.frozen); XCTAssertFalse(thawedRealm.frozen); XCTAssertTrue(frozenRealm.frozen); } - (void)testRefreshFrozen { RLMRealm *realm = [RLMRealm defaultRealm]; RLMRealm *frozenRealm = realm.freeze; XCTAssertFalse([realm refresh]); XCTAssertFalse([frozenRealm refresh]); [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@0]]; }]; XCTAssertFalse([frozenRealm refresh]); XCTAssertEqual(0U, [IntObject allObjectsInRealm:frozenRealm].count); } - (void)testForbiddenMethodsOnFrozenRealm { RLMRealm *realm = [RLMRealm defaultRealm].freeze; RLMAssertThrowsWithReason([realm setAutorefresh:YES], @"Auto-refresh cannot be enabled for frozen Realms."); RLMAssertThrowsWithReason([realm beginWriteTransaction], @"Can't perform transactions on a frozen Realm"); RLMAssertThrowsWithReason([realm addNotificationBlock:^(RLMNotification, RLMRealm *) { }], @"Frozen Realms do not change and do not have change notifications."); RLMAssertThrowsWithReason(([[IntObject allObjectsInRealm:realm] addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { }]), @"Frozen Realms do not change and do not have change notifications."); } - (void)testFrozenRealmCaching { RLMRealm *realm = [RLMRealm defaultRealm]; RLMRealm *fr1 = realm.freeze; RLMRealm *fr2 = realm.freeze; XCTAssertEqual(fr1, fr2); // note: pointer equality as it should return the same instance [realm transactionWithBlock:^{ }]; RLMRealm *fr3 = realm.freeze; RLMRealm *fr4 = realm.freeze; XCTAssertEqual(fr3, fr4); XCTAssertNotEqual(fr1, fr3); } - (void)testReadAfterInvalidateFrozen { RLMRealm *realm = [RLMRealm defaultRealm].freeze; [realm invalidate]; RLMAssertThrowsWithReason([IntObject allObjectsInRealm:realm], @"Cannot read from a frozen Realm which has been invalidated."); } - (void)testThaw { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertFalse(realm.frozen); [realm beginWriteTransaction]; [IntObject createInRealm: realm withValue:@[@1]]; [realm commitWriteTransaction]; RLMRealm *frozenRealm = [realm freeze]; XCTAssertTrue(frozenRealm.frozen); IntObject *frozenObj = [[IntObject objectsInRealm:frozenRealm where:@"intCol = 1"] firstObject]; XCTAssertTrue(frozenObj.frozen); RLMRealm *thawedRealm = [realm thaw]; XCTAssertFalse(thawedRealm.frozen); IntObject *thawedObj = [[IntObject objectsInRealm:thawedRealm where:@"intCol = 1"] firstObject]; [realm beginWriteTransaction]; thawedObj.intCol = 2; [realm commitWriteTransaction]; XCTAssertNotEqual(thawedObj.intCol, frozenObj.intCol); IntObject *nilObj = [[IntObject objectsInRealm:thawedRealm where:@"intCol = 1"] firstObject]; XCTAssertNil(nilObj); } - (void)testThawDifferentThread { RLMRealm *frozenRealm = [[RLMRealm defaultRealm] freeze]; XCTAssertTrue(frozenRealm.frozen); // Thaw on a thread which already has a Realm should use existing reference. [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; RLMRealm *thawed = [frozenRealm thaw]; XCTAssertFalse(thawed.frozen); XCTAssertEqual(thawed, realm); }]; // Thaw on thread without existing reference. [self dispatchAsyncAndWait:^{ RLMRealm *thawed = [frozenRealm thaw]; XCTAssertFalse(thawed.frozen); }]; } - (void)testThawPreviousVersion { RLMRealm *realm = RLMRealm.defaultRealm; RLMRealm *frozenRealm = [realm freeze]; XCTAssertTrue(frozenRealm.frozen); XCTAssertEqual(frozenRealm.isEmpty, [[RLMRealm defaultRealm] isEmpty]); [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@1]]; [realm commitWriteTransaction]; XCTAssertNotEqual(frozenRealm.isEmpty, realm.isEmpty, "Contents of frozen Realm should not mutate"); RLMRealm *thawed = [frozenRealm thaw]; XCTAssertFalse(thawed.isFrozen); XCTAssertEqual(thawed.isEmpty, realm.isEmpty, "Thawed realm should reflect transactions since the original reference was frozen"); XCTAssertNotEqual(thawed.isEmpty, frozenRealm.isEmpty); } #pragma mark - Assorted tests - (void)testIsEmpty { RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertTrue(realm.isEmpty, @"Realm should be empty on creation."); [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; XCTAssertFalse(realm.isEmpty, @"Realm should not be empty within a write transaction after adding an object."); [realm cancelWriteTransaction]; XCTAssertTrue(realm.isEmpty, @"Realm should be empty after canceling a write transaction that added an object."); [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; XCTAssertFalse(realm.isEmpty, @"Realm should not be empty after committing a write transaction that added an object."); } - (void)testRealmFileAccessNilPath { RLMAssertThrowsWithReasonMatching([RLMRealm realmWithURL:self.nonLiteralNil], @"Realm path must not be empty", @"nil path"); } - (void)testRealmFileAccessNoExistingFile { NSURL *fileURL = [NSURL fileURLWithPath:RLMRealmPathForFile(@"filename.realm")]; [[NSFileManager defaultManager] removeItemAtPath:fileURL.path error:nil]; XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]); NSError *error; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = fileURL; XCTAssertNotNil([RLMRealm realmWithConfiguration:configuration error:&error], @"Database should have been created"); XCTAssertNil(error); } - (void)testRealmFileAccessInvalidFile { NSString *content = @"Some content"; NSData *fileContents = [content dataUsingEncoding:NSUTF8StringEncoding]; NSURL *fileURL = [NSURL fileURLWithPath:RLMRealmPathForFile(@"filename.realm")]; [[NSFileManager defaultManager] removeItemAtPath:fileURL.path error:nil]; assert(![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]); [[NSFileManager defaultManager] createFileAtPath:fileURL.path contents:fileContents attributes:nil]; NSError *error; RLMRealmConfiguration *configuration = [RLMRealmConfiguration new]; configuration.fileURL = fileURL; XCTAssertNil([RLMRealm realmWithConfiguration:configuration error:&error]); RLMValidateRealmError(error, RLMErrorInvalidDatabase, @"Failed to open Realm file at path '%@': file is non-empty but too small (12 bytes) to be a valid Realm.", fileURL.path); } - (void)testRealmFileAccessFileIsDirectory { NSURL *testURL = RLMTestRealmURL(); [[NSFileManager defaultManager] createDirectoryAtPath:testURL.path withIntermediateDirectories:NO attributes:nil error:nil]; NSError *error; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = testURL; XCTAssertNil([RLMRealm realmWithConfiguration:configuration error:&error]); RLMValidateRealmError(error, RLMErrorFileOperationFailed, @"Failed to open Realm file at path '%@': Is a directory", testURL.path); } #if !TARGET_OS_TV - (void)testRealmFifoError { NSFileManager *manager = [NSFileManager defaultManager]; NSURL *testURL = RLMTestRealmURL(); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = testURL; // Create the expected fifo URL and create a directory. // Note that creating a file when a directory with the same name exists produces a different errno, which is good. NSURL *fifoURL = [[testURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"realm.note"]; assert(![manager fileExistsAtPath:fifoURL.path]); [manager createDirectoryAtPath:fifoURL.path withIntermediateDirectories:YES attributes:nil error:nil]; // Ensure that it doesn't try to fall back to putting it in the temp directory auto oldTempDir = realm::DBOptions::get_sys_tmp_dir(); realm::DBOptions::set_sys_tmp_dir(""); NSError *error; XCTAssertNil([RLMRealm realmWithConfiguration:configuration error:&error], @"Should not have been able to open FIFO"); RLMValidateRealmError(error, RLMErrorFileExists, @"Cannot create fifo at path '%@': a non-fifo entry already exists at that path.", [testURL.path stringByAppendingString:@".note"]); realm::DBOptions::set_sys_tmp_dir(std::move(oldTempDir)); } #endif - (void)testMultipleRealms { // Create one StringObject in two different realms RLMRealm *defaultRealm = [RLMRealm defaultRealm]; RLMRealm *testRealm = self.realmWithTestPath; [defaultRealm beginWriteTransaction]; [testRealm beginWriteTransaction]; [StringObject createInRealm:defaultRealm withValue:@[@"a"]]; [StringObject createInRealm:testRealm withValue:@[@"b"]]; [testRealm commitWriteTransaction]; [defaultRealm commitWriteTransaction]; // Confirm that objects were added to the correct realms RLMResults *defaultObjects = [StringObject allObjectsInRealm:defaultRealm]; RLMResults *testObjects = [StringObject allObjectsInRealm:testRealm]; XCTAssertEqual(defaultObjects.count, 1U, @"Expecting 1 object"); XCTAssertEqual(testObjects.count, 1U, @"Expecting 1 object"); XCTAssertEqualObjects([defaultObjects.firstObject stringCol], @"a", @"Expecting column to be 'a'"); XCTAssertEqualObjects([testObjects.firstObject stringCol], @"b", @"Expecting column to be 'b'"); } // iOS uses a different locking scheme which breaks how we stop core from reinitializing the lock file #ifndef REALM_FILELOCK_EMULATION - (void)testInvalidLockFile { // Create the realm file and lock file @autoreleasepool { [RLMRealm defaultRealm]; } NSString *path = RLMRealmConfiguration.defaultConfiguration.fileURL.path; int fd = open([path stringByAppendingString:@".lock"].UTF8String, O_RDWR); XCTAssertNotEqual(-1, fd); // Change the value of the mutex size field in the shared info header uint8_t value = 255; pwrite(fd, &value, 1, 1); // Ensure that SharedGroup can't get an exclusive lock on the lock file so // that it can't just recreate it int ret = flock(fd, LOCK_SH); XCTAssertEqual(0, ret); NSError *error; RLMRealm *realm = [RLMRealm realmWithConfiguration:RLMRealmConfiguration.defaultConfiguration error:&error]; XCTAssertNil(realm); RLMValidateRealmError(error, RLMErrorIncompatibleLockFile, @"Realm file '%@' is currently open in another process which cannot share access with this process. This could either be due to the existing process being a different architecture or due to the existing process using an incompatible version of Realm. If the other process is Realm Studio, you may need to update it (or update Realm if your Studio version is too new), and if using an iOS simulator, make sure that you are using a 64-bit simulator. Underlying problem: Architecture mismatch: Mutex size is 255 but should be 1.", path); flock(fd, LOCK_UN); close(fd); } #endif - (void)testCannotMigrateRealmWhenRealmIsOpen { RLMRealm *realm = [self realmWithTestPath]; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = realm.configuration.fileURL; XCTAssertThrows([RLMRealm performMigrationForConfiguration:configuration error:nil]); } - (void)testNotificationPipeBufferOverfull { RLMRealm *realm = [self inMemoryRealmWithIdentifier:@"test"]; // pipes have a 8 KB buffer on OS X, so verify we don't block after 8192 commits for (int i = 0; i < 9000; ++i) { [realm transactionWithBlock:^{}]; } } - (NSArray *)pathsFor100Realms { NSMutableArray *paths = [NSMutableArray array]; for (int i = 0; i < 100; ++i) { NSString *realmFileName = [NSString stringWithFormat:@"test.%d.realm", i]; [paths addObject:RLMRealmPathForFile(realmFileName)]; } return paths; } - (void)testCanCreate100RealmsWithoutBreakingGCD { NSMutableArray *realms = [NSMutableArray array]; for (NSString *realmPath in self.pathsFor100Realms) { [realms addObject:[RLMRealm realmWithURL:[NSURL fileURLWithPath:realmPath]]]; } XCTestExpectation *expectation = [self expectationWithDescription:@"Block dispatched to concurrent queue should be executed"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [expectation fulfill]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testThreadIDReuse { // Open Realms on new threads until we get repeated thread IDs, while // retaining each Realm opened. This verifies that we don't get a Realm from // an old thread that no longer exists from the cache. NSMutableArray *realms = [NSMutableArray array]; std::unordered_set threadIds; bool done = false; while (!done) { std::thread([&] { RLMRealm *realm = [RLMRealm defaultRealm]; [realms addObject:realm]; (void)[IntObject allObjectsInRealm:realm].count; [realm refresh]; if (!threadIds.insert(pthread_self()).second) { done = true; } }).join(); } } - (void)testAuxiliaryFilesAreExcludedFromBackup { RLMSetSkipBackupAttribute(true); @autoreleasepool { [RLMRealm defaultRealm]; } #if TARGET_OS_TV NSArray *auxiliaryFileExtensions = @[@"management", @"lock"]; // tvOS does not support named pipes #else NSArray *auxiliaryFileExtensions = @[@"management", @"lock", @"note"]; #endif NSURL *fileURL = RLMRealmConfiguration.defaultConfiguration.fileURL; for (NSString *pathExtension in auxiliaryFileExtensions) { NSNumber *attribute = nil; NSError *error = nil; BOOL success = [[fileURL URLByAppendingPathExtension:pathExtension] getResourceValue:&attribute forKey:NSURLIsExcludedFromBackupKey error:&error]; XCTAssertTrue(success); XCTAssertNil(error); XCTAssertTrue(attribute.boolValue); } RLMSetSkipBackupAttribute(false); } - (void)testAuxiliaryFilesAreExcludedFromBackupPerformance { RLMSetSkipBackupAttribute(true); [self measureBlock:^{ @autoreleasepool { [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; [RLMRealm defaultRealm]; } @autoreleasepool { [RLMRealm defaultRealm]; } }]; NSURL *fileURL = RLMRealmConfiguration.defaultConfiguration.fileURL; #if !TARGET_OS_TV for (NSString *pathExtension in @[@"management", @"lock", @"note"]) { #else for (NSString *pathExtension in @[@"management", @"lock"]) { #endif NSNumber *attribute = nil; NSError *error = nil; BOOL success = [[fileURL URLByAppendingPathExtension:pathExtension] getResourceValue:&attribute forKey:NSURLIsExcludedFromBackupKey error:&error]; XCTAssertTrue(success); XCTAssertNil(error); XCTAssertTrue(attribute.boolValue); } } - (void)testRealmExists { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; XCTAssertFalse([RLMRealm fileExistsForConfiguration:config]); @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } XCTAssertTrue([RLMRealm fileExistsForConfiguration:config]); [RLMRealm deleteFilesForConfiguration:config error:nil]; XCTAssertFalse([RLMRealm fileExistsForConfiguration:config]); } - (void)testDeleteNonexistentRealmFile { NSError *error; XCTAssertFalse([RLMRealm deleteFilesForConfiguration:RLMRealmConfiguration.defaultConfiguration error:&error]); XCTAssertNil(error); } - (void)testDeleteClosedRealmFile { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; } NSError *error; XCTAssertTrue([RLMRealm deleteFilesForConfiguration:config error:&error]); XCTAssertNil(error); NSFileManager *fm = NSFileManager.defaultManager; XCTAssertTrue([fm fileExistsAtPath:[config.fileURL.path stringByAppendingPathExtension:@"lock"]]); XCTAssertFalse([fm fileExistsAtPath:[config.fileURL.path stringByAppendingPathExtension:@"management"]]); #if !TARGET_OS_TV XCTAssertFalse([fm fileExistsAtPath:[config.fileURL.path stringByAppendingPathExtension:@"note"]]); #endif } - (void)testDeleteRealmFileWithMissingManagementFiles { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; [NSFileManager.defaultManager createFileAtPath:config.fileURL.path contents:nil attributes:nil]; NSError *error; XCTAssertTrue([RLMRealm deleteFilesForConfiguration:config error:&error]); XCTAssertNil(error); } - (void)testDeleteRealmFileWithReadOnlyManagementFiles { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; NSFileManager *fm = NSFileManager.defaultManager; [fm createFileAtPath:config.fileURL.path contents:nil attributes:nil]; NSString *notificationPipe = [config.fileURL.path stringByAppendingPathExtension:@"note"]; [fm createFileAtPath:notificationPipe contents:nil attributes:@{NSFileImmutable: @YES}]; NSError *error; XCTAssertTrue([RLMRealm deleteFilesForConfiguration:config error:&error]); XCTAssertEqual(error.domain, NSCocoaErrorDomain); XCTAssertEqual(error.code, NSFileWriteNoPermissionError); [fm setAttributes:@{NSFileImmutable: @NO} ofItemAtPath:notificationPipe error:nil]; } - (void)testDeleteOpenRealmFile { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; __attribute__((objc_precise_lifetime)) RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; NSError *error; XCTAssertFalse([RLMRealm deleteFilesForConfiguration:config error:&error]); XCTAssertEqual(error.code, RLMErrorAlreadyOpen); XCTAssertTrue([NSFileManager.defaultManager fileExistsAtPath:config.fileURL.path]); } @end @interface RLMLoggerTests : RLMTestCase @property (nonatomic, strong) RLMLogger *logger; @end @implementation RLMLoggerTests - (void)setUp { _logger = RLMLogger.defaultLogger; } - (void)tearDown { RLMLogger.defaultLogger = _logger; } - (void)testSetDefaultLogLevel { __block NSMutableString *logs = [[NSMutableString alloc] init]; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelAll logFunction:^(RLMLogLevel level, NSString *message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertEqual([RLMLogger defaultLogger].level, RLMLogLevelAll); XCTAssertTrue([logs containsString:@"5 DB:"]); // Detail XCTAssertTrue([logs containsString:@"7 DB:"]); // Trace [logs setString: @""]; logger.level = RLMLogLevelDetail; @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertEqual([RLMLogger defaultLogger].level, RLMLogLevelDetail); XCTAssertTrue([logs containsString:@"5 DB:"]); // Detail XCTAssertFalse([logs containsString:@"7 DB:"]); // Trace } - (void)testDefaultLogger { __block NSMutableString *logs = [[NSMutableString alloc] init]; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelOff logFunction:^(RLMLogLevel level, NSString *message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; XCTAssertEqual(RLMLogger.defaultLogger.level, RLMLogLevelOff); @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertTrue([logs length] == 0); // Test LogLevel Detail logger.level = RLMLogLevelDetail; @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertTrue([logs length] > 0); XCTAssertTrue([logs containsString:@"5 DB:"]); // Detail XCTAssertFalse([logs containsString:@"7 DB:"]); // Trace // Test LogLevel All logger.level = RLMLogLevelAll; @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertTrue([logs length] > 0); XCTAssertTrue([logs containsString:@"5 DB:"]); // Detail XCTAssertTrue([logs containsString:@"7 DB:"]); // Trace [logs setString: @""]; // Init Custom Logger RLMLogger.defaultLogger = [[RLMLogger alloc] initWithLevel:RLMLogLevelDebug logFunction:^(RLMLogLevel level, NSString * message) { [logs appendFormat:@" %@ %lu %@", [NSDate date], level, message]; }]; XCTAssertEqual(RLMLogger.defaultLogger.level, RLMLogLevelDebug); @autoreleasepool { [RLMRealm defaultRealm]; } XCTAssertTrue([logs containsString:@"5 DB:"]); // Detail XCTAssertFalse([logs containsString:@"7 DB:"]); // Trace } - (void)testCustomLoggerLogMessage { __block NSMutableString *logs = [[NSMutableString alloc] init]; RLMLogger *logger = [[RLMLogger alloc] initWithLevel:RLMLogLevelInfo logFunction:^(RLMLogLevel level, NSString * message) { [logs appendFormat:@" %@ %lu %@.", [NSDate date], level, message]; }]; RLMLogger.defaultLogger = logger; [logger logWithLevel:RLMLogLevelInfo message:@"%@ IMPORTANT INFO %i", @"TEST:", 0]; [logger logWithLevel:RLMLogLevelTrace message:@"IMPORTANT TRACE"]; XCTAssertTrue([logs containsString:@"TEST: IMPORTANT INFO 0"]); // Detail XCTAssertFalse([logs containsString:@"IMPORTANT TRACE"]); // Trace } @end ================================================ FILE: Realm/Tests/ResultsTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" #import #import @interface ResultsTests : RLMTestCase @end @implementation ResultsTests - (void)testFastEnumeration { RLMRealm *realm = self.realmWithTestPath; // enumerate empty array for (__unused id obj in [AggregateObject allObjectsInRealm:realm]) { XCTFail(@"Should be empty"); } [realm beginWriteTransaction]; for (int i = 0; i < 18; ++i) { [AggregateObject createInRealm:realm withValue:@[@10, @1.2f, @0.0, @YES, NSDate.date]]; } [realm commitWriteTransaction]; RLMResults *result = [AggregateObject objectsInRealm:realm where:@"intCol < %i", 100]; XCTAssertEqual(result.count, 18U); __weak id objects[18]; NSInteger count = 0; for (AggregateObject *ao in result) { XCTAssertNotNil(ao, @"Object is not nil and accessible"); if (count > 16) { // 16 is the size of blocks fast enumeration happens to ask for at // the moment, but of course that's just an implementation detail // that may change XCTAssertNil(objects[count - 16]); } objects[count++] = ao; } XCTAssertEqual(count, 18, @"should have enumerated 18 objects"); for (int i = 0; i < count; i++) { XCTAssertNil(objects[i], @"Object should have been released"); } @autoreleasepool { for (AggregateObject *ao in result) { objects[0] = ao; break; } } XCTAssertNil(objects[0], @"Object should have been released"); } - (void)testFirst { XCTAssertNil(IntObject.allObjects.firstObject); XCTAssertNil([IntObject objectsWhere:@"intCol > 5"].firstObject); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@10]]; [IntObject createInDefaultRealmWithValue:@[@20]]; [realm commitWriteTransaction]; XCTAssertEqual(10, [IntObject.allObjects.firstObject intCol]); XCTAssertEqual(10, [[IntObject objectsWhere:@"intCol > 5"].firstObject intCol]); XCTAssertEqual(20, [[IntObject objectsWhere:@"intCol > 10"].firstObject intCol]); } - (void)testLast { XCTAssertNil(IntObject.allObjects.lastObject); XCTAssertNil([IntObject objectsWhere:@"intCol > 5"].lastObject); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@20]]; [IntObject createInDefaultRealmWithValue:@[@10]]; [realm commitWriteTransaction]; XCTAssertEqual(10, [IntObject.allObjects.lastObject intCol]); XCTAssertEqual(10, [[IntObject objectsWhere:@"intCol > 5"].lastObject intCol]); XCTAssertEqual(20, [[IntObject objectsWhere:@"intCol > 10"].lastObject intCol]); } - (void)testSubscript { RLMAssertThrowsWithReasonMatching([IntObject allObjects][0], @"0.*less than 0"); RLMAssertThrowsWithReasonMatching([IntObject objectsWhere:@"intCol > 5"][0], @"0.*less than 0"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@10]]; [IntObject createInDefaultRealmWithValue:@[@20]]; [realm commitWriteTransaction]; XCTAssertEqual(10, [IntObject.allObjects[0] intCol]); XCTAssertEqual(10, [[IntObject objectsWhere:@"intCol > 5"][0] intCol]); XCTAssertEqual(20, [[IntObject objectsWhere:@"intCol > 10"][0] intCol]); RLMAssertThrowsWithReasonMatching([IntObject allObjects][2], @"2.*less than 2"); RLMAssertThrowsWithReasonMatching([IntObject objectsWhere:@"intCol > 5"][2], @"2.*less than 2"); } - (void)testObjectsAtIndexes { NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; [indexSet addIndex:0]; [indexSet addIndex:1]; XCTAssertNil([[IntObject allObjects] objectsAtIndexes:indexSet]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [IntObject createInDefaultRealmWithValue:@[@10]]; [IntObject createInDefaultRealmWithValue:@[@20]]; [realm commitWriteTransaction]; XCTAssertEqual([[[IntObject allObjects] objectsAtIndexes:indexSet][0] intCol], 10); XCTAssertEqual([[[IntObject allObjects] objectsAtIndexes:indexSet][1] intCol], 20); [indexSet addIndex:3]; XCTAssertNil([[IntObject allObjects] objectsAtIndexes:indexSet]); XCTAssertNil([[IntObject allObjects] objectsAtIndexes:indexSet]); } - (void)testValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; XCTAssertEqualObjects([[AggregateObject allObjectsInRealm:realm] valueForKey:@"intCol"], @[]); NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; XCTAssertEqualObjects([[AggregateObject allObjectsInRealm:realm] valueForKey:@"intCol"], (@[@0, @1, @0, @1, @0, @1, @0, @1, @0, @0])); XCTAssertTrue([[[[AggregateObject allObjectsInRealm:realm] valueForKey:@"self"] firstObject] isEqualToObject:[AggregateObject allObjectsInRealm:realm].firstObject]); XCTAssertTrue([[[[AggregateObject allObjectsInRealm:realm] valueForKey:@"self"] lastObject] isEqualToObject:[AggregateObject allObjectsInRealm:realm].lastObject]); XCTAssertEqualObjects([[AggregateObject objectsInRealm:realm where:@"intCol != 1"] valueForKey:@"intCol"], (@[@0, @0, @0, @0, @0, @0])); XCTAssertTrue([[[[AggregateObject objectsInRealm:realm where:@"intCol != 1"] valueForKey:@"self"] firstObject] isEqualToObject:[AggregateObject objectsInRealm:realm where:@"intCol != 1"].firstObject]); XCTAssertTrue([[[[AggregateObject objectsInRealm:realm where:@"intCol != 1"] valueForKey:@"self"] lastObject] isEqualToObject:[AggregateObject objectsInRealm:realm where:@"intCol != 1"].lastObject]); [realm commitWriteTransaction]; XCTAssertEqualObjects([[AggregateObject allObjectsInRealm:realm] valueForKey:@"intCol"], (@[@0, @1, @0, @1, @0, @1, @0, @1, @0, @0])); XCTAssertEqualObjects([[AggregateObject objectsInRealm:realm where:@"intCol != 1"] valueForKey:@"intCol"], (@[@0, @0, @0, @0, @0, @0])); XCTAssertThrows([[AggregateObject allObjectsInRealm:realm] valueForKey:@"invalid"]); } - (void)testSetValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [[AggregateObject allObjectsInRealm:realm] setValue:@25 forKey:@"intCol"]; XCTAssertEqualObjects([[AggregateObject allObjectsInRealm:realm] valueForKey:@"intCol"], (@[@25, @25, @25, @25, @25, @25, @25, @25, @25, @25])); [[AggregateObject objectsInRealm:realm where:@"floatCol > 1"] setValue:@10 forKey:@"intCol"]; XCTAssertEqualObjects([[AggregateObject objectsInRealm:realm where:@"floatCol > 1"] valueForKey:@"intCol"], (@[@10, @10, @10, @10, @10, @10])); XCTAssertThrows([[AggregateObject allObjectsInRealm:realm] valueForKey:@"invalid"]); [[AggregateObject objectsInRealm:realm where:@"intCol != 5"] setValue:@5 forKey:@"intCol"]; XCTAssertEqualObjects([[AggregateObject allObjectsInRealm:realm] valueForKey:@"intCol"], (@[@5, @5, @5, @5, @5, @5, @5, @5, @5, @5])); [realm commitWriteTransaction]; RLMAssertThrowsWithReasonMatching([[AggregateObject allObjectsInRealm:realm] setValue:@25 forKey:@"intCol"], @"write transaction"); RLMAssertThrowsWithReasonMatching([[AggregateObject objectsInRealm:realm where:@"floatCol > 1"] setValue:@10 forKey:@"intCol"], @"write transaction"); } - (void)testObjectAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *noArray = [AggregateObject objectsWhere:@"boolCol == NO"]; RLMResults *yesArray = [AggregateObject objectsWhere:@"boolCol == YES"]; RLMResults *allArray = [AggregateObject allObjects]; XCTAssertEqual(0, [noArray sumOfProperty:@"intCol"].intValue); XCTAssertEqual(0, [allArray sumOfProperty:@"intCol"].intValue); XCTAssertNil([noArray averageOfProperty:@"intCol"]); XCTAssertNil([allArray averageOfProperty:@"intCol"]); XCTAssertNil([noArray minOfProperty:@"intCol"]); XCTAssertNil([allArray minOfProperty:@"intCol"]); XCTAssertNil([noArray maxOfProperty:@"intCol"]); XCTAssertNil([allArray maxOfProperty:@"intCol"]); [realm beginWriteTransaction]; NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput, @2.5]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput, @2.5]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput, @2.5]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput, @2.5]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput, @0.0]]; [realm commitWriteTransaction]; // SUM :::::::::::::::::::::::::::::::::::::::::::::: // Test int sum XCTAssertEqual([noArray sumOfProperty:@"intCol"].integerValue, 4); XCTAssertEqual([yesArray sumOfProperty:@"intCol"].integerValue, 0); XCTAssertEqual([allArray sumOfProperty:@"intCol"].integerValue, 4); // Test float sum XCTAssertEqualWithAccuracy([noArray sumOfProperty:@"floatCol"].floatValue, 0.0f, 0.1f); XCTAssertEqualWithAccuracy([yesArray sumOfProperty:@"floatCol"].floatValue, 7.2f, 0.1f); XCTAssertEqualWithAccuracy([allArray sumOfProperty:@"floatCol"].floatValue, 7.2f, 0.1f); // Test double sum XCTAssertEqualWithAccuracy([noArray sumOfProperty:@"doubleCol"].doubleValue, 10.0, 0.1f); XCTAssertEqualWithAccuracy([yesArray sumOfProperty:@"doubleCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([allArray sumOfProperty:@"doubleCol"].doubleValue, 10.0, 0.1f); // Test RLMValue sum XCTAssertEqualWithAccuracy([noArray sumOfProperty:@"anyCol"].doubleValue, 10.0, 0.1f); XCTAssertEqualWithAccuracy([yesArray sumOfProperty:@"anyCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([allArray sumOfProperty:@"anyCol"].doubleValue, 10.0, 0.1f); // Test invalid column name RLMAssertThrowsWithReasonMatching([yesArray sumOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([allArray sumOfProperty:@"foo"], @"foo.*AggregateObject"); // Test operation not supported RLMAssertThrowsWithReasonMatching([yesArray sumOfProperty:@"boolCol"], @"sum.*bool"); RLMAssertThrowsWithReasonMatching([allArray sumOfProperty:@"boolCol"], @"sum.*bool"); // Average :::::::::::::::::::::::::::::::::::::::::::::: // Test int average XCTAssertEqualWithAccuracy([noArray averageOfProperty:@"intCol"].doubleValue, 1.0, 0.1f); XCTAssertEqualWithAccuracy([yesArray averageOfProperty:@"intCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([allArray averageOfProperty:@"intCol"].doubleValue, 0.4, 0.1f); // Test float average XCTAssertEqualWithAccuracy([noArray averageOfProperty:@"floatCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([yesArray averageOfProperty:@"floatCol"].doubleValue, 1.2, 0.1f); XCTAssertEqualWithAccuracy([allArray averageOfProperty:@"floatCol"].doubleValue, 0.72, 0.1f); // Test double average XCTAssertEqualWithAccuracy([noArray averageOfProperty:@"doubleCol"].doubleValue, 2.5, 0.1f); XCTAssertEqualWithAccuracy([yesArray averageOfProperty:@"doubleCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([allArray averageOfProperty:@"doubleCol"].doubleValue, 1.0, 0.1f); // Test RLMValue average XCTAssertEqualWithAccuracy([noArray averageOfProperty:@"anyCol"].doubleValue, 2.5, 0.1f); XCTAssertEqualWithAccuracy([yesArray averageOfProperty:@"anyCol"].doubleValue, 0.0, 0.1f); XCTAssertEqualWithAccuracy([allArray averageOfProperty:@"anyCol"].doubleValue, 1.0, 0.1f); // Test invalid column name RLMAssertThrowsWithReasonMatching([yesArray averageOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([allArray averageOfProperty:@"foo"], @"foo.*AggregateObject"); // Test operation not supported RLMAssertThrowsWithReasonMatching([yesArray averageOfProperty:@"boolCol"], @"average.*bool"); RLMAssertThrowsWithReasonMatching([allArray averageOfProperty:@"boolCol"], @"average.*bool"); // MIN :::::::::::::::::::::::::::::::::::::::::::::: // Test int min XCTAssertEqual(1, [[noArray minOfProperty:@"intCol"] intValue]); XCTAssertEqual(0, [[yesArray minOfProperty:@"intCol"] intValue]); XCTAssertEqual(0, [[allArray minOfProperty:@"intCol"] intValue]); // Test float min XCTAssertEqual(0.0f, [[noArray minOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(1.2f, [[yesArray minOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(0.0f, [[allArray minOfProperty:@"floatCol"] floatValue]); // Test double min XCTAssertEqual(2.5, [[noArray minOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqual(0.0, [[yesArray minOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqual(0.0, [[allArray minOfProperty:@"doubleCol"] doubleValue]); // Test RLMValue min XCTAssertEqual(2.5, [[noArray minOfProperty:@"anyCol"] doubleValue]); XCTAssertEqual(0.0, [[yesArray minOfProperty:@"anyCol"] doubleValue]); XCTAssertEqual(0.0, [[allArray minOfProperty:@"anyCol"] doubleValue]); // Test date min XCTAssertEqualObjects(dateMaxInput, [noArray minOfProperty:@"dateCol"]); XCTAssertEqualObjects(dateMinInput, [yesArray minOfProperty:@"dateCol"]); XCTAssertEqualObjects(dateMinInput, [allArray minOfProperty:@"dateCol"]); // Test invalid column name RLMAssertThrowsWithReasonMatching([yesArray minOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([allArray minOfProperty:@"foo"], @"foo.*AggregateObject"); // Test operation not supported RLMAssertThrowsWithReasonMatching([yesArray minOfProperty:@"boolCol"], @"min.*bool"); RLMAssertThrowsWithReasonMatching([allArray minOfProperty:@"boolCol"], @"min.*bool"); // MAX :::::::::::::::::::::::::::::::::::::::::::::: // Test int max XCTAssertEqual(1, [[noArray maxOfProperty:@"intCol"] intValue]); XCTAssertEqual(0, [[yesArray maxOfProperty:@"intCol"] intValue]); XCTAssertEqual(1, [[allArray maxOfProperty:@"intCol"] intValue]); // Test float max XCTAssertEqual(0.0f, [[noArray maxOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(1.2f, [[yesArray maxOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(1.2f, [[allArray maxOfProperty:@"floatCol"] floatValue]); // Test double max XCTAssertEqual(2.5, [[noArray maxOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqual(0.0, [[yesArray maxOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqual(2.5, [[allArray maxOfProperty:@"doubleCol"] doubleValue]); // Test RLMValue max XCTAssertEqual(2.5, [[noArray maxOfProperty:@"anyCol"] doubleValue]); XCTAssertEqual(0.0, [[yesArray maxOfProperty:@"anyCol"] doubleValue]); XCTAssertEqual(2.5, [[allArray maxOfProperty:@"anyCol"] doubleValue]); // Test date max XCTAssertEqualObjects(dateMaxInput, [noArray maxOfProperty:@"dateCol"]); XCTAssertEqualObjects(dateMinInput, [yesArray maxOfProperty:@"dateCol"]); XCTAssertEqualObjects(dateMaxInput, [allArray maxOfProperty:@"dateCol"]); // Test invalid column name RLMAssertThrowsWithReasonMatching([yesArray maxOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([allArray maxOfProperty:@"foo"], @"foo.*AggregateObject"); // Test operation not supported RLMAssertThrowsWithReasonMatching([yesArray maxOfProperty:@"boolCol"], @"max.*bool"); RLMAssertThrowsWithReasonMatching([allArray maxOfProperty:@"boolCol"], @"max.*bool"); } - (void)testRenamedPropertyAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *results = [RenamedProperties1 allObjectsInRealm:realm]; XCTAssertEqual(0, [results sumOfProperty:@"propA"].intValue); XCTAssertNil([results averageOfProperty:@"propA"]); XCTAssertNil([results minOfProperty:@"propA"]); XCTAssertNil([results maxOfProperty:@"propA"]); XCTAssertThrows([results sumOfProperty:@"prop 1"]); [realm transactionWithBlock:^{ [RenamedProperties1 createInRealm:realm withValue:@[@1, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@2, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@3, @""]]; }]; XCTAssertEqual(6, [results sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [results averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[results minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[results maxOfProperty:@"propA"] intValue]); } -(void)testRenamedPropertyObservation { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [LinkToRenamedProperties createInRealm:realm withValue:@[]]; }]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; RLMResults *allObjects = [LinkToRenamedProperties allObjectsInRealm:realm]; id token = [allObjects addNotificationBlock:^(__unused RLMResults *results, RLMCollectionChange *change, __unused NSError *error) { XCTAssertNotNil(results); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; } keyPaths:@[@"link"]]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ LinkToRenamedProperties *object = (LinkToRenamedProperties *)[LinkToRenamedProperties allObjectsInRealm:realm].firstObject; RenamedProperties *linkedObject = [RenamedProperties createInRealm:realm withValue:@[@1, @""]]; object.link = linkedObject; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testValueForCollectionOperationKeyPath { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *c1e1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; EmployeeObject *c1e2 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; EmployeeObject *c1e3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"InspiringNames LLC", @"employees": @[c1e1, c1e2, c1e3], @"employeeSet": @[c1e1, c1e2, c1e3]}]; EmployeeObject *c2e1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; EmployeeObject *c2e2 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; EmployeeObject *c2e3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG", @"employees": @[c2e1, c2e2, c2e3], @"employeeSet": @[c2e1, c2e2, c2e3]}]; EmployeeObject *c3e1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @21, @"hired": @YES}]; [CompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG", @"employees": @[c3e1], @"employeeSet": @[c3e1]}]; [realm commitWriteTransaction]; RLMResults *allCompanies = [CompanyObject allObjects]; RLMResults *allEmployees = [EmployeeObject allObjects]; // count operator XCTAssertEqual([[allCompanies valueForKeyPath:@"@count"] integerValue], 3); // numeric operators XCTAssertEqual([[allEmployees valueForKeyPath:@"@min.age"] intValue], 20); XCTAssertEqual([[allEmployees valueForKeyPath:@"@max.age"] intValue], 40); XCTAssertEqual([[allEmployees valueForKeyPath:@"@sum.age"] integerValue], 206); XCTAssertEqualWithAccuracy([[allEmployees valueForKeyPath:@"@avg.age"] doubleValue], 29.43, 0.1f); // collection XCTAssertEqualObjects([allCompanies valueForKeyPath:@"@unionOfObjects.name"], (@[@"InspiringNames LLC", @"ABC AG", @"ABC AG"])); XCTAssertEqualObjects([allCompanies valueForKeyPath:@"@distinctUnionOfObjects.name"], (@[@"ABC AG", @"InspiringNames LLC"])); XCTAssertEqualObjects([allCompanies valueForKeyPath:@"employees.@unionOfArrays.name"], (@[@"Joe", @"John", @"Jill", @"A", @"B", @"C", @"A"])); XCTAssertEqualObjects([[allCompanies valueForKeyPath:@"employees.@distinctUnionOfArrays.name"] sortedArrayUsingSelector:@selector(compare:)], (@[@"A", @"B", @"C", @"Jill", @"Joe", @"John"])); // invalid key paths RLMAssertThrowsWithReasonMatching([allCompanies valueForKeyPath:@"@invalid.name"], @"Unsupported KVC collection operator found in key path '@invalid.name'"); RLMAssertThrowsWithReasonMatching([allCompanies valueForKeyPath:@"@sum"], @"Missing key path for KVC collection operator sum in key path '@sum'"); RLMAssertThrowsWithReasonMatching([allCompanies valueForKeyPath:@"@sum."], @"Missing key path for KVC collection operator sum in key path '@sum.'"); RLMAssertThrowsWithReasonMatching([allCompanies valueForKeyPath:@"@sum.employees.@sum.age"], @"Nested key paths.*not supported"); } - (void)testArrayDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; for (NSInteger i = 0; i < 1012; ++i) { EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; person.age = 24; person.hired = YES; [realm addObject:person]; } [realm commitWriteTransaction]; NSString *description = [[EmployeeObject allObjects] description]; XCTAssertTrue([description rangeOfString:@"name"].location != NSNotFound); XCTAssertTrue([description rangeOfString:@"Mary"].location != NSNotFound); XCTAssertTrue([description rangeOfString:@"age"].location != NSNotFound); XCTAssertTrue([description rangeOfString:@"24"].location != NSNotFound); XCTAssertTrue([description rangeOfString:@"912 objects skipped"].location != NSNotFound); } - (void)testIndexOfObject { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; StringObject *so = [StringObject createInRealm:realm withValue:@[@""]]; StringObject *deletedObject = [StringObject createInRealm:realm withValue:@[@""]]; [realm deleteObject:deletedObject]; [realm commitWriteTransaction]; EmployeeObject *unmanaged = [[EmployeeObject alloc] init]; RLMResults *results = [EmployeeObject objectsWhere:@"hired = YES"]; XCTAssertEqual(0U, [results indexOfObject:po1]); XCTAssertEqual(1U, [results indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:po2]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:unmanaged]); RLMAssertThrowsWithReasonMatching([results indexOfObject:so], @"StringObject.*EmployeeObject"); RLMAssertThrowsWithReasonMatching([results indexOfObject:deletedObject], @"Object has been deleted or invalidated"); [results lastObject]; // Force to tableview mode XCTAssertEqual(0U, [results indexOfObject:po1]); XCTAssertEqual(1U, [results indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:po2]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:unmanaged]); RLMAssertThrowsWithReasonMatching([results indexOfObject:so], @"StringObject.*EmployeeObject"); RLMAssertThrowsWithReasonMatching([results indexOfObject:deletedObject], @"Object has been deleted or invalidated"); // reverse order from sort results = [[EmployeeObject objectsWhere:@"hired = YES"] sortedResultsUsingKeyPath:@"age" ascending:YES]; XCTAssertEqual(1U, [results indexOfObject:po1]); XCTAssertEqual(0U, [results indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:po2]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:unmanaged]); RLMAssertThrowsWithReasonMatching([results indexOfObject:so], @"StringObject.*EmployeeObject"); RLMAssertThrowsWithReasonMatching([results indexOfObject:deletedObject], @"Object has been deleted or invalidated"); results = [EmployeeObject allObjects]; XCTAssertEqual(0U, [results indexOfObject:po1]); XCTAssertEqual(1U, [results indexOfObject:po2]); XCTAssertEqual(2U, [results indexOfObject:po3]); XCTAssertEqual((NSUInteger)NSNotFound, [results indexOfObject:unmanaged]); RLMAssertThrowsWithReasonMatching([results indexOfObject:so], @"StringObject.*EmployeeObject"); RLMAssertThrowsWithReasonMatching([results indexOfObject:deletedObject], @"Object has been deleted or invalidated"); } - (void)testIndexOfObjectWhere { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @25, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Phil", @"age": @38, @"hired": @NO}]; [realm commitWriteTransaction]; RLMResults *results = [EmployeeObject objectsWhere:@"hired = YES"]; XCTAssertEqual(0U, ([results indexOfObjectWhere:@"age = %d", 40])); XCTAssertEqual(1U, ([results indexOfObjectWhere:@"age = %d", 25])); XCTAssertEqual((NSUInteger)NSNotFound, ([results indexOfObjectWhere:@"age = %d", 30])); results = [EmployeeObject allObjects]; XCTAssertEqual(0U, ([results indexOfObjectWhere:@"age = %d", 40])); XCTAssertEqual(1U, ([results indexOfObjectWhere:@"age = %d", 30])); XCTAssertEqual(2U, ([results indexOfObjectWhere:@"age = %d", 25])); XCTAssertEqual((NSUInteger)NSNotFound, ([results indexOfObjectWhere:@"age = %d", 35])); results = [[EmployeeObject allObjects] sortedResultsUsingKeyPath:@"age" ascending:YES]; NSUInteger youngestHired = [results indexOfObjectWhere:@"hired = YES"]; XCTAssertEqual(0U, youngestHired); XCTAssertEqualObjects(@"Jill", [results[youngestHired] name]); NSUInteger youngestNotHired = [results indexOfObjectWhere:@"hired = NO"]; XCTAssertEqual(1U, youngestNotHired); XCTAssertEqualObjects(@"John", [results[youngestNotHired] name]); } - (void)testSubqueryLifetime { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @50, @"hired": @YES}]; [realm commitWriteTransaction]; RLMResults *subarray = nil; { __attribute((objc_precise_lifetime)) RLMResults *results = [EmployeeObject objectsWhere:@"hired = YES"]; subarray = [results objectsWhere:@"age = 40"]; } [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; XCTAssertEqualObjects(@"Joe", subarray[0][@"name"]); } - (void)testMultiSortLifetime { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"John", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Jill", @"age": @50, @"hired": @YES}]; [realm commitWriteTransaction]; RLMResults *subarray = nil; { __attribute((objc_precise_lifetime)) RLMResults *results = [[EmployeeObject allObjects] sortedResultsUsingKeyPath:@"age" ascending:YES]; subarray = [results sortedResultsUsingKeyPath:@"age" ascending:NO]; } [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; XCTAssertEqual(3U, subarray.count); XCTAssertEqualObjects(@"Jill", subarray[0][@"name"]); XCTAssertEqualObjects(@"Joe", subarray[1][@"name"]); XCTAssertEqualObjects(@"John", subarray[2][@"name"]); } - (void)testSortingExistingQuery { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; RLMResults *sortedAge = [[EmployeeObject allObjects] sortedResultsUsingKeyPath:@"age" ascending:YES]; RLMResults *sortedName = [sortedAge sortedResultsUsingKeyPath:@"name" ascending:NO]; XCTAssertEqual(20, [(EmployeeObject *)sortedAge[0] age]); XCTAssertEqual(40, [(EmployeeObject *)sortedName[0] age]); } - (void)testRerunningSortedQuery { RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *sortedAge = [[EmployeeObject allObjects] sortedResultsUsingKeyPath:@"age" ascending:YES]; [sortedAge lastObject]; // Force creation of the TableView RLMResults *sortedName = [sortedAge sortedResultsUsingKeyPath:@"name" ascending:NO]; [sortedName lastObject]; // Force creation of the TableView RLMResults *filtered = [sortedName objectsWhere:@"age > 20"]; [filtered lastObject]; // Force creation of the TableView [realm beginWriteTransaction]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; [EmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; [realm commitWriteTransaction]; XCTAssertEqual(3U, sortedAge.count); XCTAssertEqual(3U, sortedName.count); XCTAssertEqual(2U, filtered.count); XCTAssertEqual(20, [(EmployeeObject *)sortedAge[0] age]); XCTAssertEqual(30, [(EmployeeObject *)sortedAge[1] age]); XCTAssertEqual(40, [(EmployeeObject *)sortedAge[2] age]); XCTAssertEqual(40, [(EmployeeObject *)sortedName[0] age]); XCTAssertEqual(30, [(EmployeeObject *)sortedName[1] age]); XCTAssertEqual(20, [(EmployeeObject *)sortedName[2] age]); XCTAssertEqual(40, [(EmployeeObject *)filtered[0] age]); XCTAssertEqual(30, [(EmployeeObject *)filtered[1] age]); } - (void)testLiveUpdateFirst { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; RLMResults *objects = [IntObject objectsInRealm:realm where:@"intCol = 0"]; XCTAssertNotNil([objects firstObject]); [objects.firstObject setIntCol:1]; XCTAssertNil([objects firstObject]); [realm cancelWriteTransaction]; } - (void)testLiveUpdateLast { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@0]]; RLMResults *objects = [IntObject objectsInRealm:realm where:@"intCol = 0"]; XCTAssertNotNil([objects lastObject]); [objects.lastObject setIntCol:1]; XCTAssertNil([objects lastObject]); [realm cancelWriteTransaction]; } static vm_size_t get_resident_size(void) { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size); return info.resident_size; } - (void)testQueryMemoryUsage { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; [realm addObject:obj]; [realm commitWriteTransaction]; NSPredicate *pred = [NSPredicate predicateWithFormat:@"stringCol = 'a'"]; // Check for memory leaks when creating queries by comparing the memory usage // before and after creating a very large number of queries. Anything less // than doubling is allowed as there's going to be some natural fluctuation, // and failing to clean up 10k queries resulted in far more than doubling. vm_size_t size = get_resident_size(); for (int i = 0; i < 10000; ++i) { @autoreleasepool { RLMResults *matches = [StringObject objectsInRealm:realm withPredicate:pred]; XCTAssertEqualObjects([matches[0] stringCol], @"a"); } } XCTAssertLessThan(get_resident_size(), size * 2); } - (void)testDeleteAllObjects { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@"name1"]]; [StringObject createInRealm:realm withValue:@[@"name2"]]; [realm commitWriteTransaction]; RLMResults *results = [StringObject objectsInRealm:realm where:@"stringCol = 'name1'"]; RLMAssertThrowsWithReasonMatching([realm deleteObjects:results], @"write transaction"); [realm beginWriteTransaction]; [realm deleteObjects:results]; [realm commitWriteTransaction]; XCTAssertEqual(0U, results.count); XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count); results = [StringObject allObjectsInRealm:realm]; RLMAssertThrowsWithReasonMatching([realm deleteObjects:results], @"write transaction"); [realm beginWriteTransaction]; [realm deleteObjects:results]; [realm commitWriteTransaction]; XCTAssertEqual(0U, results.count); XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); } - (void)testEnumerateAndDeleteTableResults { RLMRealm *realm = self.realmWithTestPath; const int count = 40; [realm beginWriteTransaction]; for (int i = 0; i < count; ++i) { [IntObject createInRealm:realm withValue:@[@(i)]]; } int enumeratedCount = 0; for (IntObject *io in [IntObject allObjectsInRealm:realm]) { ++enumeratedCount; [realm deleteObject:io]; } XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count); XCTAssertEqual(count, enumeratedCount); [realm cancelWriteTransaction]; } - (void)testEnumerateAndMutateQueryCondition { RLMRealm *realm = self.realmWithTestPath; const int count = 40; [realm beginWriteTransaction]; for (int i = 0; i < count; ++i) { [IntObject createInRealm:realm withValue:@[@(0)]]; } int enumeratedCount = 0; for (IntObject *io in [IntObject objectsInRealm:realm where:@"intCol = 0"]) { ++enumeratedCount; io.intCol = enumeratedCount; } XCTAssertEqual(0U, [IntObject objectsInRealm:realm where:@"intCol = 0"].count); XCTAssertEqual(count, enumeratedCount); [realm cancelWriteTransaction]; } - (void)testManualRefreshDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; const int count = 40; [realm beginWriteTransaction]; for (int i = 0; i < count; ++i) { [IntObject createInRealm:realm withValue:@[@(0)]]; } [realm commitWriteTransaction]; realm.autorefresh = NO; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [realm deleteObjects:[IntObject allObjectsInRealm:realm]]; [realm commitWriteTransaction]; }]; int enumeratedCount = 0; for (IntObject *io in [IntObject allObjectsInRealm:realm]) { [realm refresh]; XCTAssertTrue(io.invalidated); ++enumeratedCount; } XCTAssertEqual(enumeratedCount, count); } - (void)testCallInvalidateDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; const int count = 40; [realm beginWriteTransaction]; for (int i = 0; i < count; ++i) { [IntObject createInRealm:realm withValue:@[@(0)]]; } [realm commitWriteTransaction]; int enumeratedCount = 0; @try { for (__unused IntObject *io in [IntObject objectsInRealm:realm where:@"intCol = 0"]) { ++enumeratedCount; [realm invalidate]; } XCTFail(@"Should have thrown an exception"); } @catch (NSException *e) { XCTAssertEqualObjects(e.reason, @"Collection is no longer valid"); } XCTAssertEqual(16, enumeratedCount); // Also test with the enumeration done within a write transaction [realm beginWriteTransaction]; enumeratedCount = 0; @try { for (__unused IntObject *io in [IntObject objectsInRealm:realm where:@"intCol = 0"]) { ++enumeratedCount; [realm invalidate]; } XCTFail(@"Should have thrown an exception"); } @catch (NSException *e) { XCTAssertEqualObjects(e.reason, @"Collection is no longer valid"); } XCTAssertEqual(16, enumeratedCount); } - (void)testBeginWriteTransactionDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; const int count = 40; [realm beginWriteTransaction]; for (int i = 0; i < count; ++i) { [IntObject createInRealm:realm withValue:@[@(0)]]; } [realm commitWriteTransaction]; for (IntObject *io in [IntObject objectsInRealm:realm where:@"intCol = 0"]) { [realm beginWriteTransaction]; io.intCol = 1; [realm commitWriteTransaction]; } XCTAssertEqual(40U, [IntObject objectsInRealm:realm where:@"intCol = 1"].count); } - (void)testAllMethodsCheckThread { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; }]; RLMResults *results = [IntObject allObjects]; XCTAssertNoThrow([results isInvalidated]); XCTAssertNoThrow([results objectAtIndex:0]); XCTAssertNoThrow([results firstObject]); XCTAssertNoThrow([results lastObject]); XCTAssertNoThrow([results indexOfObject:[IntObject allObjects].firstObject]); XCTAssertNoThrow([results indexOfObjectWhere:@"intCol = 0"]); XCTAssertNoThrow([results indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([results objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([results objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([results sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([results sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow([results minOfProperty:@"intCol"]); XCTAssertNoThrow([results maxOfProperty:@"intCol"]); XCTAssertNoThrow([results sumOfProperty:@"intCol"]); XCTAssertNoThrow([results averageOfProperty:@"intCol"]); XCTAssertNoThrow(results[0]); XCTAssertNoThrow([results valueForKey:@"intCol"]); [self dispatchAsyncAndWait:^{ XCTAssertThrows([results isInvalidated]); XCTAssertThrows([results objectAtIndex:0]); XCTAssertThrows([results firstObject]); XCTAssertThrows([results lastObject]); XCTAssertThrows([results indexOfObject:[IntObject allObjects].firstObject]); XCTAssertThrows([results indexOfObjectWhere:@"intCol = 0"]); XCTAssertThrows([results indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertThrows([results objectsWhere:@"intCol = 0"]); XCTAssertThrows([results objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertThrows([results sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertThrows([results sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertThrows([results minOfProperty:@"intCol"]); XCTAssertThrows([results maxOfProperty:@"intCol"]); XCTAssertThrows([results sumOfProperty:@"intCol"]); XCTAssertThrows([results averageOfProperty:@"intCol"]); XCTAssertThrows(results[0]); XCTAssertThrows([results valueForKey:@"intCol"]); }]; } - (void)testAllMethodsCheckForInvalidation { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; }]; RLMResults *results = [IntObject allObjects]; XCTAssertFalse(results.isInvalidated); XCTAssertNoThrow([results objectAtIndex:0]); XCTAssertNoThrow([results firstObject]); XCTAssertNoThrow([results lastObject]); XCTAssertNoThrow([results indexOfObject:[IntObject allObjects].firstObject]); XCTAssertNoThrow([results indexOfObjectWhere:@"intCol = 0"]); XCTAssertNoThrow([results indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([results objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([results objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([results sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([results sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow([results minOfProperty:@"intCol"]); XCTAssertNoThrow([results maxOfProperty:@"intCol"]); XCTAssertNoThrow([results sumOfProperty:@"intCol"]); XCTAssertNoThrow([results averageOfProperty:@"intCol"]); XCTAssertNoThrow(results[0]); XCTAssertNoThrow([results valueForKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in results);})); [realm invalidate]; XCTAssertTrue(results.isInvalidated); XCTAssertThrows([results objectAtIndex:0]); XCTAssertThrows([results firstObject]); XCTAssertThrows([results lastObject]); XCTAssertThrows([results indexOfObject:[IntObject allObjects].firstObject]); XCTAssertThrows([results indexOfObjectWhere:@"intCol = 0"]); XCTAssertThrows([results indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertThrows([results objectsWhere:@"intCol = 0"]); XCTAssertThrows([results objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertThrows([results sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertThrows([results sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertThrows([results minOfProperty:@"intCol"]); XCTAssertThrows([results maxOfProperty:@"intCol"]); XCTAssertThrows([results sumOfProperty:@"intCol"]); XCTAssertThrows([results averageOfProperty:@"intCol"]); XCTAssertThrows(results[0]); XCTAssertThrows([results valueForKey:@"intCol"]); XCTAssertThrows(({for (__unused id obj in results);})); } - (void)testResultsDependingOnDeletedLinkView { RLMRealm *realm = [RLMRealm defaultRealm]; __block IntegerArrayPropertyObject *arrayObject; __block IntegerSetPropertyObject *setObject; [realm transactionWithBlock:^{ IntObject* intObject = [IntObject createInDefaultRealmWithValue:@[@0]]; arrayObject = [IntegerArrayPropertyObject createInDefaultRealmWithValue:@[ @0, @[ intObject ] ]]; setObject = [IntegerSetPropertyObject createInDefaultRealmWithValue:@[ @0, @[ intObject ] ]]; }]; RLMResults *arrayResults = [arrayObject.array sortedResultsUsingKeyPath:@"intCol" ascending:YES]; [arrayResults firstObject]; RLMResults *unevaluatedResults = [arrayObject.array sortedResultsUsingKeyPath:@"intCol" ascending:YES]; [realm transactionWithBlock:^{ [realm deleteObject:arrayObject]; }]; XCTAssertFalse(arrayResults.isInvalidated); XCTAssertFalse(unevaluatedResults.isInvalidated); XCTAssertEqual(0u, arrayResults.count); XCTAssertEqual(0u, unevaluatedResults.count); XCTAssertEqualObjects(nil, arrayResults.firstObject); XCTAssertEqualObjects(nil, unevaluatedResults.firstObject); RLMResults *setResults = [setObject.set sortedResultsUsingKeyPath:@"intCol" ascending:YES]; [setResults firstObject]; RLMResults *unevaluatedResults2 = [setObject.set sortedResultsUsingKeyPath:@"intCol" ascending:YES]; [realm transactionWithBlock:^{ [realm deleteObject:setObject]; }]; XCTAssertFalse(setResults.isInvalidated); XCTAssertFalse(unevaluatedResults2.isInvalidated); XCTAssertEqual(0u, setResults.count); XCTAssertEqual(0u, unevaluatedResults2.count); XCTAssertEqualObjects(nil, setResults.firstObject); XCTAssertEqualObjects(nil, unevaluatedResults2.firstObject); } - (void)testResultsDependingOnDeletedTableView { RLMRealm *realm = [RLMRealm defaultRealm]; __block DogObject *dog; [realm transactionWithBlock:^{ dog = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; [OwnerObject createInDefaultRealmWithValue:@[ @"John", dog ]]; }]; RLMResults *results = [dog.owners objectsWhere:@"name != 'Not a real name'"]; [results firstObject]; RLMResults *unevaluatedResults = [dog.owners objectsWhere:@"name != 'Not a real name'"]; [realm transactionWithBlock:^{ [realm deleteObject:dog]; }]; XCTAssertFalse(results.isInvalidated); XCTAssertFalse(unevaluatedResults.isInvalidated); XCTAssertEqual(0u, results.count); XCTAssertEqual(0u, unevaluatedResults.count); XCTAssertEqualObjects(nil, results.firstObject); XCTAssertEqualObjects(nil, unevaluatedResults.firstObject); } - (void)testResultsDependingOnLinkingObjects { RLMRealm *realm = [RLMRealm defaultRealm]; __block DogObject *dog; __block OwnerObject *owner; [realm transactionWithBlock:^{ dog = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; owner = [OwnerObject createInDefaultRealmWithValue:@[ @"John", dog ]]; }]; XCTestExpectation *expectation = [self expectationWithDescription:@""]; RLMResults *results = [DogObject objectsWhere:@"ANY owners.name == 'James'"]; RLMNotificationToken *token = [results addNotificationBlock:^(__unused RLMResults *results, RLMCollectionChange *change, __unused NSError *error) { if (change != nil) { [expectation fulfill]; } CFRunLoopStop(CFRunLoopGetCurrent()); }]; // Consume the initial notification. CFRunLoopRun(); XCTAssertEqual(0u, results.count); XCTAssertNil(results.firstObject); [realm transactionWithBlock:^{ owner.name = @"James"; }]; XCTAssertEqual(1u, results.count); XCTAssertEqualObjects(dog.dogName, [results.firstObject dogName]); [self waitForExpectationsWithTimeout:1.0 handler:nil]; [token invalidate]; } - (void)testDistinctQuery { RLMRealm *realm = [RLMRealm defaultRealm]; __block DogObject *fido; __block DogObject *cujo; __block DogObject *buster; [realm transactionWithBlock:^{ fido = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; cujo = [DogObject createInDefaultRealmWithValue:@[ @"Cujo", @3 ]]; buster = [DogObject createInDefaultRealmWithValue:@[ @"Buster", @5 ]]; }]; RLMResults *results = [[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"age"]]; NSMutableArray *ages = NSMutableArray.new; for (id result in results) { [ages addObject: result]; } XCTAssertEqual(2u, results.count); XCTAssertEqualObjects([ages valueForKey:@"age"], (@[@3, @5])); } - (void)testDistinctQueryWithMultipleKeyPaths { RLMRealm *realm = [RLMRealm defaultRealm]; __block DogObject *fido; __block DogObject *fido2; __block DogObject *fido3; __block DogObject *cujo; __block DogObject *buster; __block DogObject *buster2; __block DogObject *rotunda; [realm transactionWithBlock:^{ fido = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; fido2 = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; fido3 = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @4 ]]; cujo = [DogObject createInDefaultRealmWithValue:@[ @"Cujo", @3 ]]; buster = [DogObject createInDefaultRealmWithValue:@[ @"Buster", @3 ]]; buster2 = [DogObject createInDefaultRealmWithValue:@[ @"Buster", @3 ]]; rotunda = [DogObject createInDefaultRealmWithValue:@[ @"Rotunda", @7 ]]; }]; RLMResults *results = [[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"dogName", @"age"]]; NSMutableArray *resultsArr = NSMutableArray.new; for (DogObject *result in results) { [resultsArr addObject:[NSString stringWithFormat:@"%@/%@", result.dogName, @(result.age)]]; } XCTAssertEqualObjects(resultsArr, (@[@"Fido/3", @"Fido/4", @"Cujo/3", @"Buster/3", @"Rotunda/7"])); } - (void)testDistinctQueryWithMultilevelKeyPath { RLMRealm *realm = [RLMRealm defaultRealm]; __block OwnerObject *owner1; __block OwnerObject *owner2; __block OwnerObject *owner3; __block DogObject *dog1; __block DogObject *dog2; __block DogObject *dog3; [realm transactionWithBlock:^{ dog1 = [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; dog2 = [DogObject createInDefaultRealmWithValue:@[ @"Cujo", @3 ]]; dog3 = [DogObject createInDefaultRealmWithValue:@[ @"Rotunda", @7 ]]; owner1 = [OwnerObject createInDefaultRealmWithValue:@[ @"Joe", dog1 ]]; owner2 = [OwnerObject createInDefaultRealmWithValue:@[ @"Marie", dog2 ]]; owner3 = [OwnerObject createInDefaultRealmWithValue:@[ @"Marie", dog3 ]]; }]; RLMResults *results = [[OwnerObject allObjects] distinctResultsUsingKeyPaths:@[@"dog.age"]]; NSMutableArray *resultsArr = NSMutableArray.new; for (OwnerObject *result in results) { [resultsArr addObject:@(result.dog.age)]; } XCTAssertEqualObjects(resultsArr, (@[@3, @7])); } - (void)testDistinctQueryThrowsInvalidKeyPathsSpecified { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; [DogObject createInDefaultRealmWithValue:@[ @"Fido", @3 ]]; [DogObject createInDefaultRealmWithValue:@[ @"Fido", @5 ]]; AggregateObject *ao1 = [AggregateObject createInDefaultRealmWithValue:@[ @0 ]]; AggregateObject *ao2 = [AggregateObject createInDefaultRealmWithValue:@[ @0 ]]; [AggregateArrayObject createInDefaultRealmWithValue:@[@[ao1, ao2]]]; [AggregateSetObject createInDefaultRealmWithValue:@[@[ao1, ao2]]]; [AllTypesObject createInDefaultRealmWithValue:@[]]; }]; XCTAssertThrows([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@""]]); XCTAssertThrows([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@" "]]); XCTAssertThrows([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"\n"]]); XCTAssertThrows(([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"dogName", @""]])); XCTAssertThrows(([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"dogName", @" "]])); XCTAssertThrows(([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"dogName", @"\n"]])); XCTAssertThrows(([[DogObject allObjects] distinctResultsUsingKeyPaths:@[@"@max.age"]])); XCTAssertThrows([[AllTypesObject allObjects] distinctResultsUsingKeyPaths:@[@"linkingObjectsCol"]]); XCTAssertThrows([[AllTypesObject allObjects] distinctResultsUsingKeyPaths:@[@"objectCol"]]); XCTAssertThrows([[AggregateArrayObject allObjects] distinctResultsUsingKeyPaths:@[@"array"]]); XCTAssertThrows([[AggregateSetObject allObjects] distinctResultsUsingKeyPaths:@[@"set"]]); } #pragma mark - Frozen Results static RLMResults *testResults(void) { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@0]]; [IntObject createInDefaultRealmWithValue:@[@1]]; [IntObject createInDefaultRealmWithValue:@[@2]]; }]; return [IntObject allObjects]; } - (void)testIsFrozen { RLMResults *unfrozen = testResults(); RLMResults *frozen = [unfrozen freeze]; XCTAssertFalse(unfrozen.isFrozen); XCTAssertTrue(frozen.isFrozen); } - (void)testFreezingFrozenObjectReturnsSelf { RLMResults *results = testResults(); RLMResults *frozen = [results freeze]; XCTAssertNotEqual(results, frozen); XCTAssertNotEqual(results.freeze, frozen); XCTAssertEqual(frozen, frozen.freeze); } - (void)testFreezeFromWrongThread { RLMResults *results = testResults(); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([results freeze], @"Realm accessed from incorrect thread"); }]; } - (void)testAccessFrozenResultsFromDifferentThread { RLMResults *frozen = [testResults() freeze]; [self dispatchAsyncAndWait:^{ XCTAssertEqualObjects([frozen valueForKey:@"intCol"], (@[@0, @1, @2])); }]; } - (void)testObserveFrozenResults { RLMResults *frozen = [testResults() freeze]; id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {}; RLMAssertThrowsWithReason([frozen addNotificationBlock:block], @"Frozen Realms do not change and do not have change notifications."); } - (void)testQueryFrozenResults { RLMResults *frozen = [testResults() freeze]; XCTAssertEqualObjects([[frozen objectsWhere:@"intCol > 0"] valueForKey:@"intCol"], (@[@1, @2])); } - (void)testFrozenResultsDoNotUpdate { RLMResults *frozen = [testResults() freeze]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [IntObject createInDefaultRealmWithValue:@[@3]]; }]; XCTAssertEqual(frozen.count, 3); XCTAssertEqual([frozen objectsWhere:@"intCol = 3"].count, 0); } - (void)testMultithreadedFrozenResultsEnumeration { RLMResults *frozen = [testResults() freeze]; dispatch_apply(100, DISPATCH_APPLY_AUTO, ^(__unused size_t i) { for (__unused id obj in frozen); }); } @end ================================================ FILE: Realm/Tests/SchemaTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "RLMMultiProcessTestCase.h" #import "TestUtils.h" #import "RLMAccessor.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.h" #import "RLMProperty_Private.h" #import "RLMRealmConfiguration_Private.hpp" #import "RLMRealm_Dynamic.h" #import "RLMRealm_Private.hpp" #import "RLMSchema_Private.hpp" #import "RLMUtil.hpp" #import #import #import #import @interface SchemaTestClassBase : RLMObject @property IntObject *baseCol; @end @implementation SchemaTestClassBase @end @interface SchemaTestClassFirstChild : SchemaTestClassBase @property IntObject *firstChildCol; @end @implementation SchemaTestClassFirstChild @end @interface SchemaTestClassSecondChild : SchemaTestClassBase @property IntObject *secondChildCol; @end @implementation SchemaTestClassSecondChild @end RLM_COLLECTION_TYPE(SchemaTestClassBase) RLM_COLLECTION_TYPE(SchemaTestClassFirstChild) RLM_COLLECTION_TYPE(SchemaTestClassSecondChild) @interface SchemaTestClassLink : RLMObject @property SchemaTestClassBase *base; @property SchemaTestClassFirstChild *child; @property SchemaTestClassSecondChild *secondChild; @property RLM_GENERIC_ARRAY(SchemaTestClassBase) *baseArray; @property RLM_GENERIC_ARRAY(SchemaTestClassFirstChild) *childArray; @property RLM_GENERIC_ARRAY(SchemaTestClassSecondChild) *secondChildArray; @property RLM_GENERIC_SET(SchemaTestClassBase) *baseSet; @property RLM_GENERIC_SET(SchemaTestClassFirstChild) *childSet; @property RLM_GENERIC_SET(SchemaTestClassSecondChild) *secondChildSet; @end @implementation SchemaTestClassLink @end @interface NonDefaultObject : RLMObject @property int intCol; @end @implementation NonDefaultObject + (BOOL)shouldIncludeInDefaultSchema { return NO; } @end RLM_COLLECTION_TYPE(NonDefaultObject); @interface NonDefaultArrayObject : RLMObject @property RLM_GENERIC_ARRAY(NonDefaultObject) *array; @end @implementation NonDefaultArrayObject + (BOOL)shouldIncludeInDefaultSchema { return NO; } @end @class MutualLink2Object; @interface MutualLink1Object : RLMObject @property (nullable) MutualLink2Object *object; @end @implementation MutualLink1Object + (BOOL)shouldIncludeInDefaultSchema { return NO; } @end @interface MutualLink2Object : RLMObject @property (nullable) MutualLink1Object *object; @end @implementation MutualLink2Object + (BOOL)shouldIncludeInDefaultSchema { return NO; } @end @interface SchemaTestClassWithSingleDuplicatePropertyBase : FakeObject @property NSString *string; @end @implementation SchemaTestClassWithSingleDuplicatePropertyBase @end @interface SchemaTestClassWithSingleDuplicateProperty : SchemaTestClassWithSingleDuplicatePropertyBase @property NSString *string; @end @implementation SchemaTestClassWithSingleDuplicateProperty @synthesize string; @end @interface SchemaTestClassWithMultipleDuplicatePropertiesBase : FakeObject @property NSString *string; @property int integer; @end @implementation SchemaTestClassWithMultipleDuplicatePropertiesBase @end @interface SchemaTestClassWithMultipleDuplicateProperties : SchemaTestClassWithMultipleDuplicatePropertiesBase @property NSString *string; @property int integer; @end @implementation SchemaTestClassWithMultipleDuplicateProperties @synthesize string; @synthesize integer; @end @interface UnindexableProperty : FakeObject @property double unindexable; @end @implementation UnindexableProperty + (NSArray *)indexedProperties { return @[@"unindexable"]; } @end @interface InvalidPrimaryKeyType : FakeObject @property double primaryKey; @end @implementation InvalidPrimaryKeyType + (NSString *)primaryKey { return @"primaryKey"; } @end @interface MissingPrimaryKey : FakeObject @property int pk; @end @implementation MissingPrimaryKey + (NSString *)primaryKey { return @"primaryKey"; } @end @interface RequiredLinkProperty : FakeObject @property BoolObject *object; @end @implementation RequiredLinkProperty + (NSArray *)requiredProperties { return @[@"object"]; } @end @interface InvalidNSNumberProtocolObject : FakeObject @property NSNumber *number; @end @implementation InvalidNSNumberProtocolObject @end @interface InvalidNSNumberNoProtocolObject : FakeObject @property NSNumber *number; @end @implementation InvalidNSNumberNoProtocolObject @end @class InvalidReadWriteLinkingObjectsProperty; @class InvalidLinkingObjectsPropertiesMethod; @class ValidLinkingObjectsPropertyWithProtocol; @class InvalidLinkingObjectsPropertyProtocol; @interface SchemaTestsLinkSource : FakeObject @property InvalidReadWriteLinkingObjectsProperty *irwlop; @property InvalidLinkingObjectsPropertiesMethod *iloprm; @property ValidLinkingObjectsPropertyWithProtocol *vlopwp; @property InvalidLinkingObjectsPropertyProtocol *ilopp; @end @implementation SchemaTestsLinkSource @end @interface MixedProperty : FakeObject @property id mixed; @end @implementation MixedProperty @end @interface LinkFromEmbeddedToTopLevel : FakeEmbeddedObject @property IntObject *link; @end @implementation LinkFromEmbeddedToTopLevel @end @interface ArrayFromEmbeddedToTopLevel : FakeEmbeddedObject @property RLMArray *array; @end @implementation ArrayFromEmbeddedToTopLevel @end @interface EmbeddedObjectWithPrimaryKey : FakeEmbeddedObject @property int pk; @end @implementation EmbeddedObjectWithPrimaryKey + (NSString *)primaryKey { return @"pk"; } @end RLM_COLLECTION_TYPE(SchemaTestsLinkSource) @interface InvalidReadWriteLinkingObjectsProperty : FakeObject @property RLMLinkingObjects *linkingObjects; @end @implementation InvalidReadWriteLinkingObjectsProperty + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:SchemaTestsLinkSource.class propertyName:@"irwlop"] }; } @end @interface InvalidLinkingObjectsPropertiesMethod : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @end @implementation InvalidLinkingObjectsPropertiesMethod @end @interface ValidLinkingObjectsPropertyWithProtocol : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @end @implementation ValidLinkingObjectsPropertyWithProtocol + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:SchemaTestsLinkSource.class propertyName:@"vlopwp"] }; } @end RLM_COLLECTION_TYPE(NotARealClass) @interface InvalidLinkingObjectsPropertyProtocol : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @end @implementation InvalidLinkingObjectsPropertyProtocol + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:SchemaTestsLinkSource.class propertyName:@"ilopp"] }; } @end @interface InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @property InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink *link; @end @implementation InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink.class propertyName:@"nosuchproperty"] }; } @end @interface InvalidLinkingObjectsPropertySourcePropertyNotALink : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @property int64_t integer; @end @implementation InvalidLinkingObjectsPropertySourcePropertyNotALink + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:InvalidLinkingObjectsPropertySourcePropertyNotALink.class propertyName:@"integer"] }; } @end @interface InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere : FakeObject @property (readonly) RLMLinkingObjects *linkingObjects; @property IntObject *link; @end @implementation InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere + (NSDictionary *)linkingObjectsProperties { return @{ @"linkingObjects": [RLMPropertyDescriptor descriptorWithClass:InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere.class propertyName:@"link"] }; } @end @interface OrphanObject : RLMEmbeddedObject @property int value; @end @implementation OrphanObject @end @interface SchemaTests : RLMMultiProcessTestCase @end @implementation SchemaTests - (void)testNoSchemaForUnmanagedObjectClasses { RLMSchema *schema = [RLMSchema sharedSchema]; XCTAssertNil([schema schemaForClassName:@"RLMObject"]); XCTAssertNil([schema schemaForClassName:@"RLMObjectBase"]); XCTAssertNil([schema schemaForClassName:@"RLMDynamicObject"]); } - (void)testSchemaWithObjectClasses { RLMSchema *schema = [RLMSchema schemaWithObjectClasses:@[RLMDynamicObject.class, StringObject.class]]; XCTAssertEqualObjects((@[@"RLMDynamicObject", @"StringObject"]), [[schema.objectSchema valueForKey:@"className"] sortedArrayUsingSelector:@selector(compare:)]); XCTAssertNil([RLMSchema.sharedSchema schemaForClassName:@"RLMDynamicObject"]); } - (void)testInheritanceInitialization { Class testClasses[] = { [SchemaTestClassBase class], [SchemaTestClassFirstChild class], [SchemaTestClassSecondChild class], [SchemaTestClassLink class], [IntObject class] }; auto pred = ^(Class lft, Class rgt) { return (uintptr_t)lft < (uintptr_t)rgt; }; auto checkSchema = ^(RLMSchema *schema, NSString *className, NSDictionary *properties) { RLMObjectSchema *objectSchema = schema[className]; XCTAssertEqualObjects(className, objectSchema.className); XCTAssertEqualObjects(className, [objectSchema.unmanagedClass className]); XCTAssertEqualObjects(className, [objectSchema.accessorClass className]); XCTAssertEqual(objectSchema.properties.count, properties.count); for (NSString *propName in properties) { XCTAssertEqualObjects(properties[propName], [objectSchema[propName] objectClassName]); } }; // Test each permutation of loading orders and verify that all properties // are initialized correctly std::sort(testClasses, std::end(testClasses), pred); unsigned long iteration = 0; do @autoreleasepool { // Clean up any existing overridden things for (Class cls : testClasses) { // Ensure that the className method isn't used during schema init // as it may not be overriden yet Class metaClass = object_getClass(cls); IMP imp = imp_implementationWithBlock(^{ return nil; }); class_replaceMethod(metaClass, @selector(className), imp, "@:"); } NSMutableArray *objectSchemas = [NSMutableArray arrayWithCapacity:4U]; for (Class cls : testClasses) { [objectSchemas addObject:[RLMObjectSchema schemaForObjectClass:cls]]; } RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = objectSchemas; for (RLMObjectSchema *objectSchema in objectSchemas) { NSString *name = [NSString stringWithFormat:@"RLMTestAccessor_%lu_%@", iteration, objectSchema.className]; objectSchema.accessorClass = RLMManagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, name.UTF8String); objectSchema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema); } ++iteration; for (Class cls : testClasses) { NSString *className = NSStringFromClass(cls); // Restore the className method Class metaClass = object_getClass(cls); IMP imp = imp_implementationWithBlock(^{ return className; }); class_replaceMethod(metaClass, @selector(className), imp, "@:"); } // Verify that each class has the correct properties and className // for generated subclasses checkSchema(schema, @"SchemaTestClassBase", @{@"baseCol": @"IntObject"}); checkSchema(schema, @"SchemaTestClassFirstChild", @{@"baseCol": @"IntObject", @"firstChildCol": @"IntObject"}); checkSchema(schema, @"SchemaTestClassSecondChild", @{@"baseCol": @"IntObject", @"secondChildCol": @"IntObject"}); checkSchema(schema, @"SchemaTestClassLink", @{@"base": @"SchemaTestClassBase", @"baseArray": @"SchemaTestClassBase", @"baseSet": @"SchemaTestClassBase", @"child": @"SchemaTestClassFirstChild", @"childArray": @"SchemaTestClassFirstChild", @"childSet": @"SchemaTestClassFirstChild", @"secondChild": @"SchemaTestClassSecondChild", @"secondChildArray": @"SchemaTestClassSecondChild", @"secondChildSet": @"SchemaTestClassSecondChild"}); // Test creating objects of each class [self deleteFiles]; RLMRealm *realm = [self realmWithTestPathAndSchema:schema]; [realm beginWriteTransaction]; [realm createObject:@"SchemaTestClassBase" withValue:@{@"baseCol": @[@0]}]; [realm createObject:@"SchemaTestClassFirstChild" withValue:@{@"baseCol": @[@0], @"firstChildCol": @[@0]}]; [realm createObject:@"SchemaTestClassSecondChild" withValue:@{@"baseCol": @[@0], @"secondChildCol": @[@0]}]; [realm commitWriteTransaction]; } while (std::next_permutation(testClasses, std::end(testClasses), pred)); } - (void)testObjectSchema { // Setting up some test data // Due to the automatic initialization of a new realm with all visible classes inheriting from // RLMObject, it's difficult to define test cases that verify the absolute correctness of a // realm's current type catalogue unless its expected configuration is known at compile time // (requires that the set of expected types is always up-to-date). Instead, only a partial // verification is performed, which only requires the availability of a well-defined subset of // types and ignores any other types that may be included in the realm's type catalogue. // If a more fine-grained control with the realm's type inclusion mechanism is introduced later // on, these tests should be altered to verify all types. NSArray *expectedTypes = @[@"AllTypesObject", @"LinkToAllTypesObject", @"StringObject", @"MixedObject", @"IntObject"]; NSString *unexpectedType = @"__$ThisTypeShouldNotOccur$__"; // Getting the test realm NSMutableArray *objectSchema = [NSMutableArray array]; for (NSString *className in expectedTypes) { [objectSchema addObject:[RLMObjectSchema schemaForObjectClass:NSClassFromString(className)]]; } RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = objectSchema; // create realm with schema @autoreleasepool { [self realmWithTestPathAndSchema:schema]; } // get dynamic realm and extract schema RLMRealm *realm = [self realmWithTestPathAndSchema:nil]; schema = realm.schema; // Test 1: Does the objectSchema return the right number of object schemas? NSArray *objectSchemas = schema.objectSchema; XCTAssertTrue(objectSchemas.count >= expectedTypes.count, @"Expecting %lu object schemas in database found %lu", (unsigned long)expectedTypes.count, (unsigned long)objectSchemas.count); // Test 2: Does the object schema array contain the expected schemas? NSUInteger identifiedTypesCount = 0; for (NSString *expectedType in expectedTypes) { NSUInteger occurrenceCount = 0; for (RLMObjectSchema *objectSchema in objectSchemas) { if ([objectSchema.className isEqualToString:expectedType]) { occurrenceCount++; } } XCTAssertEqual(occurrenceCount, (NSUInteger)1, @"Expecting single occurrence of object schema for type %@ found %lu", expectedType, (unsigned long)occurrenceCount); if (occurrenceCount > 0) { identifiedTypesCount++; } } // Test 3: Does the object schema array contains at least the expected classes XCTAssertTrue(identifiedTypesCount >= expectedTypes.count, @"Unexpected object schemas in database. Found %lu out of %lu expected", (unsigned long)identifiedTypesCount, (unsigned long)expectedTypes.count); // Test 4: Test querying object schemas using schemaForClassName: for expected types for (NSString *expectedType in expectedTypes) { XCTAssertNotNil([schema schemaForClassName:expectedType], @"Expecting to find object schema for type %@ in realm using query, found none", expectedType); } // Test 5: Test querying object schemas using schemaForClassName: for unexpected types XCTAssertNil([schema schemaForClassName:unexpectedType], @"Expecting not to find object schema for type %@ in realm using query, did find", unexpectedType); // Test 6: Test querying object schemas using subscription for unexpected types for (NSString *expectedType in expectedTypes) { XCTAssertNotNil(schema[expectedType], @"Expecting to find object schema for type %@ in realm using subscription, found none", expectedType); } // Test 7: Test querying object schemas using subscription for unexpected types XCTAssertThrows(schema[unexpectedType], @"Expecting asking schema for type %@ in realm using subscription to throw", unexpectedType); // Test 8: RLMObject should not appear in the shared object schema XCTAssertThrows(RLMSchema.sharedSchema[@"RLMObject"]); } - (void)testDescription { NSArray *expectedTypes = @[@"AllTypesObject", @"StringObject", @"IntObject"]; NSMutableArray *objectSchema = [NSMutableArray array]; for (NSString *className in expectedTypes) { [objectSchema addObject:[RLMObjectSchema schemaForObjectClass:NSClassFromString(className)]]; } RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = objectSchema; XCTAssertEqualObjects(schema.description, @"Schema {\n" @"\tAllTypesObject {\n" @"\t\tboolCol {\n" @"\t\t\ttype = bool;\n" @"\t\t\tcolumnName = boolCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tintCol {\n" @"\t\t\ttype = int;\n" @"\t\t\tcolumnName = intCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tfloatCol {\n" @"\t\t\ttype = float;\n" @"\t\t\tcolumnName = floatCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tdoubleCol {\n" @"\t\t\ttype = double;\n" @"\t\t\tcolumnName = doubleCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tstringCol {\n" @"\t\t\ttype = string;\n" @"\t\t\tcolumnName = stringCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tbinaryCol {\n" @"\t\t\ttype = data;\n" @"\t\t\tcolumnName = binaryCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tdateCol {\n" @"\t\t\ttype = date;\n" @"\t\t\tcolumnName = dateCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tcBoolCol {\n" @"\t\t\ttype = bool;\n" @"\t\t\tcolumnName = cBoolCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tlongCol {\n" @"\t\t\ttype = int;\n" @"\t\t\tcolumnName = longCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tdecimalCol {\n" @"\t\t\ttype = decimal128;\n" @"\t\t\tcolumnName = decimalCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tobjectIdCol {\n" @"\t\t\ttype = object id;\n" @"\t\t\tcolumnName = objectIdCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tuuidCol {\n" @"\t\t\ttype = uuid;\n" @"\t\t\tcolumnName = uuidCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tobjectCol {\n" @"\t\t\ttype = object;\n" @"\t\t\tobjectClassName = StringObject;\n" @"\t\t\tlinkOriginPropertyName = (null);\n" @"\t\t\tcolumnName = objectCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = YES;\n" @"\t\t}\n" @"\t\tmixedObjectCol {\n" @"\t\t\ttype = object;\n" @"\t\t\tobjectClassName = MixedObject;\n" @"\t\t\tlinkOriginPropertyName = (null);\n" @"\t\t\tcolumnName = mixedObjectCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = YES;\n" @"\t\t}\n" @"\t\tanyCol {\n" @"\t\t\ttype = mixed;\n" @"\t\t\tcolumnName = anyCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t\tlinkingObjectsCol {\n" @"\t\t\ttype = linking objects;\n" @"\t\t\tobjectClassName = LinkToAllTypesObject;\n" @"\t\t\tlinkOriginPropertyName = allTypesCol;\n" @"\t\t\tcolumnName = linkingObjectsCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = YES;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t}\n" @"\tIntObject {\n" @"\t\tintCol {\n" @"\t\t\ttype = int;\n" @"\t\t\tcolumnName = intCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = NO;\n" @"\t\t}\n" @"\t}\n" @"\tStringObject {\n" @"\t\tstringCol {\n" @"\t\t\ttype = string;\n" @"\t\t\tcolumnName = stringCol;\n" @"\t\t\tindexed = NO;\n" @"\t\t\tisPrimary = NO;\n" @"\t\t\tarray = NO;\n" @"\t\t\tset = NO;\n" @"\t\t\tdictionary = NO;\n" @"\t\t\toptional = YES;\n" @"\t\t}\n" @"\t}\n" @"}"); } - (void)testClassWithDuplicateProperties { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:SchemaTestClassWithSingleDuplicateProperty.class], @"'string' .* multiple times .* 'SchemaTestClassWithSingleDuplicateProperty'"); RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:SchemaTestClassWithMultipleDuplicateProperties.class], @"'SchemaTestClassWithMultipleDuplicateProperties' .* declared multiple times"); } - (void)testClassWithInvalidPrimaryKey { XCTAssertThrows([RLMObjectSchema schemaForObjectClass:InvalidPrimaryKeyType.class]); } - (void)testClassWithMissingPrimaryKey { RLMAssertThrowsWithReason([RLMObjectSchema schemaForObjectClass:MissingPrimaryKey.class], @"Primary key property 'primaryKey' does not exist on object 'MissingPrimaryKey'"); } - (void)testClassWithUnindexableProperty { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:UnindexableProperty.class]; RLMSchema *schema = [[RLMSchema alloc] init]; schema.objectSchema = @[objectSchema]; RLMAssertThrowsWithReasonMatching([self realmWithTestPathAndSchema:schema], @"Property 'UnindexableProperty.unindexable' of type 'double' cannot be indexed"); } - (void)testClassWithRequiredNullableProperties { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class]; XCTAssertFalse([objectSchema[@"stringCol"] optional]); XCTAssertFalse([objectSchema[@"binaryCol"] optional]); } - (void)testClassWithRequiredPrimitiveArrayProperties { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllPrimitiveArrays.class]; XCTAssertFalse(objectSchema[@"intObj"].optional); XCTAssertFalse(objectSchema[@"boolObj"].optional); XCTAssertFalse(objectSchema[@"floatObj"].optional); XCTAssertFalse(objectSchema[@"doubleObj"].optional); XCTAssertFalse(objectSchema[@"stringObj"].optional); XCTAssertFalse(objectSchema[@"dateObj"].optional); XCTAssertFalse(objectSchema[@"dataObj"].optional); } - (void)testClassWithOptionalPrimitiveArrayProperties { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalPrimitiveArrays.class]; XCTAssertTrue(objectSchema[@"intObj"].optional); XCTAssertTrue(objectSchema[@"boolObj"].optional); XCTAssertTrue(objectSchema[@"floatObj"].optional); XCTAssertTrue(objectSchema[@"doubleObj"].optional); XCTAssertTrue(objectSchema[@"stringObj"].optional); XCTAssertTrue(objectSchema[@"dateObj"].optional); XCTAssertTrue(objectSchema[@"dataObj"].optional); } - (void)testClassWithRequiredLinkProperty { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:RequiredLinkProperty.class], @"cannot be made required.*'object'"); } - (void)testClassWithInvalidNSNumberProtocolProperty { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:InvalidNSNumberProtocolObject.class], @"Property 'number' is of type \"NSNumber\" which is not a supported NSNumber object type."); } - (void)testClassWithInvalidNSNumberNoProtocolProperty { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:InvalidNSNumberNoProtocolObject.class], @"Property 'number' requires a protocol defining the contained type"); } - (void)testClassWithReadWriteLinkingObjectsProperty { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:InvalidReadWriteLinkingObjectsProperty.class], @"Property 'linkingObjects' must be declared as readonly .* linking objects"); } - (void)testClassWithInvalidLinkingObjectsPropertiesMethod { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:InvalidLinkingObjectsPropertiesMethod.class], @"Property 'linkingObjects' .* but \\+linkingObjectsProperties .* class or property"); } - (void)testClassWithLinkingObjectsPropertyWithProtocol { RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:ValidLinkingObjectsPropertyWithProtocol.class]; XCTAssertEqual(objectSchema[@"linkingObjects"].type, RLMPropertyTypeLinkingObjects); } - (void)testClassWithInvalidLinkingObjectsPropertyProtocol { RLMAssertThrowsWithReasonMatching([RLMObjectSchema schemaForObjectClass:InvalidLinkingObjectsPropertyProtocol.class], @"Property 'linkingObjects' .* type RLMLinkingObjects.*conflicting class name.*'SchemaTestsLinkSource'"); } - (void)testClassWithInvalidLinkingObjectsPropertyMissingSourcePropertyOfLink { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = [RLMSchema schemaWithObjectClasses:@[ InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink.class ]]; RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil], @"Property 'InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink.nosuchproperty' declared as origin of linking objects property 'InvalidLinkingObjectsPropertyMissingSourcePropertyOfLink.linkingObjects' does not exist"); } - (void)testClassWithInvalidLinkingObjectsPropertySourcePropertyNotALink { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = [RLMSchema schemaWithObjectClasses:@[ InvalidLinkingObjectsPropertySourcePropertyNotALink.class ]]; RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil], @"Property 'InvalidLinkingObjectsPropertySourcePropertyNotALink.integer' declared as origin of linking objects property 'InvalidLinkingObjectsPropertySourcePropertyNotALink.linkingObjects' is not a link"); } - (void)testClassWithInvalidLinkingObjectsPropertySourcePropertysLinkElsewhere { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = [RLMSchema schemaWithObjectClasses:@[ InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere.class, IntObject.class ]]; RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil], @"Property 'InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere.link' declared as origin of linking objects property 'InvalidLinkingObjectsPropertySourcePropertyLinksElsewhere.linkingObjects' links to type 'IntObject'"); } - (void)testMixedIsRejected { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; RLMAssertThrowsWithReasonMatching(config.objectClasses = @[[MixedProperty class]], @"Property 'mixed' is declared as 'id'.*"); } - (void)testEmebeddedLinkingToNonEmbedded { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[[LinkFromEmbeddedToTopLevel class], [IntObject class]]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); config.objectClasses = @[[ArrayFromEmbeddedToTopLevel class], [IntObject class]]; XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); } - (void)testEmbeddedWithPrimaryKey { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[[EmbeddedObjectWithPrimaryKey class]]; RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:config error:nil], @"Embedded object type 'EmbeddedObjectWithPrimaryKey' cannot have a primary key."); } // Can't spawn child processes on iOS #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR && !TARGET_OS_MACCATALYST - (void)testPartialSharedSchemaInit { if (self.isParent) { RLMRunChildAndWait(); return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // Verify that opening with class subsets without the shared schema being // initialized works config.objectClasses = @[IntObject.class]; @autoreleasepool { RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(1U, realm.schema.objectSchema.count); XCTAssertNoThrow(realm.schema[@"IntObject"]); } config.objectClasses = @[IntObject.class, StringObject.class]; @autoreleasepool { RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(2U, realm.schema.objectSchema.count); XCTAssertNoThrow(realm.schema[@"IntObject"]); XCTAssertNoThrow(realm.schema[@"StringObject"]); } // Verify that the shared schema generated afterwards is valid config.objectClasses = nil; @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; // Shared schema shouldn't have accessor classes for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) { const char *actualClassName = class_getName(objectSchema.objectClass); XCTAssertEqual(nullptr, strstr(actualClassName, "RLMAccessor")); XCTAssertEqual(nullptr, strstr(actualClassName, "RLMUnmanaged")); } // Shared schema shouldn't have duplicate entries XCTAssertEqual(realm.schema.objectSchema.count, [NSSet setWithArray:[realm.schema.objectSchema valueForKey:@"className"]].count); // Shared schema should have the ones that were used in the subsets XCTAssertNoThrow(realm.schema[@"IntObject"]); XCTAssertNoThrow(realm.schema[@"StringObject"]); } } - (void)testPartialSharedSchemaInitInheritance { if (self.isParent) { RLMRunChildAndWait(); return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[NumberObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(1U, realm.schema.objectSchema.count); XCTAssertEqualObjects(@"NumberObject", [[[[NumberObject alloc] init] objectSchema] className]); // Verify that child class doesn't use the parent class's schema XCTAssertEqualObjects(@"NumberDefaultsObject", [[[[NumberDefaultsObject alloc] init] objectSchema] className]); } - (void)testCreateUnmanagedObjectWithUninitializedSchema { if (self.isParent) { RLMRunChildAndWait(); return; } XCTAssertTrue(RLMSchema.partialSharedSchema.objectSchema.count == 0); XCTAssertNoThrow([[IntObject alloc] initWithValue:@[@0]]); XCTAssertNoThrow([[NonDefaultObject alloc] initWithValue:@[@0]]); } - (void)testCreateUnmanagedObjectWithNestedObjectWithUninitializedSchema { if (self.isParent) { RLMRunChildAndWait(); return; } XCTAssertTrue(RLMSchema.partialSharedSchema.objectSchema.count == 0); XCTAssertNoThrow([[IntegerArrayPropertyObject alloc] initWithValue:(@[@0, @[@[@0]]])]); XCTAssertNoThrow([[NonDefaultArrayObject alloc] initWithValue:@[@[@[@0]]]]); XCTAssertNoThrow([[MutualLink1Object alloc] initWithValue:@[@[@{}]]]); } - (void)testKVOSubclassesDoNotAppearInSchema { if (self.isParent) { RLMRunChildAndWait(); return; } auto obj = [IntObject new]; [obj addObserver:self forKeyPath:@"intCol" options:0 context:0]; for (RLMObjectSchema *objectSchema in RLMRealm.defaultRealm.schema.objectSchema) { XCTAssertEqual([objectSchema.className rangeOfString:@"RLM:"].location, NSNotFound); } [obj removeObserver:self forKeyPath:@"intCol" context:0]; } #if !DEBUG - (void)testMultipleProcessesTryingToInitializeSchema { RLMRealm *syncRealm = [self realmWithTestPath]; if (!self.isParent) { RLMSchema *schema = [RLMSchema schemaWithObjectClasses:@[IntObject.class]]; RLMProperty *prop = ((NSArray *)[schema.objectSchema[0] properties])[0]; prop.type = RLMPropertyTypeFloat; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = schema; config.schemaVersion = 1; [syncRealm transactionWithBlock:^{ [StringObject createInRealm:syncRealm withValue:@[@""]]; }]; [RLMRealm realmWithConfiguration:config error:nil]; return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IntObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; // Hold a write transaction to prevent the child processes from performing // the migration immediately [realm beginWriteTransaction]; // Spawn a bunch of child processes which will all try to perform the migration dispatch_group_t group = dispatch_group_create(); for (int i = 0; i < 5; ++i) { dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ RLMRunChildAndWait(); }); } // Wait for all five to be immediately before the point where they will try // to perform the migration. There's inherently a race condition here in // as in theory all but one process could be suspended immediately after // committing the signalling commit and then not get woken up until after // the migration is complete, but in practice it won't happen and we can't // wait for someone to be waiting on a mutex. XCTestExpectation *notificationFired = [self expectationWithDescription:@"notification fired"]; RLMNotificationToken *token = [syncRealm addNotificationBlock:^(NSString *, RLMRealm *) { if ([StringObject allObjectsInRealm:syncRealm].count == 5) { [notificationFired fulfill]; } }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; [token invalidate]; // Release the write transaction and let them run [realm cancelWriteTransaction]; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } #endif - (void)testOpeningFileWithDifferentClassSubsetsInDifferentProcesses { if (!self.isParent) { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[StringObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertEqual(1U, realm.schema.objectSchema.count); // Verify that the StringObject table actually exists [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[@""]]; [realm commitWriteTransaction]; return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IntObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; realm.autorefresh = false; XCTAssertEqual(1U, realm.schema.objectSchema.count); [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@1]]; [realm commitWriteTransaction]; RLMRunChildAndWait(); // Should be able to advance over the transaction creating a new table and // inserting a row into it XCTAssertNoThrow([realm refresh]); // Verify that the IntObject table didn't break XCTAssertEqual(1, [[IntObject allObjectsInRealm:realm].firstObject intCol]); [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@2]]; // StringObject still isn't usable in this process since it isn't in the // class subset XCTAssertThrows([StringObject createInRealm:realm withValue:@[@""]]); [realm commitWriteTransaction]; } - (void)testAddingIndexToExistingColumnInBackgroundProcess { if (!self.isParent) { RLMSchema *schema = [RLMSchema schemaWithObjectClasses:@[IntObject.class]]; RLMObjectSchema *objectSchema = schema.objectSchema[0]; RLMProperty *prop = objectSchema.properties[0]; prop.indexed = YES; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = schema; [RLMRealm realmWithConfiguration:config error:nil]; return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IntObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; realm.autorefresh = false; XCTAssertEqual(1U, realm.schema.objectSchema.count); // Insert a value to ensure stuff actually happens when the index is added/removed [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@1]]; [realm commitWriteTransaction]; RLMRunChildAndWait(); // Should accept the index change XCTAssertNoThrow([realm refresh]); } - (void)testRemovingIndexFromExistingColumnInBackgroundProcess { if (!self.isParent) { RLMSchema *schema = [RLMSchema schemaWithObjectClasses:@[IndexedStringObject.class]]; RLMObjectSchema *objectSchema = schema.objectSchema[0]; RLMProperty *prop = objectSchema.properties[0]; prop.indexed = NO; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = schema; [RLMRealm realmWithConfiguration:config error:nil]; return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IndexedStringObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; realm.autorefresh = false; XCTAssertEqual(1U, realm.schema.objectSchema.count); // Insert a value to ensure stuff actually happens when the index is added/removed [realm beginWriteTransaction]; [IndexedStringObject createInRealm:realm withValue:@[@"1"]]; [realm commitWriteTransaction]; RLMRunChildAndWait(); // Should accept the index change XCTAssertNoThrow([realm refresh]); } - (void)testMigratingToLaterVersionInBackgroundProcess { if (!self.isParent) { RLMSchema *schema = [RLMSchema schemaWithObjectClasses:@[IntObject.class]]; RLMObjectSchema *objectSchema = schema.objectSchema[0]; RLMProperty *prop = objectSchema.properties[0]; prop.type = RLMPropertyTypeFloat; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.customSchema = schema; config.schemaVersion = 1; [RLMRealm realmWithConfiguration:config error:nil]; return; } RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[IntObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; realm.autorefresh = false; [realm beginWriteTransaction]; [IntObject createInRealm:realm withValue:@[@1]]; [realm commitWriteTransaction]; RLMRunChildAndWait(); // Should fail to refresh since we can't use later versions of the file due // to the schema change XCTAssertThrows([realm refresh]); XCTAssertThrows([realm beginWriteTransaction]); // Should have been left in a sensible state after the errors XCTAssertEqual(1, [[IntObject allObjectsInRealm:realm].firstObject intCol]); } - (void)testInsertingColumnsInBackgroundProcess { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaMode = realm::SchemaMode::AdditiveDiscovered; if (!self.isParent) { config.dynamic = true; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm beginWriteTransaction]; auto table = realm->_info[@"IntObject"].table(); table->add_column(realm::type_String, realm::util::format("col%1", table->get_column_count()).c_str()); [realm commitWriteTransaction]; return; } RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; [realm transactionWithBlock:^{ [IntObject createInRealm:realm withValue:@[@5]]; }]; RLMRunChildAndWait(); XCTAssertEqual(5, [[IntObject allObjectsInRealm:realm].firstObject intCol]); XCTAssertEqual(1U, [IntObject objectsInRealm:realm where:@"intCol = 5"].count); __block IntObject *io = [IntObject new]; io.intCol = 6; [realm transactionWithBlock:^{ [realm addObject:io]; }]; XCTAssertEqual(io.intCol, 6); XCTAssertEqualObjects(io[@"intCol"], @6); [realm transactionWithBlock:^{ io = [IntObject createInRealm:realm withValue:@[@7]]; }]; XCTAssertEqual(io.intCol, 7); [realm transactionWithBlock:^{ io = [IntObject createInRealm:realm withValue:@{@"intCol": @8}]; }]; XCTAssertEqual(io.intCol, 8); [realm transactionWithBlock:^{ io.intCol = 9; }]; XCTAssertEqual(io.intCol, 9); [realm transactionWithBlock:^{ io[@"intCol"] = @10; }]; XCTAssertEqual(io.intCol, 10); // Create query, add column, run query RLMResults *query = [IntObject objectsInRealm:realm where:@"intCol > 5"]; RLMRunChildAndWait(); XCTAssertEqual(query.count, 3U); // Create query, create TV, add column, reevaluate query query = [IntObject objectsInRealm:realm where:@"intCol > 5"]; (void)[query lastObject]; RLMRunChildAndWait(); XCTAssertEqual(query.count, 3U); } - (void)testExplicitlyIncludedEmbeddedOrphanIsAllowedForLocalRealm { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[OrphanObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; XCTAssertNotNil([realm.schema schemaForClassName:@"OrphanObject"]); } - (void)testDynamicUnmanagedAccessorsBeforeSharedSchemaInit { if (self.isParent) { RLMRunChildAndWait(); return; } IntObject *io = [IntObject new]; io[@"intCol"] = @10; XCTAssertEqualObjects(io[@"intCol"], @10); } #endif @end ================================================ FILE: Realm/Tests/SectionedResultsTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 "RLMTestCase.h" #import @interface SectionedResultsTests : RLMTestCase @end @implementation SectionedResultsTests - (void)createObjects { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ StringObject *strObj1 = [[StringObject alloc] initWithValue:@[@"foo"]]; StringObject *strObj2 = [[StringObject alloc] initWithValue:@[@"bar"]]; StringObject *strObj3 = [[StringObject alloc] initWithValue:@[@"apple"]]; StringObject *strObj4 = [[StringObject alloc] initWithValue:@[@"apples"]]; StringObject *strObj5 = [[StringObject alloc] initWithValue:@[@"zebra"]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:strObj5]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:strObj5]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:1 stringObject:strObj5]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:strObj4]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:strObj4]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:2 stringObject:strObj3]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:strObj2]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:strObj1]]; [AllTypesObject createInRealm:realm withValue:[AllTypesObject values:3 stringObject:strObj1]]; }]; } - (void)createPrimitiveObject { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ AllPrimitiveArrays *arrObj = [AllPrimitiveArrays new]; [arrObj.stringObj addObject:@"foo"]; [arrObj.stringObj addObject:@"fab"]; [arrObj.stringObj addObject:@"bar"]; [arrObj.stringObj addObject:@"baz"]; [realm addObject:arrObj]; }]; } - (void)testCreationFromResults { [self createObjects]; RLMRealm *realm = self.realmWithTestPath; RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectCol.stringCol" ascending:YES keyBlock:^id(AllTypesObject *value) { return [value.objectCol.stringCol substringToIndex:1]; }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 4); XCTAssertEqual(sr[0].count, 3); XCTAssertEqual(sr[1].count, 1); XCTAssertEqual(sr[2].count, 2); XCTAssertEqual(sr[3].count, 3); } - (void)testCreationFromPrimitiveResults { [self createPrimitiveObject]; RLMRealm *realm = self.realmWithTestPath; AllPrimitiveArrays *obj = [AllPrimitiveArrays allObjectsInRealm:realm][0]; RLMSectionedResults *sr = [obj.stringObj sectionedResultsSortedUsingKeyPath:@"self" ascending:YES keyBlock:^id (NSString* value) { return [value substringToIndex:1]; }]; XCTAssertEqual(sr.count, 2); [realm transactionWithBlock:^{ [obj.stringObj addObject:@"hello"]; }]; XCTAssertEqual(sr.count, 3); [realm transactionWithBlock:^{ [obj.stringObj addObject:@"zebra"]; }]; XCTAssertEqual(sr.count, 4); } - (NSDictionary *)keyPathsAndValues { return @{ @"intCol": @{ @1: @[@1, @1, @1, @3, @3, @3], @0: @[@2, @2, @2] }, @"longCol": @{ @0: @[@((long long)1 * INT_MAX + 1), @((long long)1 * INT_MAX + 1), @((long long)1 * INT_MAX + 1), @((long long)3 * INT_MAX + 1), @((long long)3 * INT_MAX + 1), @((long long)3 * INT_MAX + 1)], @-1: @[@((long long)2 * INT_MAX + 1), @((long long)2 * INT_MAX + 1), @((long long)2 * INT_MAX + 1)] }, @"boolCol": @{ @NO: @[@NO, @NO, @NO], @YES: @[@YES, @YES, @YES, @YES, @YES, @YES] }, @"cBoolCol": @{ @NO: @[@NO, @NO, @NO], @YES: @[@YES, @YES, @YES, @YES, @YES, @YES] }, @"floatCol": @{ @(1.1f): @[@(1.1f * 1), @(1.1f * 1), @(1.1f * 1)], @(2.2f): @[@(1.1f * 2), @(1.1f * 2), @(1.1f * 2), @(1.1f * 3), @(1.1f * 3), @(1.1f * 3)] }, @"doubleCol": @{ @(1.11): @[@(1.11 * 1), @(1.11 * 1), @(1.11 * 1)], @(2.2): @[@(1.11 * 2), @(1.11 * 2), @(1.11 * 2), @(1.11 * 3), @(1.11 * 3), @(1.11 * 3)] }, @"stringCol": @{ @"a": @[@"a", @"a", @"a"], @"b": @[@"b", @"b", @"b"], @"c": @[@"c", @"c", @"c"] }, @"objectCol.stringCol": @{ @"a": @[@"apple", @"apples", @"apples"], @"b": @[@"bar"], @"f": @[@"foo", @"foo"], @"z": @[@"zebra", @"zebra", @"zebra"] }, @"dateCol": @{ @5: @[[NSDate dateWithTimeIntervalSince1970:1], [NSDate dateWithTimeIntervalSince1970:1], [NSDate dateWithTimeIntervalSince1970:1], [NSDate dateWithTimeIntervalSince1970:2], [NSDate dateWithTimeIntervalSince1970:2], [NSDate dateWithTimeIntervalSince1970:2], [NSDate dateWithTimeIntervalSince1970:3], [NSDate dateWithTimeIntervalSince1970:3], [NSDate dateWithTimeIntervalSince1970:3]] }, @"decimalCol": @{ @"one": @[[[RLMDecimal128 alloc] initWithNumber:@(1)], [[RLMDecimal128 alloc] initWithNumber:@(1)], [[RLMDecimal128 alloc] initWithNumber:@(1)]], @"two": @[[[RLMDecimal128 alloc] initWithNumber:@(2)], [[RLMDecimal128 alloc] initWithNumber:@(2)], [[RLMDecimal128 alloc] initWithNumber:@(2)]], @"three": @[[[RLMDecimal128 alloc] initWithNumber:@(3)], [[RLMDecimal128 alloc] initWithNumber:@(3)], [[RLMDecimal128 alloc] initWithNumber:@(3)]] }, @"uuidCol": @{ @"a": @[[[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"], [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"], [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]], @"b": @[[[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"], [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"], [[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]], @"c": @[[[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"], [[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"], [[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"]] }, @"anyCol": @{ @1: @[@3, @3, @3], @0: @[@2, @2, @2, @4, @4, @4] } }; } - (id)sectionKeyForValue:(id)value { switch (value.rlm_anyValueType) { case RLMAnyValueTypeInt: return [NSNumber numberWithInt:(((NSNumber *)value).intValue % 2)]; case RLMAnyValueTypeBool: return value; case RLMAnyValueTypeFloat: return [(NSNumber *)value isEqualToNumber:@(1.1f * 1)] ? @(1.1f) : @(2.2f); case RLMAnyValueTypeDouble: return [(NSNumber *)value isEqualToNumber:@(1.11 * 1)] ? @(1.11) : @(2.2); case RLMAnyValueTypeString: return [(NSString *)value substringToIndex:1]; case RLMAnyValueTypeDate: { NSCalendar *calendar = [NSCalendar currentCalendar]; [calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; NSDateComponents *comp = [calendar components:NSCalendarUnitWeekday fromDate:(NSDate *)value]; return [NSNumber numberWithInteger:(NSInteger)comp.weekday]; } case RLMAnyValueTypeDecimal128: switch ((int)((RLMDecimal128 *)value).doubleValue) { case 1: return @"one"; case 2: return @"two"; case 3: return @"three"; default: XCTFail(); } case RLMAnyValueTypeUUID: if ([(NSUUID *)value isEqual:[[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]]) { return @"a"; } else if ([(NSUUID *)value isEqual:[[NSUUID alloc] initWithUUIDString:@"137DECC8-B300-4954-A233-F89909F4FD89"]]) { return @"b"; } else if ([(NSUUID *)value isEqual:[[NSUUID alloc] initWithUUIDString:@"b84e8912-a7c2-41cd-8385-86d200d7b31e"]]) { return @"c"; } case RLMAnyValueTypeAny: return [NSNumber numberWithInt:(((NSNumber *)value).intValue % 2)];; default: XCTFail(); return nil; } } - (void)testAllSupportedTypes { [self createObjects]; RLMRealm *realm = self.realmWithTestPath; RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; void(^testBlock)(NSString *) = ^(NSString *keyPath) { __block int algoRunCount = 0; RLMSectionedResults, AllTypesObject *> *sr = [results sectionedResultsSortedUsingKeyPath:keyPath ascending:YES keyBlock:^id(id value) { algoRunCount++; return [self sectionKeyForValue:[value valueForKeyPath:keyPath]]; }]; NSDictionary *values = [self keyPathsAndValues][keyPath]; for (RLMSection *section in sr) { NSArray *a = values[section.key]; for (NSUInteger i = 0; i < section.count; i++) { XCTAssertEqualObjects(a[i], [section[i] valueForKeyPath:keyPath]); } } XCTAssertEqual(algoRunCount, 9); }; testBlock(@"intCol"); testBlock(@"boolCol"); testBlock(@"floatCol"); testBlock(@"doubleCol"); testBlock(@"stringCol"); testBlock(@"objectCol.stringCol"); testBlock(@"dateCol"); testBlock(@"cBoolCol"); testBlock(@"longCol"); testBlock(@"decimalCol"); testBlock(@"uuidCol"); testBlock(@"anyCol"); } - (void)testAllSupportedOptionalTypes { NSDictionary *values = @{ @"intObj": @1, @"floatObj": @1.0f, @"doubleObj": @1.0, @"boolObj": @YES, @"string": @"foo", @"date": [NSDate dateWithTimeIntervalSince1970:1], @"decimal": [[RLMDecimal128 alloc] initWithNumber:@1], @"uuidCol": [[NSUUID alloc] initWithUUIDString:@"85d4fbee-6ec6-47df-bfa1-615931903d7e"] }; RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [AllOptionalTypes createInRealm:realm withValue:values]; [realm addObject:[AllOptionalTypes new]]; }]; RLMResults *results = [AllOptionalTypes allObjectsInRealm:realm]; void(^testBlock)(NSString *) = ^(NSString *keyPath) { __block int algoRunCount = 0; RLMSectionedResults, AllOptionalTypes *> *sr = [results sectionedResultsSortedUsingKeyPath:keyPath ascending:YES keyBlock:^id(AllOptionalTypes *value) { algoRunCount++; if ([value valueForKeyPath:keyPath]) { return @"Not null"; } else { return nil; } }]; RLMSection *nullSection = sr[0]; XCTAssertEqualObjects(nullSection.key, NSNull.null); XCTAssertNil([nullSection[0] valueForKeyPath:keyPath]); RLMSection *nonNullSection = sr[1]; XCTAssertEqualObjects(nonNullSection.key, @"Not null"); XCTAssertEqualObjects([nonNullSection[0] valueForKeyPath:keyPath], values[keyPath]); XCTAssertEqual(algoRunCount, 2); }; testBlock(@"intObj"); testBlock(@"floatObj"); testBlock(@"doubleObj"); testBlock(@"boolObj"); testBlock(@"string"); testBlock(@"date"); testBlock(@"decimal"); testBlock(@"uuidCol"); } - (void)testObjectIdCol { RLMRealm *realm = self.realmWithTestPath; __block RLMObjectId *oid1; __block RLMObjectId *oid2; [realm transactionWithBlock:^{ NSDictionary *ato1Values = [AllTypesObject values:0 stringObject:nil]; oid1 = ato1Values[@"objectIdCol"]; [AllTypesObject createInRealm:realm withValue:ato1Values]; NSDictionary *ato2Values = [AllTypesObject values:0 stringObject:nil]; oid2 = ato2Values[@"objectIdCol"]; [AllTypesObject createInRealm:realm withValue:ato2Values]; AllOptionalTypes *ot1 = [AllOptionalTypes new]; ot1.objectId = oid1; AllOptionalTypes *ot2 = [AllOptionalTypes new]; [realm addObjects:@[ot1, ot2]]; }]; RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMResults *resultsOpt = [AllOptionalTypes allObjectsInRealm:realm]; __block int sectionAlgoCount = 0; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectIdCol" ascending:YES keyBlock:^id(id value) { id v = [value valueForKeyPath:@"objectIdCol"]; sectionAlgoCount++; return [((RLMObjectId *)v) isEqual:oid1] ? @"a" : @"b"; }]; NSDictionary *values = @{@"a": oid1, @"b": oid2}; for (RLMSection *section in sr) { RLMObjectId *oid = values[section.key]; for (NSUInteger i = 0; i < section.count; i++) { XCTAssertEqualObjects(oid, [section[i] valueForKeyPath:@"objectIdCol"]); } } XCTAssertEqual(sectionAlgoCount, 2); sectionAlgoCount = 0; RLMSectionedResults *srOpt = [resultsOpt sectionedResultsSortedUsingKeyPath:@"objectId" ascending:YES keyBlock:^id(id value) { sectionAlgoCount++; id v = [value valueForKeyPath:@"objectId"]; return !v ? @"b" : @"a"; }]; values = @{@"a": oid1, @"b": NSNull.null}; for (RLMSection *section in srOpt) { RLMObjectId *oid = values[section.key]; for (NSUInteger i = 0; i < section.count; i++) { id v = [section[i] valueForKeyPath:@"objectId"]; if ([((NSString *)section.key) isEqualToString:@"b"]) { XCTAssertNil(v); } else { XCTAssertEqualObjects(oid, v); } } } XCTAssertEqual(sectionAlgoCount, 2); } - (void)testBinaryCol { RLMRealm *realm = self.realmWithTestPath; __block NSData *d1; __block NSData *d2; [realm transactionWithBlock:^{ NSDictionary *ato1Values = [AllTypesObject values:0 stringObject:nil]; d1 = ato1Values[@"binaryCol"]; [AllTypesObject createInRealm:realm withValue:ato1Values]; NSDictionary *ato2Values = [AllTypesObject values:0 stringObject:nil]; d2 = ato2Values[@"binaryCol"]; [AllTypesObject createInRealm:realm withValue:ato2Values]; AllOptionalTypes *ot1 = [AllOptionalTypes new]; ot1.data = d1; AllOptionalTypes *ot2 = [AllOptionalTypes new]; [realm addObjects:@[ot1, ot2]]; }]; RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMResults *resultsOpt = [AllOptionalTypes allObjectsInRealm:realm]; __block int sectionAlgoCount = 0; // Sorting on binary col is unsupported RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"intCol" ascending:YES keyBlock:^id(id value) { id v = [value valueForKeyPath:@"binaryCol"]; sectionAlgoCount++; return [((NSData *)v) isEqual:d1] ? @"a" : @"b"; }]; NSDictionary *values = @{@"a": d1, @"b": d2}; for (RLMSection *section in sr) { RLMObjectId *oid = values[section.key]; for (NSUInteger i = 0; i < section.count; i++) { XCTAssertEqualObjects(oid, [section[i] valueForKeyPath:@"binaryCol"]); } } XCTAssertEqual(sectionAlgoCount, 2); sectionAlgoCount = 0; RLMSectionedResults *srOpt = [resultsOpt sectionedResultsSortedUsingKeyPath:@"intObj" ascending:YES keyBlock:^id(id value) { sectionAlgoCount++; id v = [value valueForKeyPath:@"data"]; return !v ? @"b" : @"a"; }]; values = @{@"a": d1, @"b": NSNull.null}; for (RLMSection *section in srOpt) { NSData *d = values[section.key]; for (NSUInteger i = 0; i < section.count; i++) { id v = [section[i] valueForKeyPath:@"data"]; if ([((NSString *)section.key) isEqualToString:@"b"]) { XCTAssertNil(v); } else { XCTAssertEqualObjects(d, v); } } } XCTAssertEqual(sectionAlgoCount, 2); } - (void)testAllKeys { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"apple"]]; [StringObject createInRealm:realm withValue:@[@"any"]]; [StringObject createInRealm:realm withValue:@[@"banana"]]; }]; RLMResults *results = [StringObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"stringCol" ascending:YES keyBlock:^id(StringObject *value) { return value.firstLetter; }]; XCTAssertEqualObjects(sr.allKeys, (@[@"a", @"b"])); } - (void)testDescription { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [StringObject createInRealm:realm withValue:@[@"apple"]]; [StringObject createInRealm:realm withValue:@[@"any"]]; [StringObject createInRealm:realm withValue:@[@"banana"]]; }]; RLMResults *results = [StringObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"stringCol" ascending:YES keyBlock:^id(StringObject *value) { return value.firstLetter; }]; NSString *expDesc = @"(?s)RLMSectionedResults\\ \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[a\\] RLMSection \\<0x[a-z0-9]+\\> \\(\n" @"\t\t\\[0\\] StringObject \\{\n" @"\t\t\tstringCol = any;\n" @"\t\t\\},\n" @"\t\t\\[1\\] StringObject \\{\n" @"\t\t\tstringCol = apple;\n" @"\t\t\\}\n" @"\t\\),\n" @"\t\\[b\\] RLMSection \\<0x[a-z0-9]+\\> \\(\n" @"\t\t\\[0\\] StringObject \\{\n" @"\t\t\tstringCol = banana;\n" @"\t\t\\}\n" @"\t\\)\n" @"\\)"; RLMAssertMatches(sr.description, expDesc); expDesc = @"RLMSection \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[0\\] StringObject \\{\n" @"\t\tstringCol = any;\n" @"\t\\},\n" @"\t\\[1\\] StringObject \\{\n" @"\t\tstringCol = apple;\n" @"\t\\}\n" @"\\)"; RLMAssertMatches(sr[0].description, expDesc); } - (void)testFastEnumeration { for (int i = 0; i < 10; i++) { [self createObjects]; } RLMRealm *realm = self.realmWithTestPath; __block NSUInteger algoRunCount = 0; __block NSUInteger forLoopCount = 0; RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectCol.stringCol" ascending:YES keyBlock:^id(AllTypesObject *value) { algoRunCount++; return value.stringCol; }]; for (RLMSection *section in sr) { for (AllTypesObject *o __unused in section) { forLoopCount++; } } XCTAssertEqual(algoRunCount, results.count); XCTAssertEqual(forLoopCount, results.count); forLoopCount = 0; [self createObjects]; algoRunCount = 0; for (RLMSection *section in sr) { for (AllTypesObject *o __unused in section) { forLoopCount++; } } XCTAssertEqual(algoRunCount, results.count); XCTAssertEqual(forLoopCount, results.count); forLoopCount = 0; algoRunCount = 0; NSUInteger originalCount = results.count; for (RLMSection *section in sr) { for (AllTypesObject *o __unused in section) { forLoopCount++; } [self createObjects]; } // transaction inside the 'for in' should not invoke the section key // callback until the next access of the SectionedResults collection. XCTAssertEqual(algoRunCount, 0); XCTAssertEqual(forLoopCount, originalCount); } static RLMSectionedResultsChange *getChange(SectionedResultsTests *self, void (^block)(RLMRealm *)) { __block RLMSectionedResultsChange *changes; RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *results = [StringObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"stringCol" ascending:YES keyBlock:^id(StringObject *value) { return value.firstLetter; }]; id token = [sr addNotificationBlock:^(RLMSectionedResults *sr, RLMSectionedResultsChange *c) { changes = c; XCTAssertNotNil(sr); CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ block(realm); }]; }]; [(RLMNotificationToken *)token invalidate]; token = nil; return changes; } static void ExpectChange(id self, NSArray *insertions, NSArray *deletions, NSArray *modifications, NSArray *sectionsToInsert, NSArray *sectionsToRemove, void (^block)(RLMRealm *)) { RLMSectionedResultsChange *changes = getChange(self, block); XCTAssertNotNil(changes); if (!changes) { return; } XCTAssertEqualObjects(insertions, changes.insertions); XCTAssertEqualObjects(deletions, changes.deletions); XCTAssertEqualObjects(modifications, changes.modifications); XCTAssertEqual(sectionsToInsert.count, changes.sectionsToInsert.count); XCTAssertEqual(sectionsToRemove.count, changes.sectionsToRemove.count); for (NSIndexPath *insertion in insertions) { NSArray *filtered = [insertions filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"section == %d", insertion.section]]; XCTAssertEqualObjects(filtered, [changes insertionsInSection:insertion.section]); } for (NSIndexPath *deletion in deletions) { NSArray *filtered = [deletions filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"section == %d", deletion.section]]; XCTAssertEqualObjects(filtered, [changes deletionsInSection:deletion.section]); } for (NSIndexPath *modification in modifications) { NSArray *filtered = [modifications filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"section == %d", modification.section]]; XCTAssertEqualObjects(filtered, [changes modificationsInSection:modification.section]); } for (NSNumber *i in sectionsToInsert) { XCTAssertTrue([changes.sectionsToInsert containsIndex:i.unsignedIntegerValue]); } for (NSNumber *i in sectionsToRemove) { XCTAssertTrue([changes.sectionsToRemove containsIndex:i.unsignedIntegerValue]); } } - (void)testNotifications { StringObject *o1 = [[StringObject alloc] initWithValue:@[@"any"]]; StringObject *o2 = [[StringObject alloc] initWithValue:@[@"zebra"]]; StringObject *o3 = [[StringObject alloc] initWithValue:@[@"apple"]]; StringObject *o4 = [[StringObject alloc] initWithValue:@[@"zulu"]]; StringObject *o5 = [[StringObject alloc] initWithValue:@[@"banana"]]; StringObject *o6 = [[StringObject alloc] initWithValue:@[@"beans"]]; // Insertions ExpectChange(self, @[[NSIndexPath indexPathForItem:0 inSection:0], [NSIndexPath indexPathForItem:1 inSection:0], [NSIndexPath indexPathForItem:0 inSection:1], [NSIndexPath indexPathForItem:0 inSection:2], [NSIndexPath indexPathForItem:1 inSection:2]], @[], @[], @[@0, @1, @2], @[], ^(RLMRealm *realm) { [realm addObjects:@[o1, o2, o3, o4, o5]]; }); ExpectChange(self, @[[NSIndexPath indexPathForItem:1 inSection:1]], @[], @[], @[], @[], ^(RLMRealm *realm) { [realm addObject:o6]; }); // Deletions ExpectChange(self, @[], @[[NSIndexPath indexPathForItem:0 inSection:1]], @[], @[], @[], ^(RLMRealm *realm) { StringObject *o = [[[StringObject allObjectsInRealm:realm] objectsWhere:@"stringCol = 'banana'"] firstObject]; [realm deleteObject:o]; // o5 will now be invalidated. }); // Modifications ExpectChange(self, @[], @[], @[[NSIndexPath indexPathForItem:0 inSection:1]], @[], @[], ^(RLMRealm *realm) { StringObject *o = [[[StringObject allObjectsInRealm:realm] objectsWhere:@"stringCol = 'beans'"] firstObject]; o.stringCol = @"breakfast"; }); // Move object from one section to another ExpectChange(self, @[[NSIndexPath indexPathForItem:0 inSection:0]], @[], @[], @[], @[@1], ^(RLMRealm *realm) { StringObject *o = [[[StringObject allObjectsInRealm:realm] objectsWhere:@"stringCol = 'breakfast'"] firstObject]; o.stringCol = @"all"; }); // Move object from one section to a new section ExpectChange(self, @[[NSIndexPath indexPathForItem:0 inSection:0], [NSIndexPath indexPathForItem:1 inSection:0], [NSIndexPath indexPathForItem:2 inSection:0]], @[], @[], @[@0], @[@0], ^(RLMRealm *realm) { RLMResults *objs = [[StringObject allObjectsInRealm:realm] objectsWhere:@"stringCol BEGINSWITH 'a'"]; for(StringObject *o in objs) { o.stringCol = @"max"; } }); } - (void)testNotificationsOnSection { __block RLMSectionedResultsChange *changes; RLMRealm *realm = self.realmWithTestPath; [self createObjects]; RLMResults *results = [StringObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"stringCol" ascending:YES keyBlock:^id(StringObject *value) { return value.firstLetter; }]; RLMSection *section = sr[0]; RLMNotificationToken *token = [section addNotificationBlock:^(RLMSection *r, RLMSectionedResultsChange *c) { changes = c; XCTAssertNotNil(r); CFRunLoopStop(CFRunLoopGetCurrent()); } keyPaths:@[@"stringCol"]]; CFRunLoopRun(); [self waitForNotification:RLMRealmDidChangeNotification realm:self.realmWithTestPath block:^{ RLMRealm *r = self.realmWithTestPath; [r transactionWithBlock:^{ StringObject *o = [StringObject allObjectsInRealm:r][0]; o.stringCol = @"app"; }]; }]; XCTAssertEqualObjects(changes.insertions, @[[NSIndexPath indexPathForItem:0 inSection:0]]); XCTAssertEqualObjects(changes.modifications, @[]); XCTAssertEqualObjects(changes.deletions, @[]); XCTAssertEqual(changes.sectionsToInsert.count, 0); XCTAssertEqual(changes.sectionsToRemove.count, 0); [token invalidate]; } static RLMSectionedResultsChange *getChangePrimitive(SectionedResultsTests *self, void (^block)(RLMRealm *)) { __block RLMSectionedResultsChange *changes; RLMRealm *realm = [RLMRealm defaultRealm]; AllPrimitiveArrays *obj = [AllPrimitiveArrays allObjectsInRealm:realm][0]; RLMSectionedResults *sr = [obj.stringObj sectionedResultsSortedUsingKeyPath:@"self" ascending:YES keyBlock:^id (id value) { return [value substringToIndex:1]; }]; id token = [sr addNotificationBlock:^(RLMSectionedResults *sr, RLMSectionedResultsChange *c) { changes = c; XCTAssertNotNil(sr); CFRunLoopStop(CFRunLoopGetCurrent()); }]; CFRunLoopRun(); [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ block(realm); }]; }]; [(RLMNotificationToken *)token invalidate]; token = nil; return changes; } static void ExpectChangePrimitive(id self, NSArray *insertions, NSArray *deletions, NSArray *modifications, NSArray *sectionsToInsert, NSArray *sectionsToRemove, void (^block)(RLMRealm *)) { RLMSectionedResultsChange *changes = getChangePrimitive(self, block); XCTAssertNotNil(changes); if (!changes) { return; } XCTAssertEqualObjects(insertions, changes.insertions); XCTAssertEqualObjects(deletions, changes.deletions); XCTAssertEqualObjects(modifications, changes.modifications); XCTAssertEqual(sectionsToInsert.count, changes.sectionsToInsert.count); XCTAssertEqual(sectionsToRemove.count, changes.sectionsToRemove.count); for (NSNumber *i in sectionsToInsert) { XCTAssertTrue([changes.sectionsToInsert containsIndex:i.unsignedIntegerValue]); } for (NSNumber *i in sectionsToRemove) { XCTAssertTrue([changes.sectionsToRemove containsIndex:i.unsignedIntegerValue]); } } - (void)testNotificationsPrimitive { RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ AllPrimitiveArrays *arrObj = [AllPrimitiveArrays new]; [realm addObject:arrObj]; }]; // Insertions ExpectChangePrimitive(self, @[[NSIndexPath indexPathForItem:0 inSection:0], [NSIndexPath indexPathForItem:0 inSection:1], [NSIndexPath indexPathForItem:0 inSection:2]], @[], @[], @[@0, @1, @2], @[], ^(RLMRealm *r) { AllPrimitiveArrays *o = [AllPrimitiveArrays allObjectsInRealm:r][0]; [o.stringObj addObjects:@[@"apple", @"banana", @"orange"]]; }); // Deletions ExpectChangePrimitive(self, @[], @[], @[], @[], @[@0], ^(RLMRealm *r) { AllPrimitiveArrays *o = [AllPrimitiveArrays allObjectsInRealm:r][0]; [o.stringObj removeObjectAtIndex:0]; }); // Modifications ExpectChangePrimitive(self, @[], @[], @[[NSIndexPath indexPathForItem:0 inSection:0]], @[], @[], ^(RLMRealm *r) { AllPrimitiveArrays *o = [AllPrimitiveArrays allObjectsInRealm:r][0]; o.stringObj[0] = @"box"; // banana -> box }); // Remove elements from one section, insert into another. ExpectChangePrimitive(self, @[[NSIndexPath indexPathForItem:0 inSection:1]], @[], @[], @[@1], @[@0], ^(RLMRealm *r) { AllPrimitiveArrays *o = [AllPrimitiveArrays allObjectsInRealm:r][0]; o.stringObj[0] = @"zebra"; // open -> zebra }); } - (void)testSortDescriptors { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ AggregateArrayObject *o1 = [AggregateArrayObject new]; AggregateSetObject *o2 = [AggregateSetObject new]; AggregateObject *aggObj1 = [AggregateObject new]; aggObj1.intCol = 1; aggObj1.anyCol = @9; AggregateObject *aggObj2 = [AggregateObject new]; aggObj2.intCol = 1; aggObj2.anyCol = @10; AggregateObject *aggObj3 = [AggregateObject new]; aggObj3.intCol = 1; aggObj3.anyCol = @2; AggregateObject *aggObj4 = [AggregateObject new]; aggObj4.intCol = 2; aggObj4.anyCol = @1; [o1.array addObjects:@[aggObj1, aggObj2, aggObj3, aggObj4]]; [o2.set addObjects:@[aggObj1, aggObj2, aggObj3, aggObj4]]; [realm addObjects:@[o1, o2]]; }]; NSMutableArray *sortDescriptors = [NSMutableArray new]; [sortDescriptors addObject:[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]; [sortDescriptors addObject:[RLMSortDescriptor sortDescriptorWithKeyPath:@"anyCol" ascending:NO]]; AggregateArrayObject *arrayObj = [AggregateArrayObject allObjectsInRealm:realm][0]; AggregateSetObject *setObj = [AggregateSetObject allObjectsInRealm:realm][0]; void(^run)(id collection) = ^(id collection) { RLMSectionedResults *sr = [collection sectionedResultsUsingSortDescriptors:sortDescriptors keyBlock:^id(AggregateObject *value) { return @(value.intCol); }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 2); XCTAssertEqual(sr[0].count, 3); XCTAssertEqual(sr[1].count, 1); XCTAssertEqualObjects(sr[0].key, @1); XCTAssertEqual(sr[0][0].intCol, 1); XCTAssertEqualObjects(sr[0][0].anyCol, @10); XCTAssertEqual(sr[0][1].intCol, 1); XCTAssertEqualObjects(sr[0][1].anyCol, @9); XCTAssertEqualObjects(sr[1].key, @2); XCTAssertEqual(sr[1][0].intCol, 2); XCTAssertEqualObjects(sr[1][0].anyCol, @1); }; run(arrayObj.array); run(setObj.set); run([AggregateObject allObjectsInRealm:realm]); } - (void)testFrozenFromResults { [self createObjects]; RLMRealm *realm = self.realmWithTestPath; // Test creation from frozen RLMResults RLMResults *results = [[AllTypesObject allObjectsInRealm:realm] freeze]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectCol.stringCol" ascending:YES keyBlock:^id(AllTypesObject *value) { return [value.objectCol.stringCol substringToIndex:1]; }]; [self createObjects]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 4); XCTAssertEqual(sr[0].count, 3); XCTAssertEqual(sr[1].count, 1); XCTAssertEqual(sr[2].count, 2); XCTAssertEqual(sr[3].count, 3); XCTAssertTrue(sr[0][0].isFrozen); XCTAssertTrue(sr.isFrozen); RLMSectionedResults *thawed = [sr thaw]; XCTAssertNotNil(thawed); XCTAssertEqual(thawed.count, 4); XCTAssertEqual(thawed[0].count, 6); XCTAssertEqual(thawed[1].count, 2); XCTAssertEqual(thawed[2].count, 4); XCTAssertEqual(thawed[3].count, 6); XCTAssertFalse(thawed[0][0].isFrozen); XCTAssertFalse(thawed.isFrozen); } - (void)testFrozenSectionedResults { [self createObjects]; RLMRealm *realm = self.realmWithTestPath; // Test creation from frozen RLMResults RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectCol.stringCol" ascending:YES keyBlock:^id(AllTypesObject *value) { return [value.objectCol.stringCol substringToIndex:1]; }]; RLMSectionedResults *frozen = [sr freeze]; XCTAssertEqual(frozen, [frozen freeze]); // should return self [self createObjects]; XCTAssertNotNil(frozen); XCTAssertEqual(frozen.count, 4); XCTAssertEqual(frozen[0].count, 3); XCTAssertEqual(frozen[1].count, 1); XCTAssertEqual(frozen[2].count, 2); XCTAssertEqual(frozen[3].count, 3); XCTAssertTrue(frozen[0][0].isFrozen); XCTAssertTrue(frozen.isFrozen); RLMSectionedResults *thawed = [frozen thaw]; XCTAssertEqual(thawed, [thawed thaw]); // should return self XCTAssertNotNil(thawed); XCTAssertEqual(thawed.count, 4); XCTAssertEqual(thawed[0].count, 6); XCTAssertEqual(thawed[1].count, 2); XCTAssertEqual(thawed[2].count, 4); XCTAssertEqual(thawed[3].count, 6); XCTAssertFalse(thawed[0][0].isFrozen); XCTAssertFalse(thawed.isFrozen); } - (void)testFrozenSection { [self createObjects]; RLMRealm *realm = self.realmWithTestPath; // Test creation from frozen RLMResults RLMResults *results = [AllTypesObject allObjectsInRealm:realm]; RLMSectionedResults *sr = [results sectionedResultsSortedUsingKeyPath:@"objectCol.stringCol" ascending:YES keyBlock:^id(AllTypesObject *value) { return [value.objectCol.stringCol substringToIndex:1]; }]; RLMSection *frozen = [sr[0] freeze]; XCTAssertEqual(frozen, [frozen freeze]); // should return self [self createObjects]; XCTAssertNotNil(frozen); XCTAssertEqual(frozen.count, 3); XCTAssertTrue(frozen[0].isFrozen); XCTAssertTrue(frozen.isFrozen); RLMSection *thawed = [frozen thaw]; XCTAssertEqual(thawed, [thawed thaw]); // should return self XCTAssertNotNil(thawed); XCTAssertEqual(thawed.count, 6); XCTAssertFalse(thawed[0].isFrozen); XCTAssertFalse(thawed.isFrozen); } - (void)testInitFromRLMArray { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [MixedObject createInRealm:realm withValue:@[NSNull.null, @[@5, @3, @2, @4]]]; }]; MixedObject *obj = [MixedObject allObjectsInRealm:realm][0]; RLMSectionedResults *sr = [obj.anyArray sectionedResultsSortedUsingKeyPath:@"self" ascending:YES keyBlock:^id(id value) { return @(((NSNumber *)value).intValue % 2); }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 2); XCTAssertEqual(sr[0].count, 2); XCTAssertEqual(sr[1].count, 2); XCTAssertEqualObjects(sr[0].key, @0); XCTAssertEqualObjects(sr[0][0], @2); XCTAssertEqualObjects(sr[0][1], @4); XCTAssertEqualObjects(sr[1].key, @1); XCTAssertEqualObjects(sr[1][0], @3); XCTAssertEqualObjects(sr[1][1], @5); // Descending sr = [obj.anyArray sectionedResultsSortedUsingKeyPath:@"self" ascending:NO keyBlock:^id(id value) { return @(((NSNumber *)value).intValue % 2); }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 2); XCTAssertEqual(sr[0].count, 2); XCTAssertEqual(sr[1].count, 2); XCTAssertEqualObjects(sr[0].key, @1); XCTAssertEqualObjects(sr[0][0], @5); XCTAssertEqualObjects(sr[0][1], @3); XCTAssertEqualObjects(sr[1].key, @0); XCTAssertEqualObjects(sr[1][0], @4); XCTAssertEqualObjects(sr[1][1], @2); } - (void)testInitFromRLMSet { RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ AllPrimitiveSets *o = [AllPrimitiveSets new]; [o.intObj addObject:@5]; [o.intObj addObject:@4]; [o.intObj addObject:@1]; [o.intObj addObject:@2]; [realm addObject:o]; }]; AllPrimitiveSets *obj = [AllPrimitiveSets allObjectsInRealm:realm][0]; RLMSectionedResults *sr = [obj.intObj sectionedResultsSortedUsingKeyPath:@"self" ascending:YES keyBlock:^id(id value) { return @(((NSNumber *)value).intValue % 2); }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 2); XCTAssertEqual(sr[0].count, 2); XCTAssertEqual(sr[1].count, 2); XCTAssertEqualObjects(sr[0].key, @1); XCTAssertEqualObjects(sr[0][0], @1); XCTAssertEqualObjects(sr[0][1], @5); XCTAssertEqualObjects(sr[1].key, @0); XCTAssertEqualObjects(sr[1][0], @2); XCTAssertEqualObjects(sr[1][1], @4); // Descending sr = [obj.intObj sectionedResultsSortedUsingKeyPath:@"self" ascending:NO keyBlock:^id(id value) { return @(((NSNumber *)value).intValue % 2); }]; XCTAssertNotNil(sr); XCTAssertEqual(sr.count, 2); XCTAssertEqual(sr[0].count, 2); XCTAssertEqual(sr[1].count, 2); XCTAssertEqualObjects(sr[0].key, @1); XCTAssertEqualObjects(sr[0][0], @5); XCTAssertEqualObjects(sr[0][1], @1); XCTAssertEqualObjects(sr[1].key, @0); XCTAssertEqualObjects(sr[1][0], @4); XCTAssertEqualObjects(sr[1][1], @2); } @end ================================================ FILE: Realm/Tests/SetPropertyTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 "RLMTestCase.h" @implementation DogSetObject @end @interface SetPropertyTests : RLMTestCase @end @implementation SetPropertyTests - (void)testUnmanagedUnion { AllPrimitiveSets *setObj1 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets new]; [setObj1.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [setObj2.stringObj addObjects:@[@"one", @"two", @"three"]]; [setObj3.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [setObj1.stringObj unionSet:setObj2.stringObj]; XCTAssertEqual(setObj1.stringObj.count, 5U); XCTAssertTrue([setObj1.stringObj isEqual:setObj3.stringObj]); } - (void)testUnmanagedIntersect { AllPrimitiveSets *setObj1 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets new]; [setObj1.stringObj addObjects:@[@"ten", @"one", @"nine", @"two", @"eight"]]; [setObj2.stringObj addObjects:@[@"five", @"six", @"seven", @"eight", @"nine"]]; [setObj3.stringObj addObjects:@[@"nine", @"eight"]]; [setObj1.stringObj intersectSet:setObj2.stringObj]; XCTAssertTrue([setObj1.stringObj intersectsSet:setObj2.stringObj]); XCTAssertEqual(setObj1.stringObj.count, 2U); XCTAssertTrue([setObj1.stringObj isEqual:setObj3.stringObj]); } - (void)testUnmanagedMinus { AllPrimitiveSets *setObj1 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets new]; [setObj1.stringObj addObjects:@[@"ten", @"one", @"nine", @"two", @"eight"]]; [setObj2.stringObj addObjects:@[@"five", @"six", @"seven", @"eight", @"nine"]]; [setObj3.stringObj addObjects:@[@"ten", @"one", @"two"]]; [setObj1.stringObj minusSet:setObj2.stringObj]; XCTAssertEqual(setObj1.stringObj.count, 3U); XCTAssertTrue([setObj1.stringObj isEqual:setObj3.stringObj]); } - (void)testUnmanagedIsSubsetOfSet { AllPrimitiveSets *setObj1 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets new]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets new]; [setObj1.stringObj addObjects:@[@"ten", @"one", @"nine", @"two", @"eight"]]; [setObj2.stringObj addObjects:@[@"five", @"six", @"seven", @"eight", @"nine"]]; [setObj3.stringObj addObjects:@[@"two", @"one", @"ten"]]; XCTAssertFalse([setObj1.stringObj isSubsetOfSet:setObj2.stringObj]); XCTAssertTrue([setObj3.stringObj isSubsetOfSet:setObj1.stringObj]); } - (void)testManagedIsSubsetOfSet { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; AllPrimitiveSets *setObj1 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; [setObj1.stringObj addObjects:@[@"ten", @"one", @"nine", @"two", @"eight"]]; [setObj2.stringObj addObjects:@[@"five", @"six", @"seven", @"eight", @"nine"]]; [setObj3.stringObj addObjects:@[@"two", @"one", @"ten"]]; [realm commitWriteTransaction]; AllPrimitiveSets *unman = [AllPrimitiveSets new]; XCTAssertThrows([setObj1.stringObj isSubsetOfSet:unman.stringObj]); XCTAssertThrows([setObj1.stringObj isSubsetOfSet:setObj2.intObj]); XCTAssertFalse([setObj1.stringObj isSubsetOfSet:setObj2.stringObj]); XCTAssertTrue([setObj3.stringObj isSubsetOfSet:setObj1.stringObj]); } - (void)testManagedIntersect { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; AllPrimitiveSets *setObj1 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; [setObj1.stringObj addObjects:@[@"ten", @"one", @"nine", @"two", @"eight"]]; [setObj2.stringObj addObjects:@[@"five", @"six", @"seven", @"eight", @"nine"]]; [setObj3.stringObj addObjects:@[@"nine", @"eight"]]; [realm commitWriteTransaction]; AllPrimitiveSets *unman = [AllPrimitiveSets new]; XCTAssertThrows([setObj1.stringObj intersectSet:setObj2.stringObj]); XCTAssertTrue([setObj1.stringObj intersectsSet:setObj2.stringObj]); [realm beginWriteTransaction]; XCTAssertThrows([setObj1.stringObj intersectSet:unman.stringObj]); [setObj1.stringObj intersectSet:setObj2.stringObj]; [realm commitWriteTransaction]; XCTAssertTrue([setObj1.stringObj intersectsSet:setObj2.stringObj]); XCTAssertEqual(setObj1.stringObj.count, 2U); } - (void)testManagedUnion { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; AllPrimitiveSets *setObj1 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; [setObj1.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [setObj2.stringObj addObjects:@[@"one", @"two", @"three"]]; [setObj3.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [realm commitWriteTransaction]; XCTAssertThrows([setObj1.stringObj unionSet:setObj2.stringObj]); XCTAssertThrows([setObj2.stringObj unionSet:setObj1.stringObj]); [realm beginWriteTransaction]; [setObj1.stringObj unionSet:setObj2.stringObj]; [setObj2.stringObj unionSet:setObj3.stringObj]; [realm commitWriteTransaction]; XCTAssertEqual(setObj1.stringObj.count, 5U); XCTAssertEqual(setObj2.stringObj.count, 5U); } - (void)testManagedMinus { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; AllPrimitiveSets *setObj1 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj2 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; AllPrimitiveSets *setObj3 = [AllPrimitiveSets createInRealm:realm withValue:@[]]; [setObj1.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [setObj2.stringObj addObjects:@[@"one", @"two", @"three"]]; [setObj3.stringObj addObjects:@[@"one", @"two", @"three", @"four", @"five"]]; [realm commitWriteTransaction]; XCTAssertThrows([setObj1.stringObj minusSet:setObj2.stringObj]); XCTAssertThrows([setObj2.stringObj minusSet:setObj1.stringObj]); [realm beginWriteTransaction]; [setObj1.stringObj minusSet:setObj2.stringObj]; [setObj2.stringObj minusSet:setObj3.stringObj]; [realm commitWriteTransaction]; XCTAssertEqual(setObj1.stringObj.count, 2U); XCTAssertTrue([setObj1.stringObj.allObjects[0] isEqualToString:@"five"]); XCTAssertTrue([setObj1.stringObj.allObjects[1] isEqualToString:@"four"]); XCTAssertEqual(setObj1.stringObj.count, 2U); XCTAssertEqual(setObj2.stringObj.count, 0U); } - (void)testDeleteObjectInUnmanagedSet { SetPropertyObject *set = [[SetPropertyObject alloc] init]; set.name = @"name"; StringObject *stringObj1 = [[StringObject alloc] init]; stringObj1.stringCol = @"a"; StringObject *stringObj2 = [[StringObject alloc] init]; stringObj2.stringCol = @"b"; StringObject *stringObj3 = [[StringObject alloc] init]; stringObj3.stringCol = @"c"; [set.set addObject:stringObj1]; [set.set addObject:stringObj2]; [set.set addObject:stringObj3]; IntObject *intObj1 = [[IntObject alloc] init]; intObj1.intCol = 0; IntObject *intObj2 = [[IntObject alloc] init]; intObj2.intCol = 1; IntObject *intObj3 = [[IntObject alloc] init]; intObj3.intCol = 2; [set.intSet addObject:intObj1]; [set.intSet addObject:intObj2]; [set.intSet addObject:intObj3]; XCTAssertTrue([set.set containsObject:stringObj1]); XCTAssertTrue([set.set containsObject:stringObj2]); XCTAssertTrue([set.set containsObject:stringObj3]); XCTAssertEqual(set.set.count, 3U, @"Should have 3 elements in string set"); XCTAssertTrue([set.intSet containsObject:intObj1]); XCTAssertTrue([set.intSet containsObject:intObj2]); XCTAssertTrue([set.intSet containsObject:intObj3]); XCTAssertEqual(set.intSet.count, 3U, @"Should have 3 elements in int set"); [set.set removeObject:stringObj3]; XCTAssertTrue([set.set containsObject:stringObj1]); XCTAssertTrue([set.set containsObject:stringObj1]); XCTAssertEqual(set.set.count, 2U, @"Should have 2 elements in string set"); [set.set removeObject:stringObj2]; XCTAssertEqualObjects(set.set.allObjects[0], stringObj1, @"Objects should be equal"); XCTAssertEqual(set.set.count, 1U, @"Should have 1 elements in string set"); [set.set removeObject:stringObj1]; XCTAssertEqual(set.set.count, 0U, @"Should have 0 elements in string set"); [set.intSet removeAllObjects]; XCTAssertEqual(set.intSet.count, 0U, @"Should have 0 elements in int set"); } - (void)testUnmanagedSetSort { AllPrimitiveSets *setObj1 = [AllPrimitiveSets new]; XCTAssertThrows([setObj1.stringObj sortedResultsUsingKeyPath:@"age" ascending:YES]); XCTAssertThrows([setObj1.stringObj sortedResultsUsingDescriptors:@[]]); } - (void)testPopulateEmptySet { RLMRealm *r = [self realmWithTestPath]; [r beginWriteTransaction]; SetPropertyObject *setObj1 = [SetPropertyObject new]; XCTAssertNotNil(setObj1.set, @"Should be able to get an empty set"); XCTAssertEqual(setObj1.set.count, 0U, @"Should start with no set elements"); StringObject *str1 = [StringObject createInRealm:r withValue:@[@"a"]]; StringObject *str2 = [StringObject createInRealm:r withValue:@[@"b"]]; StringObject *str3 = [StringObject createInRealm:r withValue:@[@"c"]]; IntObject *int1 = [IntObject createInRealm:r withValue:@[@1]]; IntObject *int2 = [IntObject createInRealm:r withValue:@[@2]]; IntObject *int3 = [IntObject createInRealm:r withValue:@[@3]]; [setObj1.set addObjects:@[str1, str2, str3, str1, str2, str3]]; [setObj1.intSet addObjects:@[int1, int2, int3, int1, int2, int3]]; [r addObject:setObj1]; [r commitWriteTransaction]; RLMResults *results = [SetPropertyObject allObjectsInRealm:r]; XCTAssertFalse(setObj1.isInvalidated); XCTAssertEqual(results.count, 1U); XCTAssertEqual(results[0].set.count, 3U); XCTAssertEqualObjects(results[0].set, setObj1.set); XCTAssertEqual(results[0].intSet.count, 3U); XCTAssertEqualObjects(results[0].intSet, setObj1.intSet); RLMSet *setProp = setObj1.set; RLMAssertThrowsWithReasonMatching([setProp addObject:(id)@"another one"], @"write transaction"); // make sure we can fast enumerate for (RLMObject *obj in setObj1.set) { XCTAssertTrue(obj.description.length, @"Object should have description"); } } -(void)testModifyDetatchedSet { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; SetPropertyObject *setObj = [SetPropertyObject createInRealm:realm withValue:@[@"setObject", @[], @[]]]; XCTAssertNotNil(setObj.set, @"Should be able to get an empty set"); XCTAssertEqual(setObj.set.count, 0U, @"Should start with no set elements"); StringObject *obj = [[StringObject alloc] init]; obj.stringCol = @"a"; RLMSet *set = setObj.set; [set addObject:obj]; [set addObject:[StringObject createInRealm:realm withValue:@[@"b"]]]; [realm commitWriteTransaction]; XCTAssertEqual(set.count, 2U, @"Should have two elements in set"); XCTAssertEqualObjects([set.allObjects[0] stringCol], @"a", @"First element should have property value 'a'"); XCTAssertEqualObjects([setObj.set.allObjects[1] stringCol], @"b", @"Second element should have property value 'b'"); RLMAssertThrowsWithReasonMatching([set addObject:obj], @"write transaction"); } - (void)testDeleteUnmanagedObjectWithSetProperty { AllPrimitiveSets *setObj = [AllPrimitiveSets new]; [setObj.stringObj addObject:@"a"]; RLMSet *stringSet = setObj.stringObj; XCTAssertFalse(stringSet.isInvalidated, @"stringObj should be valid after creation."); setObj = nil; XCTAssertFalse(stringSet.isInvalidated, @"stringObj should still be valid after parent deletion."); } - (void)testDeleteObjectWithSetProperty { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; SetPropertyObject *setObj = [SetPropertyObject createInRealm:realm withValue:@[@"setObject", @[@[@"a"]], @[]]]; RLMSet *stringSet = setObj.set; XCTAssertFalse(stringSet.isInvalidated, @"stringSet should be valid after creation."); [realm deleteObject:setObj]; XCTAssertTrue(stringSet.isInvalidated, @"stringSet should be invalid after parent deletion."); [realm commitWriteTransaction]; } - (void)testDeleteObjectInSetProperty { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; SetPropertyObject *setObj = [SetPropertyObject createInRealm:realm withValue:@[@"setObject", @[@[@"a"]], @[]]]; RLMSet *stringSet = setObj.set; StringObject *firstObject = stringSet.allObjects[0]; [realm deleteObjects:[StringObject allObjectsInRealm:realm]]; XCTAssertFalse(stringSet.isInvalidated, @"stringSet should be valid after member object deletion."); XCTAssertTrue(firstObject.isInvalidated, @"firstObject should be invalid after deletion."); XCTAssertEqual(stringSet.count, 0U, @"stringSet.count should be zero after deleting its only member."); [realm commitWriteTransaction]; } -(void)testInsertMultiple { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; SetPropertyObject *obj = [SetPropertyObject createInRealm:realm withValue:@[@"setObject", @[], @[]]]; StringObject *child1 = [StringObject createInRealm:realm withValue:@[@"a"]]; StringObject *child2 = [[StringObject alloc] init]; child2.stringCol = @"b"; [obj.set addObjects:@[child2, child1]]; [realm commitWriteTransaction]; RLMResults *children = [StringObject allObjectsInRealm:realm]; XCTAssertEqualObjects([children[0] stringCol], @"a", @"First child should be 'a'"); XCTAssertEqualObjects([children[1] stringCol], @"b", @"Second child should be 'b'"); } - (void)testAddInvalidated { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; [realm addObject:person]; [realm deleteObjects:[EmployeeObject allObjects]]; RLMAssertThrowsWithReasonMatching([company.employeeSet addObject:person], @"invalidated"); [realm cancelWriteTransaction]; } - (void)testAddNil { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; CompanyObject *company = [CompanyObject createInDefaultRealmWithValue:@[@"company", @[]]]; RLMAssertThrowsWithReason([company.employeeSet addObject:self.nonLiteralNil], @"Invalid nil value for set of 'EmployeeObject'."); [realm cancelWriteTransaction]; } - (void)testUnmanaged { RLMRealm *realm = [self realmWithTestPath]; SetPropertyObject *setObj = [[SetPropertyObject alloc] init]; setObj.name = @"name"; XCTAssertNotNil(setObj.set, @"RLMSet property should get created on access"); XCTAssertEqual(setObj.set.count, 0U, @"No objects added yet"); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; [setObj.set addObject:obj1]; [setObj.set addObject:obj2]; [setObj.set addObject:obj3]; XCTAssertTrue([setObj.set containsObject:obj1]); XCTAssertTrue([setObj.set containsObject:obj2]); XCTAssertTrue([setObj.set containsObject:obj3]); [realm beginWriteTransaction]; [realm addObject:setObj]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; [setObj.set removeObject:obj3]; XCTAssertEqual(setObj.set.count, 2U, @"2 objects left"); [setObj.set addObject:obj1]; [setObj.set removeAllObjects]; XCTAssertEqual(setObj.set.count, 0U, @"All objects removed"); [realm commitWriteTransaction]; SetPropertyObject *setObj2 = [[SetPropertyObject alloc] init]; IntObject *intObj = [[IntObject alloc] init]; intObj.intCol = 1; RLMAssertThrowsWithReasonMatching([setObj2.set addObject:(id)intObj], @"IntObject.*StringObject"); [setObj2.intSet addObject:intObj]; XCTAssertThrows([setObj2.intSet objectsWhere:@"intCol == 1"], @"Should throw on unmanaged RLMSet"); XCTAssertThrows(([setObj2.intSet objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol == %i", 1]]), @"Should throw on unmanaged RLMSet"); XCTAssertThrows([setObj2.intSet sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"Should throw on unmanaged RLMSet"); // test unmanaged with literals __unused SetPropertyObject *obj = [[SetPropertyObject alloc] initWithValue:@[@"n", @[], @[[[IntObject alloc] initWithValue: @[@1]]]]]; } - (void)testComparision { RLMRealm *realm = [self realmWithTestPath]; SetPropertyObject *set = [[SetPropertyObject alloc] init]; SetPropertyObject *set2 = [[SetPropertyObject alloc] init]; set.name = @"name"; set.name = @"name2"; XCTAssertNotNil(set.set, @"RLMSet property should get created on access"); XCTAssertNotNil(set2.set, @"RLMSet property should get created on access"); XCTAssertTrue([set.set isEqual:set2.set], @"Empty sets should be equal"); XCTAssertEqual(set.set.count, 0U); XCTAssertEqual(set2.set.count, 0U); StringObject *obj1 = [[StringObject alloc] init]; obj1.stringCol = @"a"; StringObject *obj2 = [[StringObject alloc] init]; obj2.stringCol = @"b"; StringObject *obj3 = [[StringObject alloc] init]; obj3.stringCol = @"c"; [set.set addObject:obj1]; [set.set addObject:obj2]; [set.set addObject:obj3]; [set2.set addObject:obj1]; [set2.set addObject:obj2]; [set2.set addObject:obj3]; XCTAssertTrue([set.set isEqual:set2.set], @"Sets should be equal"); XCTAssertTrue([set.set isEqualToSet:set2.set], @"Sets should be equal"); [set2.set removeObject:obj3]; XCTAssertFalse([set.set isEqual:set2.set], @"Sets should not be equal"); XCTAssertFalse([set.set isEqualToSet:set2.set], @"Sets should not be equal"); [set2.set addObject:obj3]; XCTAssertTrue([set.set isEqual:set2.set], @"Sets should be equal"); XCTAssertTrue([set.set isEqualToSet:set2.set], @"Sets should be equal"); [realm beginWriteTransaction]; [realm addObject:set]; [realm commitWriteTransaction]; XCTAssertFalse([set.set isEqual:set2.set], @"Comparing a managed set to an unmanaged one should fail"); XCTAssertThrows([set.set isEqualToSet:set2.set], @"Right hand side value must be a managed Set."); XCTAssertFalse([set2.set isEqual:set.set], @"Comparing a managed set to an unmanaged one should fail"); XCTAssertFalse([set2.set isEqualToSet:set.set], @"Comparing a managed set to an unmanaged one should fail"); } - (void)testUnmanagedPrimitive { AllPrimitiveSets *obj = [[AllPrimitiveSets alloc] init]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMSet class]]); [obj.intObj addObject:@1]; XCTAssertEqualObjects(obj.intObj.allObjects[0], @1); XCTAssertThrows([obj.intObj addObject:@""]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; obj = [AllPrimitiveSets createInRealm:realm withValue:@[@[],@[],@[],@[],@[],@[],@[]]]; XCTAssertTrue([obj.intObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.floatObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.doubleObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.boolObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.stringObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.dataObj isKindOfClass:[RLMSet class]]); XCTAssertTrue([obj.dateObj isKindOfClass:[RLMSet class]]); [obj.intObj addObject:@5]; XCTAssertTrue([obj.intObj containsObject:@5]); [realm cancelWriteTransaction]; } - (void)testFastEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; [realm commitWriteTransaction]; // enumerate empty set for (__unused id obj in company.employeeSet) { XCTFail(@"Should be empty"); } [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; [company.employeeSet addObject:eo]; } [realm commitWriteTransaction]; XCTAssertEqual(company.employeeSet.count, 30U); __weak id objects[30]; NSInteger count = 0; for (EmployeeObject *e in company.employeeSet) { XCTAssertNotNil(e, @"Object is not nil and accessible"); if (count > 16) { // 16 is the size of blocks fast enumeration happens to ask for at // the moment, but of course that's just an implementation detail // that may change XCTAssertNil(objects[count - 16]); } objects[count++] = e; } XCTAssertEqual(count, 30, @"should have enumerated 30 objects"); for (int i = 0; i < count; i++) { XCTAssertNil(objects[i], @"Object should have been released"); } @autoreleasepool { for (EmployeeObject *e in company.employeeSet) { objects[0] = e; break; } } XCTAssertNil(objects[0], @"Object should have been released"); } - (void)testModifyDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; const size_t totalCount = 40; for (size_t i = 0; i < totalCount; ++i) { [company.employeeSet addObject:[EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]]; } size_t count = 0; for (EmployeeObject *eo in company.employeeSet) { ++count; [company.employeeSet removeObject:eo]; } XCTAssertEqual(totalCount, count); XCTAssertEqual(0U, company.employeeSet.count); [realm cancelWriteTransaction]; // Unmanaged set company = [[CompanyObject alloc] init]; for (size_t i = 0; i < totalCount; ++i) { [company.employeeSet addObject:[[EmployeeObject alloc] initWithValue:@[@"name", @(i), @NO]]]; } count = 0; for (EmployeeObject *eo in company.employeeSet) { ++count; [company.employeeSet addObject:eo]; } XCTAssertEqual(totalCount, count); XCTAssertEqual(totalCount, company.employeeSet.count); } - (void)testDeleteDuringEnumeration { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [realm addObject:company]; const size_t totalCount = 40; for (size_t i = 0; i < totalCount; ++i) { [company.employeeSet addObject:[EmployeeObject createInRealm:realm withValue:@[@"name", @(i), @NO]]]; } [realm commitWriteTransaction]; [realm beginWriteTransaction]; for (__unused EmployeeObject *eo in company.employeeSet) { [realm deleteObjects:company.employeeSet]; } [realm commitWriteTransaction]; } - (void)testValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; XCTAssertEqual(((NSSet *)[company.employeeSet valueForKey:@"name"]).count, 0U); [realm addObject:company]; [realm commitWriteTransaction]; XCTAssertEqual(((NSSet *)[company.employeeSet valueForKey:@"name"]).count, 0U); // managed NSMutableSet *ages = [NSMutableSet set]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { [ages addObject:@(i)]; EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employeeSet addObject:eo]; } [realm commitWriteTransaction]; RLM_GENERIC_SET(EmployeeObject) *employeeObjects = [company valueForKey:@"employeeSet"]; NSMutableSet *kvcAgeProperties = [NSMutableSet set]; for (EmployeeObject *employee in employeeObjects) { [kvcAgeProperties addObject:@(employee.age)]; } XCTAssertEqualObjects(kvcAgeProperties, ages); XCTAssertEqualObjects([NSSet setWithSet:[company.employeeSet valueForKey:@"age"]], ages); XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@count"] integerValue], 30); XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@min.age"] integerValue], 0); XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@max.age"] integerValue], 29); XCTAssertEqualWithAccuracy([[company.employeeSet valueForKeyPath:@"@avg.age"] doubleValue], 14.5, 0.1f); XCTAssertEqualObjects([company.employeeSet valueForKeyPath:@"@unionOfObjects.age"], (@[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29])); XCTAssertEqualObjects([company.employeeSet valueForKeyPath:@"@distinctUnionOfObjects.name"], (@[@"Joe"])); RLMAssertThrowsWithReasonMatching([company.employeeSet valueForKeyPath:@"@sum.dogs.@sum.age"], @"Nested key paths.*not supported"); // unmanaged object company = [[CompanyObject alloc] init]; [ages removeAllObjects]; for (int i = 0; i < 30; ++i) { [ages addObject:@(i)]; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employeeSet addObject:eo]; } for (EmployeeObject *e in [[company.employeeSet valueForKey:@"self"] allObjects]) { XCTAssertTrue([company.employeeSet containsObject:e]); } XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@count"] integerValue], 30); XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@min.age"] integerValue], 0); XCTAssertEqual([[company.employeeSet valueForKeyPath:@"@max.age"] integerValue], 29); XCTAssertEqualWithAccuracy([[company.employeeSet valueForKeyPath:@"@avg.age"] doubleValue], 14.5, 0.1f); RLMAssertThrowsWithReasonMatching([company.employeeSet valueForKeyPath:@"@unionOfObjects.age"], @"this class does not implement the unionOfObjects"); RLMAssertThrowsWithReasonMatching([company.employeeSet valueForKeyPath:@"@distinctUnionOfObjects.name"], @"this class does not implement the distinctUnionOfObjects"); RLMAssertThrowsWithReasonMatching([company.employeeSet valueForKeyPath:@"@sum.dogs.@sum.age"], @"Nested key paths.*not supported"); } - (void)testSetValueForKey { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employeeSet setValue:@"name" forKey:@"name"]; XCTAssertEqual(((NSSet *)[company.employeeSet valueForKey:@"name"]).count, 0U); [realm addObject:company]; [realm commitWriteTransaction]; XCTAssertThrows([company.employeeSet setValue:@10 forKey:@"age"]); XCTAssertEqual(((NSSet *)[company.employeeSet valueForKey:@"name"]).count, 0U); // managed NSMutableSet *ages = [NSMutableSet set]; [realm beginWriteTransaction]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employeeSet addObject:eo]; } [company.employeeSet setValue:@20 forKey:@"age"]; [realm commitWriteTransaction]; XCTAssertTrue([[company.employeeSet valueForKey:@"age"] isSubsetOfSet:ages]); // unmanaged object company = [[CompanyObject alloc] init]; ages = [NSMutableSet set]; for (int i = 0; i < 30; ++i) { [ages addObject:@(20)]; EmployeeObject *eo = [[EmployeeObject alloc] initWithValue:@{@"name": @"Joe", @"age": @(i), @"hired": @YES}]; [company.employeeSet addObject:eo]; } [company.employeeSet setValue:@20 forKey:@"age"]; XCTAssertTrue([[company.employeeSet valueForKey:@"age"] isSubsetOfSet:ages]); } - (void)testObjectAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; AggregateSetObject *obj = [AggregateSetObject new]; XCTAssertEqual(0, [obj.set sumOfProperty:@"intCol"].intValue); XCTAssertNil([obj.set averageOfProperty:@"intCol"]); XCTAssertNil([obj.set minOfProperty:@"intCol"]); XCTAssertNil([obj.set maxOfProperty:@"intCol"]); NSDate *dateMinInput = [NSDate date]; NSDate *dateMaxInput = [dateMinInput dateByAddingTimeInterval:1000]; [realm transactionWithBlock:^{ [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@1, @0.0f, @2.5, @NO, dateMaxInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [AggregateObject createInRealm:realm withValue:@[@0, @1.2f, @0.0, @YES, dateMinInput]]; [obj.set addObjects:[AggregateObject allObjectsInRealm:realm]]; }]; void (^test)(void) = ^{ RLMSet *set = obj.set; // SUM XCTAssertEqual([set sumOfProperty:@"intCol"].integerValue, 4); XCTAssertEqualWithAccuracy([set sumOfProperty:@"floatCol"].floatValue, 7.2f, 0.1f); XCTAssertEqualWithAccuracy([set sumOfProperty:@"doubleCol"].doubleValue, 10.0, 0.1f); RLMAssertThrowsWithReasonMatching([set sumOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([set sumOfProperty:@"boolCol"], @"sum.*bool"); RLMAssertThrowsWithReasonMatching([set sumOfProperty:@"dateCol"], @"sum.*date"); // Average XCTAssertEqualWithAccuracy([set averageOfProperty:@"intCol"].doubleValue, 0.4, 0.1f); XCTAssertEqualWithAccuracy([set averageOfProperty:@"floatCol"].doubleValue, 0.72, 0.1f); XCTAssertEqualWithAccuracy([set averageOfProperty:@"doubleCol"].doubleValue, 1.0, 0.1f); RLMAssertThrowsWithReasonMatching([set averageOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([set averageOfProperty:@"boolCol"], @"average.*bool"); RLMAssertThrowsWithReasonMatching([set averageOfProperty:@"dateCol"], @"average.*date"); // MIN XCTAssertEqual(0, [[set minOfProperty:@"intCol"] intValue]); XCTAssertEqual(0.0f, [[set minOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(0.0, [[set minOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMinInput, [set minOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([set minOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([set minOfProperty:@"boolCol"], @"min.*bool"); // MAX XCTAssertEqual(1, [[set maxOfProperty:@"intCol"] intValue]); XCTAssertEqual(1.2f, [[set maxOfProperty:@"floatCol"] floatValue]); XCTAssertEqual(2.5, [[set maxOfProperty:@"doubleCol"] doubleValue]); XCTAssertEqualObjects(dateMaxInput, [set maxOfProperty:@"dateCol"]); RLMAssertThrowsWithReasonMatching([set maxOfProperty:@"foo"], @"foo.*AggregateObject"); RLMAssertThrowsWithReasonMatching([set maxOfProperty:@"boolCol"], @"max.*bool"); }; test(); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; test(); } - (void)testRenamedPropertyAggregate { RLMRealm *realm = [RLMRealm defaultRealm]; LinkToRenamedProperties1 *obj = [LinkToRenamedProperties1 new]; XCTAssertEqual(0, [obj.set sumOfProperty:@"propA"].intValue); XCTAssertNil([obj.set averageOfProperty:@"propA"]); XCTAssertNil([obj.set minOfProperty:@"propA"]); XCTAssertNil([obj.set maxOfProperty:@"propA"]); XCTAssertThrows([obj.set sumOfProperty:@"prop 1"]); [realm transactionWithBlock:^{ [RenamedProperties1 createInRealm:realm withValue:@[@1, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@2, @""]]; [RenamedProperties1 createInRealm:realm withValue:@[@3, @""]]; [obj.set addObjects:[RenamedProperties1 allObjectsInRealm:realm]]; }]; XCTAssertEqual(6, [obj.set sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.set averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.set minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.set maxOfProperty:@"propA"] intValue]); [realm transactionWithBlock:^{ [realm addObject:obj]; }]; XCTAssertEqual(6, [obj.set sumOfProperty:@"propA"].intValue); XCTAssertEqual(2.0, [obj.set averageOfProperty:@"propA"].doubleValue); XCTAssertEqual(1, [[obj.set minOfProperty:@"propA"] intValue]); XCTAssertEqual(3, [[obj.set maxOfProperty:@"propA"] intValue]); } -(void)testRenamedPropertyObservation { RLMRealm *realm = self.realmWithTestPath; __block LinkToRenamedProperties *obj; [realm transactionWithBlock:^{ obj = [LinkToRenamedProperties createInRealm:realm withValue:@[]]; RenamedProperties *linkedObject = [RenamedProperties createInRealm:realm withValue:@[@1, @""]]; [obj.set addObjects:@[linkedObject]]; }]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [obj.set addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; } keyPaths:@[@"stringCol"]]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMSet *set = [(LinkToRenamedProperties *)[LinkToRenamedProperties allObjectsInRealm:realm].firstObject set]; [set setValue:@"newValue" forKey:@"stringCol"]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testValueForCollectionOperationKeyPath { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *e1 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"A", @"age": @20, @"hired": @YES}]; EmployeeObject *e2 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"B", @"age": @30, @"hired": @NO}]; EmployeeObject *e3 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"C", @"age": @40, @"hired": @YES}]; EmployeeObject *e4 = [PrimaryEmployeeObject createInRealm:realm withValue:@{@"name": @"D", @"age": @50, @"hired": @YES}]; PrimaryCompanyObject *c1 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG", @"employeeSet": @[e1, e2, e3, e2]}]; PrimaryCompanyObject *c2 = [PrimaryCompanyObject createInRealm:realm withValue:@{@"name": @"ABC AG 2", @"employeeSet": @[e1, e4]}]; SetOfPrimaryCompanies *companies = [SetOfPrimaryCompanies createInRealm:realm withValue:@[@[c1, c2]]]; [realm commitWriteTransaction]; // count operator XCTAssertEqual([[c1.employeeSet valueForKeyPath:@"@count"] integerValue], 3); // numeric operators XCTAssertEqual([[c1.employeeSet valueForKeyPath:@"@min.age"] intValue], 20); XCTAssertEqual([[c1.employeeSet valueForKeyPath:@"@max.age"] intValue], 40); XCTAssertEqual([[c1.employeeSet valueForKeyPath:@"@sum.age"] integerValue], 90); XCTAssertEqualWithAccuracy([[c1.employeeSet valueForKeyPath:@"@avg.age"] doubleValue], 30, 0.1f); // collection XCTAssertEqualObjects([c1.employeeSet valueForKeyPath:@"@unionOfObjects.name"], (@[@"A", @"B", @"C"])); XCTAssertEqualObjects([[c1.employeeSet valueForKeyPath:@"@distinctUnionOfObjects.name"] sortedArrayUsingSelector:@selector(compare:)], (@[@"A", @"B", @"C"])); XCTAssertEqualObjects([NSSet setWithArray:[companies.companies valueForKeyPath:@"@unionOfArrays.employeeSet"]], ([NSSet setWithArray:@[e1, e2, e3, e4]])); NSComparator cmp = ^NSComparisonResult(id obj1, id obj2) { return [[obj1 name] compare:[obj2 name]]; }; XCTAssertThrows([[companies.companies valueForKeyPath:@"@distinctUnionOfSets.employees"] sortedArrayUsingComparator:cmp]); // invalid key paths RLMAssertThrowsWithReasonMatching([c1.employeeSet valueForKeyPath:@"@invalid.name"], @"Unsupported KVC collection operator found in key path '@invalid.name'"); RLMAssertThrowsWithReasonMatching([c1.employeeSet valueForKeyPath:@"@sum"], @"Missing key path for KVC collection operator sum in key path '@sum'"); RLMAssertThrowsWithReasonMatching([c1.employeeSet valueForKeyPath:@"@sum."], @"Missing key path for KVC collection operator sum in key path '@sum.'"); RLMAssertThrowsWithReasonMatching([c1.employeeSet valueForKeyPath:@"@sum.employees.@sum.age"], @"Nested key paths.*not supported"); } - (void)testCrossThreadAccess { CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; EmployeeObject *eo = [[EmployeeObject alloc] init]; eo.name = @"Joe"; eo.age = 40; eo.hired = YES; [company.employeeSet addObject:eo]; RLMSet *employees = company.employeeSet; // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(company.employeeSet); XCTAssertNoThrow([employees allObjects]); }]; RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [realm addObject:company]; [realm commitWriteTransaction]; employees = company.employeeSet; XCTAssertNoThrow(company.employeeSet); XCTAssertNoThrow([employees allObjects]); [self dispatchAsyncAndWait:^{ XCTAssertThrows(company.employeeSet); XCTAssertThrows([employees allObjects]); }]; } - (void)testSortByNoColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; RLMSet *set = [DogSetObject createInDefaultRealmWithValue:@[@[a2, b1, a1, b2]]].dogs; [realm commitWriteTransaction]; RLMResults *notActuallySorted = [set sortedResultsUsingDescriptors:@[]]; XCTAssertTrue([set.allObjects[0] isEqualToObject:notActuallySorted[0]]); XCTAssertTrue([set.allObjects[1] isEqualToObject:notActuallySorted[1]]); XCTAssertTrue([set.allObjects[2] isEqualToObject:notActuallySorted[2]]); XCTAssertTrue([set.allObjects[3] isEqualToObject:notActuallySorted[3]]); } - (void)testSortByMultipleColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; DogSetObject *set = [DogSetObject createInDefaultRealmWithValue:@[@[a1, a2, b1, b2]]]; [realm commitWriteTransaction]; bool (^checkOrder)(NSArray *, NSArray *, NSArray *) = ^bool(NSArray *properties, NSArray *ascending, NSArray *dogs) { NSArray *sort = @[[RLMSortDescriptor sortDescriptorWithKeyPath:properties[0] ascending:[ascending[0] boolValue]], [RLMSortDescriptor sortDescriptorWithKeyPath:properties[1] ascending:[ascending[1] boolValue]]]; RLMResults *actual = [set.dogs sortedResultsUsingDescriptors:sort]; return [actual[0] isEqualToObject:dogs[0]] && [actual[1] isEqualToObject:dogs[1]] && [actual[2] isEqualToObject:dogs[2]] && [actual[3] isEqualToObject:dogs[3]]; }; // Check each valid sort XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @YES], @[a1, a2, b1, b2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @NO], @[a2, a1, b2, b1])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @YES], @[b1, b2, a1, a2])); XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @NO], @[b2, b1, a2, a1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @YES], @[a1, b1, a2, b2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @NO], @[b1, a1, b2, a2])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @YES], @[a2, b2, a1, b1])); XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @NO], @[b2, a2, b1, a1])); } - (void)testSortByRenamedColumns { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; id value = @{@"set": @[@[@1, @"c"], @[@2, @"b"], @[@3, @"a"]]}; LinkToRenamedProperties *obj = [LinkToRenamedProperties createInRealm:realm withValue:value]; XCTAssertEqualObjects([[obj.set sortedResultsUsingKeyPath:@"intCol" ascending:YES] valueForKeyPath:@"intCol"], (@[@1, @2, @3])); XCTAssertEqualObjects([[obj.set sortedResultsUsingKeyPath:@"intCol" ascending:NO] valueForKeyPath:@"intCol"], (@[@3, @2, @1])); [realm cancelWriteTransaction]; } - (void)testDeleteLinksAndObjectsInSet { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *po1 = [EmployeeObject createInRealm:realm withValue:@[@"Joe", @40, @YES]]; EmployeeObject *po2 = [EmployeeObject createInRealm:realm withValue:@[@"John", @30, @NO]]; EmployeeObject *po3 = [EmployeeObject createInRealm:realm withValue:@[@"Jill", @25, @YES]]; CompanyObject *company = [[CompanyObject alloc] init]; company.name = @"name"; [company.employeeSet addObjects:[EmployeeObject allObjects]]; [realm addObject:company]; [realm commitWriteTransaction]; RLMSet *peopleInCompany = company.employeeSet; // Delete link to employee XCTAssertThrowsSpecificNamed([peopleInCompany removeObject:po2], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertEqual(peopleInCompany.count, 3U, @"No links should have been deleted"); [realm beginWriteTransaction]; XCTAssertNoThrow([peopleInCompany removeObject:po3], @"Should delete link to employee"); [realm commitWriteTransaction]; XCTAssertEqual(peopleInCompany.count, 2U, @"link deleted when accessing via links"); EmployeeObject *test = peopleInCompany.allObjects[0]; XCTAssertEqual(test.age, po1.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po1.name, @"Should be equal"); XCTAssertEqual(test.hired, po1.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po1], @"Should be equal"); test = peopleInCompany.allObjects[1]; XCTAssertEqual(test.age, po2.age, @"Should be equal"); XCTAssertEqualObjects(test.name, po2.name, @"Should be equal"); XCTAssertEqual(test.hired, po2.hired, @"Should be equal"); XCTAssertTrue([test isEqualToObject:po2], @"Should be equal"); XCTAssertThrowsSpecificNamed([peopleInCompany removeObject:po3], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed([peopleInCompany removeAllObjects], NSException, @"RLMException", @"Not allowed in read transaction"); XCTAssertThrowsSpecificNamed([peopleInCompany addObject:po2], NSException, @"RLMException", @"Not allowed in read transaction"); [realm beginWriteTransaction]; XCTAssertNoThrow([peopleInCompany removeObject:po3], @"Should delete last link"); XCTAssertEqual(peopleInCompany.count, 2U, @"2 remaining links"); [peopleInCompany removeObject:po2]; XCTAssertEqual(peopleInCompany.count, 1U, @"1 link replaced"); [peopleInCompany addObject:po1]; XCTAssertEqual(peopleInCompany.count, 1U, @"1 link"); XCTAssertNoThrow([peopleInCompany removeAllObjects], @"Should delete all links"); XCTAssertEqual(peopleInCompany.count, 0U, @"0 remaining links"); [realm commitWriteTransaction]; RLMResults *allPeople = [EmployeeObject allObjects]; XCTAssertEqual(allPeople.count, 3U, @"Only links should have been deleted, not the employees"); } - (void)testSetDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; RLMSet *employees = [CompanyObject createInDefaultRealmWithValue:@[@"company"]].employeeSet; RLMSet *ints = [AllPrimitiveSets createInDefaultRealmWithValue:@[]].intObj; for (NSInteger i = 0; i < 1012; ++i) { EmployeeObject *person = [[EmployeeObject alloc] init]; person.name = @"Mary"; person.age = 24; person.hired = YES; [employees addObject:person]; [ints addObject:@(i + 100)]; } [realm commitWriteTransaction]; RLMAssertMatches(employees.description, @"(?s)RLMSet\\ \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[0\\] EmployeeObject \\{\n" @"\t\tname = Mary;\n" @"\t\tage = 24;\n" @"\t\thired = 1;\n" @"\t\\},\n" @".*\n" @"\t... 912 objects skipped.\n" @"\\)"); RLMAssertMatches(ints.description, @"(?s)RLMSet\\ \\<0x[a-z0-9]+\\> \\(\n" @"\t\\[0\\] 100,\n" @"\t\\[1\\] 101,\n" @"\t\\[2\\] 102,\n" @".*\n" @"\t... 912 objects skipped.\n" @"\\)"); } - (void)testUnmanagedAssignment { IntObject *io1 = [[IntObject alloc] init]; IntObject *io2 = [[IntObject alloc] init]; IntObject *io3 = [[IntObject alloc] init]; SetPropertyObject *set1 = [[SetPropertyObject alloc] init]; SetPropertyObject *set2 = [[SetPropertyObject alloc] init]; set1.intSet = (id)@[io1, io2]; set2.intSet = (id)@[io1, io2]; XCTAssertEqualObjects([set1.intSet valueForKey:@"self"], [set2.intSet valueForKey:@"self"]); [set1 setValue:@[io3, io1] forKey:@"intSet"]; [set2 setValue:@[io3, io1] forKey:@"intSet"]; XCTAssertEqualObjects([set1.intSet valueForKey:@"self"], [set2.intSet valueForKey:@"self"]); set1[@"intSet"] = @[io2, io3]; set2[@"intSet"] = @[io2, io3]; XCTAssertEqualObjects([set1.intSet valueForKey:@"self"], [set2.intSet valueForKey:@"self"]); // Assigning RLMSet shallow copies set2.intSet = set1.intSet; XCTAssertEqualObjects([set2.intSet valueForKey:@"self"], [set1.intSet valueForKey:@"self"]); [set1.intSet removeAllObjects]; [set2.intSet removeAllObjects]; XCTAssertEqualObjects([set2.intSet valueForKey:@"self"], [set1.intSet valueForKey:@"self"]); } - (void)testManagedAssignment { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; IntObject *io1 = [IntObject createInRealm:realm withValue:@[@1]]; IntObject *io2 = [IntObject createInRealm:realm withValue:@[@2]]; IntObject *io3 = [IntObject createInRealm:realm withValue:@[@3]]; SetPropertyObject *set1 = [SetPropertyObject createInRealm:realm withValue:@[@""]]; SetPropertyObject *set2 = [SetPropertyObject createInRealm:realm withValue:@[@""]]; set1.intSet = (id)@[io1, io2]; XCTAssertEqualObjects([set1.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@1, @2]])); [set1 setValue:@[io3, io1] forKey:@"intSet"]; XCTAssertEqualObjects([set1.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@1, @3]])); set1[@"intSet"] = (id)@[io2, io3]; XCTAssertEqualObjects([set1.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@2, @3]])); // Assigning RLMSet shallow copies set2.intSet = set1.intSet; XCTAssertEqualObjects([set2.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@3, @2]])); [set1.intSet removeAllObjects]; XCTAssertEqualObjects([set2.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@3, @2]])); // Self-assignment is a no-op set2.intSet = set2.intSet; XCTAssertEqualObjects([set2.intSet valueForKey:@"intCol"], ([NSSet setWithArray:@[@3, @2]])); set2[@"intSet"] = set2[@"intSet"]; XCTAssertEqualObjects([set2[@"intSet"] valueForKey:@"intCol"], ([NSSet setWithArray:@[@3, @2]])); [realm cancelWriteTransaction]; } - (void)testAssignIncorrectType { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[@[@"a"]], @[@[@0]]]]; RLMAssertThrowsWithReason(set.intSet = (id)set.set, @"RLMSet does not match expected type 'IntObject' for property 'SetPropertyObject.intSet'."); RLMAssertThrowsWithReason(set[@"intSet"] = set[@"set"], @"RLMSet does not match expected type 'IntObject' for property 'SetPropertyObject.intSet'."); [realm cancelWriteTransaction]; } - (void)testNotificationSentInitially { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [set.intSet addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); XCTAssertNil(change); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentAfterCommit { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block bool first = true; __block id expectation = [self expectationWithDescription:@""]; id token = [set.set addNotificationBlock:^(RLMSet *set, RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); XCTAssert(first ? !change : !!change); XCTAssertNil(error); first = false; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; expectation = [self expectationWithDescription:@""]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMSet *set = ((SetPropertyObject *)[SetPropertyObject allObjectsInRealm:realm].firstObject).set; [set addObject:[[StringObject alloc] init]]; }]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationNotSentForUnrelatedChange { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; id expectation = [self expectationWithDescription:@""]; id token = [set.intSet addNotificationBlock:^(__unused RLMSet *set, __unused RLMCollectionChange *change, __unused NSError *error) { // will throw if it's incorrectly called a second time due to the // unrelated write transaction [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; }]; }]; }]; [(RLMNotificationToken *)token invalidate]; } - (void)testNotificationSentOnlyForActualRefresh { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [set.set addNotificationBlock:^(RLMSet *set, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); XCTAssertNil(error); // will throw if it's called a second time before we create the new // expectation object immediately before manually refreshing [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; // Turn off autorefresh, so the background commit should not result in a notification realm.autorefresh = NO; // All notification blocks are called as part of a single runloop event, so // waiting for this one also waits for the above one to get a chance to run [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{ [self dispatchAsyncAndWait:^{ RLMRealm *realm = self.realmWithTestPath; [realm transactionWithBlock:^{ RLMSet *set = ((SetPropertyObject *)[SetPropertyObject allObjectsInRealm:realm].firstObject).set; [set addObject:[[StringObject alloc] init]]; }]; }]; }]; expectation = [self expectationWithDescription:@""]; [realm refresh]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [(RLMNotificationToken *)token invalidate]; } - (void)testDeletingObjectWithNotificationsRegistered { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; SetPropertyObject *set = [SetPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]]; [realm commitWriteTransaction]; __block id expectation = [self expectationWithDescription:@""]; id token = [set.set addNotificationBlock:^(RLMSet *set, __unused RLMCollectionChange *change, NSError *error) { XCTAssertNotNil(set); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2.0 handler:nil]; [realm beginWriteTransaction]; [realm deleteObject:set]; [realm commitWriteTransaction]; [(RLMNotificationToken *)token invalidate]; } static RLMSet *managedTestSet(void) { RLMRealm *realm = [RLMRealm defaultRealm]; __block RLMSet *set; [realm transactionWithBlock:^{ SetPropertyObject *obj = [SetPropertyObject createInDefaultRealmWithValue:@[@"", @[], @[@[@0], @[@1]]]]; set = obj.intSet; }]; return set; } - (void)testAllMethodsCheckThread { RLMSet *set = managedTestSet(); IntObject *io = set.allObjects[0]; RLMRealm *realm = set.realm; [realm beginWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReasonMatching([set count], @"thread"); RLMAssertThrowsWithReasonMatching([set allObjects], @"thread"); RLMAssertThrowsWithReasonMatching([set addObject:io], @"thread"); RLMAssertThrowsWithReasonMatching([set addObjects:@[io]], @"thread"); RLMAssertThrowsWithReasonMatching([set removeAllObjects], @"thread"); RLMAssertThrowsWithReasonMatching([set objectsWhere:@"intCol = 0"], @"thread"); RLMAssertThrowsWithReasonMatching([set objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"thread"); RLMAssertThrowsWithReasonMatching([set sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"thread"); RLMAssertThrowsWithReasonMatching([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"thread"); RLMAssertThrowsWithReasonMatching(set.allObjects[0], @"thread"); RLMAssertThrowsWithReasonMatching([set valueForKey:@"intCol"], @"thread"); RLMAssertThrowsWithReasonMatching([set setValue:@1 forKey:@"intCol"], @"thread"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in set);}), @"thread"); }]; [realm cancelWriteTransaction]; } - (void)testAllMethodsCheckForInvalidation { RLMSet *set = managedTestSet(); IntObject *io = set.allObjects[0]; RLMRealm *realm = set.realm; [realm beginWriteTransaction]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); XCTAssertNoThrow([set count]); XCTAssertNoThrow([set allObjects]); XCTAssertNoThrow([set addObject:io]); XCTAssertNoThrow([set addObjects:@[io]]); XCTAssertNoThrow([set removeObject:io]); XCTAssertNoThrow([set removeAllObjects]); [set addObjects:@[io, io, io]]; XCTAssertNoThrow([set objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([set objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([set sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow(set.allObjects[0]); XCTAssertNoThrow([set valueForKey:@"intCol"]); XCTAssertNoThrow([set setValue:@1 forKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in set);})); [realm cancelWriteTransaction]; [realm invalidate]; [realm beginWriteTransaction]; io = [IntObject createInDefaultRealmWithValue:@[@0]]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); RLMAssertThrowsWithReasonMatching([set count], @"invalidated"); RLMAssertThrowsWithReasonMatching([set allObjects], @"invalidated"); RLMAssertThrowsWithReasonMatching([set addObject:io], @"invalidated"); RLMAssertThrowsWithReasonMatching([set addObjects:@[io]], @"invalidated"); RLMAssertThrowsWithReasonMatching([set removeObject:io], @"invalidated"); RLMAssertThrowsWithReasonMatching([set removeAllObjects], @"invalidated"); RLMAssertThrowsWithReasonMatching([set objectsWhere:@"intCol = 0"], @"invalidated"); RLMAssertThrowsWithReasonMatching([set objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]], @"invalidated"); RLMAssertThrowsWithReasonMatching([set sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"invalidated"); RLMAssertThrowsWithReasonMatching([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]], @"invalidated"); RLMAssertThrowsWithReasonMatching(set.allObjects[0], @"invalidated"); RLMAssertThrowsWithReasonMatching([set valueForKey:@"intCol"], @"invalidated"); RLMAssertThrowsWithReasonMatching([set setValue:@1 forKey:@"intCol"], @"invalidated"); RLMAssertThrowsWithReasonMatching(({for (__unused id obj in set);}), @"invalidated"); [realm cancelWriteTransaction]; } - (void)testMutatingMethodsCheckForWriteTransaction { RLMSet *set = managedTestSet(); IntObject *io = set.allObjects[0]; XCTAssertNoThrow([set objectClassName]); XCTAssertNoThrow([set realm]); XCTAssertNoThrow([set isInvalidated]); XCTAssertNoThrow([set count]); XCTAssertNoThrow([set allObjects]); XCTAssertNoThrow([set objectsWhere:@"intCol = 0"]); XCTAssertNoThrow([set objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol = 0"]]); XCTAssertNoThrow([set sortedResultsUsingKeyPath:@"intCol" ascending:YES]); XCTAssertNoThrow([set sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:@"intCol" ascending:YES]]]); XCTAssertNoThrow([set valueForKey:@"intCol"]); XCTAssertNoThrow(({for (__unused id obj in set);})); RLMAssertThrowsWithReasonMatching([set addObject:io], @"write transaction"); RLMAssertThrowsWithReasonMatching([set addObjects:@[io]], @"write transaction"); RLMAssertThrowsWithReasonMatching([set removeAllObjects], @"write transaction"); RLMAssertThrowsWithReasonMatching([set setValue:@1 forKey:@"intCol"], @"write transaction"); } - (void)testIsFrozen { RLMSet *unfrozen = managedTestSet(); RLMSet *frozen = [unfrozen freeze]; XCTAssertFalse(unfrozen.isFrozen); XCTAssertTrue(frozen.isFrozen); } - (void)testFreezingFrozenObjectReturnsSelf { RLMSet *set = managedTestSet(); RLMSet *frozen = [set freeze]; XCTAssertNotEqual(set, frozen); XCTAssertNotEqual(set.freeze, frozen); XCTAssertEqual(frozen, frozen.freeze); } - (void)testFreezeFromWrongThread { RLMSet *set = managedTestSet(); [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason([set freeze], @"Realm accessed from incorrect thread"); }]; } - (void)testAccessFrozenFromDifferentThread { RLMSet *frozen = [managedTestSet() freeze]; [self dispatchAsyncAndWait:^{ XCTAssertEqualObjects([(NSSet *)[frozen valueForKey:@"intCol"] allObjects], (@[@0, @1])); }]; } - (void)testObserveFrozenSet { RLMSet *frozen = [managedTestSet() freeze]; id block = ^(__unused BOOL deleted, __unused NSArray *changes, __unused NSError *error) {}; RLMAssertThrowsWithReason([frozen addNotificationBlock:block], @"Frozen Realms do not change and do not have change notifications."); } - (void)testQueryFrozenSet { RLMSet *frozen = [managedTestSet() freeze]; XCTAssertEqualObjects([[frozen objectsWhere:@"intCol > 0"] valueForKey:@"intCol"], (@[@1])); } - (void)testFrozenSetsDoNotUpdate { RLMSet *set = managedTestSet(); RLMSet *frozen = [set freeze]; XCTAssertEqual(frozen.count, 2); [set.realm transactionWithBlock:^{ [set removeObject:set.allObjects[0]]; }]; XCTAssertEqual(frozen.count, 2); } @end ================================================ FILE: Realm/Tests/Swift/RLMTestCaseUtils.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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. // //////////////////////////////////////////////////////////////////////////// #if canImport(RealmTestSupport) import RealmTestSupport #endif extension RLMTestCase { func assertThrowsWithReasonMatching(_ block: @autoclosure @escaping () -> T, _ regexString: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { RLMAssertThrowsWithReasonMatchingSwift(self, { _ = block() }, regexString, message, fileName, lineNumber) } } ================================================ FILE: Realm/Tests/Swift/RealmObjcSwiftTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Realm/Tests/Swift/Swift-Tests-Bridging-Header.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestObjects.h" #import "RLMMultiProcessTestCase.h" #import "TestUtils.h" ================================================ FILE: Realm/Tests/Swift/SwiftArrayPropertyTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif extension RLMObject { @discardableResult class func create(in realm: RLMRealm, withValue value: [Any]) -> Self { create(in: realm, withValue: value as Any) } @discardableResult class func createInDefaultRealm(withValue value: [Any]) -> Self { createInDefaultRealm(withValue: value as Any) } @discardableResult class func createOrUpdate(in realm: RLMRealm, withValue value: [Any]) -> Self { create(in: realm, withValue: value as Any) } @discardableResult class func createOrUpdateInDefaultRealm(withValue value: [Any]) -> Self { createOrUpdateInDefaultRealm(withValue: value as Any) } } class SwiftRLMArrayPropertyTests: RLMTestCase { // Swift models func testBasicArray() { let string = SwiftRLMStringObject() string.stringCol = "string" let realm = realmWithTestPath() realm.beginWriteTransaction() realm.add(string) try! realm.commitWriteTransaction() XCTAssertEqual(SwiftRLMStringObject.allObjects(in: realm).count, UInt(1), "There should be a single SwiftRLMStringObject in the realm") let array = SwiftRLMArrayPropertyObject() array.name = "arrayObject" array.array.add(string) XCTAssertEqual(array.array.count, UInt(1)) XCTAssertEqual(array.array.firstObject()!.stringCol, "string") realm.beginWriteTransaction() realm.add(array) array.array.add(string) try! realm.commitWriteTransaction() let arrayObjects = SwiftRLMArrayPropertyObject.allObjects(in: realm) as! RLMResults XCTAssertEqual(arrayObjects.count, UInt(1), "There should be a single SwiftRLMStringObject in the realm") let cmp = arrayObjects.firstObject()!.array.firstObject()! XCTAssertTrue(string.isEqual(to: cmp), "First array object should be the string object we added") } func testPopulateEmptyArray() { let realm = realmWithTestPath() realm.beginWriteTransaction() let array = SwiftRLMArrayPropertyObject.create(in: realm, withValue: ["arrayObject"]) XCTAssertNotNil(array.array, "Should be able to get an empty array") XCTAssertEqual(array.array.count, UInt(0), "Should start with no array elements") let obj = SwiftRLMStringObject() obj.stringCol = "a" array.array.add(obj) array.array.add(SwiftRLMStringObject.create(in: realm, withValue: ["b"])) array.array.add(obj) try! realm.commitWriteTransaction() XCTAssertEqual(array.array.count, UInt(3), "Should have three elements in array") XCTAssertEqual(array.array[0].stringCol, "a", "First element should have property value 'a'") XCTAssertEqual(array.array[1].stringCol, "b", "Second element should have property value 'b'") XCTAssertEqual(array.array[2].stringCol, "a", "Third element should have property value 'a'") for obj in array.array { XCTAssertFalse(obj.description.isEmpty, "Object should have description") } } func testModifyDetatchedArray() { let realm = realmWithTestPath() realm.beginWriteTransaction() let arObj = SwiftRLMArrayPropertyObject.create(in: realm, withValue: ["arrayObject"]) XCTAssertNotNil(arObj.array, "Should be able to get an empty array") XCTAssertEqual(arObj.array.count, UInt(0), "Should start with no array elements") let obj = SwiftRLMStringObject() obj.stringCol = "a" let array = arObj.array array.add(obj) array.add(SwiftRLMStringObject.create(in: realm, withValue: ["b"])) try! realm.commitWriteTransaction() XCTAssertEqual(array.count, UInt(2), "Should have two elements in array") XCTAssertEqual(array[0].stringCol, "a", "First element should have property value 'a'") XCTAssertEqual(array[1].stringCol, "b", "Second element should have property value 'b'") } func testInsertMultiple() { let realm = realmWithTestPath() realm.beginWriteTransaction() let obj = SwiftRLMArrayPropertyObject.create(in: realm, withValue: ["arrayObject"]) let child1 = SwiftRLMStringObject.create(in: realm, withValue: ["a"]) let child2 = SwiftRLMStringObject() child2.stringCol = "b" obj.array.addObjects([child2, child1] as NSArray) try! realm.commitWriteTransaction() let children = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual((children[0] as! SwiftRLMStringObject).stringCol, "a", "First child should be 'a'") XCTAssertEqual((children[1] as! SwiftRLMStringObject).stringCol, "b", "Second child should be 'b'") } // FIXME: Support unmanaged RLMArray's in Swift-defined models // func testUnmanaged() { // let realm = realmWithTestPath() // // let array = SwiftRLMArrayPropertyObject() // array.name = "name" // XCTAssertNotNil(array.array, "RLMArray property should get created on access") // // let obj = SwiftRLMStringObject() // obj.stringCol = "a" // array.array.addObject(obj) // array.array.addObject(obj) // // realm.beginWriteTransaction() // realm.addObject(array) // try! realm.commitWriteTransaction() // // XCTAssertEqual(array.array.count, UInt(2), "Should have two elements in array") // XCTAssertEqual((array.array[0] as SwiftRLMStringObject).stringCol, "a", "First element should have property value 'a'") // XCTAssertEqual((array.array[1] as SwiftRLMStringObject).stringCol, "a", "Second element should have property value 'a'") // } // Objective-C models func testBasicArray_objc() { let string = StringObject() string.stringCol = "string" let realm = realmWithTestPath() realm.beginWriteTransaction() realm.add(string) try! realm.commitWriteTransaction() XCTAssertEqual(StringObject.allObjects(in: realm).count, UInt(1), "There should be a single StringObject in the realm") let array = ArrayPropertyObject() array.name = "arrayObject" array.array.add(string) realm.beginWriteTransaction() realm.add(array) try! realm.commitWriteTransaction() let arrayObjects = ArrayPropertyObject.allObjects(in: realm) XCTAssertEqual(arrayObjects.count, UInt(1), "There should be a single StringObject in the realm") let cmp = (arrayObjects.firstObject() as! ArrayPropertyObject).array.firstObject()! XCTAssertTrue(string.isEqual(to: cmp), "First array object should be the string object we added") } func testPopulateEmptyArray_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let array = ArrayPropertyObject.create(in: realm, withValue: ["arrayObject"]) XCTAssertNotNil(array.array, "Should be able to get an empty array") XCTAssertEqual(array.array.count, UInt(0), "Should start with no array elements") let obj = StringObject() obj.stringCol = "a" array.array.add(obj) array.array.add(StringObject.create(in: realm, withValue: ["b"])) array.array.add(obj) try! realm.commitWriteTransaction() XCTAssertEqual(array.array.count, UInt(3), "Should have three elements in array") XCTAssertEqual((array.array[0]).stringCol!, "a", "First element should have property value 'a'") XCTAssertEqual((array.array[1]).stringCol!, "b", "Second element should have property value 'b'") XCTAssertEqual((array.array[2]).stringCol!, "a", "Third element should have property value 'a'") for idx in 0.. EmployeeObject { let employee = EmployeeObject() employee.age = age employee.name = name employee.hired = hired realm.add(employee) return employee } func testDeleteLinksAndObjectsInArray_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeEmployee(realm, 40, "Joe", true) _ = makeEmployee(realm, 30, "John", false) let po3 = makeEmployee(realm, 25, "Jill", true) let company = CompanyObject() company.name = "name" realm.add(company) company.employees.addObjects(EmployeeObject.allObjects(in: realm)) try! realm.commitWriteTransaction() let peopleInCompany: RLMArray = company.employees! XCTAssertEqual(peopleInCompany.count, UInt(3), "No links should have been deleted") realm.beginWriteTransaction() peopleInCompany.removeObject(at: 1) // Should delete link to employee try! realm.commitWriteTransaction() XCTAssertEqual(peopleInCompany.count, UInt(2), "link deleted when accessing via links") var test = peopleInCompany[0] XCTAssertEqual(test.age, po1.age, "Should be equal") XCTAssertEqual(test.name!, po1.name!, "Should be equal") XCTAssertEqual(test.hired, po1.hired, "Should be equal") // XCTAssertEqual(test, po1, "Should be equal") //FIXME, should work. Asana : https://app.asana.com/0/861870036984/13123030433568 test = peopleInCompany[1] XCTAssertEqual(test.age, po3.age, "Should be equal") XCTAssertEqual(test.name!, po3.name!, "Should be equal") XCTAssertEqual(test.hired, po3.hired, "Should be equal") // XCTAssertEqual(test, po3, "Should be equal") //FIXME, should work. Asana : https://app.asana.com/0/861870036984/13123030433568 let allPeople = EmployeeObject.allObjects(in: realm) XCTAssertEqual(allPeople.count, UInt(3), "Only links should have been deleted, not the employees") } func testIndexOfObject_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeEmployee(realm, 40, "Joe", true) let po2 = makeEmployee(realm, 30, "John", false) let po3 = makeEmployee(realm, 25, "Jill", true) try! realm.commitWriteTransaction() let results = EmployeeObject.objects(in: realm, where: "hired = YES") XCTAssertEqual(UInt(2), results.count) XCTAssertEqual(UInt(0), results.index(of: po1)); XCTAssertEqual(UInt(1), results.index(of: po3)); XCTAssertEqual(NSNotFound, Int(results.index(of: po2))); } func testIndexOfObjectWhere_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = makeEmployee(realm, 40, "Joe", true) _ = makeEmployee(realm, 30, "John", false) _ = makeEmployee(realm, 25, "Jill", true) try! realm.commitWriteTransaction() let results = EmployeeObject.objects(in: realm, where: "hired = YES") XCTAssertEqual(UInt(2), results.count) XCTAssertEqual(UInt(0), results.indexOfObject(where: "age = %d", 40)) XCTAssertEqual(UInt(1), results.indexOfObject(where: "age = %d", 25)) XCTAssertEqual(NSNotFound, Int(results.indexOfObject(where: "age = %d", 30))) } func testSortingExistingQuery_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = makeEmployee(realm, 20, "A", true) _ = makeEmployee(realm, 30, "B", false) _ = makeEmployee(realm, 40, "C", true) try! realm.commitWriteTransaction() let sortedByAge = EmployeeObject.allObjects(in: realm).sortedResults(usingKeyPath: "age", ascending: true) let sortedByName = sortedByAge.sortedResults(usingKeyPath: "name", ascending: false) XCTAssertEqual(Int32(20), (sortedByAge[0] as! EmployeeObject).age) XCTAssertEqual(Int32(40), (sortedByName[0] as! EmployeeObject).age) } } ================================================ FILE: Realm/Tests/Swift/SwiftDynamicTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm.Dynamic import Realm.Private import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMDynamicTests: RLMTestCase { // Swift models func testDynamicRealmExists() { autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = RLMRealm(url: RLMTestRealmURL()) realm.beginWriteTransaction() _ = SwiftRLMDynamicObject.create(in: realm, withValue: ["column1", 1]) _ = SwiftRLMDynamicObject.create(in: realm, withValue: ["column2", 2]) try! realm.commitWriteTransaction() } let dyrealm = realm(withTestPathAndSchema: nil) XCTAssertNotNil(dyrealm, "realm should not be nil") // verify schema let dynSchema = dyrealm.schema[SwiftRLMDynamicObject.className()] XCTAssertNotNil(dynSchema, "Should be able to get object schema dynamically") XCTAssertEqual(dynSchema.properties.count, Int(2)) XCTAssertEqual(dynSchema.properties[0].name, "stringCol") XCTAssertEqual(dynSchema.properties[1].type, RLMPropertyType.int) // verify object type let array = SwiftRLMDynamicObject.allObjects(in: dyrealm) XCTAssertEqual(array.count, UInt(2)) XCTAssertEqual(array.objectClassName, SwiftRLMDynamicObject.className()) } func testDynamicProperties() { autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = RLMRealm(url: RLMTestRealmURL()) realm.beginWriteTransaction() _ = SwiftRLMDynamicObject.create(in: realm, withValue: ["column1", 1]) _ = SwiftRLMDynamicObject.create(in: realm, withValue: ["column2", 2]) try! realm.commitWriteTransaction() } // verify properties let dyrealm = realm(withTestPathAndSchema: nil) let array = dyrealm.allObjects("SwiftRLMDynamicObject") XCTAssertTrue(array[0]["intCol"] as! NSNumber == 1) XCTAssertTrue(array[1]["stringCol"] as! String == "column2") } // Objective-C models func testDynamicRealmExists_objc() { autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = RLMRealm(url: RLMTestRealmURL()) realm.beginWriteTransaction() _ = DynamicTestObject.create(in: realm, withValue: ["column1", 1]) _ = DynamicTestObject.create(in: realm, withValue: ["column2", 2]) try! realm.commitWriteTransaction() } let dyrealm = realm(withTestPathAndSchema: nil) XCTAssertNotNil(dyrealm, "realm should not be nil") // verify schema let dynSchema = dyrealm.schema[DynamicTestObject.className()] XCTAssertNotNil(dynSchema, "Should be able to get object schema dynamically") XCTAssertTrue(dynSchema.properties.count == 2) XCTAssertTrue(dynSchema.properties[0].name == "stringCol") XCTAssertTrue(dynSchema.properties[1].type == RLMPropertyType.int) // verify object type let array = DynamicTestObject.allObjects(in: dyrealm) XCTAssertEqual(array.count, UInt(2)) XCTAssertEqual(array.objectClassName, DynamicTestObject.className()) } func testDynamicProperties_objc() { autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = RLMRealm(url: RLMTestRealmURL()) realm.beginWriteTransaction() _ = DynamicTestObject.create(in: realm, withValue: ["column1", 1]) _ = DynamicTestObject.create(in: realm, withValue: ["column2", 2]) try! realm.commitWriteTransaction() } // verify properties let dyrealm = realm(withTestPathAndSchema: nil) let array = dyrealm.allObjects("DynamicTestObject") XCTAssertTrue(array[0]["intCol"] as! NSNumber == 1) XCTAssertTrue(array[1]["stringCol"] as! String == "column2") } func testDynamicTypes_objc() { let obj1 = AllTypesObject.values(1, stringObject: nil, mixedObject: nil)! let obj2 = AllTypesObject.values(2, stringObject: StringObject(value: ["string"]), mixedObject: MixedObject(value: ["string"]))! autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = self.realmWithTestPath() realm.beginWriteTransaction() _ = AllTypesObject.create(in: realm, withValue: obj1) _ = AllTypesObject.create(in: realm, withValue: obj2) try! realm.commitWriteTransaction() } // verify properties let dyrealm = realm(withTestPathAndSchema: nil) let results = dyrealm.allObjects(AllTypesObject.className()) XCTAssertEqual(results.count, UInt(2)) let robj1 = results[0] let robj2 = results[1] let schema = dyrealm.schema[AllTypesObject.className()] let props = schema.properties.filter { $0.type != .object } for prop in props { XCTAssertTrue((obj1[prop.name] as AnyObject).isEqual(robj1[prop.name])) XCTAssertTrue((obj2[prop.name] as AnyObject).isEqual(robj2[prop.name])) } // check sub object type XCTAssertTrue(schema.properties[12].objectClassName! == "StringObject") XCTAssertTrue(schema.properties[13].objectClassName! == "MixedObject") // check object equality XCTAssertNil(robj1["objectCol"], "object should be nil") XCTAssertNil(robj1["mixedObjectCol"], "object should be nil") XCTAssertTrue((robj2["objectCol"] as! RLMObject)["stringCol"] as! String == "string") XCTAssertTrue((robj2["mixedObjectCol"] as! RLMObject)["anyCol"] as! String == "string") } func testDynamicTypesMixedCollection_objc() { let obj1 = AllTypesObject.values(5, stringObject: StringObject(value: ["newString"]), mixedObject: MixedObject(value: [["string", 45, false]]))! autoreleasepool { // open realm in autoreleasepool to create tables and then dispose let realm = self.realmWithTestPath() realm.beginWriteTransaction() _ = AllTypesObject.create(in: realm, withValue: obj1) try! realm.commitWriteTransaction() } let dyrealm = realm(withTestPathAndSchema: nil) let results = dyrealm.allObjects(AllTypesObject.className()) XCTAssertEqual(results.count, UInt(1)) let robj1 = results[0] // Mixed List XCTAssertTrue(((robj1["mixedObjectCol"] as! RLMObject)["anyCol"] as! RLMManagedArray)[0] as! String == "string") XCTAssertTrue(((robj1["mixedObjectCol"] as! RLMObject)["anyCol"] as! RLMManagedArray)[1] as! Int == 45) XCTAssertTrue(((robj1["mixedObjectCol"] as! RLMObject)["anyCol"] as! RLMManagedArray)[2] as! Bool == false) } } ================================================ FILE: Realm/Tests/Swift/SwiftLinkTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMLinkTests: RLMTestCase { // Swift models func testBasicLink() { let realm = realmWithTestPath() let owner = SwiftRLMOwnerObject() owner.name = "Tim" owner.dog = SwiftRLMDogObject() owner.dog!.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() let owners = SwiftRLMOwnerObject.allObjects(in: realm) let dogs = SwiftRLMDogObject.allObjects(in: realm) XCTAssertEqual(owners.count, UInt(1), "Expecting 1 owner") XCTAssertEqual(dogs.count, UInt(1), "Expecting 1 dog") XCTAssertEqual((owners[0] as! SwiftRLMOwnerObject).name, "Tim", "Tim is named Tim") XCTAssertEqual((dogs[0] as! SwiftRLMDogObject).dogName, "Harvie", "Harvie is named Harvie") let tim = owners[0] as! SwiftRLMOwnerObject XCTAssertEqual(tim.dog!.dogName, "Harvie", "Tim's dog should be Harvie") } func testMultipleOwnerLink() { let realm = realmWithTestPath() let owner = SwiftRLMOwnerObject() owner.name = "Tim" owner.dog = SwiftRLMDogObject() owner.dog!.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() XCTAssertEqual(SwiftRLMOwnerObject.allObjects(in: realm).count, UInt(1), "Expecting 1 owner") XCTAssertEqual(SwiftRLMDogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") realm.beginWriteTransaction() let fiel = SwiftRLMOwnerObject.create(in: realm, withValue: ["Fiel", NSNull()]) fiel.dog = owner.dog try! realm.commitWriteTransaction() XCTAssertEqual(SwiftRLMOwnerObject.allObjects(in: realm).count, UInt(2), "Expecting 2 owners") XCTAssertEqual(SwiftRLMDogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") } func testLinkRemoval() { let realm = realmWithTestPath() let owner = SwiftRLMOwnerObject() owner.name = "Tim" owner.dog = SwiftRLMDogObject() owner.dog!.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() XCTAssertEqual(SwiftRLMOwnerObject.allObjects(in: realm).count, UInt(1), "Expecting 1 owner") XCTAssertEqual(SwiftRLMDogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") realm.beginWriteTransaction() realm.delete(owner.dog!) try! realm.commitWriteTransaction() XCTAssertNil(owner.dog, "Dog should be nullified when deleted") // refresh owner and check let owner2 = SwiftRLMOwnerObject.allObjects(in: realm).firstObject() as! SwiftRLMOwnerObject XCTAssertNotNil(owner2, "Should have 1 owner") XCTAssertNil(owner2.dog, "Dog should be nullified when deleted") XCTAssertEqual(SwiftRLMDogObject.allObjects(in: realm).count, UInt(0), "Expecting 0 dogs") } func testLinkingObjects() { let realm = realmWithTestPath() let target = SwiftRLMLinkTargetObject() target.id = 0 let source = SwiftRLMLinkSourceObject() source.id = 1234 source.link = target XCTAssertEqual(0, target.backlinks!.count) realm.beginWriteTransaction() realm.add(source) try! realm.commitWriteTransaction() XCTAssertNotNil(target.realm) XCTAssertEqual(1, target.backlinks!.count) XCTAssertEqual(1234, (target.backlinks!.firstObject() as! SwiftRLMLinkSourceObject).id) } // FIXME - disabled until we fix commit log issue which break transacions when leaking realm objects // func testCircularLinks() { // let realm = realmWithTestPath() // // let obj = SwiftRLMCircleObject() // obj.data = "a" // obj.next = obj // // realm.beginWriteTransaction() // realm.addObject(obj) // obj.next.data = "b" // try! realm.commitWriteTransaction() // // let obj2 = SwiftRLMCircleObject.allObjectsInRealm(realm).firstObject() as SwiftRLMCircleObject // XCTAssertEqual(obj2.data, "b", "data should be 'b'") // XCTAssertEqual(obj2.data, obj2.next.data, "objects should be equal") // } // Objective-C models func testBasicLink_objc() { let realm = realmWithTestPath() let owner = OwnerObject() owner.name = "Tim" owner.dog = DogObject() owner.dog.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() let owners = OwnerObject.allObjects(in: realm) let dogs = DogObject.allObjects(in: realm) XCTAssertEqual(owners.count, UInt(1), "Expecting 1 owner") XCTAssertEqual(dogs.count, UInt(1), "Expecting 1 dog") XCTAssertEqual((owners[0] as! OwnerObject).name!, "Tim", "Tim is named Tim") XCTAssertEqual((dogs[0] as! DogObject).dogName!, "Harvie", "Harvie is named Harvie") let tim = owners[0] as! OwnerObject XCTAssertEqual(tim.dog.dogName!, "Harvie", "Tim's dog should be Harvie") } func testMultipleOwnerLink_objc() { let realm = realmWithTestPath() let owner = OwnerObject() owner.name = "Tim" owner.dog = DogObject() owner.dog.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() XCTAssertEqual(OwnerObject.allObjects(in: realm).count, UInt(1), "Expecting 1 owner") XCTAssertEqual(DogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") realm.beginWriteTransaction() let fiel = OwnerObject.create(in: realm, withValue: ["Fiel", NSNull()]) fiel.dog = owner.dog try! realm.commitWriteTransaction() XCTAssertEqual(OwnerObject.allObjects(in: realm).count, UInt(2), "Expecting 2 owners") XCTAssertEqual(DogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") } func testLinkRemoval_objc() { let realm = realmWithTestPath() let owner = OwnerObject() owner.name = "Tim" owner.dog = DogObject() owner.dog.dogName = "Harvie" realm.beginWriteTransaction() realm.add(owner) try! realm.commitWriteTransaction() XCTAssertEqual(OwnerObject.allObjects(in: realm).count, UInt(1), "Expecting 1 owner") XCTAssertEqual(DogObject.allObjects(in: realm).count, UInt(1), "Expecting 1 dog") realm.beginWriteTransaction() realm.delete(owner.dog) try! realm.commitWriteTransaction() XCTAssertNil(owner.dog, "Dog should be nullified when deleted") // refresh owner and check let owner2 = OwnerObject.allObjects(in: realm).firstObject() as! OwnerObject XCTAssertNotNil(owner2, "Should have 1 owner") XCTAssertNil(owner2.dog, "Dog should be nullified when deleted") XCTAssertEqual(DogObject.allObjects(in: realm).count, UInt(0), "Expecting 0 dogs") } // FIXME - disabled until we fix commit log issue which break transacions when leaking realm objects // func testCircularLinks_objc() { // let realm = realmWithTestPath() // // let obj = CircleObject() // obj.data = "a" // obj.next = obj // // realm.beginWriteTransaction() // realm.addObject(obj) // obj.next.data = "b" // try! realm.commitWriteTransaction() // // let obj2 = CircleObject.allObjectsInRealm(realm).firstObject() as CircleObject // XCTAssertEqual(obj2.data, "b", "data should be 'b'") // XCTAssertEqual(obj2.data, obj2.next.data, "objects should be equal") // } } ================================================ FILE: Realm/Tests/Swift/SwiftObjectInterfaceTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif class OuterClass { class InnerClass { } } class SwiftRLMStringObjectSubclass: SwiftRLMStringObject { @objc dynamic var stringCol2 = "" } class SwiftRLMSelfRefrencingSubclass: SwiftRLMStringObject { @objc dynamic var objects = RLMArray(objectClassName: SwiftRLMSelfRefrencingSubclass.className()) @objc dynamic var objectSet = RLMSet(objectClassName: SwiftRLMSelfRefrencingSubclass.className()) } class SwiftRLMDefaultObject: RLMObject { @objc dynamic var intCol = 1 @objc dynamic var boolCol = true override class func defaultPropertyValues() -> [AnyHashable : Any]? { return ["intCol": 2] } } class SwiftRLMOptionalNumberObject: RLMObject { @objc dynamic var intCol: NSNumber? = 1 @objc dynamic var floatCol: NSNumber? = 2.2 as Float as NSNumber @objc dynamic var doubleCol: NSNumber? = 3.3 @objc dynamic var boolCol: NSNumber? = true } class SwiftRLMObjectInterfaceTests: RLMTestCase { // Swift models func testSwiftRLMObject() { let realm = realmWithTestPath() realm.beginWriteTransaction() let obj = SwiftRLMObject() realm.add(obj) obj.boolCol = true obj.intCol = 1234 obj.floatCol = 1.1 obj.doubleCol = 2.2 obj.stringCol = "abcd" obj.binaryCol = "abcd".data(using: String.Encoding.utf8) obj.dateCol = Date(timeIntervalSince1970: 123) obj.objectCol = SwiftRLMBoolObject() obj.objectCol.boolCol = true obj.arrayCol.add(obj.objectCol) obj.setCol.add(obj.objectCol) obj.uuidCol = UUID(uuidString: "00000000-0000-0000-0000-000000000000") obj.rlmValue = NSString("I am a mixed value") try! realm.commitWriteTransaction() let data = "abcd".data(using: String.Encoding.utf8) let firstObj = SwiftRLMObject.allObjects(in: realm).firstObject() as! SwiftRLMObject XCTAssertEqual(firstObj.boolCol, true, "should be true") XCTAssertEqual(firstObj.intCol, 1234, "should be 1234") XCTAssertEqual(firstObj.floatCol, Float(1.1), "should be 1.1") XCTAssertEqual(firstObj.doubleCol, 2.2, "should be 2.2") XCTAssertEqual(firstObj.stringCol, "abcd", "should be abcd") XCTAssertEqual(firstObj.binaryCol!, data!) XCTAssertEqual(firstObj.dateCol, Date(timeIntervalSince1970: 123), "should be epoch + 123") XCTAssertEqual(firstObj.objectCol.boolCol, true, "should be true") XCTAssertEqual(firstObj.uuidCol?.uuidString, "00000000-0000-0000-0000-000000000000") XCTAssertEqual(firstObj.rlmValue as! NSString, NSString("I am a mixed value")) XCTAssertEqual(obj.arrayCol.count, UInt(1), "array count should be 1") XCTAssertEqual(obj.arrayCol.firstObject()!.boolCol, true, "should be true") XCTAssertEqual(obj.setCol.count, UInt(1), "set count should be 1") XCTAssertEqual(obj.setCol.allObjects[0].boolCol, true, "should be true") } func testDefaultValueSwiftRLMObject() { let realm = realmWithTestPath() realm.beginWriteTransaction() realm.add(SwiftRLMObject()) try! realm.commitWriteTransaction() let data = "a".data(using: String.Encoding.utf8) let firstObj = SwiftRLMObject.allObjects(in: realm).firstObject() as! SwiftRLMObject XCTAssertEqual(firstObj.boolCol, false, "should be false") XCTAssertEqual(firstObj.intCol, 123, "should be 123") XCTAssertEqual(firstObj.floatCol, Float(1.23), "should be 1.23") XCTAssertEqual(firstObj.doubleCol, 12.3, "should be 12.3") XCTAssertEqual(firstObj.stringCol, "a", "should be a") XCTAssertEqual(firstObj.binaryCol!, data!) XCTAssertEqual(firstObj.dateCol, Date(timeIntervalSince1970: 1), "should be epoch + 1") XCTAssertEqual(firstObj.objectCol.boolCol, false, "should be false") XCTAssertEqual(firstObj.arrayCol.count, UInt(0), "array count should be zero") XCTAssertEqual(firstObj.setCol.count, UInt(0), "set count should be zero") XCTAssertEqual(firstObj.uuidCol!.uuidString, "00000000-0000-0000-0000-000000000000") XCTAssertEqual(firstObj.uuidCol!.uuidString, "00000000-0000-0000-0000-000000000000") XCTAssertEqual(firstObj.rlmValue as! NSString, NSString("A Mixed Object")) } func testMergedDefaultValuesSwiftRLMObject() { let realm = self.realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMDefaultObject.create(in: realm, withValue: NSDictionary()) try! realm.commitWriteTransaction() let object = SwiftRLMDefaultObject.allObjects(in: realm).firstObject() as! SwiftRLMDefaultObject XCTAssertEqual(object.intCol, 2, "defaultPropertyValues should override native property default value") XCTAssertEqual(object.boolCol, true, "native property default value should be used if defaultPropertyValues doesn't contain that key") } func testSubclass() { // test className methods XCTAssertEqual("SwiftRLMStringObject", SwiftRLMStringObject.className()) XCTAssertEqual("SwiftRLMStringObjectSubclass", SwiftRLMStringObjectSubclass.className()) let realm = RLMRealm.default() realm.beginWriteTransaction() _ = SwiftRLMStringObject.createInDefaultRealm(withValue: ["string"]) _ = SwiftRLMStringObjectSubclass.createInDefaultRealm(withValue: ["string", "string2"]) try! realm.commitWriteTransaction() // ensure creation in proper table XCTAssertEqual(UInt(1), SwiftRLMStringObjectSubclass.allObjects().count) XCTAssertEqual(UInt(1), SwiftRLMStringObject.allObjects().count) try! realm.transaction { // create self referencing subclass let sub = SwiftRLMSelfRefrencingSubclass.createInDefaultRealm(withValue: ["string"]) let sub2 = SwiftRLMSelfRefrencingSubclass() sub.objects.add(sub2) sub.objectSet.add(sub2) } } func testOptionalNSNumberProperties() { let realm = realmWithTestPath() let no = SwiftRLMOptionalNumberObject() XCTAssertEqual([.int, .float, .double, .bool], no.objectSchema.properties.map { $0.type }) XCTAssertEqual(1, no.intCol!) XCTAssertEqual(2.2 as Float as NSNumber, no.floatCol!) XCTAssertEqual(3.3, no.doubleCol!) XCTAssertEqual(true, no.boolCol!) try! realm.transaction { realm.add(no) no.intCol = nil no.floatCol = nil no.doubleCol = nil no.boolCol = nil } XCTAssertNil(no.intCol) XCTAssertNil(no.floatCol) XCTAssertNil(no.doubleCol) XCTAssertNil(no.boolCol) try! realm.transaction { no.intCol = 1.1 no.floatCol = 2.2 as Float as NSNumber no.doubleCol = 3.3 no.boolCol = false } XCTAssertEqual(1, no.intCol!) XCTAssertEqual(2.2 as Float as NSNumber, no.floatCol!) XCTAssertEqual(3.3, no.doubleCol!) XCTAssertEqual(false, no.boolCol!) } func testOptionalSwiftRLMProperties() { let realm = realmWithTestPath() try! realm.transaction { realm.add(SwiftRLMOptionalObject()) } let firstObj = SwiftRLMOptionalObject.allObjects(in: realm).firstObject() as! SwiftRLMOptionalObject XCTAssertNil(firstObj.optObjectCol) XCTAssertNil(firstObj.optStringCol) XCTAssertNil(firstObj.optNSStringCol) XCTAssertNil(firstObj.optBinaryCol) XCTAssertNil(firstObj.optDateCol) XCTAssertNil(firstObj.uuidCol) try! realm.transaction { firstObj.optObjectCol = SwiftRLMBoolObject() firstObj.optObjectCol!.boolCol = true firstObj.optStringCol = "Hi!" firstObj.optNSStringCol = "Hi!" firstObj.optBinaryCol = Data(bytes: "hi", count: 2) firstObj.optDateCol = Date(timeIntervalSinceReferenceDate: 10) firstObj.uuidCol = UUID(uuidString: "00000000-0000-0000-0000-000000000000") } XCTAssertTrue(firstObj.optObjectCol!.boolCol) XCTAssertEqual(firstObj.optStringCol!, "Hi!") XCTAssertEqual(firstObj.optNSStringCol!, "Hi!") XCTAssertEqual(firstObj.optBinaryCol!, Data(bytes: "hi", count: 2)) XCTAssertEqual(firstObj.optDateCol!, Date(timeIntervalSinceReferenceDate: 10)) XCTAssertEqual(firstObj.uuidCol!.uuidString, "00000000-0000-0000-0000-000000000000") try! realm.transaction { firstObj.optObjectCol = nil firstObj.optStringCol = nil firstObj.optNSStringCol = nil firstObj.optBinaryCol = nil firstObj.optDateCol = nil firstObj.uuidCol = nil } XCTAssertNil(firstObj.optObjectCol) XCTAssertNil(firstObj.optStringCol) XCTAssertNil(firstObj.optNSStringCol) XCTAssertNil(firstObj.optBinaryCol) XCTAssertNil(firstObj.optDateCol) XCTAssertNil(firstObj.uuidCol) } func testSwiftRLMClassNameIsDemangled() { XCTAssertEqual(SwiftRLMObject.className(), "SwiftRLMObject", "Calling className() on Swift class should return demangled name") } func testPrimitiveArray() { let obj = SwiftRLMPrimitiveArrayObject() let str = "str" as NSString let data = Data("str".utf8) as NSData let date = NSDate() let str2 = "str2" as NSString let data2 = Data("str2".utf8) as NSData let date2 = NSDate(timeIntervalSince1970: 0) obj.stringCol.add(str) XCTAssertEqual(obj.stringCol[0], str) XCTAssertEqual(obj.stringCol.index(of: str), 0) XCTAssertEqual(obj.stringCol.index(of: str2), UInt(NSNotFound)) obj.dataCol.add(data) XCTAssertEqual(obj.dataCol[0], data) XCTAssertEqual(obj.dataCol.index(of: data), 0) XCTAssertEqual(obj.dataCol.index(of: data2), UInt(NSNotFound)) obj.dateCol.add(date) XCTAssertEqual(obj.dateCol[0], date) XCTAssertEqual(obj.dateCol.index(of: date), 0) XCTAssertEqual(obj.dateCol.index(of: date2), UInt(NSNotFound)) obj.optStringCol.add(str) XCTAssertEqual(obj.optStringCol[0], str) obj.optDataCol.add(data) XCTAssertEqual(obj.optDataCol[0], data) obj.optDateCol.add(date) XCTAssertEqual(obj.optDateCol[0], date) obj.optStringCol.add(NSNull()) XCTAssertEqual(obj.optStringCol[1], NSNull()) obj.optDataCol.add(NSNull()) XCTAssertEqual(obj.optDataCol[1], NSNull()) obj.optDateCol.add(NSNull()) XCTAssertEqual(obj.optDateCol[1], NSNull()) assertThrowsWithReasonMatching(obj.optDataCol.add(str), ".*") } func testPrimitiveSet() { let obj = SwiftRLMPrimitiveSetObject() let str = "str" as NSString let data = Data("str".utf8) as NSData let date = NSDate() obj.stringCol.add(str) XCTAssertTrue(obj.stringCol.contains(str)) obj.dataCol.add(data) XCTAssertTrue(obj.dataCol.contains(data)) obj.dateCol.add(date) XCTAssertTrue(obj.dateCol.contains(date)) obj.optStringCol.add(str) XCTAssertTrue(obj.optStringCol.contains(str)) obj.optDataCol.add(data) XCTAssertTrue(obj.optDataCol.contains(data)) obj.optDateCol.add(date) XCTAssertTrue(obj.optDateCol.contains(date)) obj.optStringCol.add(NSNull()) XCTAssertTrue(obj.optStringCol.contains(NSNull())) obj.optDataCol.add(NSNull()) XCTAssertTrue(obj.optDataCol.contains(NSNull())) obj.optDateCol.add(NSNull()) XCTAssertTrue(obj.optDateCol.contains(NSNull())) assertThrowsWithReasonMatching(obj.optDataCol.add(str), ".*") } func testUuidPrimitiveArray() { let obj = SwiftRLMPrimitiveArrayObject() let uuidA = NSUUID(uuidString: "00000000-0000-0000-0000-000000000000")! let uuidB = NSUUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")! obj.uuidCol.add(uuidA) XCTAssertEqual(obj.uuidCol[0], uuidA) XCTAssertEqual(obj.uuidCol.index(of: uuidA), 0) XCTAssertEqual(obj.uuidCol.index(of: uuidB), UInt(NSNotFound)) obj.optUuidCol.add(NSNull()) XCTAssertEqual(obj.optUuidCol[0], NSNull()) obj.optUuidCol.add(uuidA) XCTAssertEqual(obj.optUuidCol[1], uuidA) obj.optUuidCol.add(NSNull()) XCTAssertEqual(obj.optUuidCol[2], NSNull()) } func testUuidPrimitiveSet() { let obj = SwiftRLMPrimitiveSetObject() let uuidA = NSUUID(uuidString: "00000000-0000-0000-0000-000000000000")! obj.uuidCol.add(uuidA) XCTAssertTrue(obj.uuidCol.contains(uuidA)) obj.optUuidCol.add(NSNull()) XCTAssertTrue(obj.optUuidCol.contains(NSNull())) obj.optUuidCol.add(uuidA) XCTAssertTrue(obj.optUuidCol.contains(uuidA)) obj.optUuidCol.add(NSNull()) XCTAssertTrue(obj.optUuidCol.contains(NSNull())) } // Objective-C models // Note: Swift doesn't support custom accessor names // so we test to make sure models with custom accessors can still be accessed func testCustomAccessors() { let realm = realmWithTestPath() realm.beginWriteTransaction() let ca = CustomAccessorsObject.create(in: realm, withValue: ["name", 2]) XCTAssertEqual(ca.name!, "name", "name property should be name.") ca.age = 99 XCTAssertEqual(ca.age, Int32(99), "age property should be 99") try! realm.commitWriteTransaction() } func testClassExtension() { let realm = realmWithTestPath() realm.beginWriteTransaction() let bObject = BaseClassStringObject() bObject.intCol = 1 bObject.stringCol = "stringVal" realm.add(bObject) try! realm.commitWriteTransaction() let objectFromRealm = BaseClassStringObject.allObjects(in: realm)[0] as! BaseClassStringObject XCTAssertEqual(objectFromRealm.intCol, Int32(1), "Should be 1") XCTAssertEqual(objectFromRealm.stringCol!, "stringVal", "Should be stringVal") } func testCreateOrUpdate() { let realm = RLMRealm.default() realm.beginWriteTransaction() SwiftRLMPrimaryStringObject.createOrUpdateInDefaultRealm(withValue: ["string", 1]) let objects = SwiftRLMPrimaryStringObject.allObjects() as! RLMResults XCTAssertEqual(objects.count, UInt(1), "Should have 1 object") XCTAssertEqual(objects[0].intCol, 1, "Value should be 1") SwiftRLMPrimaryStringObject.createOrUpdateInDefaultRealm(withValue: ["string2", 2]) XCTAssertEqual(objects.count, UInt(2), "Should have 2 objects") SwiftRLMPrimaryStringObject.createOrUpdateInDefaultRealm(withValue: ["string", 3]) XCTAssertEqual(objects.count, UInt(2), "Should have 2 objects") XCTAssertEqual(objects[0].intCol, 3, "Value should be 3") try! realm.commitWriteTransaction() } func testObjectForPrimaryKey() { let realm = RLMRealm.default() realm.beginWriteTransaction() SwiftRLMPrimaryStringObject.createOrUpdateInDefaultRealm(withValue: ["string", 1]) let obj = SwiftRLMPrimaryStringObject.object(forPrimaryKey: "string") XCTAssertNotNil(obj!) XCTAssertEqual(obj!.intCol, 1) realm.cancelWriteTransaction() } // if this fails (and you haven't changed the test module name), the checks // for swift class names and the demangling logic need to be updated func testNSStringFromClassDemangledTopLevelClassNames() { #if SWIFT_PACKAGE XCTAssertEqual(NSStringFromClass(OuterClass.self), "RealmObjcSwiftTests.OuterClass") #else XCTAssertEqual(NSStringFromClass(OuterClass.self), "Tests.OuterClass") #endif } // if this fails (and you haven't changed the test module name), the prefix // check in RLMSchema initialization needs to be updated func testNestedClassNameMangling() { #if SWIFT_PACKAGE XCTAssertEqual(NSStringFromClass(OuterClass.InnerClass.self), "_TtCC19RealmObjcSwiftTests10OuterClass10InnerClass") #else XCTAssertEqual(NSStringFromClass(OuterClass.InnerClass.self), "_TtCC5Tests10OuterClass10InnerClass") #endif } } ================================================ FILE: Realm/Tests/Swift/SwiftPropertyTypeTest.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMPropertyTypeTest: RLMTestCase { func testLongType() { let longNumber: Int64 = 17179869184 let intNumber: Int64 = 2147483647 let negativeLongNumber: Int64 = -17179869184 let updatedLongNumber: Int64 = 8589934592 let realm = realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMLongObject.create(in: realm, withValue: [NSNumber(value: longNumber)]) _ = SwiftRLMLongObject.create(in: realm, withValue: [NSNumber(value: intNumber)]) _ = SwiftRLMLongObject.create(in: realm, withValue: [NSNumber(value: negativeLongNumber)]) try! realm.commitWriteTransaction() let objects = SwiftRLMLongObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(3), "3 rows expected") XCTAssertEqual((objects[0] as! SwiftRLMLongObject).longCol, longNumber, "2 ^ 34 expected") XCTAssertEqual((objects[1] as! SwiftRLMLongObject).longCol, intNumber, "2 ^ 31 - 1 expected") XCTAssertEqual((objects[2] as! SwiftRLMLongObject).longCol, negativeLongNumber, "-2 ^ 34 expected") realm.beginWriteTransaction() (objects[0] as! SwiftRLMLongObject).longCol = updatedLongNumber try! realm.commitWriteTransaction() XCTAssertEqual((objects[0] as! SwiftRLMLongObject).longCol, updatedLongNumber, "After update: 2 ^ 33 expected") } func testIntSizes() { let realm = realmWithTestPath() let v8 = Int8(1) << 5 let v16 = Int16(1) << 12 let v32 = Int32(1) << 30 // 1 << 40 doesn't auto-promote to Int64 on 32-bit platforms let v64 = Int64(1) << 40 try! realm.transaction { let obj = SwiftRLMAllIntSizesObject() obj.int8 = v8 XCTAssertEqual(obj.int8, v8) obj.int16 = v16 XCTAssertEqual(obj.int16, v16) obj.int32 = v32 XCTAssertEqual(obj.int32, v32) obj.int64 = v64 XCTAssertEqual(obj.int64, v64) realm.add(obj) } let obj = SwiftRLMAllIntSizesObject.allObjects(in: realm)[0] as! SwiftRLMAllIntSizesObject XCTAssertEqual(obj.int8, v8) XCTAssertEqual(obj.int16, v16) XCTAssertEqual(obj.int32, v32) XCTAssertEqual(obj.int64, v64) } func testIntSizes_objc() { let realm = realmWithTestPath() let v16 = Int16(1) << 12 let v32 = Int32(1) << 30 // 1 << 40 doesn't auto-promote to Int64 on 32-bit platforms let v64 = Int64(1) << 40 try! realm.transaction { let obj = AllIntSizesObject() obj.int16 = v16 XCTAssertEqual(obj.int16, v16) obj.int32 = v32 XCTAssertEqual(obj.int32, v32) obj.int64 = v64 XCTAssertEqual(obj.int64, v64) realm.add(obj) } let obj = AllIntSizesObject.allObjects(in: realm)[0] as! AllIntSizesObject XCTAssertEqual(obj.int16, v16) XCTAssertEqual(obj.int32, v32) XCTAssertEqual(obj.int64, v64) } func testLazyVarProperties() { let realm = realmWithTestPath() let succeeded : Void? = try? realm.transaction { realm.add(SwiftRLMLazyVarObject()) } XCTAssertNotNil(succeeded, "Writing an NSObject-based object with an lazy property should work.") } func testIgnoredLazyVarProperties() { let realm = realmWithTestPath() let succeeded : Void? = try? realm.transaction { realm.add(SwiftRLMIgnoredLazyVarObject()) } XCTAssertNotNil(succeeded, "Writing an object with an ignored lazy property should work.") } func testObjectiveCTypeProperties() { let realm = realmWithTestPath() var object: SwiftRLMObjectiveCTypesObject! let now = NSDate() let data = Data("fizzbuzz".utf8) as NSData try! realm.transaction { object = SwiftRLMObjectiveCTypesObject() realm.add(object) object.stringCol = "Hello world!" object.dateCol = now object.dataCol = data object.numCol = 42 } XCTAssertEqual("Hello world!", object.stringCol) XCTAssertEqual(now, object.dateCol) XCTAssertEqual(data, object.dataCol) XCTAssertEqual(42, object.numCol) } } ================================================ FILE: Realm/Tests/Swift/SwiftRLMDictionaryTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMDictionaryTests: RLMTestCase { // Swift models func testFastEnumeration() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dObj = SwiftRLMDictionaryPropertyObject.create(in: realm, withValue: []) let dict = dObj.dict let dateMinInput = Date() let dateMaxInput = dateMinInput.addingTimeInterval(1000) dict["0"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) dict["1"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput]) dict["2"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) dict["3"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput]) dict["4"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) dict["5"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput]) dict["6"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) dict["7"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput]) dict["8"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) dict["9"] = SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput]) try! realm.commitWriteTransaction() XCTAssertEqual(dict.count, UInt(10), "10 objects added") var totalSum = 0 for (key, value) in dict { let obj = dict[key] as! SwiftRLMAggregateObject if let ao = value as? SwiftRLMAggregateObject { XCTAssertEqual(obj.doubleCol, ao.doubleCol) totalSum += ao.intCol } } XCTAssertEqual(totalSum, 100, "total sum should be 100") } func testKeyType() { let unmanaged = SwiftRLMDictionaryPropertyObject() XCTAssertEqual(unmanaged.dict.keyType, .string) let realm = realmWithTestPath() realm.beginWriteTransaction() let managed = SwiftRLMDictionaryPropertyObject.create(in: realm, withValue: []) try! realm.commitWriteTransaction() XCTAssertEqual(managed.dict.keyType, .string) } func testObjectAggregate() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dateMinInput = Date() let dateMaxInput = dateMinInput.addingTimeInterval(1000) let dObj = SwiftRLMDictionaryPropertyObject.create(in: realm, withValue: ["dict" : [ "0": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "1": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "2": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "3": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "4": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "5": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "6": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "7": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "8": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "9": [0, 1.2 as Float, 0 as Double, true, dateMinInput] ] as [String: [Any]]]) try! realm.commitWriteTransaction() XCTAssertEqual(dObj.dict.count, UInt(10), "10 objects added") let noArray = dObj.dict.objects(where: "boolCol == NO") let yesArray = dObj.dict.objects(where: "boolCol == YES") // SUM :::::::::::::::::::::::::::::::::::::::::::::: // Test int sum XCTAssertEqual(noArray.sum(ofProperty: "intCol").intValue, 4, "Sum should be 4") XCTAssertEqual(yesArray.sum(ofProperty: "intCol").intValue, 0, "Sum should be 0") // Test float sum XCTAssertEqual(noArray.sum(ofProperty: "floatCol").floatValue, Float(0), accuracy: 0.1, "Sum should be 0.0") XCTAssertEqual(yesArray.sum(ofProperty: "floatCol").floatValue, Float(7.2), accuracy: 0.1, "Sum should be 7.2") // Test double sum XCTAssertEqual(noArray.sum(ofProperty: "doubleCol").doubleValue, Double(10), accuracy: 0.1, "Sum should be 10.0") XCTAssertEqual(yesArray.sum(ofProperty: "doubleCol").doubleValue, Double(0), accuracy: 0.1, "Sum should be 0.0") // Average :::::::::::::::::::::::::::::::::::::::::::::: // Test int average XCTAssertEqual(noArray.average(ofProperty: "intCol")!.doubleValue, Double(1), accuracy: 0.1, "Average should be 1.0") XCTAssertEqual(yesArray.average(ofProperty: "intCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") // Test float average XCTAssertEqual(noArray.average(ofProperty: "floatCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") XCTAssertEqual(yesArray.average(ofProperty: "floatCol")!.doubleValue, Double(1.2), accuracy: 0.1, "Average should be 1.2") // Test double average XCTAssertEqual(noArray.average(ofProperty: "doubleCol")!.doubleValue, Double(2.5), accuracy: 0.1, "Average should be 2.5") XCTAssertEqual(yesArray.average(ofProperty: "doubleCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") // MIN :::::::::::::::::::::::::::::::::::::::::::::: // Test int min var min = noArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(1), "Minimum should be 1") min = yesArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(0), "Minimum should be 0") // Test float min min = noArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(0), accuracy: 0.1, "Minimum should be 0.0f") min = yesArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(1.2), accuracy: 0.1, "Minimum should be 1.2f") // Test double min min = noArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(2.5), accuracy: 0.1, "Minimum should be 1.5") min = yesArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(0), accuracy: 0.1, "Minimum should be 0.0") // Test date min var dateMinOutput = noArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMaxInput, "Minimum should be dateMaxInput") dateMinOutput = yesArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMinInput, "Minimum should be dateMinInput") // MAX :::::::::::::::::::::::::::::::::::::::::::::: // Test int max var max = noArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 1, "Maximum should be 8") max = yesArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 0, "Maximum should be 10") // Test float max max = noArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(0), accuracy: 0.1, "Maximum should be 0.0f") max = yesArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(1.2), accuracy: 0.1, "Maximum should be 1.2f") // Test double max max = noArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(2.5), accuracy: 0.1, "Maximum should be 3.5") max = yesArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(0), accuracy: 0.1, "Maximum should be 0.0") // Test date max var dateMaxOutput = noArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMaxInput, "Maximum should be dateMaxInput") dateMaxOutput = yesArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMinInput, "Maximum should be dateMinInput") } func testDictionaryDescription() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dObj = SwiftRLMDictionaryEmployeeObject.create(in: realm, withValue: []) let dict = dObj.dict for i in 0..<1012 { dict[String(i) as NSString] = makeRlmEmployee(realm, 24, "Mary", true) } try! realm.commitWriteTransaction() let description = dict.description XCTAssertTrue((description as NSString).range(of: "name").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "Mary").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "age").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "24").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMDictionary") } func testDeleteLinksAndObjectsInDictionary() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeRlmEmployee(realm, 40, "Joe", true) let po2 = makeRlmEmployee(realm, 30, "John", false) let po3 = makeRlmEmployee(realm, 25, "Jill", true) let company = SwiftRLMCompanyObject() realm.add(company) company.employeeMap["Joe" as NSString] = po1 company.employeeMap["John" as NSString] = po2 company.employeeMap["Jill" as NSString] = po3 try! realm.commitWriteTransaction() let peopleInCompany = company.employeeMap XCTAssertEqual(peopleInCompany.count, UInt(3), "No links should have been deleted") realm.beginWriteTransaction() peopleInCompany.removeObject(forKey: "John" as NSString) // Should delete link to employee try! realm.commitWriteTransaction() XCTAssertEqual(peopleInCompany.count, UInt(2), "link deleted when accessing via links") var test = peopleInCompany["Joe" as NSString]! XCTAssertEqual(test.age, po1.age, "Should be equal") XCTAssertEqual(test.name, po1.name, "Should be equal") XCTAssertEqual(test.hired, po1.hired, "Should be equal") test = peopleInCompany["Jill" as NSString]! XCTAssertEqual(test.age, po3.age, "Should be equal") XCTAssertEqual(test.name, po3.name, "Should be equal") XCTAssertEqual(test.hired, po3.hired, "Should be equal") realm.beginWriteTransaction() peopleInCompany["Jill" as NSString] = nil XCTAssertEqual(peopleInCompany.count, UInt(1), "1 remaining link") peopleInCompany["Joe" as NSString] = po2 XCTAssertEqual(peopleInCompany.count, UInt(1), "1 link replaced") peopleInCompany.removeAllObjects() XCTAssertEqual(peopleInCompany.count, UInt(0), "0 remaining links") try! realm.commitWriteTransaction() let allPeople = SwiftRLMEmployeeObject.allObjects(in: realm) XCTAssertEqual(allPeople.count, UInt(3), "Only links should have been deleted, not the employees") } // Objective-C models func testFastEnumeration_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dateMinInput = Date() let dateMaxInput = dateMinInput.addingTimeInterval(1000) let dObj = AggregateDictionaryObject.create(in: realm, withValue: ["dictionary" : [ "0": [10, 1.2 as Float, 0 as Double, true, dateMinInput], "1": [10, 0 as Float, 2.5 as Double, false, dateMaxInput], "2": [10, 1.2 as Float, 0 as Double, true, dateMinInput], "3": [10, 0 as Float, 2.5 as Double, false, dateMaxInput], "4": [10, 1.2 as Float, 0 as Double, true, dateMinInput], "5": [10, 0 as Float, 2.5 as Double, false, dateMaxInput], "6": [10, 1.2 as Float, 0 as Double, true, dateMinInput], "7": [10, 0 as Float, 2.5 as Double, false, dateMaxInput], "8": [10, 1.2 as Float, 0 as Double, true, dateMinInput], "9": [10, 1.2 as Float, 0 as Double, true, dateMinInput] ] as [String: [Any]]]) try! realm.commitWriteTransaction() XCTAssertEqual(dObj.dictionary!.count, UInt(10), "10 objects added") var totalSum: CInt = 0 for key in dObj.dictionary!.allKeys { if let ao = dObj.dictionary[key] { totalSum += ao.intCol } } XCTAssertEqual(totalSum, CInt(100), "total sum should be 100") } func testObjectAggregate_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dateMinInput = Date() let dateMaxInput = dateMinInput.addingTimeInterval(1000) let dObj = AggregateDictionaryObject.create(in: realm, withValue: ["dictionary" : [ "0": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "1": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "2": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "3": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "4": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "5": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "6": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "7": [1, 0 as Float, 2.5 as Double, false, dateMaxInput], "8": [0, 1.2 as Float, 0 as Double, true, dateMinInput], "9": [0, 1.2 as Float, 0 as Double, true, dateMinInput] ] as [String: [Any]]]) try! realm.commitWriteTransaction() XCTAssertEqual(dObj.dictionary!.count, UInt(10), "10 objects added") let noArray = dObj.dictionary!.objects(where: "boolCol == NO") let yesArray = dObj.dictionary!.objects(where: "boolCol == YES") // SUM :::::::::::::::::::::::::::::::::::::::::::::: // Test int sum XCTAssertEqual(noArray.sum(ofProperty: "intCol").intValue, 4, "Sum should be 4") XCTAssertEqual(yesArray.sum(ofProperty: "intCol").intValue, 0, "Sum should be 0") // Test float sum XCTAssertEqual(noArray.sum(ofProperty: "floatCol").floatValue, Float(0), accuracy: 0.1, "Sum should be 0.0") XCTAssertEqual(yesArray.sum(ofProperty: "floatCol").floatValue, Float(7.2), accuracy: 0.1, "Sum should be 7.2") // Test double sum XCTAssertEqual(noArray.sum(ofProperty: "doubleCol").doubleValue, Double(10), accuracy: 0.1, "Sum should be 10.0") XCTAssertEqual(yesArray.sum(ofProperty: "doubleCol").doubleValue, Double(0), accuracy: 0.1, "Sum should be 0.0") // Average :::::::::::::::::::::::::::::::::::::::::::::: // Test int average XCTAssertEqual(noArray.average(ofProperty: "intCol")!.doubleValue, Double(1), accuracy: 0.1, "Average should be 1.0") XCTAssertEqual(yesArray.average(ofProperty: "intCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") // Test float average XCTAssertEqual(noArray.average(ofProperty: "floatCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") XCTAssertEqual(yesArray.average(ofProperty: "floatCol")!.doubleValue, Double(1.2), accuracy: 0.1, "Average should be 1.2") // Test double average XCTAssertEqual(noArray.average(ofProperty: "doubleCol")!.doubleValue, Double(2.5), accuracy: 0.1, "Average should be 2.5") XCTAssertEqual(yesArray.average(ofProperty: "doubleCol")!.doubleValue, Double(0), accuracy: 0.1, "Average should be 0.0") // MIN :::::::::::::::::::::::::::::::::::::::::::::: // Test int min var min = noArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(1), "Minimum should be 1") min = yesArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(0), "Minimum should be 0") // Test float min min = noArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(0), accuracy: 0.1, "Minimum should be 0.0f") min = yesArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(1.2), accuracy: 0.1, "Minimum should be 1.2f") // Test double min min = noArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(2.5), accuracy: 0.1, "Minimum should be 1.5") min = yesArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(0), accuracy: 0.1, "Minimum should be 0.0") // Test date min var dateMinOutput = noArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMaxInput, "Minimum should be dateMaxInput") dateMinOutput = yesArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMinInput, "Minimum should be dateMinInput") // MAX :::::::::::::::::::::::::::::::::::::::::::::: // Test int max var max = noArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 1, "Maximum should be 8") max = yesArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 0, "Maximum should be 10") // Test float max max = noArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(0), accuracy: 0.1, "Maximum should be 0.0f") max = yesArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(1.2), accuracy: 0.1, "Maximum should be 1.2f") // Test double max max = noArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(2.5), accuracy: 0.1, "Maximum should be 3.5") max = yesArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(0), accuracy: 0.1, "Maximum should be 0.0") // Test date max var dateMaxOutput = noArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMaxInput, "Maximum should be dateMaxInput") dateMaxOutput = yesArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMinInput, "Maximum should be dateMinInput") } func testDictionaryDescription_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let dObj = CompanyObject.create(in: realm, withValue: []) for i in 0..<1012 { dObj.employeeDict![String(i) as NSString] = makeEmployee(realm, 24, "Mary", true) } try! realm.commitWriteTransaction() let description = dObj.employeeDict!.description XCTAssertTrue((description as NSString).range(of: "name").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "Mary").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "age").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMDictionary") XCTAssertTrue((description as NSString).range(of: "24").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMDictionary") } func makeRlmEmployee(_ realm: RLMRealm, _ age: Int32, _ name: String, _ hired: Bool) -> SwiftRLMEmployeeObject { return SwiftRLMEmployeeObject.create(in: realm, withValue: [name, age, hired]) } func makeEmployee(_ realm: RLMRealm, _ age: Int32, _ name: String, _ hired: Bool) -> EmployeeObject { return EmployeeObject.create(in: realm, withValue: ["age": age, "name": name, "hired": hired] as [String: Any]) } func testDeleteLinksAndObjectsInDictionary_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeEmployee(realm, 40, "Joe", true) let po2 = makeEmployee(realm, 30, "John", false) let po3 = makeEmployee(realm, 25, "Jill", true) let company = CompanyObject.create(in: realm, withValue: ["employeeDict": [ "Joe": po1, "John": po2, "Jill": po3]]) try! realm.commitWriteTransaction() let peopleInCompany = company.employeeDict! XCTAssertEqual(peopleInCompany.count, UInt(3), "No links should have been deleted") realm.beginWriteTransaction() peopleInCompany["John" as NSString] = nil // Should delete link to employee try! realm.commitWriteTransaction() XCTAssertEqual(peopleInCompany.count, UInt(2), "link deleted when accessing via links") var test = peopleInCompany["Joe" as NSString]! XCTAssertEqual(test.age, po1.age, "Should be equal") XCTAssertEqual(test.name!, po1.name!, "Should be equal") XCTAssertEqual(test.hired, po1.hired, "Should be equal") test = peopleInCompany["Jill" as NSString]! XCTAssertEqual(test.age, po3.age, "Should be equal") XCTAssertEqual(test.name!, po3.name!, "Should be equal") XCTAssertEqual(test.hired, po3.hired, "Should be equal") let allPeople = EmployeeObject.allObjects(in: realm) XCTAssertEqual(allPeople.count, UInt(3), "Only links should have been deleted, not the employees") } func testIndexOfObject_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeEmployee(realm, 40, "Joe", true) let po2 = makeEmployee(realm, 30, "John", false) let po3 = makeEmployee(realm, 25, "Jill", true) let company = CompanyObject.create(in: realm, withValue: ["employeeDict": ["Joe": po1, "John": po2, "Jill": po3]]) try! realm.commitWriteTransaction() let results = company.employeeDict.objects(where: "hired = YES").sortedResults(usingKeyPath: "name", ascending: false) XCTAssertEqual(UInt(2), results.count) XCTAssertEqual(UInt(0), results.index(of: po1)); XCTAssertEqual(UInt(1), results.index(of: po3)); XCTAssertEqual(NSNotFound, Int(results.index(of: po2))); } func testSortingExistingQuery_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let company = CompanyObject() company.employeeDict["Joe" as NSString] = makeEmployee(realm, 20, "A", true) company.employeeDict["John" as NSString] = makeEmployee(realm, 30, "B", false) company.employeeDict["Jill" as NSString] = makeEmployee(realm, 40, "C", true) realm.add(company) try! realm.commitWriteTransaction() let sortedByAge = company.employeeDict.sortedResults(usingKeyPath: "age", ascending: true) let sortedByName = sortedByAge.sortedResults(usingKeyPath: "name", ascending: false) XCTAssertEqual(Int32(20), sortedByAge[0].age) XCTAssertEqual(Int32(40), sortedByName[0].age) } } ================================================ FILE: Realm/Tests/Swift/SwiftRealmTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif @available(iOS 13.0, *) // For @MainActor class SwiftRLMRealmTests: RLMTestCase { // No models func testRealmExists() { let realm = realmWithTestPath() XCTAssertNotNil(realm, "realm should not be nil"); XCTAssertTrue((realm as AnyObject) is RLMRealm, "realm should be of class RLMRealm") } func testEmptyWriteTransaction() { let realm = realmWithTestPath() realm.beginWriteTransaction() try! realm.commitWriteTransaction() } // Swift models func testRealmAddAndRemoveObjects() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMStringObject.create(in: realm, withValue: ["a"]) _ = SwiftRLMStringObject.create(in: realm, withValue: ["b"]) _ = SwiftRLMStringObject.create(in: realm, withValue: ["c"]) XCTAssertEqual(SwiftRLMStringObject.allObjects(in: realm).count, UInt(3), "Expecting 3 objects") try! realm.commitWriteTransaction() // test again after write transaction var objects = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(3), "Expecting 3 objects") XCTAssertEqual((objects[0] as! SwiftRLMStringObject).stringCol, "a", "Expecting column to be 'a'") realm.beginWriteTransaction() realm.delete(objects[2] as! SwiftRLMStringObject) realm.delete(objects[0] as! SwiftRLMStringObject) XCTAssertEqual(SwiftRLMStringObject.allObjects(in: realm).count, UInt(1), "Expecting 1 object") try! realm.commitWriteTransaction() objects = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "Expecting 1 object") XCTAssertEqual((objects[0] as! SwiftRLMStringObject).stringCol, "b", "Expecting column to be 'b'") } @MainActor func testRealmIsUpdatedAfterBackgroundUpdate() { let realm = realmWithTestPath() // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") let token = realm.addNotificationBlock { note, realm in XCTAssertNotNil(realm, "Realm should not be nil") notificationFired.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchAsync { let realm = unsafeSelf.realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMStringObject.create(in: realm, withValue: ["string"]) try! realm.commitWriteTransaction() } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() // get object let objects = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "There should be 1 object of type StringObject") XCTAssertEqual((objects[0] as! SwiftRLMStringObject).stringCol, "string", "Value of first column should be 'string'") } func testRealmIgnoresProperties() { let realm = realmWithTestPath() let object = SwiftRLMIgnoredPropertiesObject() realm.beginWriteTransaction() object.name = "@fz" object.age = 31 realm.add(object) try! realm.commitWriteTransaction() // This shouldn't do anything. realm.beginWriteTransaction() object.runtimeProperty = NSObject() try! realm.commitWriteTransaction() let objects = SwiftRLMIgnoredPropertiesObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "There should be 1 object of type SwiftRLMIgnoredPropertiesObject") let retrievedObject = objects[0] as! SwiftRLMIgnoredPropertiesObject XCTAssertNil(retrievedObject.runtimeProperty, "Ignored property should be nil") XCTAssertEqual(retrievedObject.name, "@fz", "Value of the name column doesn't match the assigned one.") XCTAssertEqual(retrievedObject.objectSchema.properties.count, 2, "Only 'name' and 'age' properties should be detected by Realm") } @MainActor func testUpdatingSortedArrayAfterBackgroundUpdate() { let realm = realmWithTestPath() let objs = SwiftRLMIntObject.allObjects(in: realm) let objects = SwiftRLMIntObject.allObjects(in: realm).sortedResults(usingKeyPath: "intCol", ascending: true) let updateComplete = expectation(description: "background update complete") let token = realm.addNotificationBlock() { (_, _) in XCTAssertEqual(objs.count, UInt(2)) XCTAssertEqual(objs.sortedResults(usingKeyPath: "intCol", ascending: true).count, UInt(2)) XCTAssertEqual(objects.count, UInt(2)) updateComplete.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchAsync { let realm = unsafeSelf.realmWithTestPath() try! realm.transaction { var obj = SwiftRLMIntObject() obj.intCol = 2; realm.add(obj) obj = SwiftRLMIntObject() obj.intCol = 1; realm.add(obj) } } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() } @MainActor func testRealmIsUpdatedImmediatelyAfterBackgroundUpdate() { let realm = realmWithTestPath() let notificationFired = expectation(description: "notification fired") let token = realm.addNotificationBlock { note, realm in XCTAssertNotNil(realm, "Realm should not be nil") notificationFired.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchAsync { let realm = unsafeSelf.realmWithTestPath() let obj = SwiftRLMStringObject(value: ["string"]) realm.beginWriteTransaction() realm.add(obj) try! realm.commitWriteTransaction() let objects = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1)) XCTAssertEqual((objects[0] as! SwiftRLMStringObject).stringCol, "string") } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() // get object let objects = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1)) XCTAssertEqual((objects[0] as! SwiftRLMStringObject).stringCol, "string") } // Objective-C models func testRealmAddAndRemoveObjects_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = StringObject.create(in: realm, withValue: ["a"]) _ = StringObject.create(in: realm, withValue: ["b"]) _ = StringObject.create(in: realm, withValue: ["c"]) XCTAssertEqual(StringObject.allObjects(in: realm).count, UInt(3), "Expecting 3 objects") try! realm.commitWriteTransaction() // test again after write transaction var objects = StringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(3), "Expecting 3 objects") XCTAssertEqual((objects[0] as! StringObject).stringCol!, "a", "Expecting column to be 'a'") realm.beginWriteTransaction() realm.delete(objects[2] as! StringObject) realm.delete(objects[0] as! StringObject) XCTAssertEqual(StringObject.allObjects(in: realm).count, UInt(1), "Expecting 1 object") try! realm.commitWriteTransaction() objects = StringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "Expecting 1 object") XCTAssertEqual((objects[0] as! StringObject).stringCol!, "b", "Expecting column to be 'b'") } @MainActor func testRealmIsUpdatedAfterBackgroundUpdate_objc() { let realm = realmWithTestPath() // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") let token = realm.addNotificationBlock { note, realm in XCTAssertNotNil(realm, "Realm should not be nil") if note == RLMNotification.DidChange { notificationFired.fulfill() } } nonisolated(unsafe) let unsafeSelf = self dispatchAsync { let realm = unsafeSelf.realmWithTestPath() realm.beginWriteTransaction() _ = StringObject.create(in: realm, withValue: ["string"]) try! realm.commitWriteTransaction() } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() // get object let objects = StringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "There should be 1 object of type StringObject") XCTAssertEqual((objects[0] as! StringObject).stringCol!, "string", "Value of first column should be 'string'") } @MainActor func testRealmIsUpdatedImmediatelyAfterBackgroundUpdate_objc() { let realm = realmWithTestPath() // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") let token = realm.addNotificationBlock { note, realm in XCTAssertNotNil(realm, "Realm should not be nil") notificationFired.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchAsync { let realm = unsafeSelf.realmWithTestPath() let obj = StringObject(value: ["string"]) try! realm.transaction { realm.add(obj) } let objects = StringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "There should be 1 object of type StringObject") XCTAssertEqual((objects[0] as! StringObject).stringCol!, "string", "Value of first column should be 'string'") } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() // get object let objects = StringObject.allObjects(in: realm) XCTAssertEqual(objects.count, UInt(1), "There should be 1 object of type RLMTestObject") XCTAssertEqual((objects[0] as! StringObject).stringCol!, "string", "Value of first column should be 'string'") } } ================================================ FILE: Realm/Tests/Swift/SwiftSchemaTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import Realm import Realm.Private #if canImport(RealmTestSupport) import RealmTestSupport #endif #if os(macOS) class InitLinkedToClass: RLMObject { @objc dynamic var value: SwiftRLMIntObject! = SwiftRLMIntObject(value: [0]) } class SwiftRLMNonDefaultObject: RLMObject { @objc dynamic var value = 0 public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMLinkedNonDefaultObject: RLMObject { @objc dynamic var obj: SwiftRLMNonDefaultObject? public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMNonDefaultArrayObject: RLMObject { @objc dynamic var array = RLMArray(objectClassName: SwiftRLMNonDefaultObject.className()) public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMNonDefaultSetObject: RLMObject { @objc dynamic var set = RLMSet(objectClassName: SwiftRLMNonDefaultObject.className()) public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMNonDefaultDictionaryObject: RLMObject { @objc dynamic var dictionary = RLMDictionary(objectClassName: SwiftRLMNonDefaultObject.className(), keyType: .string) public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMMutualLink1Object: RLMObject { @objc dynamic var object: SwiftRLMMutualLink2Object? public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftRLMMutualLink2Object: RLMObject { @objc dynamic var object: SwiftRLMMutualLink1Object? public override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class IgnoredLinkPropertyObject : RLMObject { @objc dynamic var value = 0 var obj = SwiftRLMIntObject() override class func ignoredProperties() -> [String] { return ["obj"] } } @MainActor class SwiftRLMRecursingSchemaTestObject : RLMObject { @objc dynamic var propertyWithIllegalDefaultValue: SwiftRLMIntObject? = { if mayAccessSchema { let realm = RLMRealm.default() return SwiftRLMIntObject.allObjects().firstObject() as! SwiftRLMIntObject? } return nil }() static var mayAccessSchema = false } class InvalidArrayType: FakeObject { @objc dynamic var array = RLMArray(objectClassName: "invalid class") } class InvalidSetType: FakeObject { @objc dynamic var set = RLMSet(objectClassName: "invalid class") } class InvalidDictionaryType: FakeObject { @objc dynamic var dictionary = RLMDictionary(objectClassName: "invalid class", keyType: .string) } @MainActor class InitAppendsToArrayProperty : RLMObject { @objc dynamic var propertyWithIllegalDefaultValue: RLMArray = { if mayAppend { mayAppend = false let array = RLMArray(objectClassName: InitAppendsToArrayProperty.className()) array.add(InitAppendsToArrayProperty()) return array } return RLMArray(objectClassName: InitAppendsToArrayProperty.className()) }() static var mayAppend = false } class NoProps: FakeObject { // no @objc properties } class OnlyComputedSource: RLMObject { @objc dynamic var link: OnlyComputedTarget? } class OnlyComputedTarget: RLMObject { @objc dynamic var backlinks: RLMLinkingObjects? override class func linkingObjectsProperties() -> [String : RLMPropertyDescriptor] { return ["backlinks": RLMPropertyDescriptor(with: OnlyComputedSource.self, propertyName: "link")] } } class OnlyComputedNoBacklinksProps: FakeObject { var computedProperty: String { return "Test_String" } } class RequiresObjcName: RLMObject { #if compiler(>=5.10) nonisolated(unsafe) static var enable = false #else static var enable = false #endif override class func _realmIgnoreClass() -> Bool { return !enable } } class ClassWrappingObjectSubclass { class Inner: RequiresObjcName { @objc dynamic var value = 0 } } struct StructWrappingObjectSubclass { class Inner: RequiresObjcName { @objc dynamic var value = 0 } } enum EnumWrappingObjectSubclass { class Inner: RequiresObjcName { @objc dynamic var value = 0 } } private class PrivateClassWithoutExplicitObjcName: RequiresObjcName { @objc dynamic var value = 0 } class SwiftRLMSchemaTests: RLMMultiProcessTestCase { func testWorksAtAll() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") } } func testShouldRaiseObjectWithoutProperties() { assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: NoProps.self), "No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?") } func testShouldNotThrowForObjectWithOnlyBacklinksProps() { let config = RLMRealmConfiguration.default() config.objectClasses = [OnlyComputedTarget.self, OnlyComputedSource.self] config.inMemoryIdentifier = #function let r = try! RLMRealm(configuration: config) 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() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } let config = RLMRealmConfiguration.default() config.objectClasses = [IgnoredLinkPropertyObject.self] config.inMemoryIdentifier = #function let r = try! RLMRealm(configuration: config) try! r.transaction { _ = IgnoredLinkPropertyObject.create(in: r, withValue: [1]) } } func testCreateUnmanagedObjectWithUninitializedSchema() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } // Object in default schema _ = SwiftRLMIntObject() // Object not in default schema _ = SwiftRLMNonDefaultObject() } func testCreateUnmanagedObjectWithNestedObjectWithUninitializedSchema() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } // Objects in default schema // Should not throw (or crash) despite creating an object with an // uninitialized schema during schema init _ = InitLinkedToClass() // Again with an object that links to an uninitialized type // rather than creating one _ = SwiftRLMCompanyObject() // Objects not in default schema _ = SwiftRLMLinkedNonDefaultObject(value: [[1]]) _ = SwiftRLMNonDefaultArrayObject(value: [[[1]]]) _ = SwiftRLMNonDefaultSetObject(value: [[[1]]]) _ = SwiftRLMMutualLink1Object() } func testCreateUnmanagedObjectWhichCreatesAnotherClassViaInitWithValueDuringSchemaInit() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } _ = InitLinkedToClass(value: [[0]]) _ = SwiftRLMCompanyObject(value: [[["Jaden", 20, false] as [Any]]]) } func testInitUnmanagedObjectNotInClassSubsetDuringSchemaInit() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } let config = RLMRealmConfiguration.default() config.objectClasses = [IgnoredLinkPropertyObject.self] config.inMemoryIdentifier = #function _ = try! RLMRealm(configuration: config) let r = try! RLMRealm(configuration: RLMRealmConfiguration.default()) try! r.transaction { _ = IgnoredLinkPropertyObject.create(in: r, withValue: [1]) } } @MainActor func testPreventsDeadLocks() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } SwiftRLMRecursingSchemaTestObject.mayAccessSchema = true assertThrowsWithReasonMatching(RLMSchema.shared(), ".*recursive.*") } @MainActor func testAccessSchemaCreatesObjectWhichAttempsInsertionsToArrayProperty() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } // This is different from the above tests in that it is a to-many link // and it only occurs while the schema is initializing InitAppendsToArrayProperty.mayAppend = true assertThrowsWithReasonMatching(RLMSchema.shared(), ".*Object cannot be inserted unless the schema is initialized.*") } func testInvalidObjectTypeForRLMArray() { assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: InvalidArrayType.self), "RLMArray\\") } @MainActor func testInvalidNestedClass() { if isParent { XCTAssertEqual(0, runChildAndWait(), "Tests in child process failed") return } RequiresObjcName.enable = true assertThrowsWithReasonMatching(RLMSchema.sharedSchema(for: ClassWrappingObjectSubclass.Inner.self), "Object subclass '.*' must explicitly set the class's objective-c name with @objc\\(ClassName\\) because it is not a top-level public class.") assertThrowsWithReasonMatching(RLMSchema.sharedSchema(for: StructWrappingObjectSubclass.Inner.self), "Object subclass '.*' must explicitly set the class's objective-c name with @objc\\(ClassName\\) because it is not a top-level public class.") assertThrowsWithReasonMatching(RLMSchema.sharedSchema(for: EnumWrappingObjectSubclass.Inner.self), "Object subclass '.*' must explicitly set the class's objective-c name with @objc\\(ClassName\\) because it is not a top-level public class.") assertThrowsWithReasonMatching(RLMSchema.sharedSchema(for: PrivateClassWithoutExplicitObjcName.self), "Object subclass '.*' must explicitly set the class's objective-c name with @objc\\(ClassName\\) because it is not a top-level public class.") } } #endif ================================================ FILE: Realm/Tests/Swift/SwiftSetPropertyTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMSetPropertyTests: RLMTestCase { // Swift models func testBasicSet() { let string = SwiftRLMStringObject() string.stringCol = "string" let realm = realmWithTestPath() realm.beginWriteTransaction() realm.add(string) try! realm.commitWriteTransaction() XCTAssertEqual(SwiftRLMStringObject.allObjects(in: realm).count, UInt(1), "There should be a single SwiftRLMStringObject in the realm") let set = SwiftRLMSetPropertyObject() set.name = "setObject" set.set.add(string) XCTAssertEqual(set.set.count, UInt(1)) XCTAssertEqual(set.set.allObjects[0].stringCol, "string") realm.beginWriteTransaction() realm.add(set) set.set.add(string) try! realm.commitWriteTransaction() let setObjects = SwiftRLMSetPropertyObject.allObjects(in: realm) as! RLMResults XCTAssertEqual(setObjects.count, UInt(1), "There should be a single SwiftRLMStringObject in the realm") let cmp = setObjects[0].set.allObjects[0] XCTAssertTrue(string.isEqual(to: cmp), "First array object should be the string object we added") } func testPopulateEmptySet() { let realm = realmWithTestPath() realm.beginWriteTransaction() let set = SwiftRLMSetPropertyObject.create(in: realm, withValue: ["setObject"]) XCTAssertNotNil(set.set, "Should be able to get an empty set") XCTAssertEqual(set.set.count, UInt(0), "Should start with no set elements") let obj = SwiftRLMStringObject() obj.stringCol = "a" set.set.add(obj) set.set.add(SwiftRLMStringObject.create(in: realm, withValue: ["b"])) set.set.add(obj) try! realm.commitWriteTransaction() XCTAssertEqual(set.set.count, UInt(2), "Should have two elements in array") var count = 0 set.set.forEach { guard let o = $0 as? SwiftRLMStringObject else { return XCTFail("expected SwiftRLMStringObject") } XCTAssertTrue((o.stringCol.contains("a") || o.stringCol.contains("b"))) count+=1 } XCTAssertEqual(count, 2, "Loop should run 3 times") for obj in set.set { XCTAssertFalse(obj.description.isEmpty, "Object should have description") } } func testModifyDetatchedSet() { let realm = realmWithTestPath() realm.beginWriteTransaction() let setObj = SwiftRLMSetPropertyObject.create(in: realm, withValue: ["setObject"]) XCTAssertNotNil(setObj.set, "Should be able to get an empty set") XCTAssertEqual(setObj.set.count, UInt(0), "Should start with no set elements") let obj = SwiftRLMStringObject() obj.stringCol = "a" let set = setObj.set set.add(obj) set.add(SwiftRLMStringObject.create(in: realm, withValue: ["b"])) try! realm.commitWriteTransaction() XCTAssertEqual(set.count, UInt(2), "Should have two elements in set") var count = 0 set.forEach { guard let o = $0 as? SwiftRLMStringObject else { return XCTFail("expected SwiftRLMStringObject") } XCTAssertTrue((o.stringCol.contains("a") || o.stringCol.contains("b"))) count+=1 } XCTAssertEqual(count, 2, "Loop should run twice") } func testInsertMultiple() { let realm = realmWithTestPath() realm.beginWriteTransaction() let obj = SwiftRLMSetPropertyObject.create(in: realm, withValue: ["setObject"]) let child1 = SwiftRLMStringObject.create(in: realm, withValue: ["a"]) let child2 = SwiftRLMStringObject() child2.stringCol = "b" obj.set.addObjects([child2, child1] as NSArray) try! realm.commitWriteTransaction() let children = SwiftRLMStringObject.allObjects(in: realm) XCTAssertEqual((children[0] as! SwiftRLMStringObject).stringCol, "a", "First child should be 'a'") XCTAssertEqual((children[1] as! SwiftRLMStringObject).stringCol, "b", "Second child should be 'b'") } func testUnmanaged() { let realm = realmWithTestPath() let set = SwiftRLMSetPropertyObject() set.name = "name" XCTAssertNotNil(set.set, "RLMSet property should get created on access") let obj = SwiftRLMStringObject() obj.stringCol = "a" set.set.add(obj) set.set.add(obj) realm.beginWriteTransaction() realm.add(set) try! realm.commitWriteTransaction() XCTAssertEqual(set.set.count, UInt(1), "Should have one element in set") var count = 0 set.set.forEach { guard let o = $0 as? SwiftRLMStringObject else { return XCTFail("expected SwiftRLMStringObject") } XCTAssertTrue((o.stringCol.contains("a") || o.stringCol.contains("b"))) count+=1 } XCTAssertEqual(count, 1, "Loop should run once") } // Objective-C models func testBasicSet_objc() { let string = StringObject() string.stringCol = "string" let realm = realmWithTestPath() realm.beginWriteTransaction() realm.add(string) try! realm.commitWriteTransaction() XCTAssertEqual(StringObject.allObjects(in: realm).count, UInt(1), "There should be a single StringObject in the realm") let set = SetPropertyObject() set.name = "arrayObject" set.set.add(string) realm.beginWriteTransaction() realm.add(set) try! realm.commitWriteTransaction() let setObjects = SetPropertyObject.allObjects(in: realm) XCTAssertEqual(setObjects.count, UInt(1), "There should be a single StringObject in the realm") let cmp = (setObjects.firstObject() as! SetPropertyObject).set.allObjects[0] XCTAssertTrue(string.isEqual(to: cmp), "First set object should be the string object we added") } func testPopulateEmptySet_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let set = SetPropertyObject.create(in: realm, withValue: ["setObject"]) XCTAssertNotNil(set.set, "Should be able to get an empty set") XCTAssertEqual(set.set.count, UInt(0), "Should start with no set elements") let obj = StringObject() obj.stringCol = "a" set.set.add(obj) set.set.add(StringObject.create(in: realm, withValue: ["b"])) set.set.add(obj) try! realm.commitWriteTransaction() XCTAssertEqual(set.set.count, UInt(2), "Should have two elements in set") var count = 0 (set.set as RLMSet).forEach { guard let o = $0 as? StringObject else { return XCTFail("expected StringObject") } XCTAssertTrue((o.stringCol.contains("a") || o.stringCol.contains("b"))) count+=1 } XCTAssertEqual(count, 2, "Loop should run 2 times") set.set.allObjects.forEach { (o) in XCTAssertFalse(o.description.isEmpty, "Object should have description") } } func testModifyDetatchedSet_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let setObj = SetPropertyObject.create(in: realm, withValue: ["setObject"]) XCTAssertNotNil(setObj.set, "Should be able to get an empty set") XCTAssertEqual(setObj.set.count, UInt(0), "Should start with no set elements") let obj = StringObject() obj.stringCol = "a" let set = setObj.set! set.add(obj) set.add(StringObject.create(in: realm, withValue: ["b"])) try! realm.commitWriteTransaction() XCTAssertEqual(set.count, UInt(2), "Should have two elements in set") var count = 0 (set as RLMSet).forEach { guard let o = $0 as? StringObject else { return XCTFail("expected StringObject") } XCTAssertTrue((o.stringCol.contains("a") || o.stringCol.contains("b"))) count+=1 } XCTAssertEqual(count, 2, "Loop should run twice") } func testInsertMultiple_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let obj = SetPropertyObject.create(in: realm, withValue: ["setObject"]) let child1 = StringObject.create(in: realm, withValue: ["a"]) let child2 = StringObject() child2.stringCol = "b" obj.set.addObjects([child2, child1] as NSArray) try! realm.commitWriteTransaction() let children = StringObject.allObjects(in: realm) XCTAssertEqual((children[0] as! StringObject).stringCol!, "a", "First child should be 'a'") XCTAssertEqual((children[1] as! StringObject).stringCol!, "b", "Second child should be 'b'") } func testUnmanaged_objc() { let realm = realmWithTestPath() let set = SetPropertyObject() set.name = "name" XCTAssertNotNil(set.set, "RLMSet property should get created on access") let obj = StringObject() obj.stringCol = "a" set.set.add(obj) set.set.add(obj) realm.beginWriteTransaction() realm.add(set) try! realm.commitWriteTransaction() XCTAssertEqual(set.set.count, UInt(1), "Should have one element in set") var count = 0 (set.set as RLMSet).forEach { guard let o = $0 as? StringObject else { return XCTFail("expected SwiftRLMStringObject") } XCTAssertTrue(o.stringCol.contains("a")) count+=1 } XCTAssertEqual(count, 1, "Loop should run once") } } ================================================ FILE: Realm/Tests/Swift/SwiftSetTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import Realm import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMSetTests: RLMTestCase { // Swift models func testDeleteLinksAndObjectsInSet() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = SwiftRLMEmployeeObject() po1.age = 40 po1.name = "Joe" po1.hired = true let po2 = SwiftRLMEmployeeObject() po2.age = 30 po2.name = "John" po2.hired = false let po3 = SwiftRLMEmployeeObject() po3.age = 25 po3.name = "Jill" po3.hired = true realm.add(po1) realm.add(po2) realm.add(po3) let company = SwiftRLMCompanyObject() realm.add(company) company.employeeSet.addObjects(SwiftRLMEmployeeObject.allObjects(in: realm)) try! realm.commitWriteTransaction() let peopleInCompany = company.employeeSet XCTAssertEqual(peopleInCompany.count, UInt(3), "No links should have been deleted") realm.beginWriteTransaction() peopleInCompany.remove(po2) // Should delete link to employee try! realm.commitWriteTransaction() XCTAssertEqual(peopleInCompany.count, UInt(2), "link deleted when accessing via links") let test = peopleInCompany.allObjects[0] XCTAssertTrue(((test.age == po1.age) || (test.age == po3.age)), "Should be equal") XCTAssertEqual(test.name, po1.name, "Should be equal") XCTAssertEqual(test.hired, po1.hired, "Should be equal") realm.beginWriteTransaction() peopleInCompany.remove(po1) XCTAssertEqual(peopleInCompany.count, UInt(1), "1 remaining link") peopleInCompany.add(po1) XCTAssertEqual(peopleInCompany.count, UInt(2), "2 links") peopleInCompany.removeAllObjects() XCTAssertEqual(peopleInCompany.count, UInt(0), "0 remaining links") try! realm.commitWriteTransaction() } // Objective-C models func testFastEnumeration_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = SwiftRLMEmployeeObject() po1.age = 40 po1.name = "Joe" po1.hired = true let po2 = SwiftRLMEmployeeObject() po2.age = 30 po2.name = "John" po2.hired = false let po3 = SwiftRLMEmployeeObject() po3.age = 25 po3.name = "Jill" po3.hired = true realm.add(po1) realm.add(po2) realm.add(po3) let company = SwiftRLMCompanyObject() realm.add(company) company.employeeSet.addObjects(SwiftRLMEmployeeObject.allObjects(in: realm)) try! realm.commitWriteTransaction() XCTAssertEqual(company.employeeSet.count, UInt(3), "3 objects added") var totalSum: Int = 0 for obj in company.employeeSet { if let ao = obj as? SwiftRLMEmployeeObject { totalSum += ao.age } } XCTAssertEqual(totalSum, 95, "total sum should be 95") } func testObjectAggregate_objc() { let dateMinInput = Date() let dateMaxInput = dateMinInput.addingTimeInterval(1000) let realm = realmWithTestPath() realm.beginWriteTransaction() let aggSet = SwiftRLMAggregateSet() aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 0 as Float, 2.5 as Double, false, dateMaxInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) aggSet.set.add(SwiftRLMAggregateObject.create(in: realm, withValue: [10, 1.2 as Float, 0 as Double, true, dateMinInput])) realm.add(aggSet) try! realm.commitWriteTransaction() let noArray = SwiftRLMAggregateObject.objects(in: realm, where: "boolCol == NO") let yesArray = SwiftRLMAggregateObject.objects(in: realm, where: "boolCol == YES") // SUM :::::::::::::::::::::::::::::::::::::::::::::: // Test int sum XCTAssertEqual(noArray.sum(ofProperty: "intCol").intValue, 40, "Sum should be 40") XCTAssertEqual(yesArray.sum(ofProperty: "intCol").intValue, 60, "Sum should be 60") // Test float sum XCTAssertEqual(noArray.sum(ofProperty: "floatCol").floatValue, Float(0.0), accuracy: 0.1, "Sum should be 0.0") XCTAssertEqual(yesArray.sum(ofProperty: "floatCol").floatValue, Float(7.2), accuracy: 0.1, "Sum should be 7.2") // Test double sum XCTAssertEqual(noArray.sum(ofProperty: "doubleCol").doubleValue, Double(10.0), accuracy: 0.1, "Sum should be 10.0") XCTAssertEqual(yesArray.sum(ofProperty: "doubleCol").doubleValue, Double(0.0), accuracy: 0.1, "Sum should be 0.0") // Average :::::::::::::::::::::::::::::::::::::::::::::: // Test int average XCTAssertEqual(noArray.average(ofProperty: "intCol")!.doubleValue, Double(10.0), accuracy: 0.1, "Average should be 10.0") XCTAssertEqual(yesArray.average(ofProperty: "intCol")!.doubleValue, Double(10.0), accuracy: 0.1, "Average should be 10.0") // Test float average XCTAssertEqual(noArray.average(ofProperty: "floatCol")!.doubleValue, Double(0.0), accuracy: 0.1, "Average should be 0.0") XCTAssertEqual(yesArray.average(ofProperty: "floatCol")!.doubleValue, Double(1.2), accuracy: 0.1, "Average should be 1.2") // Test double average XCTAssertEqual(noArray.average(ofProperty: "doubleCol")!.doubleValue, Double(2.5), accuracy: 0.1, "Average should be 2.5") XCTAssertEqual(yesArray.average(ofProperty: "doubleCol")!.doubleValue, Double(0.0), accuracy: 0.1, "Average should be 2.5") // MIN :::::::::::::::::::::::::::::::::::::::::::::: // Test int min var min = noArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(10), "Minimum should be 10") min = yesArray.min(ofProperty: "intCol") as! NSNumber XCTAssertEqual(min.int32Value, Int32(10), "Minimum should be 10") // Test float min min = noArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(0), accuracy: 0.1, "Minimum should be 0.0f") min = yesArray.min(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(min.floatValue, Float(1.2), accuracy: 0.1, "Minimum should be 1.2f") // Test double min min = noArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(2.5), accuracy: 0.1, "Minimum should be 2.5") min = yesArray.min(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(min.doubleValue, Double(0.0), accuracy: 0.1, "Minimum should be 0.0") // Test date min var dateMinOutput = noArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMaxInput, "Minimum should be dateMaxInput") dateMinOutput = yesArray.min(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMinOutput, dateMinInput, "Minimum should be dateMinInput") // MAX :::::::::::::::::::::::::::::::::::::::::::::: // Test int max var max = noArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 10, "Maximum should be 10") max = yesArray.max(ofProperty: "intCol") as! NSNumber XCTAssertEqual(max.intValue, 10, "Maximum should be 10") // Test float max max = noArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(0.0), accuracy: 0.1, "Maximum should be 0.0f") max = yesArray.max(ofProperty: "floatCol") as! NSNumber XCTAssertEqual(max.floatValue, Float(1.2), accuracy: 0.1, "Maximum should be 1.2f") // Test double max max = noArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(2.5), accuracy: 0.1, "Maximum should be 2.5") max = yesArray.max(ofProperty: "doubleCol") as! NSNumber XCTAssertEqual(max.doubleValue, Double(0.0), accuracy: 0.1, "Maximum should be 0.0") // Test date max var dateMaxOutput = noArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMaxInput, "Maximum should be dateMaxInput") dateMaxOutput = yesArray.max(ofProperty: "dateCol") as! Date XCTAssertEqual(dateMaxOutput, dateMinInput, "Maximum should be dateMinInput") } func testSetDescription_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() for _ in 0..<300 { let po1 = SwiftRLMEmployeeObject() po1.age = 40 po1.name = "Joe" po1.hired = true let po2 = SwiftRLMEmployeeObject() po2.age = 30 po2.name = "Mary" po2.hired = false let po3 = SwiftRLMEmployeeObject() po3.age = 24 po3.name = "Jill" po3.hired = true realm.add(po1) realm.add(po2) realm.add(po3) } let company = SwiftRLMCompanyObject() realm.add(company) company.employeeSet.addObjects(SwiftRLMEmployeeObject.allObjects(in: realm)) try! realm.commitWriteTransaction() let description = company.employeeSet.description XCTAssertTrue((description as NSString).range(of: "name").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMSet") XCTAssertTrue((description as NSString).range(of: "Mary").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMSet") XCTAssertTrue((description as NSString).range(of: "age").location != Foundation.NSNotFound, "property names should be displayed when calling \"description\" on RLMSet") XCTAssertTrue((description as NSString).range(of: "24").location != Foundation.NSNotFound, "property values should be displayed when calling \"description\" on RLMSet") XCTAssertTrue((description as NSString).range(of: "800 objects skipped").location != Foundation.NSNotFound, "'800 objects skipped' should be displayed when calling \"description\" on RLMSet") } func makeEmployee(_ realm: RLMRealm, _ age: Int32, _ name: String, _ hired: Bool) -> EmployeeObject { let employee = EmployeeObject() employee.age = age employee.name = name employee.hired = hired realm.add(employee) return employee } func testDeleteLinksAndObjectsInSet_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() let po1 = makeEmployee(realm, 40, "Joe", true) _ = makeEmployee(realm, 30, "John", false) let po3 = makeEmployee(realm, 25, "Jill", true) let company = CompanyObject() company.name = "name" realm.add(company) company.employeeSet.addObjects(EmployeeObject.allObjects(in: realm)) try! realm.commitWriteTransaction() let peopleInCompany: RLMSet = company.employeeSet! XCTAssertEqual(peopleInCompany.count, UInt(3), "No links should have been deleted") realm.beginWriteTransaction() peopleInCompany.remove(po3) // Should delete link to employee try! realm.commitWriteTransaction() XCTAssertEqual(peopleInCompany.count, UInt(2), "link deleted when accessing via links") let test = peopleInCompany.allObjects[0] XCTAssertEqual(test.age, po1.age, "Should be equal") XCTAssertEqual(test.name!, po1.name!, "Should be equal") XCTAssertEqual(test.hired, po1.hired, "Should be equal") let allPeople = EmployeeObject.allObjects(in: realm) XCTAssertEqual(allPeople.count, UInt(3), "Only links should have been deleted, not the employees") } } ================================================ FILE: Realm/Tests/Swift/SwiftTestObjects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif class SwiftRLMStringObject: RLMObject { @objc dynamic var stringCol = "" } class SwiftRLMBoolObject: RLMObject { @objc dynamic var boolCol = false } class SwiftRLMIntObject: RLMObject { @objc dynamic var intCol = 0 } class SwiftRLMLongObject: RLMObject { @objc dynamic var longCol: Int64 = 0 } class SwiftRLMObject: RLMObject { @objc dynamic var boolCol = false @objc dynamic var intCol = 123 @objc dynamic var floatCol = 1.23 as Float @objc dynamic var doubleCol = 12.3 @objc dynamic var stringCol = "a" @objc dynamic var binaryCol = "a".data(using: String.Encoding.utf8) @objc dynamic var dateCol = Date(timeIntervalSince1970: 1) @objc dynamic var objectCol = SwiftRLMBoolObject() @objc dynamic var arrayCol = RLMArray(objectClassName: SwiftRLMBoolObject.className()) @objc dynamic var setCol = RLMSet(objectClassName: SwiftRLMBoolObject.className()) @objc dynamic var uuidCol = UUID(uuidString: "00000000-0000-0000-0000-000000000000") @objc dynamic var rlmValue: RLMValue = "A Mixed Object" as NSString } class SwiftRLMOptionalObject: RLMObject { @objc dynamic var optStringCol: String? @objc dynamic var optNSStringCol: NSString? @objc dynamic var optBinaryCol: Data? @objc dynamic var optDateCol: Date? @objc dynamic var optObjectCol: SwiftRLMBoolObject? @objc dynamic var uuidCol: UUID? } class SwiftRLMPrimitiveArrayObject: RLMObject { @objc dynamic var stringCol = RLMArray(objectType: .string, optional: false) @objc dynamic var optStringCol = RLMArray(objectType: .string, optional: true) @objc dynamic var dataCol = RLMArray(objectType: .data, optional: false) @objc dynamic var optDataCol = RLMArray(objectType: .data, optional: true) @objc dynamic var dateCol = RLMArray(objectType: .date, optional: false) @objc dynamic var optDateCol = RLMArray(objectType: .date, optional: true) @objc dynamic var uuidCol = RLMArray(objectType: .UUID, optional: false) @objc dynamic var optUuidCol = RLMArray(objectType: .UUID, optional: true) } class SwiftRLMPrimitiveSetObject: RLMObject { @objc dynamic var stringCol = RLMSet(objectType: .string, optional: false) @objc dynamic var optStringCol = RLMSet(objectType: .string, optional: true) @objc dynamic var dataCol = RLMSet(objectType: .data, optional: false) @objc dynamic var optDataCol = RLMSet(objectType: .data, optional: true) @objc dynamic var dateCol = RLMSet(objectType: .date, optional: false) @objc dynamic var optDateCol = RLMSet(objectType: .date, optional: true) @objc dynamic var uuidCol = RLMSet(objectType: .UUID, optional: false) @objc dynamic var optUuidCol = RLMSet(objectType: .UUID, optional: true) } class SwiftRLMDogObject: RLMObject { @objc dynamic var dogName = "" } class SwiftRLMOwnerObject: RLMObject { @objc dynamic var name = "" @objc dynamic var dog: SwiftRLMDogObject? = SwiftRLMDogObject() } class SwiftRLMAggregateObject: RLMObject { @objc dynamic var intCol = 0 @objc dynamic var floatCol = 0 as Float @objc dynamic var doubleCol = 0.0 @objc dynamic var boolCol = false @objc dynamic var dateCol = Date() } class SwiftRLMAllIntSizesObject: RLMObject { @objc dynamic var int8: Int8 = 0 @objc dynamic var int16: Int16 = 0 @objc dynamic var int32: Int32 = 0 @objc dynamic var int64: Int64 = 0 } class SwiftRLMEmployeeObject: RLMObject { @objc dynamic var name = "" @objc dynamic var age = 0 @objc dynamic var hired = false } class SwiftRLMCompanyObject: RLMObject { @objc dynamic var employees = RLMArray(objectClassName: SwiftRLMEmployeeObject.className()) @objc dynamic var employeeSet = RLMSet(objectClassName: SwiftRLMEmployeeObject.className()) @objc dynamic var employeeMap = RLMDictionary(objectClassName: SwiftRLMEmployeeObject.className(), keyType: .string) } class SwiftRLMAggregateSet: RLMObject { @objc dynamic var set = RLMSet(objectClassName: SwiftRLMAggregateObject.className()) } class SwiftRLMArrayPropertyObject: RLMObject { @objc dynamic var name = "" @objc dynamic var array = RLMArray(objectClassName: SwiftRLMStringObject.className()) @objc dynamic var intArray = RLMArray(objectClassName: SwiftRLMIntObject.className()) } class SwiftRLMSetPropertyObject: RLMObject { @objc dynamic var name = "" @objc dynamic var set = RLMSet(objectClassName: SwiftRLMStringObject.className()) @objc dynamic var intSet = RLMSet(objectClassName: SwiftRLMIntObject.className()) } class SwiftRLMDictionaryPropertyObject: RLMObject { @objc dynamic var dict = RLMDictionary(objectClassName: SwiftRLMAggregateObject.className(), keyType: .string) } class SwiftRLMDictionaryEmployeeObject: RLMObject { @objc dynamic var dict = RLMDictionary(objectClassName: SwiftRLMEmployeeObject.className(), keyType: .string) } class SwiftRLMDynamicObject: RLMObject { @objc dynamic var stringCol = "a" @objc dynamic var intCol = 0 } class SwiftRLMUTF8Object: RLMObject { @objc dynamic var 柱колоéнǢкƱаم👍 = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" } class SwiftRLMIgnoredPropertiesObject: RLMObject { @objc dynamic var name = "" @objc dynamic var age = 0 @objc dynamic var runtimeProperty: AnyObject? @objc dynamic var readOnlyProperty: Int { return 0 } override class func ignoredProperties() -> [String]? { return ["runtimeProperty"] } } class SwiftRLMPrimaryStringObject: RLMObject { @objc dynamic var stringCol = "" @objc dynamic var intCol = 0 override class func primaryKey() -> String { return "stringCol" } } class SwiftRLMLinkSourceObject: RLMObject { @objc dynamic var id = 0 @objc dynamic var link: SwiftRLMLinkTargetObject? } class SwiftRLMLinkTargetObject: RLMObject { @objc dynamic var id = 0 @objc dynamic var backlinks: RLMLinkingObjects? override class func linkingObjectsProperties() -> [String : RLMPropertyDescriptor] { return ["backlinks": RLMPropertyDescriptor(with: SwiftRLMLinkSourceObject.self, propertyName: "link")] } } class SwiftRLMLazyVarObject: RLMObject { @objc dynamic lazy var lazyProperty: String = "hello world" } class SwiftRLMIgnoredLazyVarObject: RLMObject { @objc dynamic var id = 0 @objc dynamic lazy var ignoredVar: String = "hello world" override class func ignoredProperties() -> [String] { return ["ignoredVar"] } } class SwiftRLMObjectiveCTypesObject: RLMObject { @objc dynamic var stringCol: NSString? @objc dynamic var dateCol: NSDate? @objc dynamic var dataCol: NSData? @objc dynamic var numCol: NSNumber? = 0 } ================================================ FILE: Realm/Tests/Swift/SwiftUnicodeTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import Realm #if canImport(RealmTestSupport) import RealmTestSupport #endif let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" class SwiftRLMUnicodeTests: RLMTestCase { // Swift models func testUTF8StringContents() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMStringObject.create(in: realm, withValue: [utf8TestString]) try! realm.commitWriteTransaction() let obj1 = SwiftRLMStringObject.allObjects(in: realm).firstObject() as! SwiftRLMStringObject XCTAssertEqual(obj1.stringCol, utf8TestString, "Storing and retrieving a string with UTF8 content should work") let obj2 = SwiftRLMStringObject.objects(in: realm, where: "stringCol == %@", utf8TestString).firstObject() as! SwiftRLMStringObject XCTAssertTrue(obj1.isEqual(to: obj2), "Querying a realm searching for a string with UTF8 content should work") } func testUTF8PropertyWithUTF8StringContents() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = SwiftRLMUTF8Object.create(in: realm, withValue: [utf8TestString]) try! realm.commitWriteTransaction() let obj1 = SwiftRLMUTF8Object.allObjects(in: realm).firstObject() as! SwiftRLMUTF8Object XCTAssertEqual(obj1.柱колоéнǢкƱаم👍, utf8TestString, "Storing and retrieving a string with UTF8 content should work") // Test fails because of rdar://17735684 // let obj2 = SwiftRLMUTF8Object.objectsInRealm(realm, "柱колоéнǢкƱаم👍 == %@", utf8TestString).firstObject() as SwiftRLMUTF8Object // XCTAssertEqual(obj1, obj2, "Querying a realm searching for a string with UTF8 content should work") } // Objective-C models func testUTF8StringContents_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = StringObject.create(in: realm, withValue: [utf8TestString]) try! realm.commitWriteTransaction() let obj1 = StringObject.allObjects(in: realm).firstObject() as! StringObject XCTAssertEqual(obj1.stringCol, utf8TestString, "Storing and retrieving a string with UTF8 content should work") // Temporarily commented out because variadic import seems broken let obj2 = StringObject.objects(in: realm, where: "stringCol == %@", utf8TestString).firstObject() as! StringObject XCTAssertTrue(obj1.isEqual(to: obj2), "Querying a realm searching for a string with UTF8 content should work") } func testUTF8PropertyWithUTF8StringContents_objc() { let realm = realmWithTestPath() realm.beginWriteTransaction() _ = UTF8Object.create(in: realm, withValue: [utf8TestString]) try! realm.commitWriteTransaction() let obj1 = UTF8Object.allObjects(in: realm).firstObject() as! UTF8Object XCTAssertEqual(obj1.柱колоéнǢкƱаم, utf8TestString, "Storing and retrieving a string with UTF8 content should work") // Test fails because of rdar://17735684 // let obj2 = UTF8Object.objectsInRealm(realm, "柱колоéнǢкƱаم == %@", utf8TestString).firstObject() as UTF8Object // XCTAssertEqual(obj1, obj2, "Querying a realm searching for a string with UTF8 content should work") } } ================================================ FILE: Realm/Tests/SwiftUITestHost/Info.plist ================================================ UIAppFonts Effra_Rg.ttf Effra_Bd.ttf Effra_Lt.ttf CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2021 Realm. All rights reserved. UILaunchStoryboardName LaunchScreen ================================================ FILE: Realm/Tests/SwiftUITestHost/LaunchScreen.storyboard ================================================ ================================================ FILE: Realm/Tests/SwiftUITestHost/Objects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 RealmSwift import Foundation class Reminder: EmbeddedObject, ObjectKeyIdentifiable { enum Priority: Int, PersistableEnum, CaseIterable, Identifiable, CustomStringConvertible { var id: Int { self.rawValue } case low, medium, high var description: String { switch self { case .low: return "low" case .medium: return "medium" case .high: return "high" } } } @Persisted var title = "" @Persisted var notes = "" @Persisted var isFlagged = false @Persisted var date = Date() @Persisted var isComplete = false @Persisted var priority: Priority = .low } class ReminderList: Object, ObjectKeyIdentifiable { @Persisted var name = "New List" @Persisted var icon = "list.bullet" @Persisted var reminders = RealmSwift.List() var firstLetter: String { guard let char = name.first else { return "" } return String(char) } } ================================================ FILE: Realm/Tests/SwiftUITestHost/SwiftUITestHostApp.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 RealmSwift import SwiftUI struct ReminderFormView: View { @ObservedRealmObject var reminder: Reminder var body: some View { Form { TextField("title", text: $reminder.title).accessibility(identifier: "formTitle") DatePicker("date", selection: $reminder.date) Picker("priority", selection: $reminder.priority, content: { ForEach(Reminder.Priority.allCases) { priority in Text(priority.description) .tag(priority) .accessibilityIdentifier(priority.description) } }).accessibilityIdentifier("priority_picker") } .navigationTitle(reminder.title) } } struct ReminderListView: View { @ObservedRealmObject var list: ReminderList @State var activeReminder: Reminder.ID? var body: some View { VStack { List { ForEach(list.reminders) { reminder in NavigationLink(destination: ReminderFormView(reminder: reminder), tag: reminder.id, selection: $activeReminder) { Text(reminder.title) } } .onMove(perform: $list.reminders.move) .onDelete(perform: $list.reminders.remove) } }.navigationTitle(list.name) .navigationBarItems(trailing: HStack { EditButton() Button("add") { let reminder = Reminder() $list.reminders.append(reminder) activeReminder = reminder.id }.accessibility(identifier: "addReminder") }) } } struct ReminderListResultsView: View { // Only receive notifications on "name" to work around what appears to be // a SwiftUI bug introduced in iOS 14.5: when we're two levels deep in // NagivationLinks, refreshing this view makes the second NavigationLink pop @ObservedResults(ReminderList.self, keyPaths: ["name", "icon"]) var reminders @Binding var searchFilter: String @State var activeList: ReminderList.ID? struct Row: View { @ObservedRealmObject var list: ReminderList var body: some View { HStack { Image(systemName: list.icon) TextField("List Name", text: $list.name) Spacer() Text("\(list.reminders.count)") }.accessibility(identifier: "hstack") } } var body: some View { List { ForEach(reminders) { list in NavigationLink(destination: ReminderListView(list: list), tag: list.id, selection: $activeList) { Row(list: list) }.accessibilityIdentifier(list.name).accessibilityActivationPoint(CGPoint(x: 0, y: 0)) }.onDelete(perform: $reminders.remove) }.onChange(of: searchFilter) { value in if ProcessInfo.processInfo.environment["query_type"] == "type_safe_query" { $reminders.where = value.isEmpty ? nil : { $0.name.contains(value, options: .caseInsensitive) } } else { $reminders.filter = value.isEmpty ? nil : NSPredicate(format: "name CONTAINS[c] %@", value) } } } } public extension Color { static let lightText = Color(UIColor.lightText) static let darkText = Color(UIColor.darkText) static let label = Color(UIColor.label) static let secondaryLabel = Color(UIColor.secondaryLabel) static let tertiaryLabel = Color(UIColor.tertiaryLabel) static let quaternaryLabel = Color(UIColor.quaternaryLabel) static let systemBackground = Color(UIColor.systemBackground) static let secondarySystemBackground = Color(UIColor.secondarySystemBackground) static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground) } struct SearchView: View { @Binding var searchFilter: String var body: some View { VStack { Spacer() HStack { Image(systemName: "magnifyingglass").foregroundColor(.gray) .padding(.leading, 7) .padding(.top, 7) .padding(.bottom, 7) TextField("search", text: $searchFilter) .padding(.top, 7) .padding(.bottom, 7) .accessibility(identifier: "searchField") }.background(RoundedRectangle(cornerRadius: 15) .fill(Color.secondarySystemBackground)) Spacer() }.frame(maxHeight: 40).padding() } } struct Footer: View { let realm = try! Realm() var body: some View { HStack { Button(action: { try! realm.write { realm.add(ReminderList()) } }, label: { HStack { Image(systemName: "plus.circle") Text("Add list") } }).buttonStyle(BorderlessButtonStyle()) .padding() .accessibility(identifier: "addList") Spacer() } } } @MainActor struct ContentView: View { @State var searchFilter: String = "" var content: some View { VStack { SearchView(searchFilter: $searchFilter) ReminderListResultsView(searchFilter: $searchFilter) Spacer() Footer() } .navigationBarItems(trailing: EditButton()) .navigationTitle("reminders") } var body: some View { if #available(iOS 16.0, *) { NavigationStack { content } } else { NavigationView { content } } } } struct MultiRealmContentView: View { struct RealmView: View { @Environment(\.realm) var realm var body: some View { Text(realm.configuration.inMemoryIdentifier ?? "no memory identifier") .accessibilityIdentifier("test_text_view") } } @State var showSheet = false var body: some View { NavigationView { VStack { NavigationLink("Realm A", destination: RealmView().environment(\.realmConfiguration, Realm.Configuration(inMemoryIdentifier: "realm_a"))) NavigationLink("Realm B", destination: RealmView().environment(\.realmConfiguration, Realm.Configuration(inMemoryIdentifier: "realm_b"))) Button("Realm C") { showSheet = true } }.sheet(isPresented: $showSheet, content: { RealmView().environment(\.realmConfiguration, Realm.Configuration(inMemoryIdentifier: "realm_c")) }) } } } struct UnmanagedObjectTestView: View { struct NestedViewOne: View { struct NestedViewTwo: View { @Environment(\.realm) var realm @Environment(\.presentationMode) var presentationMode @ObservedRealmObject var reminderList: ReminderList var body: some View { Button("Delete") { $reminderList.delete() presentationMode.wrappedValue.dismiss() } } } @ObservedRealmObject var reminderList: ReminderList @Environment(\.presentationMode) var presentationMode @State var shown = false var body: some View { NavigationLink("Next", destination: NestedViewTwo(reminderList: reminderList)) .isDetailLink(false) .onAppear { if shown { presentationMode.wrappedValue.dismiss() } shown = true } } } @ObservedRealmObject var reminderList = ReminderList() @Environment(\.realm) var realm @State var passToNestedView = false var body: some View { NavigationView { Form { TextField("name", text: $reminderList.name).accessibilityIdentifier("name") NavigationLink("test", destination: NestedViewOne(reminderList: reminderList), isActive: $passToNestedView) .isDetailLink(false) }.navigationBarItems(trailing: Button("Add", action: { try! realm.write { realm.add(reminderList) } passToNestedView = true }).accessibility(identifier: "addReminder")) }.onAppear { print("ReminderList: \(reminderList)") } } } struct ObservedResultsKeyPathTestView: View { @ObservedResults(ReminderList.self, keyPaths: ["reminders.isFlagged"]) var reminders var body: some View { VStack { List { ForEach(reminders) { list in ObservedResultsKeyPathTestRow(list: list) }.onDelete(perform: $reminders.remove) } .navigationBarItems(trailing: EditButton()) .navigationTitle("reminders") Footer() } } } struct ObservedResultsKeyPathTestRow: View { var list: ReminderList var body: some View { HStack { Image(systemName: list.icon) Text(list.name) }.frame(minWidth: 100).accessibility(identifier: "hstack") } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct ObservedResultsSearchableTestView: View { @ObservedResults(ReminderList.self, where: { $0.name.starts(with: "reminder") }) var reminders @State var searchFilter: String = "" var body: some View { NavigationView { List { ForEach(reminders) { reminder in Text(reminder.name) } } .searchable(text: $searchFilter, collection: $reminders, keyPath: \.name) { ForEach(reminders) { remindersFiltered in Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) } } .navigationTitle("Reminders") .navigationBarItems(trailing: Button("add") { let reminder = ReminderList() $reminders.append(reminder) }.accessibility(identifier: "addList")) } } } struct ObservedResultsConfiguration: View { @ObservedResults(ReminderList.self) var remindersA // config from `.environment` @ObservedResults(ReminderList.self, configuration: Realm.Configuration(inMemoryIdentifier: "realm_b")) var remindersB var body: some View { NavigationView { VStack { Text(remindersA.realm?.configuration.inMemoryIdentifier ?? "no memory identifier") .accessibility(identifier: "realm_a_label") Text(remindersB.realm?.configuration.inMemoryIdentifier ?? "no memory identifier") .accessibility(identifier: "realm_b_label") List { ForEach(remindersA) { reminder in Text(reminder.name) } }.accessibility(identifier: "ListA") List { ForEach(remindersB) { reminder in Text(reminder.name) } }.accessibility(identifier: "ListB") } .navigationTitle("Reminders") .navigationBarItems(leading: Button("add A") { let reminder = ReminderList() $remindersA.append(reminder) }.accessibility(identifier: "addListA") ) .navigationBarItems(trailing: Button("add B") { let reminder = ReminderList() $remindersB.append(reminder) }.accessibility(identifier: "addListB") ) } } } struct ObservedSectionedResultsKeyPathTestView: View { @ObservedSectionedResults(ReminderList.self, sectionKeyPath: \.firstLetter, keyPaths: ["reminders.isFlagged"]) var reminders var body: some View { VStack { List { ForEach(reminders) { section in Section(header: Text(section.key)) { ForEach(section) { object in ObservedResultsKeyPathTestRow(list: object) }.onDelete { $reminders.remove(atOffsets: $0, section: section) } } } } .navigationBarItems(trailing: EditButton()) .navigationTitle("reminders") Footer() } } } // swiftlint:disable:next type_name struct ObservedSectionedResultsWithSortDescriptorsView: View { @ObservedSectionedResults(ReminderList.self, sectionBlock: { $0.name.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor(keyPath: \ReminderList.name)], keyPaths: ["reminders.isFlagged"]) var reminders var body: some View { VStack { List { ForEach(reminders) { section in Section(header: Text(section.key)) { ForEach(section) { object in ObservedResultsKeyPathTestRow(list: object) } } } } .navigationBarItems(trailing: EditButton()) .navigationTitle("reminders") Footer() } } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) // swiftlint:disable:next type_name struct ObservedSectionedResultsSearchableTestView: View { @ObservedSectionedResults(ReminderList.self, sectionKeyPath: \.firstLetter, where: { $0.name.starts(with: "reminder") }) var reminders @State var searchFilter: String = "" var body: some View { NavigationView { List { ForEach(reminders) { section in Section(section.key) { ForEach(section) { object in Text(object.name) } } } } .searchable(text: $searchFilter, collection: $reminders, keyPath: \.name) { ForEach(reminders) { section in Section(section.key) { ForEach(section) { objectsFiltered in Text(objectsFiltered.name).searchCompletion(objectsFiltered.name) } } } } .navigationTitle("Reminders") .navigationBarItems(trailing: Button("add") { let realm = $reminders.wrappedValue.realm?.thaw() try! realm?.write { realm?.add(ReminderList()) } }.accessibility(identifier: "addList")) } } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct ObservedSectionedResultsConfiguration: View { @ObservedSectionedResults(ReminderList.self, sectionKeyPath: \.firstLetter) var remindersA // config from `.environment` @ObservedSectionedResults(ReminderList.self, sectionKeyPath: \.firstLetter, configuration: Realm.Configuration(inMemoryIdentifier: "realm_b")) var remindersB var body: some View { NavigationView { VStack { Text(remindersA.realm?.configuration.inMemoryIdentifier ?? "no memory identifier") .accessibility(identifier: "realm_a_label") Text(remindersB.realm?.configuration.inMemoryIdentifier ?? "no memory identifier") .accessibility(identifier: "realm_b_label") List { ForEach(remindersA) { reminderSection in Section(reminderSection.key) { ForEach(reminderSection) { object in Text(object.name) } } } }.accessibility(identifier: "ListA") List { ForEach(remindersB) { reminderSection in Section(reminderSection.key) { ForEach(reminderSection) { object in Text(object.name) } } } }.accessibility(identifier: "ListB") } .navigationTitle("Reminders") .navigationBarItems(leading: Button("add A") { let realm = $remindersA.wrappedValue.realm?.thaw() try! realm?.write { realm?.add(ReminderList()) } }.accessibility(identifier: "addListA") ) .navigationBarItems(trailing: Button("add B") { let realm = $remindersB.wrappedValue.realm?.thaw() try! realm?.write { realm?.add(ReminderList()) } }.accessibility(identifier: "addListB") ) } } } @main struct App: SwiftUI.App { var body: some Scene { if let realmPath = ProcessInfo.processInfo.environment["REALM_PATH"] { Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: URL(string: realmPath)!, deleteRealmIfMigrationNeeded: true) } else { Realm.Configuration.defaultConfiguration = Realm.Configuration(deleteRealmIfMigrationNeeded: true) } let view: AnyView = { switch ProcessInfo.processInfo.environment["test_type"] { case "multi_realm_test": return AnyView(MultiRealmContentView()) case "unmanaged_object_test": return AnyView(UnmanagedObjectTestView()) case "observed_results_key_path": return AnyView(ObservedResultsKeyPathTestView()) case "observed_results_searchable": if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { return AnyView(ObservedResultsSearchableTestView()) } else { return AnyView(EmptyView()) } case "observed_results_configuration": return AnyView(ObservedResultsConfiguration() .environment(\.realmConfiguration, Realm.Configuration(inMemoryIdentifier: "realm_a"))) case "observed_sectioned_results_key_path": return AnyView(ObservedSectionedResultsKeyPathTestView()) case "observed_sectioned_results_sort_descriptors": return AnyView(ObservedSectionedResultsWithSortDescriptorsView()) case "observed_sectioned_results_searchable": if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { return AnyView(ObservedSectionedResultsSearchableTestView()) } else { return AnyView(EmptyView()) } case "observed_sectioned_results_configuration": if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { return AnyView(ObservedSectionedResultsConfiguration() .environment(\.realmConfiguration, Realm.Configuration(inMemoryIdentifier: "realm_a"))) } else { return AnyView(EmptyView()) } default: return AnyView(ContentView()) } }() return WindowGroup { view } } } ================================================ FILE: Realm/Tests/SwiftUITestHostUITests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Realm/Tests/SwiftUITestHostUITests/SwiftUITestHostUITests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) class SwiftUITests: XCTestCase { var realm: Realm! @MainActor let app = XCUIApplication() @MainActor override func setUpWithError() throws { continueAfterFailure = false // the realm must be created before app launch otherwise we will not have // write permissions let realmPath = URL(string: "\(FileManager.default.temporaryDirectory)\(UUID())")! let configuration = Realm.Configuration(fileURL: realmPath) _ = try Realm.deleteFiles(for: configuration) self.realm = try! Realm(configuration: configuration) app.launchEnvironment = [ "REALM_PATH": realmPath.absoluteString ] } @MainActor override func tearDownWithError() throws { app.terminate() self.realm.invalidate() let config = realm.configuration self.realm = nil XCTAssertTrue(try Realm.deleteFiles(for: config)) } private func deleteString(for string: String) -> String { String(repeating: XCUIKeyboardKey.delete.rawValue, count: string.count) } @MainActor private var tables: XCUIElementQuery { if #available(iOS 16.0, *) { return app.collectionViews } else { return app.tables } } @MainActor func testSampleApp() throws { app.launch() // assert realm is empty XCTAssertEqual(realm.objects(ReminderList.self).count, 0) // add 3 lists, and assert that they have been added to // the UI and Realm let addButton = app.buttons["addList"] addButton.tap() addButton.tap() addButton.tap() XCTAssertEqual(realm.objects(ReminderList.self).count, 3) XCTAssertEqual(tables.firstMatch.cells.count, 3) // delete each person, operating from the zeroeth index for _ in 0.. CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 NSPrincipalClass $(PRINCIPAL_CLASS) LSUIElement UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight NSAppTransportSecurity NSAllowsArbitraryLoads ================================================ FILE: Realm/Tests/TestHost/main.m ================================================ // // main.m // TestHost // // Created by Thomas Goyne on 8/6/14. // Copyright (c) 2014 Realm. All rights reserved. // #import #if TARGET_OS_WATCH // watchOS doesn't support testing at this time. int main(int argc, const char *argv[]) { } #elif TARGET_OS_IPHONE || TARGET_OS_TV || TARGET_OS_MACCATALYST #import @interface RLMAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end @implementation RLMAppDelegate @end int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, NSStringFromClass([UIApplication class]), NSStringFromClass([RLMAppDelegate class])); } } #else #import int main(int argc, const char *argv[]) { @autoreleasepool { return NSApplicationMain(argc, argv); } } #endif ================================================ FILE: Realm/Tests/ThreadSafeReferenceTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 "RLMTestCase.h" #import "RLMRealmConfiguration_Private.h" #import "RLMThreadSafeReference.h" @interface ThreadSafeReferenceTests : RLMTestCase @end @implementation ThreadSafeReferenceTests /// Resolve a thread-safe reference confirming that you can't resolve it a second time. - (id)assertResolve:(RLMRealm *)realm reference:(RLMThreadSafeReference *)reference { XCTAssertFalse(reference.isInvalidated); id object = [realm resolveThreadSafeReference:reference]; XCTAssertTrue(reference.isInvalidated); RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:reference], @"Can only resolve a thread safe reference once"); return object; } - (void)testInvalidThreadSafeReferenceConstruction { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.cache = false; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; StringObject *stringObject = [[StringObject alloc] init]; ArrayPropertyObject *arrayParent = [[ArrayPropertyObject alloc] initWithValue:@[@"arrayObject", @[@[@"a"]], @[]]]; RLMArray *arrayObject = arrayParent.array; RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:stringObject], @"Cannot construct reference to unmanaged object"); RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:arrayObject], @"Cannot construct reference to unmanaged object"); [realm beginWriteTransaction]; [realm addObject:stringObject]; [realm addObject:arrayParent]; arrayObject = arrayParent.array; [realm deleteAllObjects]; [realm commitWriteTransaction]; RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:stringObject], @"Cannot construct reference to invalidated object"); RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:arrayObject], @"Cannot construct reference to invalidated object"); } - (void)testInvalidThreadSafeReferenceUsage { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; StringObject *stringObject = [StringObject createInDefaultRealmWithValue:@{@"stringCol": @"hello"}]; RLMThreadSafeReference *ref1 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject]; [realm commitWriteTransaction]; RLMThreadSafeReference *ref2 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject]; RLMThreadSafeReference *ref3 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertNil([[self realmWithTestPath] resolveThreadSafeReference:ref1]); XCTAssertNoThrow([realm resolveThreadSafeReference:ref2]); RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:ref2], @"Can only resolve a thread safe reference once"); // Assert that we can resolve a different reference to the same object. XCTAssertEqualObjects([self assertResolve:realm reference:ref3][@"stringCol"], @"hello"); }]; } - (void)testPassThreadSafeReferenceToDeletedObject { RLMRealm *realm = [RLMRealm defaultRealm]; IntObject *intObject = [[IntObject alloc] init]; [realm transactionWithBlock:^{ [realm addObject:intObject]; }]; RLMThreadSafeReference *ref1 = [RLMThreadSafeReference referenceWithThreadConfined:intObject]; RLMThreadSafeReference *ref2 = [RLMThreadSafeReference referenceWithThreadConfined:intObject]; XCTAssertEqual(0, intObject.intCol); [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; }]; }]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; XCTAssertEqualObjects([self assertResolve:realm reference:ref1][@"intCol"], @0); [realm refresh]; XCTAssertNil([self assertResolve:realm reference:ref2]); }]; } - (void)testPassThreadSafeReferencesToMultipleObjects { RLMRealm *realm = [RLMRealm defaultRealm]; StringObject *stringObject = [[StringObject alloc] init]; IntObject *intObject = [[IntObject alloc] init]; [realm transactionWithBlock:^{ [realm addObject:stringObject]; [realm addObject:intObject]; }]; RLMThreadSafeReference *stringObjectRef = [RLMThreadSafeReference referenceWithThreadConfined:stringObject]; RLMThreadSafeReference *intObjectRef = [RLMThreadSafeReference referenceWithThreadConfined:intObject]; XCTAssertEqualObjects(nil, stringObject.stringCol); XCTAssertEqual(0, intObject.intCol); [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; StringObject *stringObject = [self assertResolve:realm reference:stringObjectRef]; IntObject *intObject = [self assertResolve:realm reference:intObjectRef]; [realm transactionWithBlock:^{ stringObject.stringCol = @"the meaning of life"; intObject.intCol = 42; }]; }]; XCTAssertEqualObjects(nil, stringObject.stringCol); XCTAssertEqual(0, intObject.intCol); [realm refresh]; XCTAssertEqualObjects(@"the meaning of life", stringObject.stringCol); XCTAssertEqual(42, intObject.intCol); } - (void)testPassThreadSafeReferenceToArray { RLMRealm *realm = [RLMRealm defaultRealm]; DogArrayObject *object = [[DogArrayObject alloc] init]; [realm transactionWithBlock:^{ [realm addObject:object]; DogObject *friday = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Friday", @"age": @15}]; [object.dogs addObject:friday]; }]; RLMThreadSafeReference *dogsArrayRef = [RLMThreadSafeReference referenceWithThreadConfined:object.dogs]; XCTAssertEqual(1ul, object.dogs.count); XCTAssertEqualObjects(@"Friday", object.dogs[0].dogName); [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; RLMArray *dogs = [self assertResolve:realm reference:dogsArrayRef]; XCTAssertEqual(1ul, dogs.count); XCTAssertEqualObjects(@"Friday", dogs[0].dogName); [realm transactionWithBlock:^{ [dogs removeAllObjects]; DogObject *cookie = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Cookie", @"age": @8}]; DogObject *breezy = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Breezy", @"age": @6}]; [dogs addObjects:@[cookie, breezy]]; }]; XCTAssertEqual(2ul, dogs.count); XCTAssertEqualObjects(@"Cookie", dogs[0].dogName); XCTAssertEqualObjects(@"Breezy", dogs[1].dogName); }]; XCTAssertEqual(1ul, object.dogs.count); XCTAssertEqualObjects(@"Friday", object.dogs[0].dogName); [realm refresh]; XCTAssertEqual(2ul, object.dogs.count); XCTAssertEqualObjects(@"Cookie", object.dogs[0].dogName); XCTAssertEqualObjects(@"Breezy", object.dogs[1].dogName); } - (void)testPassThreadSafeReferenceToResults { RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *allObjects = [StringObject allObjects]; RLMResults *results = [[StringObject objectsWhere:@"stringCol != 'C'"] sortedResultsUsingKeyPath:@"stringCol" ascending:NO]; RLMThreadSafeReference *resultsRef = [RLMThreadSafeReference referenceWithThreadConfined:results]; [realm transactionWithBlock:^{ [StringObject createInDefaultRealmWithValue:@[@"A"]]; [StringObject createInDefaultRealmWithValue:@[@"B"]]; [StringObject createInDefaultRealmWithValue:@[@"C"]]; [StringObject createInDefaultRealmWithValue:@[@"D"]]; }]; XCTAssertEqual(4ul, allObjects.count); XCTAssertEqual(3ul, results.count); XCTAssertEqualObjects(@"D", results[0].stringCol); XCTAssertEqualObjects(@"B", results[1].stringCol); XCTAssertEqualObjects(@"A", results[2].stringCol); [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *results = [self assertResolve:realm reference:resultsRef]; RLMResults *allObjects = [StringObject allObjects]; XCTAssertEqual(0ul, [StringObject allObjects].count); XCTAssertEqual(0ul, results.count); [realm refresh]; XCTAssertEqual(4ul, allObjects.count); XCTAssertEqual(3ul, results.count); XCTAssertEqualObjects(@"D", results[0].stringCol); XCTAssertEqualObjects(@"B", results[1].stringCol); XCTAssertEqualObjects(@"A", results[2].stringCol); [realm transactionWithBlock:^{ [realm deleteObject:results[2]]; [realm deleteObject:results[0]]; [StringObject createInDefaultRealmWithValue:@[@"E"]]; }]; XCTAssertEqual(3ul, allObjects.count); XCTAssertEqual(2ul, results.count); XCTAssertEqualObjects(@"E", results[0].stringCol); XCTAssertEqualObjects(@"B", results[1].stringCol); }]; XCTAssertEqual(4ul, allObjects.count); XCTAssertEqual(3ul, results.count); XCTAssertEqualObjects(@"D", results[0].stringCol); XCTAssertEqualObjects(@"B", results[1].stringCol); XCTAssertEqualObjects(@"A", results[2].stringCol); [realm refresh]; XCTAssertEqual(3ul, allObjects.count); XCTAssertEqual(2ul, results.count); XCTAssertEqualObjects(@"E", results[0].stringCol); XCTAssertEqualObjects(@"B", results[1].stringCol); } - (void)testPassThreadSafeReferenceToLinkingObjects { RLMRealm *realm = [RLMRealm defaultRealm]; DogObject *dogA = [[DogObject alloc] initWithValue:@{@"dogName": @"Cookie", @"age": @10}]; DogObject *unaccessedDogB = [[DogObject alloc] initWithValue:@{@"dogName": @"Skipper", @"age": @7}]; // Ensures that an `RLMLinkingObjects` without cached results can be handed over [realm transactionWithBlock:^{ [realm addObject:[[OwnerObject alloc] initWithValue:@{@"name": @"Andrea", @"dog": dogA}]]; [realm addObject:[[OwnerObject alloc] initWithValue:@{@"name": @"Mike", @"dog": unaccessedDogB}]]; }]; XCTAssertEqual(1ul, dogA.owners.count); XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)dogA.owners[0]).name); RLMThreadSafeReference *ownersARef = [RLMThreadSafeReference referenceWithThreadConfined:dogA.owners]; RLMThreadSafeReference *ownersBRef = [RLMThreadSafeReference referenceWithThreadConfined:unaccessedDogB.owners]; [self dispatchAsyncAndWait:^{ RLMRealm *realm = [RLMRealm defaultRealm]; RLMLinkingObjects *ownersA = [self assertResolve:realm reference:ownersARef]; RLMLinkingObjects *ownersB = [self assertResolve:realm reference:ownersBRef]; XCTAssertEqual(1ul, ownersA.count); XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)ownersA[0]).name); XCTAssertEqual(1ul, ownersB.count); XCTAssertEqualObjects(@"Mike", ((OwnerObject *)ownersB[0]).name); [realm transactionWithBlock:^{ // Swap dogs OwnerObject *ownerA = ownersA[0]; OwnerObject *ownerB = ownersB[0]; DogObject *dogA = ownerA.dog; DogObject *dogB = ownerB.dog; ownerA.dog = dogB; ownerB.dog = dogA; }]; XCTAssertEqual(1ul, ownersA.count); XCTAssertEqualObjects(@"Mike", ((OwnerObject *)ownersA[0]).name); XCTAssertEqual(1ul, ownersB.count); XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)ownersB[0]).name); }]; XCTAssertEqual(1ul, dogA.owners.count); XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)dogA.owners[0]).name); XCTAssertEqual(1ul, unaccessedDogB.owners.count); XCTAssertEqualObjects(@"Mike", ((OwnerObject *)unaccessedDogB.owners[0]).name); [realm refresh]; XCTAssertEqual(1ul, dogA.owners.count); XCTAssertEqualObjects(@"Mike", ((OwnerObject *)dogA.owners[0]).name); XCTAssertEqual(1ul, unaccessedDogB.owners.count); XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)unaccessedDogB.owners[0]).name); } @end ================================================ FILE: Realm/Tests/TransactionTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" @interface TransactionTests : RLMTestCase @end @implementation TransactionTests - (void)testRealmModifyObjectsOutsideOfWriteTransaction { RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; StringObject *obj = [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; XCTAssertThrows([obj setStringCol:@"throw"], @"Setter should throw when called outside of transaction."); } - (void)testTransactionMisuse { RLMRealm *realm = [RLMRealm defaultRealm]; // Insert an object [realm beginWriteTransaction]; StringObject *obj = [StringObject createInRealm:realm withValue:@[@"a"]]; [realm commitWriteTransaction]; XCTAssertThrows([StringObject createInRealm:realm withValue:@[@"a"]], @"Outside write transaction"); XCTAssertThrows([realm commitWriteTransaction], @"No write transaction to close"); [realm beginWriteTransaction]; XCTAssertThrows([realm beginWriteTransaction], @"Write transaction already in place"); [realm commitWriteTransaction]; XCTAssertThrows([realm deleteObject:obj], @"Outside writetransaction"); } @end ================================================ FILE: Realm/Tests/UnicodeTests.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMTestCase.h" NSString * const kUTF8TestString = @"值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا"; @interface UnicodeTests : RLMTestCase @end @implementation UnicodeTests - (void)testUTF8StringContents { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [StringObject createInRealm:realm withValue:@[kUTF8TestString]]; [realm commitWriteTransaction]; StringObject *obj1 = [[StringObject allObjectsInRealm:realm] firstObject]; XCTAssertEqualObjects(obj1.stringCol, kUTF8TestString, @"Storing and retrieving a string with UTF8 content should work"); StringObject *obj2 = [[StringObject objectsInRealm:realm where:@"stringCol == %@", kUTF8TestString] firstObject]; XCTAssertTrue([obj1 isEqualToObject:obj2], @"Querying a realm searching for a string with UTF8 content should work"); } - (void)testUTF8PropertyWithUTF8StringContents { RLMRealm *realm = self.realmWithTestPath; [realm beginWriteTransaction]; [UTF8Object createInRealm:realm withValue:@[kUTF8TestString]]; [realm commitWriteTransaction]; UTF8Object *obj1 = [[UTF8Object allObjectsInRealm:realm] firstObject]; XCTAssertEqualObjects(obj1.柱колоéнǢкƱаم, kUTF8TestString, @"Storing and retrieving a string with UTF8 content should work"); // Test fails because of rdar://17735684 // NSPredicate does not support UTF8 keypaths // UTF8Object *obj2 = [[StringObject objectsInRealm:realm where:@"柱колоéнǢкƱаم == %@", kUTF8TestString] firstObject]; // XCTAssertEqualObjects(obj1, obj2, @"Querying a realm searching for a string with UTF8 content should work"); } @end ================================================ FILE: Realm/Tests/UtilTests.mm ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "RLMTestCase.h" #import "RLMConstants.h" #import "RLMUtil.hpp" #ifndef REALM_COCOA_VERSION #import "RLMVersion.h" #endif @interface UtilTests : RLMTestCase @end static BOOL RLMEqualExceptions(NSException *actual, NSException *expected) { return [actual.name isEqualToString:expected.name] && [actual.reason isEqualToString:expected.reason] && [actual.userInfo isEqual:expected.userInfo]; } @implementation UtilTests - (void)testRLMExceptionWithReasonAndUserInfo { NSString *const reason = @"Reason"; NSDictionary *expectedUserInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION, RLMRealmCoreVersionKey: @REALM_VERSION}; XCTAssertTrue(RLMEqualExceptions(RLMException(reason), [NSException exceptionWithName:RLMExceptionName reason:reason userInfo:expectedUserInfo])); } - (void)testRLMExceptionWithCPlusPlusException { std::runtime_error exception("Reason"); NSDictionary *expectedUserInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION, RLMRealmCoreVersionKey: @REALM_VERSION}; XCTAssertTrue(RLMEqualExceptions(RLMException(exception), [NSException exceptionWithName:RLMExceptionName reason:@"Reason" userInfo:expectedUserInfo])); } - (void)testRLMSetErrorOrThrowWithNilErrorPointer { NSError *error = [NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:nil]; XCTAssertThrows(RLMSetErrorOrThrow(error, nil)); } - (void)testRLMSetErrorOrThrowWithErrorPointer { NSError *error = [NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:nil]; NSError *outError = nil; XCTAssertNoThrow(RLMSetErrorOrThrow(error, &outError)); XCTAssertEqualObjects(error, outError); } @end ================================================ FILE: Realm/Tests/array_tests.py ================================================ import os, re # Tags: # (no)minmax: Type supports min() and max() # (no)comp: Type supports comparison operators # (no)sum: Type supports sum() # (no)avg: Type supports average() # r/o: Type is Required or Optional # (un)man: Type is Managed or Unmanaged # (no)any: Can accept any type types = [ # Class, Object, Property, Values, Tags ['AllPrimitiveArrays', 'unmanaged', 'boolObj', ['@NO', '@YES'], {'r', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'intObj', ['@2', '@3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'floatObj', ['@2.2f', '@3.3f'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'doubleObj', ['@2.2', '@3.3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'stringObj', ['@"a"', '@"b"'], {'r', 'unman', 'string', 'comp'}], ['AllPrimitiveArrays', 'unmanaged', 'dataObj', ['data(1)', 'data(2)'], {'r', 'unman', 'comp'}], ['AllPrimitiveArrays', 'unmanaged', 'dateObj', ['date(1)', 'date(2)'], {'r', 'minmax', 'comp', 'unman', 'date'}], ['AllPrimitiveArrays', 'unmanaged', 'decimalObj', ['decimal128(2)', 'decimal128(3)'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'objectIdObj', ['objectId(1)', 'objectId(2)'], {'r', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'uuidObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['r','unman']], ['AllPrimitiveArrays', 'unmanaged', 'anyBoolObj', ['@NO', '@YES'], {'any', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyIntObj', ['@2', '@3'], {'any', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyFloatObj', ['@2.2f', '@3.3f'], {'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyDoubleObj', ['@2.2', '@3.3'], {'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyStringObj', ['@"a"', '@"b"'], {'any', 'unman', 'string', 'comp'}], ['AllPrimitiveArrays', 'unmanaged', 'anyDataObj', ['data(1)', 'data(2)'], {'any', 'unman', 'comp'}], ['AllPrimitiveArrays', 'unmanaged', 'anyDateObj', ['date(1)', 'date(2)'], {'any', 'minmax', 'comp', 'unman', 'date'}], ['AllPrimitiveArrays', 'unmanaged', 'anyDecimalObj', ['decimal128(2)', 'decimal128(3)'], {'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyObjectIdObj', ['objectId(1)', 'objectId(2)'], {'any', 'unman'}], ['AllPrimitiveArrays', 'unmanaged', 'anyUUIDObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['any','unman']], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'boolObj', ['@NO', '@YES', 'NSNull.null'], {'o', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'intObj', ['@2', '@3', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'floatObj', ['@2.2f', '@3.3f', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'doubleObj', ['@2.2', '@3.3', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'stringObj', ['@"a"', '@"b"', 'NSNull.null'], {'o', 'unman', 'string', 'comp'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'dataObj', ['data(1)', 'data(2)', 'NSNull.null'], {'o', 'unman', 'comp'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'dateObj', ['date(1)', 'date(2)', 'NSNull.null'], {'o', 'minmax', 'comp', 'unman', 'date'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'decimalObj', ['decimal128(2)', 'decimal128(3)', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'objectIdObj', ['objectId(1)', 'objectId(2)', 'NSNull.null'], {'o', 'unman'}], ['AllOptionalPrimitiveArrays', 'optUnmanaged', 'uuidObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'NSNull.null'], ['o', 'unman']], ['AllPrimitiveArrays', 'managed', 'boolObj', ['@NO', '@YES'], {'r', 'man'}], ['AllPrimitiveArrays', 'managed', 'intObj', ['@2', '@3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'floatObj', ['@2.2f', '@3.3f'], {'r', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'doubleObj', ['@2.2', '@3.3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'stringObj', ['@"a"', '@"b"'], {'r', 'man', 'string', 'comp'}], ['AllPrimitiveArrays', 'managed', 'dataObj', ['data(1)', 'data(2)'], {'r', 'man', 'comp'}], ['AllPrimitiveArrays', 'managed', 'dateObj', ['date(1)', 'date(2)'], {'r', 'minmax', 'comp', 'man', 'date'}], ['AllPrimitiveArrays', 'managed', 'decimalObj', ['decimal128(2)', 'decimal128(3)'], {'r', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'objectIdObj', ['objectId(1)', 'objectId(2)'], {'r', 'man'}], ['AllPrimitiveArrays', 'managed', 'uuidObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['r', 'man']], ['AllPrimitiveArrays', 'managed', 'anyBoolObj', ['@NO', '@YES'], {'any', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyIntObj', ['@2', '@3'], {'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyFloatObj', ['@2.2f', '@3.3f'], {'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyDoubleObj', ['@2.2', '@3.3'], {'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyStringObj', ['@"a"', '@"b"'], {'any', 'man', 'string', 'comp'}], ['AllPrimitiveArrays', 'managed', 'anyDataObj', ['data(1)', 'data(2)'], {'any', 'man', 'comp'}], ['AllPrimitiveArrays', 'managed', 'anyDateObj', ['date(1)', 'date(2)'], {'any', 'minmax', 'comp', 'man', 'date'}], ['AllPrimitiveArrays', 'managed', 'anyDecimalObj', ['decimal128(2)', 'decimal128(3)'], {'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyObjectIdObj', ['objectId(1)', 'objectId(2)'], {'any', 'man'}], ['AllPrimitiveArrays', 'managed', 'anyUUIDObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['any', 'man']], ['AllOptionalPrimitiveArrays', 'optManaged', 'boolObj', ['@NO', '@YES', 'NSNull.null'], {'o', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'intObj', ['@2', '@3', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'floatObj', ['@2.2f', '@3.3f', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'doubleObj', ['@2.2', '@3.3', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'stringObj', ['@"a"', '@"b"', 'NSNull.null'], {'o', 'man', 'string', 'comp'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'dataObj', ['data(1)', 'data(2)', 'NSNull.null'], {'o', 'man', 'comp'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'dateObj', ['date(1)', 'date(2)', 'NSNull.null'], {'o', 'minmax', 'comp', 'man', 'date'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'decimalObj', ['decimal128(2)', 'decimal128(3)', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'objectIdObj', ['objectId(1)', 'objectId(2)', 'NSNull.null'], {'o', 'man'}], ['AllOptionalPrimitiveArrays', 'optManaged', 'uuidObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'NSNull.null'], ['o', 'man']] ] def type_name(propertyName, optional): if 'any' in propertyName: return 'mixed' return propertyName.replace('Obj', '') + ('?' if 'opt' in optional else '') types = [{'class': t[0], 'obj': t[1], 'prop': t[2], 'v0': t[3][0], 'v1': t[3][1], 'array': t[1] + '.' + t[2], 'values2': '@[' + ', '.join(t[3] * 2) + ']', 'values': '@[' + ', '.join(t[3]) + ']', 'first': t[3][0], 'last': t[3][2] if len(t[3]) == 3 else t[3][1], 'wrong': '@"a"', 'wdesc': 'a', 'wtype': 'RLMConstantString', 'type': type_name(t[2], t[1]), 'tags': set(t[4]), } for t in types] # Add negative tags to all types all_tags = set() for t in types: all_tags |= t['tags'] for t in types: for missing in all_tags - t['tags']: t['tags'].add('no' + missing) # For testing error handling we need a value of the wrong type. By default this # is a string, so for string types we need to set it to a number instead for string_type in (t for t in types if 'string' in t['tags']): string_type['wrong'] = '@2' string_type['wdesc'] = '2' string_type['wtype'] = 'RLMConstantInt' # We extract the type name from the property name, but object id and decimal128 # don't have names that work for this for type in types: type['type'] = type['type'].replace('objectId', 'object id').replace('decimal', 'decimal128') type['basetype'] = type['type'].replace('?', '') file = open(os.path.dirname(__file__) + '/PrimitiveArrayPropertyTests.tpl.m', 'rt') for line in file: # Lines without anything to expand just appear as-is if not '$' in line: print(line, end='') continue if '$allArrays' in line: line = line.replace(' ^n', '\n' + ' ' * (line.find('(') + 4)) print(' for (RLMArray *array in allArrays) {\n ' + line.replace('$allArrays', 'array') + ' }') continue filtered_types = types start = 0 end = len(types) # Limit the types to the ones which match all of the tags present in the # line, then remove the tags from the line for tag in re.findall(r'\%([a-z]+)', line): filtered_types = [t for t in filtered_types if tag in t['tags']] line = line.replace('%' + tag + ' ', '') # Places where we want multiple output lines from one input line use ^nl # for subsequent statements and ^n for things which should be indented within # parentheses. This is a pretty half-hearted attempt at producing properly # indented output. line = line.replace(' ^nl ', '\n ') line = line.replace(' ^n', '\n' + ' ' * line.find('(')) # Repeat each line for each type, replacing variables with values from the dictionary for t in filtered_types: l = line for k, v in t.items(): if k in l: l = l.replace('$' + k, v) print(l, end='') ================================================ FILE: Realm/Tests/dictionary_tests.py ================================================ import os, re # Tags: # (no)minmax: Type supports min() and max() # (no)comp: Type supports comparison operators # (no)sum: Type supports sum() # (no)avg: Type supports average() # r/o: Type is Required or Optional # (un)man: Type is Managed or Unmanaged types = [ # Class, Object, Property, Keys, Values, Tags # Bool ['AllPrimitiveDictionaries', 'unmanaged', 'boolObj', ['@"key1"', '@"key2"'], ['@NO', '@YES'], {'r', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'boolObj', ['@"key1"', '@"key2"'], ['@NO', 'NSNull.null'], {'o', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'boolObj', ['@"key1"', '@"key2"'], ['@NO', '@YES'], {'r', 'man'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'boolObj', ['@"key1"', '@"key2"'], ['@NO', 'NSNull.null'], {'o', 'man'}], # Int ['AllPrimitiveDictionaries', 'unmanaged', 'intObj', ['@"key1"', '@"key2"'], ['@2', '@3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'intObj', ['@"key1"', '@"key2"'], ['@2', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'intObj', ['@"key1"', '@"key2"'], ['@2', '@3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'intObj', ['@"key1"', '@"key2"'], ['@2', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'man'}], # String ['AllPrimitiveDictionaries', 'unmanaged', 'stringObj', ['@"key1"', '@"key2"'], ['@"bar"', '@"foo"'], {'r', 'unman', 'string', 'comp'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'stringObj', ['@"key1"', '@"key2"'], ['@"bar"', 'NSNull.null'], {'o', 'unman', 'string', 'comp'}], ['AllPrimitiveDictionaries', 'managed', 'stringObj', ['@"key1"', '@"key2"'], ['@"bar"', '@"foo"'], {'r', 'man', 'string', 'comp'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'stringObj', ['@"key1"', '@"key2"'], ['@"bar"', 'NSNull.null'], {'o', 'man', 'string', 'comp'}], # Date ['AllPrimitiveDictionaries', 'unmanaged', 'dateObj', ['@"key1"', '@"key2"'], ['date(1)', 'date(2)'], {'r', 'minmax', 'comp', 'unman', 'date'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'dateObj', ['@"key1"', '@"key2"'], ['date(1)', 'NSNull.null'], {'o', 'minmax', 'comp', 'unman', 'date'}], ['AllPrimitiveDictionaries', 'managed', 'dateObj', ['@"key1"', '@"key2"'], ['date(1)', 'date(2)'], {'r', 'minmax', 'comp', 'man', 'date'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'dateObj', ['@"key1"', '@"key2"'], ['date(1)', 'NSNull.null'], {'o', 'minmax', 'comp', 'man', 'date'}], # Float ['AllPrimitiveDictionaries', 'unmanaged', 'floatObj', ['@"key1"', '@"key2"'], ['@2.2f', '@3.3f'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'floatObj', ['@"key1"', '@"key2"'], ['@2.2f', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'floatObj', ['@"key1"', '@"key2"'], ['@2.2f', '@3.3f'], {'r', 'minmax', 'comp', 'sum', 'avg'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'floatObj', ['@"key1"', '@"key2"'], ['@2.2f', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg'}], # Double ['AllPrimitiveDictionaries', 'unmanaged', 'doubleObj', ['@"key1"', '@"key2"'], ['@2.2', '@3.3'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'doubleObj', ['@"key1"', '@"key2"'], ['@2.2', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'doubleObj', ['@"key1"', '@"key2"'], ['@2.2', '@3.3'], {'r', 'minmax', 'comp', 'sum', 'avg'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'doubleObj', ['@"key1"', '@"key2"'], ['@2.2', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg'}], # Data ['AllPrimitiveDictionaries', 'unmanaged', 'dataObj', ['@"key1"', '@"key2"'], ['data(1)', 'data(2)'], {'r', 'unman', 'comp'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'dataObj', ['@"key1"', '@"key2"'], ['data(1)', 'NSNull.null'], {'o', 'unman', 'comp'}], ['AllPrimitiveDictionaries', 'managed', 'dataObj', ['@"key1"', '@"key2"'], ['data(1)', 'data(2)'], {'r', 'comp'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'dataObj', ['@"key1"', '@"key2"'], ['data(1)', 'NSNull.null'], {'o', 'comp'}], # Double ['AllPrimitiveDictionaries', 'unmanaged', 'decimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'decimal128(3)'], {'r', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'decimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'decimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'decimal128(3)'], {'r', 'minmax', 'comp', 'sum', 'avg'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'decimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'NSNull.null'], {'o', 'minmax', 'comp', 'sum', 'avg'}], # ObjectId ['AllPrimitiveDictionaries', 'unmanaged', 'objectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'objectId(2)'], {'r', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'objectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'NSNull.null'], {'o', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'objectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'objectId(2)'], {'r'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'objectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'NSNull.null'], {'o'}], # UUID ['AllPrimitiveDictionaries', 'unmanaged', 'uuidObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], {'r', 'unman'}], ['AllOptionalPrimitiveDictionaries', 'optUnmanaged', 'uuidObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'NSNull.null'], {'o', 'unman'}], ['AllPrimitiveDictionaries', 'managed', 'uuidObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], {'r'}], ['AllOptionalPrimitiveDictionaries', 'optManaged', 'uuidObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'NSNull.null'], {'o'}], # Mixed ['AllPrimitiveDictionaries', 'unmanaged', 'anyBoolObj', ['@"key1"', '@"key2"'], ['@NO', '@YES'], {'r', 'any', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyIntObj', ['@"key1"', '@"key2"'], ['@2', '@3'], {'r', 'any', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyFloatObj', ['@"key1"', '@"key2"'], ['@2.2f', '@3.3f'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyDoubleObj', ['@"key1"', '@"key2"'], ['@2.2', '@3.3'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyStringObj', ['@"key1"', '@"key2"'], ['@"a"', '@"b"'], {'r', 'any', 'unman', 'string'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyDataObj', ['@"key1"', '@"key2"'], ['data(1)', 'data(2)'], {'r', 'any', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyDateObj', ['@"key1"', '@"key2"'], ['date(1)', 'date(2)'], {'r', 'any', 'minmax', 'comp', 'unman', 'date'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyDecimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'decimal128(3)'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyObjectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'objectId(2)'], {'r', 'any', 'unman'}], ['AllPrimitiveDictionaries', 'unmanaged', 'anyUUIDObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], {'r', 'any','unman'}], ['AllPrimitiveDictionaries', 'managed', 'anyBoolObj', ['@"key1"', '@"key2"'], ['@NO', '@YES'], {'r', 'any', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyIntObj', ['@"key1"', '@"key2"'], ['@2', '@3'], {'r', 'any', 'sum', 'avg', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyFloatObj', ['@"key1"', '@"key2"'], ['@2.2f', '@3.3f'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyDoubleObj', ['@"key1"', '@"key2"'], ['@2.2', '@3.3'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyStringObj', ['@"key1"', '@"key2"'], ['@"a"', '@"b"'], {'r', 'any', 'string', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyDataObj', ['@"key1"', '@"key2"'], ['data(1)', 'data(2)'], {'r', 'any', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyDateObj', ['@"key1"', '@"key2"'], ['date(1)', 'date(2)'], {'r', 'any', 'minmax', 'comp' 'date', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyDecimalObj', ['@"key1"', '@"key2"'], ['decimal128(2)', 'decimal128(3)'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveDictionaries', 'managed', 'anyObjectIdObj', ['@"key1"', '@"key2"'], ['objectId(1)', 'objectId(2)'], {'r', 'any'}], ['AllPrimitiveDictionaries', 'managed', 'anyUUIDObj', ['@"key1"', '@"key2"'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], {'r', 'any', 'man'}], ] def type_name(propertyName, optional): if 'any' in propertyName: return 'mixed' return propertyName.replace('Obj', '') + ('?' if 'opt' in optional else '') types = [{'class': t[0], 'obj': t[1], 'prop': t[2], 'k0': t[3][0], 'k1': t[3][1], 'v0': t[4][0], 'v1': t[4][1], 'dictionary': t[1] + '.' + t[2], 'values': '@{ ' + ', '.join('{}: {}'.format(pair[0], pair[1]) for pair in zip(t[3], t[4])) + ' }', 'firstKey': t[3][0], 'firstValue': t[4][0], 'last': t[4][2] if len(t[4]) == 3 else t[4][1], 'wrong': '@"a"', 'wdesc': 'a', 'wtype': 'RLMConstantString', 'type': type_name(t[2], t[1]), 'tags': set(t[5]), } for t in types] # Add negative tags to all types all_tags = set() for t in types: all_tags |= t['tags'] for t in types: for missing in all_tags - t['tags']: t['tags'].add('no' + missing) # For testing error handling we need a value of the wrong type. By default this # is a string, so for string types we need to set it to a number instead for string_type in (t for t in types if 'string' in t['tags']): string_type['wrong'] = '@2' string_type['wdesc'] = '2' string_type['wtype'] = 'RLMConstantInt' # We extract the type name from the property name, but object id and decimal128 # don't have names that work for this for type in types: type['type'] = type['type'].replace('objectId', 'object id').replace('decimal', 'decimal128') type['basetype'] = type['type'].replace('?', '') file = open(os.path.dirname(__file__) + '/PrimitiveDictionaryPropertyTests.tpl.m', 'rt') for line in file: # Lines without anything to expand just appear as-is if not '$' in line: print(line, end='') continue if '$allDictionaries' in line: line = line.replace(' ^n', '\n' + ' ' * (line.find('(') + 4)) line = line.replace('$allDictionaries', 'dictionary') print(' for (RLMDictionary *dictionary in allDictionaries) {\n ' + line + ' }') continue filtered_types = types start = 0 end = len(types) # Limit the types to the ones which match all of the tags present in the # line, then remove the tags from the line for tag in re.findall(r'\%([a-z]+)', line): filtered_types = [t for t in filtered_types if tag in t['tags']] line = line.replace('%' + tag + ' ', '') # Places where we want multiple output lines from one input line use ^nl # for subsequent statements and ^n for things which should be indented within # parentheses. This is a pretty half-hearted attempt at producing properly # indented output. line = line.replace(' ^nl ', '\n ') line = line.replace(' ^n', '\n' + ' ' * line.find('(')) # Repeat each line for each type, replacing variables with values from the dictionary for t in filtered_types: l = line for k, v in t.items(): if k in l: l = l.replace('$' + k, v) print(l, end='') ================================================ FILE: Realm/Tests/mixed_tests.py ================================================ import os, re # Tags: # (no)minmax: Type supports min() and max() # (no)sum: Type supports sum() # (no)avg: Type supports average() # r/o: Type is Required or Optional # (un)man: Type is Managed or Unmanaged types = [ # Class, Object, Property, Values, Tags ['AllPrimitiveRLMValues', 'unmanaged', 'boolVal', ['@NO', '@YES', 'NSNull.null'], {'o', 'unman'}, '(NSNumber *)', 'RLMPropertyTypeBool'], ['AllPrimitiveRLMValues', 'unmanaged', 'intVal', ['@2', '@3', 'NSNull.null'], {'n', 'o', 'minmax', 'sum', 'avg', 'unman'}, '(NSNumber *)', 'RLMPropertyTypeInt'], ['AllPrimitiveRLMValues', 'unmanaged', 'floatVal', ['@2.2f', '@3.3f', 'NSNull.null'], {'o', 'minmax', 'sum', 'avg', 'unman'}, '(NSNumber *)', 'RLMPropertyTypeFloat'], ['AllPrimitiveRLMValues', 'unmanaged', 'doubleVal', ['@2.2', '@3.3', 'NSNull.null'], {'o', 'minmax', 'sum', 'avg', 'unman'}, '(NSNumber *)', 'RLMPropertyTypeDouble'], ['AllPrimitiveRLMValues', 'unmanaged', 'stringVal', ['@"a"', '@"b"', 'NSNull.null'], {'o', 'unman', 'string'}, '(NSString *)', 'RLMPropertyTypeString'], ['AllPrimitiveRLMValues', 'unmanaged', 'dataVal', ['data(1)', 'data(2)', 'NSNull.null'], {'o', 'unman'}, '(NSData *)', 'RLMPropertyTypeData'], ['AllPrimitiveRLMValues', 'unmanaged', 'dateVal', ['date(1)', 'date(2)', 'NSNull.null'], {'o', 'minmax', 'unman', 'date'}, '(NSDate *)', 'RLMPropertyTypeDate'], ['AllPrimitiveRLMValues', 'unmanaged', 'decimalVal', ['decimal128(2)', 'decimal128(3)', 'NSNull.null'], {'o', 'minmax', 'sum', 'avg', 'unman'}, '(RLMDecimal128 *)', 'RLMPropertyTypeDecimal128'], ['AllPrimitiveRLMValues', 'unmanaged', 'objectIdVal', ['objectId(1)', 'objectId(2)', 'NSNull.null'], {'o', 'unman'}, '(RLMObjectId *)', 'RLMPropertyTypeObjectId'], ['AllPrimitiveRLMValues', 'unmanaged', 'uuidVal', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'NSNull.null'], {'o', 'unman'}, '(NSUUID *)', 'RLMPropertyTypeUUID'], ['AllPrimitiveRLMValues', 'managed', 'boolVal', ['@NO', '@YES'], { 'r', 'man'}, '(NSNumber *)', 'RLMPropertyTypeBool'], ['AllPrimitiveRLMValues', 'managed', 'intVal', ['@2', '@3'], {'r', 'minmax', 'sum', 'avg', 'man'}, '(NSNumber *)', 'RLMPropertyTypeBool'], ['AllPrimitiveRLMValues', 'managed', 'floatVal', ['@2.2f', '@3.3f'], {'r', 'minmax', 'sum', 'avg', 'man'}, '(NSNumber *)', 'RLMPropertyTypeInt'], ['AllPrimitiveRLMValues', 'managed', 'doubleVal', ['@2.2', '@3.3'], {'r', 'minmax', 'sum', 'avg', 'man'}, '(NSNumber *)', 'RLMPropertyTypeFloat'], ['AllPrimitiveRLMValues', 'managed', 'stringVal', ['@"a"', '@"b"'], {'r', 'man', 'string'}, '(NSString *)', 'RLMPropertyTypeDouble'], ['AllPrimitiveRLMValues', 'managed', 'dataVal', ['data(1)', 'data(2)'], {'r', 'man'}, '(NSData *)', 'RLMPropertyTypeData'], ['AllPrimitiveRLMValues', 'managed', 'dateVal', ['date(1)', 'date(2)'], {'r', 'minmax', 'man', 'date'}, '(NSDate *)', 'RLMPropertyTypeDate'], ['AllPrimitiveRLMValues', 'managed', 'decimalVal', ['decimal128(2)', 'decimal128(3)'], {'r', 'minmax', 'sum', 'avg', 'man'}, '(RLMDecimal128 *)', 'RLMPropertyTypeDecimal128'], ['AllPrimitiveRLMValues', 'managed', 'objectIdVal', ['objectId(1)', 'objectId(2)', 'NSNull.null'], {'o', 'man'}, '(RLMObjectId *)', 'RLMPropertyTypeObjectId'], ['AllPrimitiveRLMValues', 'managed', 'uuidVal', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'NSNull.null'], {'o', 'man'}, '(NSUUID *)', 'RLMPropertyTypeUUID'], ] types = [{'class': t[0], 'obj': t[1], 'prop': t[2], 'v0': t[3][0], 'v1': t[3][1], 'member': t[2], 'rlmValue': t[1] + '.' + t[2], 'value0': t[3][0], 'value1': t[3][1], 'values': '@[' + ', '.join(t[3]) + ']', 'cast': t[5], 'valueType': t[6], 'first': t[3][0], 'last': t[3][2] if len(t[3]) == 3 else t[3][1], 'wrong': '@"a"', 'wdesc': 'a', 'wtype': '__NSCFConstantString', 'type': t[2].replace('Obj', '') + ('?' if 'opt' in t[1] else ''), 'tags': t[4], } for t in types] all_tags = set() for t in types: all_tags |= t['tags'] for t in types: for missing in all_tags - t['tags']: t['tags'].add('no' + missing) for string_type in (t for t in types if 'string' in t['tags']): string_type['wrong'] = '@2' string_type['wdesc'] = '2' string_type['wtype'] = '__NSCFNumber' for type in types: type['type'] = type['type'].replace('objectId', 'object id').replace('decimal', 'decimal128') type['basetype'] = type['type'].replace('?', '') file = open(os.path.dirname(__file__) + '/PrimitiveRLMValuePropertyTests.tpl.m', 'rt') for line in file: # Lines without anything to expand just appear as-is if not '$' in line: print line, continue if '$allValues' in line: line = line.replace(' ^n', '\n' + ' ' * (line.find('(') + 4)) print ' for (RLMValue *value in allValues) {\n ' + line.replace('$allValues', 'value') + ' }' continue filtered_types = types start = 0 end = len(types) # Limit the types to the ones which match all of the tags present in the # line, then remove the tags from the line for tag in re.findall(r'\%([a-z]+)', line): filtered_types = [t for t in filtered_types if tag in t['tags']] line = line.replace('%' + tag + ' ', '') # Places where we want multiple output lines from one input line use ^nl # for subsequent statements and ^n for things which should be indented within # parentheses. This is a pretty half-hearted attempt at producing properly # indented output. line = line.replace(' ^nl ', '\n ') line = line.replace(' ^n', '\n' + ' ' * line.find('(')) # Repeat each line for each type, replacing variables with values from the dictionary for t in filtered_types: l = line for k, v in t.iteritems(): if k in l: l = l.replace('$' + k, v) print l, ================================================ FILE: Realm/Tests/set_tests.py ================================================ import os, re # Tags: # (un)minmax: Type supports min() and max() # (no)comp: Type supports comparison operators # (un)sum: Type supports sum() # (un)avg: Type supports average() # r/o: Type is Required or Optional # (un)man: Type is Managed or Unmanaged # maxtwovalues: Type that can contain only 2 values, e.g non-optional bool # nomaxvalues: Type that can contain any amount of values # (no)any: Can accept any type # (no)date: Stores dates types = [ # Class, Object, Property, Values, Values2, Tags ['AllPrimitiveSets', 'unmanaged', 'boolObj', ['@NO', '@YES'], ['@NO', '@YES'], ['r', 'unman', 'maxtwovalues']], ['AllPrimitiveSets', 'unmanaged', 'intObj', ['@2', '@3'], ['@2', '@4'], ['r', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'floatObj', ['@2.2f', '@3.3f'], ['@2.2f', '@4.4f'], ['r', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'doubleObj', ['@2.2', '@3.3'], ['@2.2', '@4.4'], ['r', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'stringObj', ['@"a"', '@"bc"'], ['@"a"', '@"de"'], ['r', 'unman', 'string', 'comp']], ['AllPrimitiveSets', 'unmanaged', 'dataObj', ['data(1)', 'data(2)'], ['data(1)', 'data(3)'], ['r', 'unman', 'comp']], ['AllPrimitiveSets', 'unmanaged', 'dateObj', ['date(1)', 'date(2)'], ['date(1)', 'date(3)'], ['r', 'minmax', 'comp', 'unman', 'date']], ['AllPrimitiveSets', 'unmanaged', 'decimalObj', ['decimal128(1)', 'decimal128(2)'], ['decimal128(1)', 'decimal128(3)'], ['r', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'objectIdObj', ['objectId(1)', 'objectId(2)'], ['objectId(1)', 'objectId(3)'], ['r', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'uuidObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['r', 'unman']], ['AllPrimitiveSets', 'unmanaged', 'anyBoolObj', ['@NO', '@YES'], ['@NO', '@YES'], {'r', 'any', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyIntObj', ['@2', '@3'], ['@2', '@4'], {'r', 'any', 'sum', 'avg', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyFloatObj', ['@2.2f', '@3.3f'], ['@4.4f', '@3.3f'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyDoubleObj', ['@2.2', '@3.3'], ['@2.2', '@4.4'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyStringObj', ['@"a"', '@"b"'], ['@"a"', '@"d"'], {'r', 'any', 'unman', 'string', 'comp'}], ['AllPrimitiveSets', 'unmanaged', 'anyDataObj', ['data(1)', 'data(2)'], ['data(1)', 'data(3)'], {'r', 'any', 'unman', 'comp'}], ['AllPrimitiveSets', 'unmanaged', 'anyDateObj', ['date(1)', 'date(2)'], ['date(1)', 'date(4)'], {'r', 'any', 'minmax', 'comp', 'unman', 'date'}], ['AllPrimitiveSets', 'unmanaged', 'anyDecimalObj', ['decimal128(1)', 'decimal128(2)'], ['decimal128(1)', 'decimal128(3)'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyObjectIdObj', ['objectId(1)', 'objectId(2)'], ['objectId(1)', 'objectId(3)'], {'r', 'any', 'unman'}], ['AllPrimitiveSets', 'unmanaged', 'anyUUIDObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['r', 'any','unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'boolObj', ['NSNull.null', '@NO', '@YES'], ['@YES', '@NO'], ['o' 'unman', 'maxtwovalues']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'intObj', ['NSNull.null', '@2', '@3'], ['@3', '@4'], ['o', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'floatObj', ['NSNull.null', '@2.2f', '@3.3f'], ['@3.3f', '@4.4f'], ['o', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'doubleObj', ['NSNull.null', '@2.2', '@3.3'], ['@3.3', '@4.4'], ['o', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'stringObj', ['NSNull.null', '@"a"', '@"bc"'], ['@"bc"', '@"de"'], ['o', 'unman', 'string', 'comp']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'dataObj', ['NSNull.null', 'data(1)', 'data(2)'], ['data(2)', 'data(3)'], ['o', 'unman', 'comp']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'dateObj', ['NSNull.null', 'date(1)', 'date(2)'], ['date(2)', 'date(3)'], ['o', 'minmax', 'comp', 'unman', 'date']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'decimalObj', ['NSNull.null', 'decimal128(1)', 'decimal128(2)'], ['decimal128(2)', 'decimal128(4)'], ['o', 'minmax', 'comp', 'sum', 'avg', 'unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'objectIdObj', ['NSNull.null', 'objectId(1)', 'objectId(2)'], ['objectId(2)', 'objectId(4)'], ['o', 'unman']], ['AllOptionalPrimitiveSets', 'optUnmanaged', 'uuidObj', ['NSNull.null','uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'uuid(@"00000000-0000-0000-0000-000000000000")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['o', 'unman']], ['AllPrimitiveSets', 'managed', 'boolObj', ['@NO', '@YES'], ['@YES', '@NO'], ['r', 'man', 'maxtwovalues']], ['AllPrimitiveSets', 'managed', 'intObj', ['@2', '@3'], ['@3', '@4'], ['r', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllPrimitiveSets', 'managed', 'floatObj', ['@2.2f', '@3.3f'], ['@3.3f', '@4.4f'], ['r', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllPrimitiveSets', 'managed', 'doubleObj', ['@2.2', '@3.3'], ['@3.3', '@4.4'], ['r', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllPrimitiveSets', 'managed', 'stringObj', ['@"a"', '@"bc"'], ['@"bc"', '@"de"'], ['r', 'nominmax', 'man', 'string', 'comp']], ['AllPrimitiveSets', 'managed', 'dataObj', ['data(1)', 'data(2)'], ['data(2)', 'data(3)'], ['r', 'man', 'comp']], ['AllPrimitiveSets', 'managed', 'dateObj', ['date(1)', 'date(2)'], ['date(2)', 'date(3)'], ['r', 'minmax', 'comp', 'man', 'date']], ['AllPrimitiveSets', 'managed', 'decimalObj', ['decimal128(1)', 'decimal128(2)'], ['decimal128(2)', 'decimal128(3)'], ['r', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllPrimitiveSets', 'managed', 'objectIdObj', ['objectId(1)', 'objectId(2)'], ['objectId(2)', 'objectId(3)'], ['r', 'nominmax', 'man']], ['AllPrimitiveSets', 'managed', 'uuidObj', ['uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'uuid(@"00000000-0000-0000-0000-000000000000")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['r', 'man']], ['AllPrimitiveSets', 'managed', 'anyBoolObj', ['@NO', '@YES'], ['@NO', '@YES'], {'r', 'any', 'man'}], ['AllPrimitiveSets', 'managed', 'anyIntObj', ['@2', '@3'], ['@2', '@4'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveSets', 'managed', 'anyFloatObj', ['@2.2f', '@3.3f'], ['@2.2f', '@4.4f'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveSets', 'managed', 'anyDoubleObj', ['@2.2', '@3.3'], ['@2.2', '@4.4'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveSets', 'managed', 'anyStringObj', ['@"a"', '@"b"'], ['@"a"', '@"d"'], {'r', 'any', 'man', 'string', 'comp'}], ['AllPrimitiveSets', 'managed', 'anyDataObj', ['data(1)', 'data(2)'], ['data(1)', 'data(3)'], {'r', 'any', 'man'}], ['AllPrimitiveSets', 'managed', 'anyDateObj', ['date(1)', 'date(2)'], ['date(1)', 'date(3)'], {'r', 'any', 'minmax', 'comp', 'man', 'date'}], ['AllPrimitiveSets', 'managed', 'anyDecimalObj', ['decimal128(1)', 'decimal128(2)'], ['decimal128(1)', 'decimal128(3)'], {'r', 'any', 'minmax', 'comp', 'sum', 'avg', 'man'}], ['AllPrimitiveSets', 'managed', 'anyObjectIdObj', ['objectId(1)', 'objectId(2)'], ['objectId(1)', 'objectId(3)'], {'r', 'any', 'man'}], ['AllPrimitiveSets', 'managed', 'anyUUIDObj', ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['r', 'any', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'boolObj', ['NSNull.null', '@NO', '@YES'], ['@YES', '@NO'], ['o', 'nominmax', 'nosum', 'noavg', 'man', 'maxtwovalues']], ['AllOptionalPrimitiveSets', 'optManaged', 'intObj', ['NSNull.null', '@2', '@3'], ['@3', '@4'], ['o', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'floatObj', ['NSNull.null', '@2.2f', '@3.3f'], ['@3.3f', '@4.4f'], ['o', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'doubleObj', ['NSNull.null', '@2.2', '@3.3'], ['@3.3', '@4.4'], ['o', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'stringObj', ['NSNull.null', '@"a"', '@"bc"'], ['@"bc"', '@"de"'], ['o', 'man', 'string', 'comp']], ['AllOptionalPrimitiveSets', 'optManaged', 'dataObj', ['NSNull.null', 'data(1)', 'data(2)'], ['data(2)', 'data(3)'], ['o', 'man', 'comp']], ['AllOptionalPrimitiveSets', 'optManaged', 'dateObj', ['NSNull.null', 'date(1)', 'date(2)'], ['date(2)', 'date(3)'], ['o', 'minmax', 'comp', 'man', 'date']], ['AllOptionalPrimitiveSets', 'optManaged', 'decimalObj', ['NSNull.null', 'decimal128(1)', 'decimal128(2)'], ['decimal128(2)', 'decimal128(3)'], ['o', 'minmax', 'comp', 'sum', 'avg', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'objectIdObj', ['NSNull.null', 'objectId(1)', 'objectId(2)'], ['objectId(2)', 'objectId(3)'], ['o', 'man']], ['AllOptionalPrimitiveSets', 'optManaged', 'uuidObj', ['NSNull.null', 'uuid(@"137DECC8-B300-4954-A233-F89909F4FD89")', 'uuid(@"00000000-0000-0000-0000-000000000000")'], ['uuid(@"00000000-0000-0000-0000-000000000000")', 'uuid(@"123DECC8-B300-4954-A233-F89909F4FD89")'], ['o', 'man']], ] def type_name(propertyName, optional): if 'any' in propertyName: return 'mixed' else: return propertyName.replace('Obj', '') + ('?' if 'opt' in optional else '') types = [{'class': t[0], 'obj': t[1], 'prop': t[2], 'v0': t[3][0], 'v1': t[3][1], 'v2': len(t[3])>2 and t[3][2] or 'NSNull.null', 'v3': t[4][0], 'v4': t[4][1], 'set': t[1] + '.' + t[2], 'set2': t[1] + '.' + t[2], 'values': '@[' + ', '.join(t[3]) + ']', 'values2': '@[' + ', '.join(t[4]) + ']', 'first': t[3][0], 'last': t[3][2] if len(t[3]) == 3 else t[3][1], 'wrong': '@"a"', 'wdesc': 'a', 'wtype': 'RLMConstantString', 'type': type_name(t[2], t[1]), 'tags': set(t[5]) } for t in types] # Add negative tags to all types all_tags = set() for t in types: all_tags |= t['tags'] for t in types: for missing in all_tags - t['tags']: t['tags'].add('no' + missing) # For testing error handling we need a value of the wrong type. By default this # is a string, so for string types we need to set it to a number instead for string_type in (t for t in types if 'string' in t['tags']): string_type['wrong'] = '@2' string_type['wdesc'] = '2' string_type['wtype'] = 'RLMConstantInt' # We extract the type name from the property name, but object id and decimal128 # don't have names that work for this for type in types: type['type'] = type['type'].replace('objectId', 'object id').replace('decimal', 'decimal128') type['basetype'] = type['type'].replace('?', '') file = open(os.path.dirname(__file__) + '/PrimitiveSetPropertyTests.tpl.m', 'rt') for line in file: if not '$' in line: print(line, end='') continue if '$allSets' in line: line = line.replace(' ^n', '\n' + ' ' * (line.find('(') + 4)) print(' for (RLMSet *set in allSets) {\n ' + line.replace('$allSets', 'set') + ' }') continue filtered_types = types start = 0 end = len(types) for tag in re.findall(r'\%([a-z]+)', line): filtered_types = [t for t in filtered_types if tag in t['tags']] line = line.replace('%' + tag + ' ', '') line = line.replace('^nl', '\n ') line = line.replace('^n', '\n' + ' ' * line.find('(')) for t in filtered_types: l = line for k, v in t.items(): if k in l: l = l.replace('$' + k, v) print(l, end='') ================================================ FILE: Realm.podspec ================================================ # coding: utf-8 Pod::Spec.new do |s| s.name = 'Realm' version = `sh build.sh get-version` s.version = version s.cocoapods_version = '>= 1.10' s.summary = 'Realm is a modern data framework & database for iOS, macOS, tvOS & watchOS.' s.description = <<-DESC The Realm Database, for Objective-C. (If you want to use Realm from Swift, see the “RealmSwift” pod.) Realm is a fast, easy-to-use replacement for Core Data & SQLite. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. DESC s.homepage = "https://realm.io" s.source = { :git => 'https://github.com/realm/realm-swift.git', :tag => "v#{s.version}" } s.author = { 'Realm' => 'realm-help@mongodb.com' } s.library = 'c++', 'z', 'compression' s.requires_arc = true s.license = { :type => 'Apache 2.0', :file => 'LICENSE' } public_header_files = 'include/Realm.h', # Realm module 'include/RLMArray.h', 'include/RLMAsymmetricObject.h', 'include/RLMAsyncTask.h', 'include/RLMCollection.h', 'include/RLMConstants.h', 'include/RLMDecimal128.h', 'include/RLMDictionary.h', 'include/RLMEmbeddedObject.h', 'include/RLMGeospatial.h', 'include/RLMError.h', 'include/RLMLogger.h', 'include/RLMMigration.h', 'include/RLMObject.h', 'include/RLMObjectBase.h', 'include/RLMObjectId.h', 'include/RLMObjectSchema.h', 'include/RLMProperty.h', 'include/RLMRealm.h', 'include/RLMRealmConfiguration.h', 'include/RLMResults.h', 'include/RLMSchema.h', 'include/RLMSectionedResults.h', 'include/RLMSet.h', 'include/RLMSwiftCollectionBase.h', 'include/RLMSwiftValueStorage.h', 'include/RLMThreadSafeReference.h', 'include/RLMValue.h', # Realm.Dynamic module 'include/RLMRealm_Dynamic.h', 'include/RLMObjectBase_Dynamic.h', # Realm.Swift module 'include/RLMSwiftObject.h' # Realm.Private module private_header_files = 'include/RLMAccessor.h', 'include/RLMArray_Private.h', 'include/RLMAsyncTask_Private.h', 'include/RLMCollection_Private.h', 'include/RLMDictionary_Private.h', 'include/RLMEvent.h', 'include/RLMLogger_Private.h', 'include/RLMObjectBase_Private.h', 'include/RLMObjectSchema_Private.h', 'include/RLMObjectStore.h', 'include/RLMObject_Private.h', 'include/RLMOptionalBase.h', 'include/RLMPropertyBase.h', 'include/RLMProperty_Private.h', 'include/RLMRealmConfiguration_Private.h', 'include/RLMRealm_Private.h', 'include/RLMResults_Private.h', 'include/RLMScheduler.h', 'include/RLMSchema_Private.h', 'include/RLMSet_Private.h', 'include/RLMSwiftProperty.h', s.ios.frameworks = 'Security' s.ios.weak_framework = 'UIKit' s.tvos.weak_framework = 'UIKit' s.watchos.weak_framework = 'UIKit' s.module_map = 'Realm/Realm.modulemap' s.compiler_flags = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"#{s.version}\"' -D__ASSERTMACROS__" s.prepare_command = 'sh scripts/setup-cocoapods.sh' s.source_files = private_header_files + ['Realm/*.{m,mm}'] s.private_header_files = private_header_files s.header_mappings_dir = 'include' s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES', 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20', 'CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF' => 'NO', 'OTHER_CPLUSPLUSFLAGS' => '-isystem "${PODS_ROOT}/Realm/include/core" -fvisibility-inlines-hidden', 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/Realm/include" "${PODS_ROOT}/Realm/include/Realm"', 'IPHONEOS_DEPLOYMENT_TARGET_1500' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET_1600' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET_2600' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET' => '$(IPHONEOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'MACOSX_DEPLOYMENT_TARGET_1500' => '10.13', 'MACOSX_DEPLOYMENT_TARGET_1600' => '10.13', 'MACOSX_DEPLOYMENT_TARGET_2600' => '10.13', 'MACOSX_DEPLOYMENT_TARGET' => '$(MACOSX_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'WATCHOS_DEPLOYMENT_TARGET_1500' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET_1600' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET_2600' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET' => '$(WATCHOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'TVOS_DEPLOYMENT_TARGET_1500' => '12.0', 'TVOS_DEPLOYMENT_TARGET_1600' => '12.0', 'TVOS_DEPLOYMENT_TARGET_2600' => '12.0', 'TVOS_DEPLOYMENT_TARGET' => '$(TVOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'OTHER_LDFLAGS' => '"-Wl,-unexported_symbols_list,${PODS_ROOT}/Realm/Configuration/Realm/PrivateSymbols.txt"', } s.preserve_paths = %w(include scripts Configuration/Realm/PrivateSymbols.txt) s.resource_bundles = {'realm_objc_privacy' => ['Realm/PrivacyInfo.xcprivacy']} s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.13' s.watchos.deployment_target = '4.0' s.tvos.deployment_target = '12.0' s.vendored_frameworks = 'core/realm-monorepo.xcframework' s.subspec 'Headers' do |s| s.source_files = public_header_files s.public_header_files = public_header_files end end ================================================ FILE: Realm.xcodeproj/Realm.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Realm.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ ACB76B772AA9D9A600C59983 /* CI */ = { isa = PBXAggregateTarget; buildConfigurationList = ACB76B7B2AA9D9A600C59983 /* Build configuration list for PBXAggregateTarget "CI" */; buildPhases = ( ); dependencies = ( ); name = CI; productName = CI; }; E83EAC791BED3D880085CCD2 /* SwiftLint */ = { isa = PBXAggregateTarget; buildConfigurationList = E83EAC7C1BED3D880085CCD2 /* Build configuration list for PBXAggregateTarget "SwiftLint" */; buildPhases = ( E83EAC7D1BED3D8F0085CCD2 /* Run SwiftLint */, ); dependencies = ( ); name = SwiftLint; productName = SwiftLint; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 0207AB87195DFA15007EFB12 /* MigrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0207AB85195DFA15007EFB12 /* MigrationTests.mm */; }; 0207AB89195DFA15007EFB12 /* SchemaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0207AB86195DFA15007EFB12 /* SchemaTests.mm */; }; 021A88321AAFB5C800EEAC84 /* EncryptionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 021A882F1AAFB5BE00EEAC84 /* EncryptionTests.mm */; }; 021A88361AAFB5CD00EEAC84 /* ObjectSchemaTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 021A88301AAFB5BE00EEAC84 /* ObjectSchemaTests.m */; }; 027A4D2C1AB1012500AA46F9 /* InterprocessTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 027A4D291AB1012500AA46F9 /* InterprocessTests.m */; }; 02AFB4631A80343600E11938 /* PropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02AFB4611A80343600E11938 /* PropertyTests.m */; }; 02AFB4671A80343600E11938 /* ResultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02AFB4621A80343600E11938 /* ResultsTests.m */; }; 02E334C31A5F41C7009F8810 /* DynamicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FBA1955FE0100FDED82 /* DynamicTests.m */; }; 0C3BD4B325C1BDF1007CFDD3 /* RLMDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0C3BD4B125C1BDF1007CFDD3 /* RLMDictionary.mm */; }; 0C3BD4D325C1C5AB007CFDD3 /* Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3BD4D225C1C5AB007CFDD3 /* Map.swift */; }; 0C5796A225643D7500744CAE /* RLMUUID.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0C57969F25643D7500744CAE /* RLMUUID.mm */; }; 0C86B33925E15B6000775FED /* PrimitiveDictionaryPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C86B33825E15B6000775FED /* PrimitiveDictionaryPropertyTests.m */; }; 0C9758BF264974660097B48D /* SwiftRLMDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9758BE264974660097B48D /* SwiftRLMDictionaryTests.swift */; }; 0CBF2DB927286FFD00635902 /* ProjectedCollectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBF2DB827286FFD00635902 /* ProjectedCollectTests.swift */; }; 0CD1632826D3DF1D0027C49B /* ProjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD1632726D3DF1C0027C49B /* ProjectionTests.swift */; }; 0CED6DB82655087200B80277 /* RLMDictionary_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C3BD50125C1DE6F007CFDD3 /* RLMDictionary_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1A7B823A1D51259F00750296 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A7B82391D51259F00750296 /* libz.tbd */; }; 1AB605D31D495927007F53DE /* RealmCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB605D21D495927007F53DE /* RealmCollection.swift */; }; 2973CCF91C175AB400FEA0FA /* fileformat-pre-null.realm in Resources */ = {isa = PBXBuildFile; fileRef = 29B7FDF71C0DE76B0023224E /* fileformat-pre-null.realm */; }; 297FBEFB1C19F696009D1118 /* RLMTestCaseUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297FBEFA1C19F696009D1118 /* RLMTestCaseUtils.swift */; }; 29B7FDF61C0DA6560023224E /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B7FDF51C0DA6560023224E /* Error.swift */; }; 29B7FDFB1C0DE8100023224E /* fileformat-pre-null.realm in Resources */ = {isa = PBXBuildFile; fileRef = 29B7FDF71C0DE76B0023224E /* fileformat-pre-null.realm */; }; 29EDB8E41A7708E700458D80 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8839B2D19E31FD90047B1A8 /* main.m */; }; 3F08725527F3B5E0007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725427F3B5E0007A1175 /* libcompression.tbd */; }; 3F0BBB9729AFDA6600FEA7A7 /* RLMScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F0BBB9529AFDA6600FEA7A7 /* RLMScheduler.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3F0BBB9929AFDA6600FEA7A7 /* RLMScheduler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F0BBB9629AFDA6600FEA7A7 /* RLMScheduler.mm */; }; 3F102CBD23DBC68300108FD2 /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F102CBC23DBC68300108FD2 /* Combine.swift */; }; 3F149CCB2668112A00111D65 /* PersistedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F149CCA2668112A00111D65 /* PersistedProperty.swift */; }; 3F1D8D33265B071000593ABA /* RLMValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F1D8D30265B071000593ABA /* RLMValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F1D8D35265B071000593ABA /* RLMValue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F1D8D31265B071000593ABA /* RLMValue.mm */; }; 3F1D8D77265B075100593ABA /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1D8D75265B075000593ABA /* MapTests.swift */; }; 3F1D8DCB265B077800593ABA /* PrimitiveRLMValuePropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F1D8D8E265B076C00593ABA /* PrimitiveRLMValuePropertyTests.m */; }; 3F1D8DFF265B078200593ABA /* RLMValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F1D8D8D265B076C00593ABA /* RLMValueTests.m */; }; 3F1D9068265C077C00593ABA /* RLMDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C3BD4B225C1BDF1007CFDD3 /* RLMDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F1D9092265C07A600593ABA /* RLMSwiftValueStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = CF44460626121B2A00BAFDB4 /* RLMSwiftValueStorage.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3F1D90BC265C08F800593ABA /* RLMSwiftValueStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF44460526121B2A00BAFDB4 /* RLMSwiftValueStorage.mm */; }; 3F1F47821B9612B300CD99A3 /* KVOTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F0F029D1B6FFE610046A4D5 /* KVOTests.mm */; }; 3F222C4E1E26F51300CA0713 /* ThreadSafeReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */; }; 3F2633C31E9D630000B32D30 /* PrimitiveListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2633C21E9D630000B32D30 /* PrimitiveListTests.swift */; }; 3F2E66641CA0BA11004761D5 /* NotificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F2E66611CA0B9D5004761D5 /* NotificationTests.m */; }; 3F3411A6273433B300EC9D25 /* ObjcBridgeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3411A5273433B300EC9D25 /* ObjcBridgeable.swift */; }; 3F40C613276D1B05007FEF1A /* CustomObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40C612276D1B05007FEF1A /* CustomObjectCreationTests.swift */; }; 3F4E0FF92654765C008B8C0B /* ModernKVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E0FF82654765C008B8C0B /* ModernKVOTests.swift */; }; 3F4E10102655CA33008B8C0B /* ModernObjectAccessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E100E2655CA33008B8C0B /* ModernObjectAccessorTests.swift */; }; 3F4E10112655CA33008B8C0B /* RealmPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E100F2655CA33008B8C0B /* RealmPropertyTests.swift */; }; 3F4F3AD523F71C790048DB43 /* RLMDecimal128.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F4F3ACF23F71C790048DB43 /* RLMDecimal128.mm */; }; 3F4F3AD723F71C790048DB43 /* RLMObjectId.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4F3AD023F71C790048DB43 /* RLMObjectId.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F4F3ADB23F71C790048DB43 /* RLMObjectId.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F4F3AD223F71C790048DB43 /* RLMObjectId.mm */; }; 3F4F3ADD23F71C790048DB43 /* RLMDecimal128.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4F3AD323F71C790048DB43 /* RLMDecimal128.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F558C8722C29A03002F0F30 /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; 3F558C8A22C29A03002F0F30 /* TestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C7E22C29A02002F0F30 /* TestUtils.mm */; }; 3F558C8B22C29A03002F0F30 /* RLMTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */; }; 3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */; }; 3F558C8F22C29A03002F0F30 /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; 3F558C9222C29A03002F0F30 /* RLMTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8322C29A02002F0F30 /* RLMTestCase.m */; }; 3F558C9322C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; 3F558C9622C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */; }; 3F572C941F2BDAAB00F6C9AB /* ThreadSafeReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F572C911F2BDA9F00F6C9AB /* ThreadSafeReferenceTests.m */; }; 3F572C971F2BDAB100F6C9AB /* PrimitiveArrayPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F572C901F2BDA9F00F6C9AB /* PrimitiveArrayPropertyTests.m */; }; 3F67DB3C1E26D69C0024533D /* RLMThreadSafeReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F67DB391E26D69C0024533D /* RLMThreadSafeReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F67DB3E1E26D69C0024533D /* RLMThreadSafeReference.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */; }; 3F7556751BE95A0C0058BC7E /* AsyncTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F7556731BE95A050058BC7E /* AsyncTests.mm */; }; 3F83E9A42630A14800FC9623 /* RLMSwiftProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F83E9A22630A14800FC9623 /* RLMSwiftProperty.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F857A482769291800F9B9B1 /* KeyPathStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F857A472769291800F9B9B1 /* KeyPathStrings.swift */; }; 3F857A49276A507200F9B9B1 /* Projection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD1632526D3DD7B0027C49B /* Projection.swift */; }; 3F8824FD1E5E335000586B35 /* MigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610011BE98D880021E04F /* MigrationTests.swift */; }; 3F8824FE1E5E335000586B35 /* ObjectAccessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610021BE98D880021E04F /* ObjectAccessorTests.swift */; }; 3F8824FF1E5E335000586B35 /* ObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610031BE98D880021E04F /* ObjectCreationTests.swift */; }; 3F8825001E5E335000586B35 /* ObjectiveCSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */; }; 3F8825011E5E335000586B35 /* ObjectSchemaInitializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610041BE98D880021E04F /* ObjectSchemaInitializationTests.swift */; }; 3F8825021E5E335000586B35 /* ObjectSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610051BE98D880021E04F /* ObjectSchemaTests.swift */; }; 3F8825031E5E335000586B35 /* ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610061BE98D880021E04F /* ObjectTests.swift */; }; 3F8825041E5E335000586B35 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610071BE98D880021E04F /* PerformanceTests.swift */; }; 3F8825051E5E335000586B35 /* PropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610081BE98D880021E04F /* PropertyTests.swift */; }; 3F8825061E5E335000586B35 /* RealmCollectionTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610091BE98D880021E04F /* RealmCollectionTypeTests.swift */; }; 3F8825071E5E335000586B35 /* RealmConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D66100A1BE98D880021E04F /* RealmConfigurationTests.swift */; }; 3F8825081E5E335000586B35 /* RealmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D66100C1BE98D880021E04F /* RealmTests.swift */; }; 3F8825091E5E335000586B35 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D66100D1BE98D880021E04F /* SchemaTests.swift */; }; 3F88250A1E5E335000586B35 /* SortDescriptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D66100E1BE98D880021E04F /* SortDescriptorTests.swift */; }; 3F88250B1E5E335000586B35 /* SwiftLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D66100F1BE98D880021E04F /* SwiftLinkTests.swift */; }; 3F88250C1E5E335000586B35 /* SwiftUnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610111BE98D880021E04F /* SwiftUnicodeTests.swift */; }; 3F88250D1E5E335000586B35 /* ThreadSafeReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F73BC841E3A870F00FE80B6 /* ThreadSafeReferenceTests.swift */; }; 3F90C1B22716169C0029000E /* TestValueFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F90C1B12716169C0029000E /* TestValueFactory.swift */; }; 3F98162A2317763000C3543D /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F9816292317763000C3543D /* libc++.tbd */; }; 3F98162B2317763600C3543D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A7B82391D51259F00750296 /* libz.tbd */; }; 3F9863BB1D36876B00641C98 /* RLMClassInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F9863B91D36876B00641C98 /* RLMClassInfo.mm */; }; 3F997773273E23D300AA9E13 /* RealmCollectionImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F997772273E23D300AA9E13 /* RealmCollectionImpl.swift */; }; 3F9F53D32718B5DA000EEB4A /* CustomPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */; }; 3F9F53D52718E8E6000EEB4A /* CustomPersistableTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */; }; 3FA5E94D266064C4008F1345 /* ModernObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */; }; 3FAF2D4129577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; 3FB19069265ECF0C00DA7C76 /* ModernObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */; }; 3FB1906B265ED23300DA7C76 /* ModernTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */; }; 3FB4FA1719F5D2740020D53B /* SwiftTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */; }; 3FB4FA1819F5D2740020D53B /* SwiftArrayPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82FA60A195632F20043A3C3 /* SwiftArrayPropertyTests.swift */; }; 3FB4FA1919F5D2740020D53B /* SwiftArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82FA60B195632F20043A3C3 /* SwiftArrayTests.swift */; }; 3FB4FA1A19F5D2740020D53B /* SwiftDynamicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E83AF538196DDE58002275B2 /* SwiftDynamicTests.swift */; }; 3FB4FA1B19F5D2740020D53B /* SwiftLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82FA60D195632F20043A3C3 /* SwiftLinkTests.swift */; }; 3FB4FA1D19F5D2740020D53B /* SwiftObjectInterfaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82FA60F195632F20043A3C3 /* SwiftObjectInterfaceTests.swift */; }; 3FB4FA1E19F5D2740020D53B /* SwiftPropertyTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F3CA681986CC86004623E1 /* SwiftPropertyTypeTest.swift */; }; 3FB4FA1F19F5D2740020D53B /* SwiftRealmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FD01955FE0100FDED82 /* SwiftRealmTests.swift */; }; 3FB4FA2019F5D2740020D53B /* SwiftUnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E891759A197A1B600068ACC6 /* SwiftUnicodeTests.swift */; }; 3FB6ABD72416A26100E318C2 /* ObjectId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB6ABD62416A26100E318C2 /* ObjectId.swift */; }; 3FB6ABD92416A27000E318C2 /* Decimal128.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB6ABD82416A27000E318C2 /* Decimal128.swift */; }; 3FBEF67B1C63D66100F6935B /* RLMCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FBEF6791C63D66100F6935B /* RLMCollection.mm */; }; 3FC3F912241808B400E27322 /* RLMEmbeddedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FC3F910241808B300E27322 /* RLMEmbeddedObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FC3F914241808B400E27322 /* RLMEmbeddedObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FC3F911241808B300E27322 /* RLMEmbeddedObject.mm */; }; 3FC3F9172419B63200E27322 /* EmbeddedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC3F9162419B63100E27322 /* EmbeddedObject.swift */; }; 3FCB1A7522A9B0A2003807FB /* CodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FCB1A7422A9B0A2003807FB /* CodableTests.swift */; }; 3FCC56E429A55607004C5057 /* RLMSwiftObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FCC56E329A55607004C5057 /* RLMSwiftObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FD0D7C729675A2E0031C196 /* RLMAsyncTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD0D7C529675A2E0031C196 /* RLMAsyncTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FD0D7C929675A2E0031C196 /* RLMAsyncTask.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FD0D7C629675A2E0031C196 /* RLMAsyncTask.mm */; }; 3FD6D1A92B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3FD6D1A82B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy */; }; 3FD6D1AC2B4CA56D00A4FEBE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3FD6D1AB2B4CA56C00A4FEBE /* PrivacyInfo.xcprivacy */; }; 3FDAB841290B4CCB00168F24 /* RLMError.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FDAB83E290B4CCB00168F24 /* RLMError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FDAB845290B4CCB00168F24 /* RLMError.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FDAB840290B4CCB00168F24 /* RLMError.mm */; }; 3FDAB848290B658300168F24 /* file-format-version-21.realm in Resources */ = {isa = PBXBuildFile; fileRef = 3FDAB847290B658300168F24 /* file-format-version-21.realm */; }; 3FDAB849290B659300168F24 /* file-format-version-21.realm in Resources */ = {isa = PBXBuildFile; fileRef = 3FDAB847290B658300168F24 /* file-format-version-21.realm */; }; 3FDAB84C290B7A0200168F24 /* file-format-version-10.realm in Resources */ = {isa = PBXBuildFile; fileRef = 3FDAB84B290B7A0200168F24 /* file-format-version-10.realm */; }; 3FDAB84D290B7A0200168F24 /* file-format-version-10.realm in Resources */ = {isa = PBXBuildFile; fileRef = 3FDAB84B290B7A0200168F24 /* file-format-version-10.realm */; }; 3FDB67152970720E0052233B /* RLMAsyncTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD0D7CB29675AE10031C196 /* RLMAsyncTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3FDCFEB619F6A8D3005E414A /* RLMSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88C36FF19745E5500C9963D /* RLMSupport.swift */; }; 3FE267D5264308680030F83C /* CollectionAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267CF264308670030F83C /* CollectionAccess.swift */; }; 3FE267D6264308680030F83C /* ComplexTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D0264308680030F83C /* ComplexTypes.swift */; }; 3FE267D7264308680030F83C /* BasicTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D1264308680030F83C /* BasicTypes.swift */; }; 3FE267D8264308680030F83C /* Persistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D2264308680030F83C /* Persistable.swift */; }; 3FE267D9264308680030F83C /* PropertyAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D3264308680030F83C /* PropertyAccessors.swift */; }; 3FE267DA264308680030F83C /* SchemaDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE267D4264308680030F83C /* SchemaDiscovery.swift */; }; 3FE2BE0323D8CAD1002860E9 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE2BE0223D8CAD1002860E9 /* CombineTests.swift */; }; 3FE5B4D724CF6909004D4EF3 /* realm-monorepo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FE5B4D424CF3F06004D4EF3 /* realm-monorepo.xcframework */; }; 3FEB383F1E70AC8800F22712 /* ObjectCreationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FEB383C1E70AC6900F22712 /* ObjectCreationTests.mm */; }; 3FEC4A3F1BBB18D400F009C3 /* SwiftSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC4A3D1BBB188B00F009C3 /* SwiftSchemaTests.swift */; }; 3FEC91562A4B5D520044BFF5 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */; }; 3FEC91582A4B5D600044BFF5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91572A4B5D600044BFF5 /* libz.tbd */; }; 3FEC91592A4B5DE90044BFF5 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */; }; 3FEC915A2A4B5DEF0044BFF5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FEC91572A4B5D600044BFF5 /* libz.tbd */; }; 3FEC915B2A4B65B30044BFF5 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; 3FF3FFAF1F0D6D6400B84599 /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FFF1BE98D880021E04F /* KVOTests.swift */; }; 3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */; }; 530BA61426DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; 530BA61526DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */; }; 53124AD925B71AF700771CE4 /* SwiftUITestHostUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53124AD825B71AF700771CE4 /* SwiftUITestHostUITests.swift */; }; 535EA9E225B0919800DBF3CD /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535EA9E125B0919800DBF3CD /* SwiftUI.swift */; }; 535EAA7525B0B02B00DBF3CD /* SwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */; }; 53626AAF25D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; 53626AB025D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; 53A34E3625CDA0AC00698930 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */; }; 53A34E3725CDA0AC00698930 /* SwiftUITestHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */; }; 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */; }; 5D03FB1F1E0DAFBA007D53EA /* PredicateUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */; }; 5D128F2A1BE984E5001F4FBF /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5D1534B81CCFF545008976D7 /* LinkingObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1534B71CCFF545008976D7 /* LinkingObjects.swift */; }; 5D1BF1FF1EF987AD00B7DC87 /* RLMCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D1BF1FE1EF9875300B7DC87 /* RLMCollection_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D3E1A2F1C1FC6D5002913BA /* RLMPredicateUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D3E1A2D1C1FC6D5002913BA /* RLMPredicateUtil.mm */; }; 5D432B8D1CC0713F00A610A9 /* LinkingObjectsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D432B8C1CC0713F00A610A9 /* LinkingObjectsTests.mm */; }; 5D6156EE1BE0689200A4BD3F /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; 5D659E851BE04556006515A0 /* RLMAccessor.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F641955FC9300FDED82 /* RLMAccessor.mm */; }; 5D659E871BE04556006515A0 /* RLMArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F671955FC9300FDED82 /* RLMArray.mm */; }; 5D659E881BE04556006515A0 /* RLMManagedArray.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F691955FC9300FDED82 /* RLMManagedArray.mm */; }; 5D659E891BE04556006515A0 /* RLMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F6C1955FC9300FDED82 /* RLMConstants.m */; }; 5D659E8A1BE04556006515A0 /* RLMSwiftCollectionBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 023B19561A3BA90D0067FB81 /* RLMSwiftCollectionBase.mm */; }; 5D659E8B1BE04556006515A0 /* RLMMigration.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0207AB7E195DF9FB007EFB12 /* RLMMigration.mm */; }; 5D659E8C1BE04556006515A0 /* RLMObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F6F1955FC9300FDED82 /* RLMObject.mm */; }; 5D659E8D1BE04556006515A0 /* RLMObjectBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 023B19581A3BA90D0067FB81 /* RLMObjectBase.mm */; }; 5D659E8E1BE04556006515A0 /* RLMObjectSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F721955FC9300FDED82 /* RLMObjectSchema.mm */; }; 5D659E8F1BE04556006515A0 /* RLMObjectStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F741955FC9300FDED82 /* RLMObjectStore.mm */; }; 5D659E901BE04556006515A0 /* RLMObservation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F0F02AD1B6FFF3D0046A4D5 /* RLMObservation.mm */; }; 5D659E921BE04556006515A0 /* RLMProperty.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F771955FC9300FDED82 /* RLMProperty.mm */; }; 5D659E931BE04556006515A0 /* RLMQueryUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F791955FC9300FDED82 /* RLMQueryUtil.mm */; }; 5D659E941BE04556006515A0 /* RLMRealm.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F7C1955FC9300FDED82 /* RLMRealm.mm */; }; 5D659E951BE04556006515A0 /* RLMRealmConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = C0D2DD061B6BDEA1004E8919 /* RLMRealmConfiguration.mm */; }; 5D659E961BE04556006515A0 /* RLMRealmUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 027A4D221AB100E000AA46F9 /* RLMRealmUtil.mm */; }; 5D659E971BE04556006515A0 /* RLMResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F6A1955FC9300FDED82 /* RLMResults.mm */; }; 5D659E981BE04556006515A0 /* RLMSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F7F1955FC9300FDED82 /* RLMSchema.mm */; }; 5D659E991BE04556006515A0 /* RLMSwiftSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F452EC519C2279800AFC154 /* RLMSwiftSupport.m */; }; 5D659E9B1BE04556006515A0 /* RLMUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1F821955FC9300FDED82 /* RLMUtil.mm */; }; 5D659EA51BE04556006515A0 /* Realm.h in Headers */ = {isa = PBXBuildFile; fileRef = E8D89B9D1955FC6D00CF2B9A /* Realm.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EA71BE04556006515A0 /* RLMAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F631955FC9300FDED82 /* RLMAccessor.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EA91BE04556006515A0 /* RLMArray.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F661955FC9300FDED82 /* RLMArray.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EAA1BE04556006515A0 /* RLMArray_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0237B5421A856F06004ACD57 /* RLMArray_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EAB1BE04556006515A0 /* RLMCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 02B8EF5B19E7048D0045A93D /* RLMCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EAC1BE04556006515A0 /* RLMConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F6B1955FC9300FDED82 /* RLMConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EAE1BE04556006515A0 /* RLMSwiftCollectionBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 023B19551A3BA90D0067FB81 /* RLMSwiftCollectionBase.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EAF1BE04556006515A0 /* RLMMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 0207AB7D195DF9FB007EFB12 /* RLMMigration.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EB01BE04556006515A0 /* RLMMigration_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0207AB7C195DF9FB007EFB12 /* RLMMigration_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EB11BE04556006515A0 /* RLMObject.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F6E1955FC9300FDED82 /* RLMObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EB21BE04556006515A0 /* RLMObject_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F6D1955FC9300FDED82 /* RLMObject_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EB31BE04556006515A0 /* RLMObjectBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 023B19571A3BA90D0067FB81 /* RLMObjectBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EB41BE04556006515A0 /* RLMObjectBase_Dynamic.h in Headers */ = {isa = PBXBuildFile; fileRef = A05FA61E1B62C3900000C9B2 /* RLMObjectBase_Dynamic.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EB51BE04556006515A0 /* RLMObjectSchema.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F711955FC9300FDED82 /* RLMObjectSchema.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EB61BE04556006515A0 /* RLMObjectSchema_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 29EDB8E91A7712E500458D80 /* RLMObjectSchema_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EB81BE04556006515A0 /* RLMObjectStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 29EDB8D71A7703C500458D80 /* RLMObjectStore.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EBC1BE04556006515A0 /* RLMProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F761955FC9300FDED82 /* RLMProperty.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EBD1BE04556006515A0 /* RLMProperty_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F751955FC9300FDED82 /* RLMProperty_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EBF1BE04556006515A0 /* RLMRealm.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F7B1955FC9300FDED82 /* RLMRealm.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EC01BE04556006515A0 /* RLMRealm_Dynamic.h in Headers */ = {isa = PBXBuildFile; fileRef = E8951F01196C96DE00D6461C /* RLMRealm_Dynamic.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EC11BE04556006515A0 /* RLMRealm_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 29EDB8E01A77070200458D80 /* RLMRealm_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EC21BE04556006515A0 /* RLMRealmConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = C0D2DD051B6BDEA1004E8919 /* RLMRealmConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EC31BE04556006515A0 /* RLMRealmConfiguration_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C0D2DD0F1B6BE0DD004E8919 /* RLMRealmConfiguration_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EC51BE04556006515A0 /* RLMResults.h in Headers */ = {isa = PBXBuildFile; fileRef = 02B8EF5819E601D80045A93D /* RLMResults.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EC61BE04556006515A0 /* RLMResults_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 29EDB8E51A7710B700458D80 /* RLMResults_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EC71BE04556006515A0 /* RLMSchema.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F7E1955FC9300FDED82 /* RLMSchema.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D659EC81BE04556006515A0 /* RLMSchema_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E81A1F7D1955FC9300FDED82 /* RLMSchema_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659EC91BE04556006515A0 /* RLMSwiftSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE79FF719BA6A5900780C9A /* RLMSwiftSupport.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D659ED21BE04556006515A0 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E81A1FB31955FCE000FDED82 /* CHANGELOG.md */; }; 5D659ED51BE04556006515A0 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = E81A1FB41955FCE000FDED82 /* LICENSE */; }; 5D660FDD1BE98C7C0021E04F /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; }; 5D660FF11BE98D670021E04F /* Aliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE31BE98D670021E04F /* Aliases.swift */; }; 5D660FF21BE98D670021E04F /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE41BE98D670021E04F /* List.swift */; }; 5D660FF31BE98D670021E04F /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE51BE98D670021E04F /* Migration.swift */; }; 5D660FF41BE98D670021E04F /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE61BE98D670021E04F /* Object.swift */; }; 5D660FF51BE98D670021E04F /* ObjectSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE71BE98D670021E04F /* ObjectSchema.swift */; }; 5D660FF61BE98D670021E04F /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE81BE98D670021E04F /* Optional.swift */; }; 5D660FF71BE98D670021E04F /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FE91BE98D670021E04F /* Property.swift */; }; 5D660FF81BE98D670021E04F /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FEA1BE98D670021E04F /* Realm.swift */; }; 5D660FFA1BE98D670021E04F /* RealmConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FEC1BE98D670021E04F /* RealmConfiguration.swift */; }; 5D660FFB1BE98D670021E04F /* Results.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FED1BE98D670021E04F /* Results.swift */; }; 5D660FFC1BE98D670021E04F /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FEE1BE98D670021E04F /* Schema.swift */; }; 5D660FFD1BE98D670021E04F /* SortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FEF1BE98D670021E04F /* SortDescriptor.swift */; }; 5D660FFE1BE98D670021E04F /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FF01BE98D670021E04F /* Util.swift */; }; 5D6610161BE98D880021E04F /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610001BE98D880021E04F /* ListTests.swift */; }; 5D6610251BE98D880021E04F /* SwiftTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610101BE98D880021E04F /* SwiftTestObjects.swift */; }; 5D6610271BE98D880021E04F /* TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6610121BE98D880021E04F /* TestCase.swift */; }; 5D66102A1BE98DD00021E04F /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; 5D66102E1BE98E500021E04F /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5D66102F1BE98E540021E04F /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5DBEC9B11F719A9D001233EC /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FF01BE98D670021E04F /* Util.swift */; }; 681EE33B25EE8E1400A9DEC5 /* AnyRealmValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */; }; 681EE34725EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */; }; 68A7B91D2543538B00C703BC /* RLMSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88C36FF19745E5500C9963D /* RLMSupport.swift */; }; AC3B33AE29DC6CEE0042F3A0 /* RLMLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3B33AB29DC6CEE0042F3A0 /* RLMLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3B33AF29DC6CEE0042F3A0 /* RLMLogger.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3B33AC29DC6CEE0042F3A0 /* RLMLogger.mm */; }; AC3B33B029DC6CEE0042F3A0 /* RLMLogger_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3B33AD29DC6CEE0042F3A0 /* RLMLogger_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; AC5300722BD03D4A00BF5950 /* MixedCollectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5300712BD03D4900BF5950 /* MixedCollectionTest.swift */; }; AC7825B92ACD90BE007ABA4B /* Geospatial.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7825B82ACD90BE007ABA4B /* Geospatial.swift */; }; AC7825BD2ACD90DA007ABA4B /* RLMGeospatial.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC7825BA2ACD90DA007ABA4B /* RLMGeospatial.mm */; }; AC7825BF2ACD90DA007ABA4B /* RLMGeospatial.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC7825C22ACD917B007ABA4B /* GeospatialTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7825C02ACD916C007ABA4B /* GeospatialTests.swift */; }; ACF08B6726DD936200686CBC /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF08B6626DD936200686CBC /* Query.swift */; }; ACFF0EC728EC5ADB0097AEE0 /* CustomColumnNameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFF0EC628EC5ADB0097AEE0 /* CustomColumnNameTests.swift */; }; C042A48D1B7522A900771ED2 /* RealmConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = C042A48C1B7522A900771ED2 /* RealmConfigurationTests.mm */; }; C0CDC0821B38DABA00C5716D /* UtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 021A88311AAFB5BE00EEAC84 /* UtilTests.mm */; }; CF040494263DF0AA00F9AEE0 /* PrimitiveMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF040493263DF0A900F9AEE0 /* PrimitiveMapTests.swift */; }; CF052EFB25DEB671008EEF86 /* DictionaryPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF052EFA25DEB671008EEF86 /* DictionaryPropertyTests.m */; }; CF0D04F1269365300038A058 /* KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF0D04F02693652E0038A058 /* KeyPathTests.swift */; }; CF25080E283B90F8007D66FE /* SectionedResultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF25080D283B90F8007D66FE /* SectionedResultsTests.m */; }; CF44461E26121C6800BAFDB4 /* RealmProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF44461D26121C6800BAFDB4 /* RealmProperty.swift */; }; CF46CC0226D931BA00DE450C /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF46CC0026D931BA00DE450C /* QueryTests.swift */; }; CF84D4F0281823B300005E27 /* SectionedResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF84D4EF281823B300005E27 /* SectionedResults.swift */; }; CF986D1E25AE3B090039D287 /* RLMSet_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CF986D1A25AE3B080039D287 /* RLMSet_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CF986D2025AE3B090039D287 /* RLMSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF986D1B25AE3B080039D287 /* RLMSet.mm */; }; CF986D2425AE3B090039D287 /* RLMSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CF986D1D25AE3B090039D287 /* RLMSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; CF986D3125AE3BD40039D287 /* PrimitiveSetPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF986D2F25AE3BD40039D287 /* PrimitiveSetPropertyTests.m */; }; CF986D3325AE3BD40039D287 /* SetPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CF986D3025AE3BD40039D287 /* SetPropertyTests.m */; }; CF986D7025AE3C550039D287 /* SwiftSetPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986D4725AE3C420039D287 /* SwiftSetPropertyTests.swift */; }; CF986D8425AE3C5A0039D287 /* SwiftSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986D4825AE3C420039D287 /* SwiftSetTests.swift */; }; CF986DE225AE3EC70039D287 /* MutableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986DE125AE3EC70039D287 /* MutableSetTests.swift */; }; CF986DF625AE3EDF0039D287 /* PrimitiveMutableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986DF525AE3EDF0039D287 /* PrimitiveMutableSetTests.swift */; }; CF9881C125DABC6500BD7E4F /* RLMManagedDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF9881C025DABC6500BD7E4F /* RLMManagedDictionary.mm */; }; CFD8D12025BB0B8B0037FE4D /* RLMManagedSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFD8D11F25BB0B8B0037FE4D /* RLMManagedSet.mm */; }; CFDBC4B628803C7200EE80E6 /* RLMSectionedResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFDBC4B328803C7200EE80E6 /* RLMSectionedResults.mm */; }; CFDBC4B728803C7200EE80E6 /* RLMSectionedResults.h in Headers */ = {isa = PBXBuildFile; fileRef = CFDBC4B428803C7200EE80E6 /* RLMSectionedResults.h */; settings = {ATTRIBUTES = (Public, ); }; }; CFDBC4C0288040E700EE80E6 /* SectionedResultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFDBC4BE288040CD00EE80E6 /* SectionedResultsTests.swift */; }; CFE9CE31265554FB00BF96D6 /* MutableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF986D8E25AE3C980039D287 /* MutableSet.swift */; }; CFE9CE3326555BBD00BF96D6 /* RealmKeyedCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE9CE3226555BBD00BF96D6 /* RealmKeyedCollection.swift */; }; CFFC8CC8262310C800929608 /* AnyRealmValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFFC8CC7262310C800929608 /* AnyRealmValueTests.swift */; }; CFFECBAA250646820010F585 /* Decimal128Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFFECBA8250646750010F585 /* Decimal128Tests.swift */; }; CFFECBAD250667B20010F585 /* Decimal128Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CFFECBAB250667A90010F585 /* Decimal128Tests.m */; }; CFFECBB0250690EA0010F585 /* ObjectIdTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CFFECBAF250690EA0010F585 /* ObjectIdTests.m */; }; CFFECBB525078EC40010F585 /* ObjectIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFFECBB225078D100010F585 /* ObjectIdTests.swift */; }; E81A1FD51955FE0100FDED82 /* ArrayPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FB81955FE0100FDED82 /* ArrayPropertyTests.m */; settings = {COMPILER_FLAGS = "-fobjc-arc-exceptions"; }; }; E81A1FDB1955FE0100FDED82 /* EnumeratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FBB1955FE0100FDED82 /* EnumeratorTests.m */; settings = {COMPILER_FLAGS = "-fobjc-arc-exceptions"; }; }; E81A1FDD1955FE0100FDED82 /* LinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FBC1955FE0100FDED82 /* LinkTests.m */; settings = {COMPILER_FLAGS = "-fobjc-arc-exceptions"; }; }; E81A1FE11955FE0100FDED82 /* ObjectInterfaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FBE1955FE0100FDED82 /* ObjectInterfaceTests.m */; settings = {COMPILER_FLAGS = "-fobjc-arc-exceptions"; }; }; E81A1FE31955FE0100FDED82 /* ObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FBF1955FE0100FDED82 /* ObjectTests.m */; settings = {COMPILER_FLAGS = "-fobjc-arc-exceptions"; }; }; E81A1FE71955FE0100FDED82 /* QueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FC11955FE0100FDED82 /* QueryTests.m */; }; E81A1FEB1955FE0100FDED82 /* RealmTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FC31955FE0100FDED82 /* RealmTests.mm */; }; E81A20021955FE0100FDED82 /* TransactionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E81A1FD11955FE0100FDED82 /* TransactionTests.m */; }; E8917598197A1B350068ACC6 /* UnicodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E8917597197A1B350068ACC6 /* UnicodeTests.m */; }; E8AE7C261EA436F800CDFF9A /* CompactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AE7C251EA436F800CDFF9A /* CompactionTests.swift */; }; E8DA16F81E81210D0055141C /* CompactionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DA16F71E81210D0055141C /* CompactionTests.m */; }; E8F992BE1F1401C100F634B5 /* RLMObjectBase_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E8F992BD1F1401C100F634B5 /* RLMObjectBase_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 3FC8BF34212B79F4001C2025 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 3F1A5E711992EB7400F45F4C; remoteInfo = TestHost; }; 3FF5165126E96D2B00618280 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 5D660FCB1BE98C560021E04F; remoteInfo = RealmSwift; }; 534DF4CF25B86F3A00655AE2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 53124AB725B71AF600771CE4; remoteInfo = SwiftUITestHost; }; 53BBF08C25B7436F00D225AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 5D660FCB1BE98C560021E04F; remoteInfo = RealmSwift; }; 5D6157011BE0A3A100A4BD3F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 3F1A5E711992EB7400F45F4C; remoteInfo = TestHost; }; 5D660FDE1BE98C7C0021E04F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 5D660FCB1BE98C560021E04F; remoteInfo = RealmSwift; }; 5D66102B1BE98DF60021E04F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 5D659E7D1BE04556006515A0; remoteInfo = Realm; }; 5DD755D11BE05828002800DA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8D89B8F1955FC6D00CF2B9A /* Project object */; proxyType = 1; remoteGlobalIDString = 5D659E7D1BE04556006515A0; remoteInfo = Realm; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 5D128F291BE984D4001F4FBF /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 5D128F2A1BE984E5001F4FBF /* Realm.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 5D66102D1BE98E360021E04F /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 5D66102E1BE98E500021E04F /* Realm.framework in Embed Frameworks */, 5D66102F1BE98E540021E04F /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0207AB7C195DF9FB007EFB12 /* RLMMigration_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMigration_Private.h; sourceTree = ""; }; 0207AB7D195DF9FB007EFB12 /* RLMMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMMigration.h; sourceTree = ""; }; 0207AB7E195DF9FB007EFB12 /* RLMMigration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMMigration.mm; sourceTree = ""; }; 0207AB85195DFA15007EFB12 /* MigrationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MigrationTests.mm; sourceTree = ""; }; 0207AB86195DFA15007EFB12 /* SchemaTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SchemaTests.mm; sourceTree = ""; }; 0217D7B819CD0ACD00DE5C32 /* Swift-Tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Swift-Tests-Bridging-Header.h"; sourceTree = ""; }; 021A882F1AAFB5BE00EEAC84 /* EncryptionTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EncryptionTests.mm; sourceTree = ""; }; 021A88301AAFB5BE00EEAC84 /* ObjectSchemaTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectSchemaTests.m; sourceTree = ""; }; 021A88311AAFB5BE00EEAC84 /* UtilTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UtilTests.mm; sourceTree = ""; }; 0237B5421A856F06004ACD57 /* RLMArray_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMArray_Private.h; sourceTree = ""; }; 023B19551A3BA90D0067FB81 /* RLMSwiftCollectionBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftCollectionBase.h; sourceTree = ""; }; 023B19561A3BA90D0067FB81 /* RLMSwiftCollectionBase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSwiftCollectionBase.mm; sourceTree = ""; }; 023B19571A3BA90D0067FB81 /* RLMObjectBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectBase.h; sourceTree = ""; }; 023B19581A3BA90D0067FB81 /* RLMObjectBase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObjectBase.mm; sourceTree = ""; }; 023B19F71A423BD20067FB81 /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; }; 027A4D211AB100E000AA46F9 /* RLMRealmUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMRealmUtil.hpp; sourceTree = ""; }; 027A4D221AB100E000AA46F9 /* RLMRealmUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMRealmUtil.mm; sourceTree = ""; }; 027A4D291AB1012500AA46F9 /* InterprocessTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InterprocessTests.m; sourceTree = ""; }; 02AFB4611A80343600E11938 /* PropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PropertyTests.m; sourceTree = ""; }; 02AFB4621A80343600E11938 /* ResultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResultsTests.m; sourceTree = ""; }; 02B8EF5819E601D80045A93D /* RLMResults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMResults.h; sourceTree = ""; }; 02B8EF5B19E7048D0045A93D /* RLMCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMCollection.h; sourceTree = ""; }; 02E334C21A5F3C45009F8810 /* Realm.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Realm.modulemap; sourceTree = ""; }; 02E334C41A5F4923009F8810 /* RLMRealm_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMRealm_Private.hpp; sourceTree = ""; }; 0C3BD4B125C1BDF1007CFDD3 /* RLMDictionary.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMDictionary.mm; sourceTree = ""; }; 0C3BD4B225C1BDF1007CFDD3 /* RLMDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMDictionary.h; sourceTree = ""; }; 0C3BD4D225C1C5AB007CFDD3 /* Map.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Map.swift; sourceTree = ""; }; 0C3BD50125C1DE6F007CFDD3 /* RLMDictionary_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMDictionary_Private.h; sourceTree = ""; }; 0C57969F25643D7500744CAE /* RLMUUID.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUUID.mm; sourceTree = ""; }; 0C7CA7C225C311DA0098A636 /* RLMManagedDictionary.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMManagedDictionary.mm; sourceTree = ""; }; 0C86B33825E15B6000775FED /* PrimitiveDictionaryPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveDictionaryPropertyTests.m; sourceTree = ""; }; 0C9758BE264974660097B48D /* SwiftRLMDictionaryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRLMDictionaryTests.swift; sourceTree = ""; }; 0CBF2DB827286FFD00635902 /* ProjectedCollectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectedCollectTests.swift; sourceTree = ""; }; 0CD1632526D3DD7B0027C49B /* Projection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Projection.swift; sourceTree = ""; }; 0CD1632726D3DF1C0027C49B /* ProjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectionTests.swift; sourceTree = ""; }; 1A1EBF861F269E8E00F47698 /* RLMResults_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMResults_Private.hpp; sourceTree = ""; }; 1A7B82391D51259F00750296 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 1AB605D21D495927007F53DE /* RealmCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmCollection.swift; sourceTree = ""; }; 26F3CA681986CC86004623E1 /* SwiftPropertyTypeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftPropertyTypeTest.swift; sourceTree = ""; }; 297FBEFA1C19F696009D1118 /* RLMTestCaseUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLMTestCaseUtils.swift; sourceTree = ""; }; 29B7FDF51C0DA6560023224E /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 29B7FDF71C0DE76B0023224E /* fileformat-pre-null.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fileformat-pre-null.realm"; sourceTree = ""; }; 29EDB8D71A7703C500458D80 /* RLMObjectStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectStore.h; sourceTree = ""; }; 29EDB8E01A77070200458D80 /* RLMRealm_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMRealm_Private.h; sourceTree = ""; }; 29EDB8E51A7710B700458D80 /* RLMResults_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMResults_Private.h; sourceTree = ""; }; 29EDB8E91A7712E500458D80 /* RLMObjectSchema_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectSchema_Private.h; sourceTree = ""; }; 3F0338491E6F466D00F9E288 /* RLMAccessor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMAccessor.hpp; sourceTree = ""; }; 3F04EA2D1992BEE400C2CE2E /* PerformanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceTests.m; sourceTree = ""; }; 3F08725427F3B5E0007A1175 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = usr/lib/libcompression.tbd; sourceTree = SDKROOT; }; 3F0BBB9529AFDA6600FEA7A7 /* RLMScheduler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMScheduler.h; sourceTree = ""; }; 3F0BBB9629AFDA6600FEA7A7 /* RLMScheduler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMScheduler.mm; sourceTree = ""; }; 3F0F029D1B6FFE610046A4D5 /* KVOTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KVOTests.mm; sourceTree = ""; }; 3F0F02AC1B6FFF3D0046A4D5 /* RLMObservation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMObservation.hpp; sourceTree = ""; }; 3F0F02AD1B6FFF3D0046A4D5 /* RLMObservation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObservation.mm; sourceTree = ""; }; 3F102CBC23DBC68300108FD2 /* Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Combine.swift; sourceTree = ""; }; 3F149CCA2668112A00111D65 /* PersistedProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistedProperty.swift; sourceTree = ""; }; 3F1A5E721992EB7400F45F4C /* TestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3F1D8D30265B071000593ABA /* RLMValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMValue.h; sourceTree = ""; }; 3F1D8D31265B071000593ABA /* RLMValue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMValue.mm; sourceTree = ""; }; 3F1D8D32265B071000593ABA /* RLMUUID_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMUUID_Private.hpp; sourceTree = ""; }; 3F1D8D75265B075000593ABA /* MapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; 3F1D8D8D265B076C00593ABA /* RLMValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RLMValueTests.m; sourceTree = ""; }; 3F1D8D8E265B076C00593ABA /* PrimitiveRLMValuePropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveRLMValuePropertyTests.m; sourceTree = ""; }; 3F1D8D8F265B076C00593ABA /* PrimitiveDictionaryPropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveDictionaryPropertyTests.tpl.m; sourceTree = ""; }; 3F1D8D90265B076C00593ABA /* PrimitiveSetPropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveSetPropertyTests.tpl.m; sourceTree = ""; }; 3F1D8D91265B076C00593ABA /* PrimitiveRLMValuePropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveRLMValuePropertyTests.tpl.m; sourceTree = ""; }; 3F1D8D92265B076C00593ABA /* PrimitiveArrayPropertyTests.tpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveArrayPropertyTests.tpl.m; sourceTree = ""; }; 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeReference.swift; sourceTree = ""; }; 3F2633C21E9D630000B32D30 /* PrimitiveListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveListTests.swift; sourceTree = ""; }; 3F2E66611CA0B9D5004761D5 /* NotificationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationTests.m; sourceTree = ""; }; 3F3411A5273433B300EC9D25 /* ObjcBridgeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjcBridgeable.swift; sourceTree = ""; }; 3F4071342A57472F00D9C4A3 /* PrivateSymbols.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrivateSymbols.txt; sourceTree = ""; }; 3F40C612276D1B05007FEF1A /* CustomObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomObjectCreationTests.swift; sourceTree = ""; }; 3F452EC519C2279800AFC154 /* RLMSwiftSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RLMSwiftSupport.m; path = Realm/RLMSwiftSupport.m; sourceTree = SOURCE_ROOT; }; 3F4E0FF82654765C008B8C0B /* ModernKVOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModernKVOTests.swift; sourceTree = ""; }; 3F4E100E2655CA33008B8C0B /* ModernObjectAccessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectAccessorTests.swift; sourceTree = ""; }; 3F4E100F2655CA33008B8C0B /* RealmPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmPropertyTests.swift; sourceTree = ""; }; 3F4E324B1B98C6C700183A69 /* RLMSchema_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMSchema_Private.hpp; sourceTree = ""; }; 3F4F3ACF23F71C790048DB43 /* RLMDecimal128.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMDecimal128.mm; sourceTree = ""; }; 3F4F3AD023F71C790048DB43 /* RLMObjectId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectId.h; sourceTree = ""; }; 3F4F3AD123F71C790048DB43 /* RLMDecimal128_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMDecimal128_Private.hpp; sourceTree = ""; }; 3F4F3AD223F71C790048DB43 /* RLMObjectId.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObjectId.mm; sourceTree = ""; }; 3F4F3AD323F71C790048DB43 /* RLMDecimal128.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMDecimal128.h; sourceTree = ""; }; 3F4F3AD423F71C790048DB43 /* RLMObjectId_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMObjectId_Private.hpp; sourceTree = ""; }; 3F558C7E22C29A02002F0F30 /* TestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TestUtils.mm; path = Realm/TestUtils/TestUtils.mm; sourceTree = ""; }; 3F558C7F22C29A02002F0F30 /* RLMTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMTestCase.h; path = Realm/TestUtils/include/RLMTestCase.h; sourceTree = ""; }; 3F558C8022C29A02002F0F30 /* RLMMultiProcessTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMMultiProcessTestCase.h; path = Realm/TestUtils/include/RLMMultiProcessTestCase.h; sourceTree = ""; }; 3F558C8122C29A02002F0F30 /* RLMTestObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMTestObjects.h; path = Realm/TestUtils/include/RLMTestObjects.h; sourceTree = ""; }; 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RLMTestObjects.m; path = Realm/TestUtils/RLMTestObjects.m; sourceTree = ""; }; 3F558C8322C29A02002F0F30 /* RLMTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RLMTestCase.m; path = Realm/TestUtils/RLMTestCase.m; sourceTree = ""; }; 3F558C8422C29A03002F0F30 /* RLMAssertions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RLMAssertions.h; path = Realm/TestUtils/include/RLMAssertions.h; sourceTree = ""; }; 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RLMMultiProcessTestCase.m; path = Realm/TestUtils/RLMMultiProcessTestCase.m; sourceTree = ""; }; 3F558C8622C29A03002F0F30 /* TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TestUtils.h; path = Realm/TestUtils/include/TestUtils.h; sourceTree = ""; }; 3F572C901F2BDA9F00F6C9AB /* PrimitiveArrayPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveArrayPropertyTests.m; sourceTree = ""; }; 3F572C911F2BDA9F00F6C9AB /* ThreadSafeReferenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeReferenceTests.m; sourceTree = ""; }; 3F67DB391E26D69C0024533D /* RLMThreadSafeReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMThreadSafeReference.h; sourceTree = ""; }; 3F67DB3A1E26D69C0024533D /* RLMThreadSafeReference_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMThreadSafeReference_Private.hpp; sourceTree = ""; }; 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMThreadSafeReference.mm; sourceTree = ""; }; 3F68BFCD1B558CA800D50FBD /* RLMPrefix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMPrefix.h; sourceTree = ""; }; 3F73BC841E3A870F00FE80B6 /* ThreadSafeReferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeReferenceTests.swift; sourceTree = ""; }; 3F7556731BE95A050058BC7E /* AsyncTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AsyncTests.mm; sourceTree = ""; }; 3F83E9A22630A14800FC9623 /* RLMSwiftProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftProperty.h; sourceTree = ""; }; 3F852BE9278E1F000009DF74 /* TestBase.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TestBase.xcconfig; sourceTree = ""; }; 3F857A472769291800F9B9B1 /* KeyPathStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPathStrings.swift; sourceTree = ""; }; 3F90C1B12716169C0029000E /* TestValueFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestValueFactory.swift; sourceTree = ""; }; 3F9816292317763000C3543D /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 3F9863B91D36876B00641C98 /* RLMClassInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMClassInfo.mm; sourceTree = ""; }; 3F9863BA1D36876B00641C98 /* RLMClassInfo.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMClassInfo.hpp; sourceTree = ""; }; 3F997772273E23D300AA9E13 /* RealmCollectionImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmCollectionImpl.swift; sourceTree = ""; }; 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistable.swift; sourceTree = ""; }; 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistableTestObjects.swift; sourceTree = ""; }; 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectCreationTests.swift; sourceTree = ""; }; 3FAF2D4029577100002EAC93 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectTests.swift; sourceTree = ""; }; 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernTestObjects.swift; sourceTree = ""; }; 3FB6ABD62416A26100E318C2 /* ObjectId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectId.swift; sourceTree = ""; }; 3FB6ABD82416A27000E318C2 /* Decimal128.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decimal128.swift; sourceTree = ""; }; 3FBEF6781C63D66100F6935B /* RLMCollection_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMCollection_Private.hpp; sourceTree = ""; }; 3FBEF6791C63D66100F6935B /* RLMCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMCollection.mm; sourceTree = ""; }; 3FC3F910241808B300E27322 /* RLMEmbeddedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMEmbeddedObject.h; sourceTree = ""; }; 3FC3F911241808B300E27322 /* RLMEmbeddedObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMEmbeddedObject.mm; sourceTree = ""; }; 3FC3F9162419B63100E27322 /* EmbeddedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedObject.swift; sourceTree = ""; }; 3FCB1A7422A9B0A2003807FB /* CodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableTests.swift; sourceTree = ""; }; 3FCC56E329A55607004C5057 /* RLMSwiftObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftObject.h; sourceTree = ""; }; 3FD0D7C529675A2E0031C196 /* RLMAsyncTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMAsyncTask.h; sourceTree = ""; }; 3FD0D7C629675A2E0031C196 /* RLMAsyncTask.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMAsyncTask.mm; sourceTree = ""; }; 3FD0D7CB29675AE10031C196 /* RLMAsyncTask_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMAsyncTask_Private.h; sourceTree = ""; }; 3FD6D1A82B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3FD6D1AB2B4CA56C00A4FEBE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3FDAB83E290B4CCB00168F24 /* RLMError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMError.h; sourceTree = ""; }; 3FDAB83F290B4CCB00168F24 /* RLMError_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMError_Private.hpp; sourceTree = ""; }; 3FDAB840290B4CCB00168F24 /* RLMError.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMError.mm; sourceTree = ""; }; 3FDAB847290B658300168F24 /* file-format-version-21.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "file-format-version-21.realm"; sourceTree = ""; }; 3FDAB84B290B7A0200168F24 /* file-format-version-10.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "file-format-version-10.realm"; sourceTree = ""; }; 3FE267CF264308670030F83C /* CollectionAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionAccess.swift; sourceTree = ""; }; 3FE267D0264308680030F83C /* ComplexTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplexTypes.swift; sourceTree = ""; }; 3FE267D1264308680030F83C /* BasicTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTypes.swift; sourceTree = ""; }; 3FE267D2264308680030F83C /* Persistable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistable.swift; sourceTree = ""; }; 3FE267D3264308680030F83C /* PropertyAccessors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyAccessors.swift; sourceTree = ""; }; 3FE267D4264308680030F83C /* SchemaDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDiscovery.swift; sourceTree = ""; }; 3FE2BE0223D8CAD1002860E9 /* CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = ""; }; 3FE5B4D424CF3F06004D4EF3 /* realm-monorepo.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "realm-monorepo.xcframework"; path = "core/realm-monorepo.xcframework"; sourceTree = ""; }; 3FE79FF719BA6A5900780C9A /* RLMSwiftSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftSupport.h; sourceTree = ""; }; 3FEB383C1E70AC6900F22712 /* ObjectCreationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjectCreationTests.mm; sourceTree = ""; }; 3FEC4A3D1BBB188B00F009C3 /* SwiftSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSchemaTests.swift; sourceTree = ""; }; 3FEC91542A4B59A40044BFF5 /* Static.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Static.xcconfig; sourceTree = ""; }; 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libcompression.tbd; sourceTree = DEVELOPER_DIR; }; 3FEC91572A4B5D600044BFF5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCustomPropertiesTests.swift; sourceTree = ""; }; 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RLMChildProcessEnvironment.m; path = Realm/TestUtils/RLMChildProcessEnvironment.m; sourceTree = ""; }; 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftUITestHost.xcconfig; sourceTree = ""; }; 53124AB825B71AF600771CE4 /* SwiftUITestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUITestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53124AD425B71AF700771CE4 /* SwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53124AD825B71AF700771CE4 /* SwiftUITestHostUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUITestHostUITests.swift; sourceTree = ""; }; 53124ADA25B71AF700771CE4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 533489DD26E0F9510085EEE1 /* RLMChildProcessEnvironment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RLMChildProcessEnvironment.h; path = Realm/TestUtils/include/RLMChildProcessEnvironment.h; sourceTree = ""; }; 535EA9E125B0919800DBF3CD /* SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUI.swift; sourceTree = ""; }; 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUITests.swift; sourceTree = ""; }; 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftUITests.xcconfig; sourceTree = ""; }; 53626AAE25D31CAC00D9515D /* Objects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Objects.swift; sourceTree = ""; }; 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUITestHostApp.swift; sourceTree = ""; }; 53A34E3525CDA0AC00698930 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCSupport.swift; sourceTree = ""; }; 5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCSupportTests.swift; sourceTree = ""; }; 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PredicateUtilTests.mm; sourceTree = ""; }; 5D1534B71CCFF545008976D7 /* LinkingObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkingObjects.swift; sourceTree = ""; }; 5D1BF1FE1EF9875300B7DC87 /* RLMCollection_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMCollection_Private.h; sourceTree = ""; }; 5D2E8F651C98DC0D00187B09 /* RLMProperty_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMProperty_Private.hpp; sourceTree = ""; }; 5D3E1A2C1C1FC6D5002913BA /* RLMPredicateUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMPredicateUtil.hpp; sourceTree = ""; }; 5D3E1A2D1C1FC6D5002913BA /* RLMPredicateUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMPredicateUtil.mm; sourceTree = ""; }; 5D432B8C1CC0713F00A610A9 /* LinkingObjectsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LinkingObjectsTests.mm; sourceTree = ""; }; 5D6156F71BE07B6B00A4BD3F /* TestHost.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = TestHost.xcconfig; sourceTree = ""; }; 5D659E6D1BE0398E006515A0 /* Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; 5D659E6E1BE0398E006515A0 /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 5D659E6F1BE0398E006515A0 /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 5D659E761BE03E0D006515A0 /* Realm.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Realm.xcconfig; sourceTree = ""; }; 5D659ED91BE04556006515A0 /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = RealmSwift.xcconfig; sourceTree = ""; }; 5D660FC01BE98BEF0021E04F /* Tests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Tests.xcconfig; sourceTree = ""; }; 5D660FCC1BE98C560021E04F /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5D660FD81BE98C7C0021E04F /* RealmSwift Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RealmSwift Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 5D660FE31BE98D670021E04F /* Aliases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Aliases.swift; sourceTree = ""; }; 5D660FE41BE98D670021E04F /* List.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; 5D660FE51BE98D670021E04F /* Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = ""; }; 5D660FE61BE98D670021E04F /* Object.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = ""; }; 5D660FE71BE98D670021E04F /* ObjectSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectSchema.swift; sourceTree = ""; }; 5D660FE81BE98D670021E04F /* Optional.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; 5D660FE91BE98D670021E04F /* Property.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; }; 5D660FEA1BE98D670021E04F /* Realm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Realm.swift; sourceTree = ""; }; 5D660FEC1BE98D670021E04F /* RealmConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmConfiguration.swift; sourceTree = ""; }; 5D660FED1BE98D670021E04F /* Results.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Results.swift; sourceTree = ""; }; 5D660FEE1BE98D670021E04F /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; 5D660FEF1BE98D670021E04F /* SortDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDescriptor.swift; sourceTree = ""; }; 5D660FF01BE98D670021E04F /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; 5D660FFF1BE98D880021E04F /* KVOTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVOTests.swift; sourceTree = ""; }; 5D6610001BE98D880021E04F /* ListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = ""; }; 5D6610011BE98D880021E04F /* MigrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationTests.swift; sourceTree = ""; }; 5D6610021BE98D880021E04F /* ObjectAccessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectAccessorTests.swift; sourceTree = ""; }; 5D6610031BE98D880021E04F /* ObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectCreationTests.swift; sourceTree = ""; }; 5D6610041BE98D880021E04F /* ObjectSchemaInitializationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectSchemaInitializationTests.swift; sourceTree = ""; }; 5D6610051BE98D880021E04F /* ObjectSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectSchemaTests.swift; sourceTree = ""; }; 5D6610061BE98D880021E04F /* ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectTests.swift; sourceTree = ""; }; 5D6610071BE98D880021E04F /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; 5D6610081BE98D880021E04F /* PropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyTests.swift; sourceTree = ""; }; 5D6610091BE98D880021E04F /* RealmCollectionTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmCollectionTypeTests.swift; sourceTree = ""; }; 5D66100A1BE98D880021E04F /* RealmConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmConfigurationTests.swift; sourceTree = ""; }; 5D66100B1BE98D880021E04F /* RealmSwiftTests-BridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RealmSwiftTests-BridgingHeader.h"; sourceTree = ""; }; 5D66100C1BE98D880021E04F /* RealmTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmTests.swift; sourceTree = ""; }; 5D66100D1BE98D880021E04F /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; 5D66100E1BE98D880021E04F /* SortDescriptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDescriptorTests.swift; sourceTree = ""; }; 5D66100F1BE98D880021E04F /* SwiftLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftLinkTests.swift; sourceTree = ""; }; 5D6610101BE98D880021E04F /* SwiftTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftTestObjects.swift; sourceTree = ""; }; 5D6610111BE98D880021E04F /* SwiftUnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUnicodeTests.swift; sourceTree = ""; }; 5D6610121BE98D880021E04F /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = ""; }; 5DD755E01BE05C19002800DA /* Tests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Tests.xcconfig; sourceTree = ""; }; 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRealmValue.swift; sourceTree = ""; }; 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+AnyRealmValue.swift"; sourceTree = ""; }; A05FA61E1B62C3900000C9B2 /* RLMObjectBase_Dynamic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMObjectBase_Dynamic.h; sourceTree = ""; }; AC3B33AB29DC6CEE0042F3A0 /* RLMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMLogger.h; sourceTree = ""; }; AC3B33AC29DC6CEE0042F3A0 /* RLMLogger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMLogger.mm; sourceTree = ""; }; AC3B33AD29DC6CEE0042F3A0 /* RLMLogger_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMLogger_Private.h; sourceTree = ""; }; AC5300712BD03D4900BF5950 /* MixedCollectionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixedCollectionTest.swift; sourceTree = ""; }; AC7825B82ACD90BE007ABA4B /* Geospatial.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Geospatial.swift; sourceTree = ""; }; AC7825BA2ACD90DA007ABA4B /* RLMGeospatial.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMGeospatial.mm; sourceTree = ""; }; AC7825BB2ACD90DA007ABA4B /* RLMGeospatial_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMGeospatial_Private.hpp; sourceTree = ""; }; AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMGeospatial.h; sourceTree = ""; }; AC7825C02ACD916C007ABA4B /* GeospatialTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeospatialTests.swift; sourceTree = ""; }; ACF08B6626DD936200686CBC /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; ACFF0EC628EC5ADB0097AEE0 /* CustomColumnNameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomColumnNameTests.swift; sourceTree = ""; }; C042A48C1B7522A900771ED2 /* RealmConfigurationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmConfigurationTests.mm; sourceTree = ""; }; C073E1201AE9B705002C0A30 /* RLMObject_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMObject_Private.hpp; sourceTree = ""; }; C0D2DD051B6BDEA1004E8919 /* RLMRealmConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMRealmConfiguration.h; sourceTree = ""; }; C0D2DD061B6BDEA1004E8919 /* RLMRealmConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMRealmConfiguration.mm; sourceTree = ""; }; C0D2DD0F1B6BE0DD004E8919 /* RLMRealmConfiguration_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMRealmConfiguration_Private.h; sourceTree = ""; }; CF040493263DF0A900F9AEE0 /* PrimitiveMapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveMapTests.swift; sourceTree = ""; }; CF052EFA25DEB671008EEF86 /* DictionaryPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictionaryPropertyTests.m; sourceTree = ""; }; CF0D04F02693652E0038A058 /* KeyPathTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathTests.swift; sourceTree = ""; }; CF25080D283B90F8007D66FE /* SectionedResultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionedResultsTests.m; sourceTree = ""; }; CF44460526121B2A00BAFDB4 /* RLMSwiftValueStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSwiftValueStorage.mm; sourceTree = ""; }; CF44460626121B2A00BAFDB4 /* RLMSwiftValueStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSwiftValueStorage.h; sourceTree = ""; }; CF44461D26121C6800BAFDB4 /* RealmProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmProperty.swift; sourceTree = ""; }; CF46CBFF26D931BA00DE450C /* QueryTests.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = QueryTests.swift.gyb; sourceTree = ""; }; CF46CC0026D931BA00DE450C /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; CF84D4EF281823B300005E27 /* SectionedResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedResults.swift; sourceTree = ""; }; CF986D1A25AE3B080039D287 /* RLMSet_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSet_Private.h; sourceTree = ""; }; CF986D1B25AE3B080039D287 /* RLMSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSet.mm; sourceTree = ""; }; CF986D1C25AE3B090039D287 /* RLMSet_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMSet_Private.hpp; sourceTree = ""; }; CF986D1D25AE3B090039D287 /* RLMSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSet.h; sourceTree = ""; }; CF986D2F25AE3BD40039D287 /* PrimitiveSetPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimitiveSetPropertyTests.m; sourceTree = ""; }; CF986D3025AE3BD40039D287 /* SetPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SetPropertyTests.m; sourceTree = ""; }; CF986D4725AE3C420039D287 /* SwiftSetPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSetPropertyTests.swift; sourceTree = ""; }; CF986D4825AE3C420039D287 /* SwiftSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSetTests.swift; sourceTree = ""; }; CF986D8E25AE3C980039D287 /* MutableSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableSet.swift; sourceTree = ""; }; CF986DE125AE3EC70039D287 /* MutableSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableSetTests.swift; sourceTree = ""; }; CF986DF525AE3EDF0039D287 /* PrimitiveMutableSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveMutableSetTests.swift; sourceTree = ""; }; CF9881C025DABC6500BD7E4F /* RLMManagedDictionary.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMManagedDictionary.mm; sourceTree = ""; }; CF9881CB25DABDE900BD7E4F /* RLMDictionary_Private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RLMDictionary_Private.hpp; sourceTree = ""; }; CFAE926A24A0A7F40033CB31 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; CFD8D11F25BB0B8B0037FE4D /* RLMManagedSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMManagedSet.mm; sourceTree = ""; }; CFDBC4B228803C7200EE80E6 /* RLMSectionedResults_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMSectionedResults_Private.hpp; sourceTree = ""; }; CFDBC4B328803C7200EE80E6 /* RLMSectionedResults.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSectionedResults.mm; sourceTree = ""; }; CFDBC4B428803C7200EE80E6 /* RLMSectionedResults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSectionedResults.h; sourceTree = ""; }; CFDBC4BB28803DD400EE80E6 /* SectionedResultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionedResultsTests.m; sourceTree = ""; }; CFDBC4BE288040CD00EE80E6 /* SectionedResultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedResultsTests.swift; sourceTree = ""; }; CFE9CE3226555BBD00BF96D6 /* RealmKeyedCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmKeyedCollection.swift; sourceTree = ""; }; CFFC8CC7262310C800929608 /* AnyRealmValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyRealmValueTests.swift; sourceTree = ""; }; CFFECBA8250646750010F585 /* Decimal128Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decimal128Tests.swift; sourceTree = ""; }; CFFECBAB250667A90010F585 /* Decimal128Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Decimal128Tests.m; sourceTree = ""; }; CFFECBAF250690EA0010F585 /* ObjectIdTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectIdTests.m; sourceTree = ""; }; CFFECBB225078D100010F585 /* ObjectIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectIdTests.swift; sourceTree = ""; }; E81A1F621955FC9300FDED82 /* Realm-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Realm-Info.plist"; sourceTree = ""; }; E81A1F631955FC9300FDED82 /* RLMAccessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMAccessor.h; sourceTree = ""; }; E81A1F641955FC9300FDED82 /* RLMAccessor.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMAccessor.mm; sourceTree = ""; }; E81A1F651955FC9300FDED82 /* RLMArray_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMArray_Private.hpp; sourceTree = ""; }; E81A1F661955FC9300FDED82 /* RLMArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMArray.h; sourceTree = ""; }; E81A1F671955FC9300FDED82 /* RLMArray.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMArray.mm; sourceTree = ""; }; E81A1F691955FC9300FDED82 /* RLMManagedArray.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = RLMManagedArray.mm; sourceTree = ""; }; E81A1F6A1955FC9300FDED82 /* RLMResults.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = RLMResults.mm; sourceTree = ""; }; E81A1F6B1955FC9300FDED82 /* RLMConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMConstants.h; sourceTree = ""; }; E81A1F6C1955FC9300FDED82 /* RLMConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RLMConstants.m; sourceTree = ""; }; E81A1F6D1955FC9300FDED82 /* RLMObject_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObject_Private.h; sourceTree = ""; }; E81A1F6E1955FC9300FDED82 /* RLMObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObject.h; sourceTree = ""; }; E81A1F6F1955FC9300FDED82 /* RLMObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObject.mm; sourceTree = ""; }; E81A1F701955FC9300FDED82 /* RLMObjectSchema_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMObjectSchema_Private.hpp; sourceTree = ""; }; E81A1F711955FC9300FDED82 /* RLMObjectSchema.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectSchema.h; sourceTree = ""; }; E81A1F721955FC9300FDED82 /* RLMObjectSchema.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMObjectSchema.mm; sourceTree = ""; }; E81A1F741955FC9300FDED82 /* RLMObjectStore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = RLMObjectStore.mm; sourceTree = ""; }; E81A1F751955FC9300FDED82 /* RLMProperty_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMProperty_Private.h; sourceTree = ""; }; E81A1F761955FC9300FDED82 /* RLMProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMProperty.h; sourceTree = ""; }; E81A1F771955FC9300FDED82 /* RLMProperty.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMProperty.mm; sourceTree = ""; }; E81A1F781955FC9300FDED82 /* RLMQueryUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMQueryUtil.hpp; sourceTree = ""; }; E81A1F791955FC9300FDED82 /* RLMQueryUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMQueryUtil.mm; sourceTree = ""; }; E81A1F7B1955FC9300FDED82 /* RLMRealm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMRealm.h; sourceTree = ""; }; E81A1F7C1955FC9300FDED82 /* RLMRealm.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMRealm.mm; sourceTree = ""; }; E81A1F7D1955FC9300FDED82 /* RLMSchema_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSchema_Private.h; sourceTree = ""; }; E81A1F7E1955FC9300FDED82 /* RLMSchema.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSchema.h; sourceTree = ""; }; E81A1F7F1955FC9300FDED82 /* RLMSchema.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMSchema.mm; sourceTree = ""; }; E81A1F811955FC9300FDED82 /* RLMUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMUtil.hpp; sourceTree = ""; }; E81A1F821955FC9300FDED82 /* RLMUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMUtil.mm; sourceTree = ""; }; E81A1FB31955FCE000FDED82 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = CHANGELOG.md; sourceTree = ""; }; E81A1FB41955FCE000FDED82 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; E81A1FB81955FE0100FDED82 /* ArrayPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ArrayPropertyTests.m; sourceTree = ""; }; E81A1FBA1955FE0100FDED82 /* DynamicTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DynamicTests.m; sourceTree = ""; }; E81A1FBB1955FE0100FDED82 /* EnumeratorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EnumeratorTests.m; sourceTree = ""; }; E81A1FBC1955FE0100FDED82 /* LinkTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LinkTests.m; sourceTree = ""; }; E81A1FBE1955FE0100FDED82 /* ObjectInterfaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectInterfaceTests.m; sourceTree = ""; }; E81A1FBF1955FE0100FDED82 /* ObjectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectTests.m; sourceTree = ""; }; E81A1FC11955FE0100FDED82 /* QueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryTests.m; sourceTree = ""; }; E81A1FC21955FE0100FDED82 /* RealmTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "RealmTests-Info.plist"; sourceTree = ""; }; E81A1FC31955FE0100FDED82 /* RealmTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmTests.mm; sourceTree = ""; }; E81A1FD01955FE0100FDED82 /* SwiftRealmTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRealmTests.swift; sourceTree = ""; }; E81A1FD11955FE0100FDED82 /* TransactionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TransactionTests.m; sourceTree = ""; }; E82FA60A195632F20043A3C3 /* SwiftArrayPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftArrayPropertyTests.swift; sourceTree = ""; }; E82FA60B195632F20043A3C3 /* SwiftArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftArrayTests.swift; sourceTree = ""; }; E82FA60D195632F20043A3C3 /* SwiftLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftLinkTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E82FA60F195632F20043A3C3 /* SwiftObjectInterfaceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftObjectInterfaceTests.swift; sourceTree = ""; }; E83AF538196DDE58002275B2 /* SwiftDynamicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftDynamicTests.swift; sourceTree = ""; }; E86900E11CC04F5B0008A8B6 /* RLMRealmConfiguration_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMRealmConfiguration_Private.hpp; sourceTree = ""; }; E8839B2C19E31FD90047B1A8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Realm/Tests/TestHost/Info.plist; sourceTree = SOURCE_ROOT; }; E8839B2D19E31FD90047B1A8 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Realm/Tests/TestHost/main.m; sourceTree = SOURCE_ROOT; }; E88C36FF19745E5500C9963D /* RLMSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLMSupport.swift; sourceTree = ""; }; E8917597197A1B350068ACC6 /* UnicodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnicodeTests.m; sourceTree = ""; }; E891759A197A1B600068ACC6 /* SwiftUnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUnicodeTests.swift; sourceTree = ""; }; E8951F01196C96DE00D6461C /* RLMRealm_Dynamic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMRealm_Dynamic.h; sourceTree = ""; }; E8AE7C251EA436F800CDFF9A /* CompactionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompactionTests.swift; sourceTree = ""; }; E8D89B9D1955FC6D00CF2B9A /* Realm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Realm.h; sourceTree = ""; }; E8D89BA31955FC6D00CF2B9A /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E8DA16F71E81210D0055141C /* CompactionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CompactionTests.m; sourceTree = ""; }; E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftTestObjects.swift; sourceTree = ""; }; E8F992BD1F1401C100F634B5 /* RLMObjectBase_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMObjectBase_Private.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1A7B82351D51235600750296 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F08725527F3B5E0007A1175 /* libcompression.tbd in Frameworks */, 1A7B823A1D51259F00750296 /* libz.tbd in Frameworks */, 3FE5B4D724CF6909004D4EF3 /* realm-monorepo.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 3F1A5E6F1992EB7400F45F4C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 53124AB525B71AF600771CE4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 53124AD125B71AF700771CE4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FC81BE98C560021E04F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F98162A2317763000C3543D /* libc++.tbd in Frameworks */, 3F98162B2317763600C3543D /* libz.tbd in Frameworks */, 5D66102A1BE98DD00021E04F /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FD51BE98C7C0021E04F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FEC91562A4B5D520044BFF5 /* libcompression.tbd in Frameworks */, 3FEC91582A4B5D600044BFF5 /* libz.tbd in Frameworks */, 3FEC915B2A4B65B30044BFF5 /* Realm.framework in Frameworks */, 5D660FDD1BE98C7C0021E04F /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8D89BA01955FC6D00CF2B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FEC91592A4B5DE90044BFF5 /* libcompression.tbd in Frameworks */, 3FEC915A2A4B5DEF0044BFF5 /* libz.tbd in Frameworks */, 5D6156EE1BE0689200A4BD3F /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1A7B82361D51254600750296 /* Frameworks */ = { isa = PBXGroup; children = ( CFAE926A24A0A7F40033CB31 /* AuthenticationServices.framework */, 3F9816292317763000C3543D /* libc++.tbd */, 3FEC91552A4B5D520044BFF5 /* libcompression.tbd */, 3F08725427F3B5E0007A1175 /* libcompression.tbd */, 3FEC91572A4B5D600044BFF5 /* libz.tbd */, 1A7B82391D51259F00750296 /* libz.tbd */, 3FE5B4D424CF3F06004D4EF3 /* realm-monorepo.xcframework */, ); name = Frameworks; sourceTree = ""; }; 3F1A5E731992EB7400F45F4C /* TestHost */ = { isa = PBXGroup; children = ( E8839B2C19E31FD90047B1A8 /* Info.plist */, E8839B2D19E31FD90047B1A8 /* main.m */, ); name = TestHost; path = ../../TestHost; sourceTree = ""; }; 3F48201C26307CE2005B40E8 /* Impl */ = { isa = PBXGroup; children = ( 3FE267D1264308680030F83C /* BasicTypes.swift */, 3FE267CF264308670030F83C /* CollectionAccess.swift */, 3FE267D0264308680030F83C /* ComplexTypes.swift */, 3F857A472769291800F9B9B1 /* KeyPathStrings.swift */, 3F3411A5273433B300EC9D25 /* ObjcBridgeable.swift */, 3FE267D2264308680030F83C /* Persistable.swift */, 3FE267D3264308680030F83C /* PropertyAccessors.swift */, 3F997772273E23D300AA9E13 /* RealmCollectionImpl.swift */, 3FE267D4264308680030F83C /* SchemaDiscovery.swift */, ); path = Impl; sourceTree = ""; }; 3F558C7D22C299DB002F0F30 /* Test Utils */ = { isa = PBXGroup; children = ( 3F558C8422C29A03002F0F30 /* RLMAssertions.h */, 533489DD26E0F9510085EEE1 /* RLMChildProcessEnvironment.h */, 530BA61326DFA1CB008FC550 /* RLMChildProcessEnvironment.m */, 3F558C8022C29A02002F0F30 /* RLMMultiProcessTestCase.h */, 3F558C8522C29A03002F0F30 /* RLMMultiProcessTestCase.m */, 3F558C7F22C29A02002F0F30 /* RLMTestCase.h */, 3F558C8322C29A02002F0F30 /* RLMTestCase.m */, 3F558C8122C29A02002F0F30 /* RLMTestObjects.h */, 3F558C8222C29A02002F0F30 /* RLMTestObjects.m */, 3F558C8622C29A03002F0F30 /* TestUtils.h */, 3F558C7E22C29A02002F0F30 /* TestUtils.mm */, ); name = "Test Utils"; sourceTree = ""; }; 53124AD725B71AF700771CE4 /* SwiftUITestHostUITests */ = { isa = PBXGroup; children = ( 53124ADA25B71AF700771CE4 /* Info.plist */, 53124AD825B71AF700771CE4 /* SwiftUITestHostUITests.swift */, ); path = SwiftUITestHostUITests; sourceTree = ""; }; 53A34E3225CDA0AC00698930 /* SwiftUITestHost */ = { isa = PBXGroup; children = ( 53A34E3525CDA0AC00698930 /* Info.plist */, 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */, 53626AAE25D31CAC00D9515D /* Objects.swift */, 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */, ); path = SwiftUITestHost; sourceTree = ""; }; 5D659E6C1BE03981006515A0 /* Realm */ = { isa = PBXGroup; children = ( 3F4071342A57472F00D9C4A3 /* PrivateSymbols.txt */, 5D659E761BE03E0D006515A0 /* Realm.xcconfig */, 5DD755E01BE05C19002800DA /* Tests.xcconfig */, ); path = Realm; sourceTree = ""; }; 5D660FB71BE98B770021E04F /* Configuration */ = { isa = PBXGroup; children = ( 5D659E6C1BE03981006515A0 /* Realm */, 5D660FBA1BE98BD80021E04F /* RealmSwift */, 5D659E6D1BE0398E006515A0 /* Base.xcconfig */, 5D659E6E1BE0398E006515A0 /* Debug.xcconfig */, 5D659E6F1BE0398E006515A0 /* Release.xcconfig */, 3FEC91542A4B59A40044BFF5 /* Static.xcconfig */, 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */, 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */, 3F852BE9278E1F000009DF74 /* TestBase.xcconfig */, 5D6156F71BE07B6B00A4BD3F /* TestHost.xcconfig */, ); path = Configuration; sourceTree = ""; }; 5D660FBA1BE98BD80021E04F /* RealmSwift */ = { isa = PBXGroup; children = ( 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */, 5D660FC01BE98BEF0021E04F /* Tests.xcconfig */, ); path = RealmSwift; sourceTree = ""; }; 5D660FCD1BE98C560021E04F /* RealmSwift */ = { isa = PBXGroup; children = ( 3F48201C26307CE2005B40E8 /* Impl */, 5D660FE31BE98D670021E04F /* Aliases.swift */, 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */, 3F102CBC23DBC68300108FD2 /* Combine.swift */, 3F9F53D22718B5DA000EEB4A /* CustomPersistable.swift */, 3FB6ABD82416A27000E318C2 /* Decimal128.swift */, 3FC3F9162419B63100E27322 /* EmbeddedObject.swift */, 29B7FDF51C0DA6560023224E /* Error.swift */, AC7825B82ACD90BE007ABA4B /* Geospatial.swift */, 5D1534B71CCFF545008976D7 /* LinkingObjects.swift */, 5D660FE41BE98D670021E04F /* List.swift */, 0C3BD4D225C1C5AB007CFDD3 /* Map.swift */, 5D660FE51BE98D670021E04F /* Migration.swift */, CF986D8E25AE3C980039D287 /* MutableSet.swift */, 5D660FE61BE98D670021E04F /* Object.swift */, 3FB6ABD62416A26100E318C2 /* ObjectId.swift */, 681EE34625EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift */, 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */, 5D660FE71BE98D670021E04F /* ObjectSchema.swift */, 5D660FE81BE98D670021E04F /* Optional.swift */, 3F149CCA2668112A00111D65 /* PersistedProperty.swift */, 3FD6D1AB2B4CA56C00A4FEBE /* PrivacyInfo.xcprivacy */, 0CD1632526D3DD7B0027C49B /* Projection.swift */, 5D660FE91BE98D670021E04F /* Property.swift */, ACF08B6626DD936200686CBC /* Query.swift */, 5D660FEA1BE98D670021E04F /* Realm.swift */, 1AB605D21D495927007F53DE /* RealmCollection.swift */, 5D660FEC1BE98D670021E04F /* RealmConfiguration.swift */, CFE9CE3226555BBD00BF96D6 /* RealmKeyedCollection.swift */, CF44461D26121C6800BAFDB4 /* RealmProperty.swift */, 5D660FED1BE98D670021E04F /* Results.swift */, 5D660FEE1BE98D670021E04F /* Schema.swift */, CF84D4EF281823B300005E27 /* SectionedResults.swift */, 5D660FEF1BE98D670021E04F /* SortDescriptor.swift */, 535EA9E125B0919800DBF3CD /* SwiftUI.swift */, 3F222C4D1E26F51300CA0713 /* ThreadSafeReference.swift */, 5D660FF01BE98D670021E04F /* Util.swift */, ); path = RealmSwift; sourceTree = ""; }; 5D660FD91BE98C7C0021E04F /* RealmSwift Tests */ = { isa = PBXGroup; children = ( 5D6610291BE98DAA0021E04F /* Supporting Files */, CFFC8CC7262310C800929608 /* AnyRealmValueTests.swift */, 3FCB1A7422A9B0A2003807FB /* CodableTests.swift */, 3FE2BE0223D8CAD1002860E9 /* CombineTests.swift */, E8AE7C251EA436F800CDFF9A /* CompactionTests.swift */, ACFF0EC628EC5ADB0097AEE0 /* CustomColumnNameTests.swift */, 3F40C612276D1B05007FEF1A /* CustomObjectCreationTests.swift */, 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */, CFFECBA8250646750010F585 /* Decimal128Tests.swift */, AC7825C02ACD916C007ABA4B /* GeospatialTests.swift */, CF0D04F02693652E0038A058 /* KeyPathTests.swift */, 5D660FFF1BE98D880021E04F /* KVOTests.swift */, 5D6610001BE98D880021E04F /* ListTests.swift */, 3F1D8D75265B075000593ABA /* MapTests.swift */, 5D6610011BE98D880021E04F /* MigrationTests.swift */, AC5300712BD03D4900BF5950 /* MixedCollectionTest.swift */, 3F4E0FF82654765C008B8C0B /* ModernKVOTests.swift */, 3F4E100E2655CA33008B8C0B /* ModernObjectAccessorTests.swift */, 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */, 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */, 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */, CF986DE125AE3EC70039D287 /* MutableSetTests.swift */, 5D6610021BE98D880021E04F /* ObjectAccessorTests.swift */, 5D6610031BE98D880021E04F /* ObjectCreationTests.swift */, 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */, CFFECBB225078D100010F585 /* ObjectIdTests.swift */, 5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */, 5D6610041BE98D880021E04F /* ObjectSchemaInitializationTests.swift */, 5D6610051BE98D880021E04F /* ObjectSchemaTests.swift */, 5D6610061BE98D880021E04F /* ObjectTests.swift */, 5D6610071BE98D880021E04F /* PerformanceTests.swift */, 3F2633C21E9D630000B32D30 /* PrimitiveListTests.swift */, CF040493263DF0A900F9AEE0 /* PrimitiveMapTests.swift */, CF986DF525AE3EDF0039D287 /* PrimitiveMutableSetTests.swift */, 0CBF2DB827286FFD00635902 /* ProjectedCollectTests.swift */, 0CD1632726D3DF1C0027C49B /* ProjectionTests.swift */, 5D6610081BE98D880021E04F /* PropertyTests.swift */, CF46CC0026D931BA00DE450C /* QueryTests.swift */, CF46CBFF26D931BA00DE450C /* QueryTests.swift.gyb */, 5D6610091BE98D880021E04F /* RealmCollectionTypeTests.swift */, 5D66100A1BE98D880021E04F /* RealmConfigurationTests.swift */, 3F4E100F2655CA33008B8C0B /* RealmPropertyTests.swift */, 5D66100C1BE98D880021E04F /* RealmTests.swift */, 5D66100D1BE98D880021E04F /* SchemaTests.swift */, CFDBC4BE288040CD00EE80E6 /* SectionedResultsTests.swift */, 5D66100E1BE98D880021E04F /* SortDescriptorTests.swift */, 5D66100F1BE98D880021E04F /* SwiftLinkTests.swift */, 5D6610101BE98D880021E04F /* SwiftTestObjects.swift */, 535EAA7425B0B02B00DBF3CD /* SwiftUITests.swift */, 5D6610111BE98D880021E04F /* SwiftUnicodeTests.swift */, 5D6610121BE98D880021E04F /* TestCase.swift */, 3FAF2D4029577100002EAC93 /* TestUtils.swift */, 3F90C1B12716169C0029000E /* TestValueFactory.swift */, 3F73BC841E3A870F00FE80B6 /* ThreadSafeReferenceTests.swift */, ); name = "RealmSwift Tests"; path = RealmSwift/Tests; sourceTree = ""; }; 5D6610291BE98DAA0021E04F /* Supporting Files */ = { isa = PBXGroup; children = ( 5D66100B1BE98D880021E04F /* RealmSwiftTests-BridgingHeader.h */, ); name = "Supporting Files"; sourceTree = ""; }; E81A1FC81955FE0100FDED82 /* Swift */ = { isa = PBXGroup; children = ( 297FBEFA1C19F696009D1118 /* RLMTestCaseUtils.swift */, 0217D7B819CD0ACD00DE5C32 /* Swift-Tests-Bridging-Header.h */, E82FA60A195632F20043A3C3 /* SwiftArrayPropertyTests.swift */, E82FA60B195632F20043A3C3 /* SwiftArrayTests.swift */, E83AF538196DDE58002275B2 /* SwiftDynamicTests.swift */, E82FA60D195632F20043A3C3 /* SwiftLinkTests.swift */, E82FA60F195632F20043A3C3 /* SwiftObjectInterfaceTests.swift */, 26F3CA681986CC86004623E1 /* SwiftPropertyTypeTest.swift */, E81A1FD01955FE0100FDED82 /* SwiftRealmTests.swift */, 0C9758BE264974660097B48D /* SwiftRLMDictionaryTests.swift */, 3FEC4A3D1BBB188B00F009C3 /* SwiftSchemaTests.swift */, CF986D4725AE3C420039D287 /* SwiftSetPropertyTests.swift */, CF986D4825AE3C420039D287 /* SwiftSetTests.swift */, E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */, E891759A197A1B600068ACC6 /* SwiftUnicodeTests.swift */, ); path = Swift; sourceTree = ""; }; E81A20061955FE1600FDED82 /* Objective-C */ = { isa = PBXGroup; children = ( E81A1FB81955FE0100FDED82 /* ArrayPropertyTests.m */, 3F7556731BE95A050058BC7E /* AsyncTests.mm */, E8DA16F71E81210D0055141C /* CompactionTests.m */, CFFECBAB250667A90010F585 /* Decimal128Tests.m */, CF052EFA25DEB671008EEF86 /* DictionaryPropertyTests.m */, E81A1FBA1955FE0100FDED82 /* DynamicTests.m */, 021A882F1AAFB5BE00EEAC84 /* EncryptionTests.mm */, E81A1FBB1955FE0100FDED82 /* EnumeratorTests.m */, 027A4D291AB1012500AA46F9 /* InterprocessTests.m */, 3F0F029D1B6FFE610046A4D5 /* KVOTests.mm */, 5D432B8C1CC0713F00A610A9 /* LinkingObjectsTests.mm */, E81A1FBC1955FE0100FDED82 /* LinkTests.m */, 0207AB85195DFA15007EFB12 /* MigrationTests.mm */, 3F2E66611CA0B9D5004761D5 /* NotificationTests.m */, 3FEB383C1E70AC6900F22712 /* ObjectCreationTests.mm */, CFFECBAF250690EA0010F585 /* ObjectIdTests.m */, E81A1FBE1955FE0100FDED82 /* ObjectInterfaceTests.m */, 021A88301AAFB5BE00EEAC84 /* ObjectSchemaTests.m */, E81A1FBF1955FE0100FDED82 /* ObjectTests.m */, 3F04EA2D1992BEE400C2CE2E /* PerformanceTests.m */, 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */, 3F572C901F2BDA9F00F6C9AB /* PrimitiveArrayPropertyTests.m */, 3F1D8D92265B076C00593ABA /* PrimitiveArrayPropertyTests.tpl.m */, 0C86B33825E15B6000775FED /* PrimitiveDictionaryPropertyTests.m */, 3F1D8D8F265B076C00593ABA /* PrimitiveDictionaryPropertyTests.tpl.m */, 3F1D8D8E265B076C00593ABA /* PrimitiveRLMValuePropertyTests.m */, 3F1D8D91265B076C00593ABA /* PrimitiveRLMValuePropertyTests.tpl.m */, CF986D2F25AE3BD40039D287 /* PrimitiveSetPropertyTests.m */, 3F1D8D90265B076C00593ABA /* PrimitiveSetPropertyTests.tpl.m */, 02AFB4611A80343600E11938 /* PropertyTests.m */, E81A1FC11955FE0100FDED82 /* QueryTests.m */, C042A48C1B7522A900771ED2 /* RealmConfigurationTests.mm */, E81A1FC31955FE0100FDED82 /* RealmTests.mm */, 02AFB4621A80343600E11938 /* ResultsTests.m */, 3F1D8D8D265B076C00593ABA /* RLMValueTests.m */, 0207AB86195DFA15007EFB12 /* SchemaTests.mm */, CFDBC4BB28803DD400EE80E6 /* SectionedResultsTests.m */, CF25080D283B90F8007D66FE /* SectionedResultsTests.m */, CF986D3025AE3BD40039D287 /* SetPropertyTests.m */, 3F572C911F2BDA9F00F6C9AB /* ThreadSafeReferenceTests.m */, E81A1FD11955FE0100FDED82 /* TransactionTests.m */, E8917597197A1B350068ACC6 /* UnicodeTests.m */, 021A88311AAFB5BE00EEAC84 /* UtilTests.mm */, ); name = "Objective-C"; sourceTree = ""; }; E88C36FE19745E3200C9963D /* Swift */ = { isa = PBXGroup; children = ( E88C36FF19745E5500C9963D /* RLMSupport.swift */, ); path = Swift; sourceTree = ""; }; E8D89B8E1955FC6D00CF2B9A = { isa = PBXGroup; children = ( 5D660FB71BE98B770021E04F /* Configuration */, 1A7B82361D51254600750296 /* Frameworks */, E81A1FB41955FCE000FDED82 /* LICENSE */, E8D89B991955FC6D00CF2B9A /* Products */, E8D89B9A1955FC6D00CF2B9A /* Realm */, E8D89BA71955FC6D00CF2B9A /* Realm Tests */, 5D660FCD1BE98C560021E04F /* RealmSwift */, 5D660FD91BE98C7C0021E04F /* RealmSwift Tests */, 3F558C7D22C299DB002F0F30 /* Test Utils */, E81A1FB31955FCE000FDED82 /* CHANGELOG.md */, ); indentWidth = 4; sourceTree = ""; tabWidth = 4; }; E8D89B991955FC6D00CF2B9A /* Products */ = { isa = PBXGroup; children = ( 5D659ED91BE04556006515A0 /* Realm.framework */, 5D660FD81BE98C7C0021E04F /* RealmSwift Tests.xctest */, 5D660FCC1BE98C560021E04F /* RealmSwift.framework */, 53124AB825B71AF600771CE4 /* SwiftUITestHost.app */, 53124AD425B71AF700771CE4 /* SwiftUITests.xctest */, 3F1A5E721992EB7400F45F4C /* TestHost.app */, E8D89BA31955FC6D00CF2B9A /* Tests.xctest */, ); name = Products; sourceTree = ""; }; E8D89B9A1955FC6D00CF2B9A /* Realm */ = { isa = PBXGroup; children = ( E8D89B9B1955FC6D00CF2B9A /* Supporting Files */, E88C36FE19745E3200C9963D /* Swift */, E8D89B9D1955FC6D00CF2B9A /* Realm.h */, E81A1F631955FC9300FDED82 /* RLMAccessor.h */, 3F0338491E6F466D00F9E288 /* RLMAccessor.hpp */, E81A1F641955FC9300FDED82 /* RLMAccessor.mm */, E81A1F661955FC9300FDED82 /* RLMArray.h */, E81A1F671955FC9300FDED82 /* RLMArray.mm */, 0237B5421A856F06004ACD57 /* RLMArray_Private.h */, E81A1F651955FC9300FDED82 /* RLMArray_Private.hpp */, 3FD0D7C529675A2E0031C196 /* RLMAsyncTask.h */, 3FD0D7C629675A2E0031C196 /* RLMAsyncTask.mm */, 3FD0D7CB29675AE10031C196 /* RLMAsyncTask_Private.h */, 3F9863BA1D36876B00641C98 /* RLMClassInfo.hpp */, 3F9863B91D36876B00641C98 /* RLMClassInfo.mm */, 02B8EF5B19E7048D0045A93D /* RLMCollection.h */, 3FBEF6791C63D66100F6935B /* RLMCollection.mm */, 5D1BF1FE1EF9875300B7DC87 /* RLMCollection_Private.h */, 3FBEF6781C63D66100F6935B /* RLMCollection_Private.hpp */, E81A1F6B1955FC9300FDED82 /* RLMConstants.h */, E81A1F6C1955FC9300FDED82 /* RLMConstants.m */, 3F4F3AD323F71C790048DB43 /* RLMDecimal128.h */, 3F4F3ACF23F71C790048DB43 /* RLMDecimal128.mm */, 3F4F3AD123F71C790048DB43 /* RLMDecimal128_Private.hpp */, 0C3BD4B225C1BDF1007CFDD3 /* RLMDictionary.h */, 0C3BD4B125C1BDF1007CFDD3 /* RLMDictionary.mm */, 0C3BD50125C1DE6F007CFDD3 /* RLMDictionary_Private.h */, CF9881CB25DABDE900BD7E4F /* RLMDictionary_Private.hpp */, 3FC3F910241808B300E27322 /* RLMEmbeddedObject.h */, 3FC3F911241808B300E27322 /* RLMEmbeddedObject.mm */, 3FDAB83E290B4CCB00168F24 /* RLMError.h */, 3FDAB840290B4CCB00168F24 /* RLMError.mm */, 3FDAB83F290B4CCB00168F24 /* RLMError_Private.hpp */, AC7825BC2ACD90DA007ABA4B /* RLMGeospatial.h */, AC7825BA2ACD90DA007ABA4B /* RLMGeospatial.mm */, AC7825BB2ACD90DA007ABA4B /* RLMGeospatial_Private.hpp */, AC3B33AB29DC6CEE0042F3A0 /* RLMLogger.h */, AC3B33AC29DC6CEE0042F3A0 /* RLMLogger.mm */, AC3B33AD29DC6CEE0042F3A0 /* RLMLogger_Private.h */, E81A1F691955FC9300FDED82 /* RLMManagedArray.mm */, CF9881C025DABC6500BD7E4F /* RLMManagedDictionary.mm */, 0C7CA7C225C311DA0098A636 /* RLMManagedDictionary.mm */, CFD8D11F25BB0B8B0037FE4D /* RLMManagedSet.mm */, 0207AB7D195DF9FB007EFB12 /* RLMMigration.h */, 0207AB7E195DF9FB007EFB12 /* RLMMigration.mm */, 0207AB7C195DF9FB007EFB12 /* RLMMigration_Private.h */, E81A1F6E1955FC9300FDED82 /* RLMObject.h */, E81A1F6F1955FC9300FDED82 /* RLMObject.mm */, E81A1F6D1955FC9300FDED82 /* RLMObject_Private.h */, C073E1201AE9B705002C0A30 /* RLMObject_Private.hpp */, 023B19571A3BA90D0067FB81 /* RLMObjectBase.h */, 023B19581A3BA90D0067FB81 /* RLMObjectBase.mm */, A05FA61E1B62C3900000C9B2 /* RLMObjectBase_Dynamic.h */, E8F992BD1F1401C100F634B5 /* RLMObjectBase_Private.h */, 3F4F3AD023F71C790048DB43 /* RLMObjectId.h */, 3F4F3AD223F71C790048DB43 /* RLMObjectId.mm */, 3F4F3AD423F71C790048DB43 /* RLMObjectId_Private.hpp */, E81A1F711955FC9300FDED82 /* RLMObjectSchema.h */, E81A1F721955FC9300FDED82 /* RLMObjectSchema.mm */, 29EDB8E91A7712E500458D80 /* RLMObjectSchema_Private.h */, E81A1F701955FC9300FDED82 /* RLMObjectSchema_Private.hpp */, 29EDB8D71A7703C500458D80 /* RLMObjectStore.h */, E81A1F741955FC9300FDED82 /* RLMObjectStore.mm */, 3F0F02AC1B6FFF3D0046A4D5 /* RLMObservation.hpp */, 3F0F02AD1B6FFF3D0046A4D5 /* RLMObservation.mm */, 5D3E1A2C1C1FC6D5002913BA /* RLMPredicateUtil.hpp */, 5D3E1A2D1C1FC6D5002913BA /* RLMPredicateUtil.mm */, 3F68BFCD1B558CA800D50FBD /* RLMPrefix.h */, E81A1F761955FC9300FDED82 /* RLMProperty.h */, E81A1F771955FC9300FDED82 /* RLMProperty.mm */, E81A1F751955FC9300FDED82 /* RLMProperty_Private.h */, 5D2E8F651C98DC0D00187B09 /* RLMProperty_Private.hpp */, E81A1F781955FC9300FDED82 /* RLMQueryUtil.hpp */, E81A1F791955FC9300FDED82 /* RLMQueryUtil.mm */, E81A1F7B1955FC9300FDED82 /* RLMRealm.h */, E81A1F7C1955FC9300FDED82 /* RLMRealm.mm */, E8951F01196C96DE00D6461C /* RLMRealm_Dynamic.h */, 29EDB8E01A77070200458D80 /* RLMRealm_Private.h */, 02E334C41A5F4923009F8810 /* RLMRealm_Private.hpp */, C0D2DD051B6BDEA1004E8919 /* RLMRealmConfiguration.h */, C0D2DD061B6BDEA1004E8919 /* RLMRealmConfiguration.mm */, C0D2DD0F1B6BE0DD004E8919 /* RLMRealmConfiguration_Private.h */, E86900E11CC04F5B0008A8B6 /* RLMRealmConfiguration_Private.hpp */, 027A4D211AB100E000AA46F9 /* RLMRealmUtil.hpp */, 027A4D221AB100E000AA46F9 /* RLMRealmUtil.mm */, 02B8EF5819E601D80045A93D /* RLMResults.h */, E81A1F6A1955FC9300FDED82 /* RLMResults.mm */, 29EDB8E51A7710B700458D80 /* RLMResults_Private.h */, 1A1EBF861F269E8E00F47698 /* RLMResults_Private.hpp */, 3F0BBB9529AFDA6600FEA7A7 /* RLMScheduler.h */, 3F0BBB9629AFDA6600FEA7A7 /* RLMScheduler.mm */, E81A1F7E1955FC9300FDED82 /* RLMSchema.h */, E81A1F7F1955FC9300FDED82 /* RLMSchema.mm */, E81A1F7D1955FC9300FDED82 /* RLMSchema_Private.h */, 3F4E324B1B98C6C700183A69 /* RLMSchema_Private.hpp */, CFDBC4B428803C7200EE80E6 /* RLMSectionedResults.h */, CFDBC4B328803C7200EE80E6 /* RLMSectionedResults.mm */, CFDBC4B228803C7200EE80E6 /* RLMSectionedResults_Private.hpp */, CF986D1D25AE3B090039D287 /* RLMSet.h */, CF986D1B25AE3B080039D287 /* RLMSet.mm */, CF986D1A25AE3B080039D287 /* RLMSet_Private.h */, CF986D1C25AE3B090039D287 /* RLMSet_Private.hpp */, 023B19551A3BA90D0067FB81 /* RLMSwiftCollectionBase.h */, 023B19561A3BA90D0067FB81 /* RLMSwiftCollectionBase.mm */, 3FCC56E329A55607004C5057 /* RLMSwiftObject.h */, 3F83E9A22630A14800FC9623 /* RLMSwiftProperty.h */, 3FE79FF719BA6A5900780C9A /* RLMSwiftSupport.h */, 3F452EC519C2279800AFC154 /* RLMSwiftSupport.m */, CF44460626121B2A00BAFDB4 /* RLMSwiftValueStorage.h */, CF44460526121B2A00BAFDB4 /* RLMSwiftValueStorage.mm */, 3F67DB391E26D69C0024533D /* RLMThreadSafeReference.h */, 3F67DB3B1E26D69C0024533D /* RLMThreadSafeReference.mm */, 3F67DB3A1E26D69C0024533D /* RLMThreadSafeReference_Private.hpp */, E81A1F811955FC9300FDED82 /* RLMUtil.hpp */, E81A1F821955FC9300FDED82 /* RLMUtil.mm */, 0C57969F25643D7500744CAE /* RLMUUID.mm */, 3F1D8D32265B071000593ABA /* RLMUUID_Private.hpp */, 3F1D8D30265B071000593ABA /* RLMValue.h */, 3F1D8D31265B071000593ABA /* RLMValue.mm */, ); path = Realm; sourceTree = ""; }; E8D89B9B1955FC6D00CF2B9A /* Supporting Files */ = { isa = PBXGroup; children = ( 023B19F71A423BD20067FB81 /* libc++.dylib */, 3FD6D1A82B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy */, E81A1F621955FC9300FDED82 /* Realm-Info.plist */, 02E334C21A5F3C45009F8810 /* Realm.modulemap */, ); name = "Supporting Files"; sourceTree = ""; }; E8D89BA71955FC6D00CF2B9A /* Realm Tests */ = { isa = PBXGroup; children = ( E81A20061955FE1600FDED82 /* Objective-C */, E8D89BA81955FC6D00CF2B9A /* Supporting Files */, E81A1FC81955FE0100FDED82 /* Swift */, 53A34E3225CDA0AC00698930 /* SwiftUITestHost */, 53124AD725B71AF700771CE4 /* SwiftUITestHostUITests */, 3F1A5E731992EB7400F45F4C /* TestHost */, ); name = "Realm Tests"; path = Realm/Tests; sourceTree = ""; }; E8D89BA81955FC6D00CF2B9A /* Supporting Files */ = { isa = PBXGroup; children = ( 3FDAB84B290B7A0200168F24 /* file-format-version-10.realm */, 3FDAB847290B658300168F24 /* file-format-version-21.realm */, 29B7FDF71C0DE76B0023224E /* fileformat-pre-null.realm */, E81A1FC21955FE0100FDED82 /* RealmTests-Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 5D659E9F1BE04556006515A0 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 5D659EA51BE04556006515A0 /* Realm.h in Headers */, 5D659EA71BE04556006515A0 /* RLMAccessor.h in Headers */, 5D659EA91BE04556006515A0 /* RLMArray.h in Headers */, 5D659EAA1BE04556006515A0 /* RLMArray_Private.h in Headers */, 3FD0D7C729675A2E0031C196 /* RLMAsyncTask.h in Headers */, 3FDB67152970720E0052233B /* RLMAsyncTask_Private.h in Headers */, 5D659EAB1BE04556006515A0 /* RLMCollection.h in Headers */, 5D1BF1FF1EF987AD00B7DC87 /* RLMCollection_Private.h in Headers */, 5D659EAC1BE04556006515A0 /* RLMConstants.h in Headers */, 3F4F3ADD23F71C790048DB43 /* RLMDecimal128.h in Headers */, 3F1D9068265C077C00593ABA /* RLMDictionary.h in Headers */, 0CED6DB82655087200B80277 /* RLMDictionary_Private.h in Headers */, 3FC3F912241808B400E27322 /* RLMEmbeddedObject.h in Headers */, 3FDAB841290B4CCB00168F24 /* RLMError.h in Headers */, AC7825BF2ACD90DA007ABA4B /* RLMGeospatial.h in Headers */, AC3B33AE29DC6CEE0042F3A0 /* RLMLogger.h in Headers */, AC3B33B029DC6CEE0042F3A0 /* RLMLogger_Private.h in Headers */, 5D659EAF1BE04556006515A0 /* RLMMigration.h in Headers */, 5D659EB01BE04556006515A0 /* RLMMigration_Private.h in Headers */, 5D659EB11BE04556006515A0 /* RLMObject.h in Headers */, 5D659EB21BE04556006515A0 /* RLMObject_Private.h in Headers */, 5D659EB31BE04556006515A0 /* RLMObjectBase.h in Headers */, 5D659EB41BE04556006515A0 /* RLMObjectBase_Dynamic.h in Headers */, E8F992BE1F1401C100F634B5 /* RLMObjectBase_Private.h in Headers */, 3F4F3AD723F71C790048DB43 /* RLMObjectId.h in Headers */, 5D659EB51BE04556006515A0 /* RLMObjectSchema.h in Headers */, 5D659EB61BE04556006515A0 /* RLMObjectSchema_Private.h in Headers */, 5D659EB81BE04556006515A0 /* RLMObjectStore.h in Headers */, 5D659EBC1BE04556006515A0 /* RLMProperty.h in Headers */, 5D659EBD1BE04556006515A0 /* RLMProperty_Private.h in Headers */, 5D659EBF1BE04556006515A0 /* RLMRealm.h in Headers */, 5D659EC01BE04556006515A0 /* RLMRealm_Dynamic.h in Headers */, 5D659EC11BE04556006515A0 /* RLMRealm_Private.h in Headers */, 5D659EC21BE04556006515A0 /* RLMRealmConfiguration.h in Headers */, 5D659EC31BE04556006515A0 /* RLMRealmConfiguration_Private.h in Headers */, 5D659EC51BE04556006515A0 /* RLMResults.h in Headers */, 5D659EC61BE04556006515A0 /* RLMResults_Private.h in Headers */, 3F0BBB9729AFDA6600FEA7A7 /* RLMScheduler.h in Headers */, 5D659EC71BE04556006515A0 /* RLMSchema.h in Headers */, 5D659EC81BE04556006515A0 /* RLMSchema_Private.h in Headers */, CFDBC4B728803C7200EE80E6 /* RLMSectionedResults.h in Headers */, CF986D2425AE3B090039D287 /* RLMSet.h in Headers */, CF986D1E25AE3B090039D287 /* RLMSet_Private.h in Headers */, 5D659EAE1BE04556006515A0 /* RLMSwiftCollectionBase.h in Headers */, 3FCC56E429A55607004C5057 /* RLMSwiftObject.h in Headers */, 3F83E9A42630A14800FC9623 /* RLMSwiftProperty.h in Headers */, 5D659EC91BE04556006515A0 /* RLMSwiftSupport.h in Headers */, 3F1D9092265C07A600593ABA /* RLMSwiftValueStorage.h in Headers */, 3F67DB3C1E26D69C0024533D /* RLMThreadSafeReference.h in Headers */, 3F1D8D33265B071000593ABA /* RLMValue.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FC91BE98C560021E04F /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 3F1A5E711992EB7400F45F4C /* TestHost */ = { isa = PBXNativeTarget; buildConfigurationList = 3F1A5E931992EB7400F45F4C /* Build configuration list for PBXNativeTarget "TestHost" */; buildPhases = ( 3F1A5E6E1992EB7400F45F4C /* Sources */, 3F1A5E6F1992EB7400F45F4C /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = TestHost; productName = TestHost; productReference = 3F1A5E721992EB7400F45F4C /* TestHost.app */; productType = "com.apple.product-type.application"; }; 53124AB725B71AF600771CE4 /* SwiftUITestHost */ = { isa = PBXNativeTarget; buildConfigurationList = 53124ADB25B71AF700771CE4 /* Build configuration list for PBXNativeTarget "SwiftUITestHost" */; buildPhases = ( 53124AB425B71AF600771CE4 /* Sources */, 53124AB525B71AF600771CE4 /* Frameworks */, 53124AB625B71AF600771CE4 /* Resources */, ); buildRules = ( ); dependencies = ( 53BBF08D25B7436F00D225AD /* PBXTargetDependency */, ); name = SwiftUITestHost; productName = SwiftUITestHost; productReference = 53124AB825B71AF600771CE4 /* SwiftUITestHost.app */; productType = "com.apple.product-type.application"; }; 53124AD325B71AF700771CE4 /* SwiftUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 53124AE125B71AF700771CE4 /* Build configuration list for PBXNativeTarget "SwiftUITests" */; buildPhases = ( 53124AD025B71AF700771CE4 /* Sources */, 53124AD125B71AF700771CE4 /* Frameworks */, 53124AD225B71AF700771CE4 /* Resources */, ); buildRules = ( ); dependencies = ( 3FF5165226E96D2B00618280 /* PBXTargetDependency */, 534DF4D025B86F3A00655AE2 /* PBXTargetDependency */, ); name = SwiftUITests; productName = SwiftUITestHostUITests; productReference = 53124AD425B71AF700771CE4 /* SwiftUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 5D659E7D1BE04556006515A0 /* Realm */ = { isa = PBXNativeTarget; buildConfigurationList = 5D659ED61BE04556006515A0 /* Build configuration list for PBXNativeTarget "Realm" */; buildPhases = ( 5D659E7E1BE04556006515A0 /* Get Version Number */, 5D659E801BE04556006515A0 /* Sources */, 5D659E9F1BE04556006515A0 /* Headers */, 1A7B82351D51235600750296 /* Frameworks */, 5D659ECE1BE04556006515A0 /* Resources */, 3F1DDB5825155E33007A9630 /* Carthage post-build workaround */, ); buildRules = ( ); dependencies = ( ); name = Realm; productName = Realm; productReference = 5D659ED91BE04556006515A0 /* Realm.framework */; productType = "com.apple.product-type.framework"; }; 5D660FCB1BE98C560021E04F /* RealmSwift */ = { isa = PBXNativeTarget; buildConfigurationList = 5D660FD11BE98C560021E04F /* Build configuration list for PBXNativeTarget "RealmSwift" */; buildPhases = ( 5D660FC71BE98C560021E04F /* Sources */, 5D660FC81BE98C560021E04F /* Frameworks */, 5D660FC91BE98C560021E04F /* Headers */, 5D660FCA1BE98C560021E04F /* Resources */, ); buildRules = ( ); dependencies = ( 5D66102C1BE98DF60021E04F /* PBXTargetDependency */, ); name = RealmSwift; productName = RealmSwift; productReference = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; productType = "com.apple.product-type.framework"; }; 5D660FD71BE98C7C0021E04F /* RealmSwift Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 5D660FE01BE98C7C0021E04F /* Build configuration list for PBXNativeTarget "RealmSwift Tests" */; buildPhases = ( 5D660FD41BE98C7C0021E04F /* Sources */, 5D660FD51BE98C7C0021E04F /* Frameworks */, 5D66102D1BE98E360021E04F /* Embed Frameworks */, 2973CCF81C175AAA00FEA0FA /* Resources */, ); buildRules = ( ); dependencies = ( 3FC8BF35212B79F4001C2025 /* PBXTargetDependency */, 5D660FDF1BE98C7C0021E04F /* PBXTargetDependency */, ); name = "RealmSwift Tests"; productName = "RealmSwift Tests"; productReference = 5D660FD81BE98C7C0021E04F /* RealmSwift Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; E8D89BA21955FC6D00CF2B9A /* Tests */ = { isa = PBXNativeTarget; buildConfigurationList = E8D89BB11955FC6D00CF2B9A /* Build configuration list for PBXNativeTarget "Tests" */; buildPhases = ( 5D304A2E1BE9AEFB0082C1A6 /* Get Version Number */, E8D89B9F1955FC6D00CF2B9A /* Sources */, E8D89BA01955FC6D00CF2B9A /* Frameworks */, 5D128F291BE984D4001F4FBF /* Embed Frameworks */, 29B7FDFA1C0DE80A0023224E /* Resources */, ); buildRules = ( ); dependencies = ( 5DD755D21BE05828002800DA /* PBXTargetDependency */, 5D6157021BE0A3A100A4BD3F /* PBXTargetDependency */, ); name = Tests; productName = RealmTests; productReference = E8D89BA31955FC6D00CF2B9A /* Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E8D89B8F1955FC6D00CF2B9A /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = RLM; LastSwiftUpdateCheck = 1230; LastTestingUpgradeCheck = 0510; LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { 3F1A5E711992EB7400F45F4C = { CreatedOnToolsVersion = 6.0; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; 53124AB725B71AF600771CE4 = { CreatedOnToolsVersion = 12.3; LastSwiftMigration = 1240; }; 53124AD325B71AF700771CE4 = { CreatedOnToolsVersion = 12.3; TestTargetID = 53124AB725B71AF600771CE4; }; 5D659E7D1BE04556006515A0 = { LastSwiftMigration = 1320; ProvisioningStyle = Automatic; }; 5D660FCB1BE98C560021E04F = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; 5D660FD71BE98C7C0021E04F = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; ACB76B772AA9D9A600C59983 = { CreatedOnToolsVersion = 15.0; }; E83EAC791BED3D880085CCD2 = { CreatedOnToolsVersion = 7.1; }; E8D89BA21955FC6D00CF2B9A = { CreatedOnToolsVersion = 6.0; LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = E8D89B921955FC6D00CF2B9A /* Build configuration list for PBXProject "Realm" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = E8D89B8E1955FC6D00CF2B9A; productRefGroup = E8D89B991955FC6D00CF2B9A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 5D659E7D1BE04556006515A0 /* Realm */, 5D660FCB1BE98C560021E04F /* RealmSwift */, E8D89BA21955FC6D00CF2B9A /* Tests */, 5D660FD71BE98C7C0021E04F /* RealmSwift Tests */, E83EAC791BED3D880085CCD2 /* SwiftLint */, 3F1A5E711992EB7400F45F4C /* TestHost */, 53124AB725B71AF600771CE4 /* SwiftUITestHost */, 53124AD325B71AF700771CE4 /* SwiftUITests */, ACB76B772AA9D9A600C59983 /* CI */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2973CCF81C175AAA00FEA0FA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FDAB84D290B7A0200168F24 /* file-format-version-10.realm in Resources */, 3FDAB849290B659300168F24 /* file-format-version-21.realm in Resources */, 2973CCF91C175AB400FEA0FA /* fileformat-pre-null.realm in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 29B7FDFA1C0DE80A0023224E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FDAB84C290B7A0200168F24 /* file-format-version-10.realm in Resources */, 3FDAB848290B658300168F24 /* file-format-version-21.realm in Resources */, 29B7FDFB1C0DE8100023224E /* fileformat-pre-null.realm in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53124AB625B71AF600771CE4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 53A34E3625CDA0AC00698930 /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53124AD225B71AF700771CE4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 5D659ECE1BE04556006515A0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 5D659ED21BE04556006515A0 /* CHANGELOG.md in Resources */, 5D659ED51BE04556006515A0 /* LICENSE in Resources */, 3FD6D1A92B4C9EFB00A4FEBE /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FCA1BE98C560021E04F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FD6D1AC2B4CA56D00A4FEBE /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3F1DDB5825155E33007A9630 /* Carthage post-build workaround */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Carthage post-build workaround"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Something that Carthage does results in Xcode skipping the ProcessXCFramework build step in some cases, which results in compilation failing. To work around this, ensure that the modified date on the XCFramework files are always newer than the built files, so that it happens on every build. This breaks incremental compilation, so we only want to do it when building for Carthage.\nif [ -n \"$CARTHAGE\" ]; then\n find \"${SRCROOT}/core\" -type f -exec touch {} \\;\nfi\n"; showEnvVarsInLog = 0; }; 5D304A2E1BE9AEFB0082C1A6 /* Get Version Number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "$(SRCROOT)/Realm/Realm-Info.plist", ); name = "Get Version Number"; outputPaths = ( "$(DERIVED_FILE_DIR)/RLMVersion.h", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "exec \"${SRCROOT}/scripts/generate-rlmversion.sh\"\n"; }; 5D659E7E1BE04556006515A0 /* Get Version Number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "$(SRCROOT)/Realm/Realm-Info.plist", ); name = "Get Version Number"; outputPaths = ( "$(DERIVED_FILE_DIR)/RLMVersion.h", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "exec \"${SRCROOT}/scripts/generate-rlmversion.sh\"\n"; showEnvVarsInLog = 0; }; E83EAC7D1BED3D8F0085CCD2 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run SwiftLint"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3F1A5E6E1992EB7400F45F4C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 29EDB8E41A7708E700458D80 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53124AB425B71AF600771CE4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 53626AAF25D31CAC00D9515D /* Objects.swift in Sources */, 53A34E3725CDA0AC00698930 /* SwiftUITestHostApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53124AD025B71AF700771CE4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 53626AB025D31CAC00D9515D /* Objects.swift in Sources */, 53124AD925B71AF700771CE4 /* SwiftUITestHostUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D659E801BE04556006515A0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 5D659E851BE04556006515A0 /* RLMAccessor.mm in Sources */, 5D659E871BE04556006515A0 /* RLMArray.mm in Sources */, 3FD0D7C929675A2E0031C196 /* RLMAsyncTask.mm in Sources */, 3F9863BB1D36876B00641C98 /* RLMClassInfo.mm in Sources */, 3FBEF67B1C63D66100F6935B /* RLMCollection.mm in Sources */, 5D659E891BE04556006515A0 /* RLMConstants.m in Sources */, 3F4F3AD523F71C790048DB43 /* RLMDecimal128.mm in Sources */, 0C3BD4B325C1BDF1007CFDD3 /* RLMDictionary.mm in Sources */, 3FC3F914241808B400E27322 /* RLMEmbeddedObject.mm in Sources */, 3FDAB845290B4CCB00168F24 /* RLMError.mm in Sources */, AC7825BD2ACD90DA007ABA4B /* RLMGeospatial.mm in Sources */, AC3B33AF29DC6CEE0042F3A0 /* RLMLogger.mm in Sources */, 5D659E881BE04556006515A0 /* RLMManagedArray.mm in Sources */, CF9881C125DABC6500BD7E4F /* RLMManagedDictionary.mm in Sources */, CFD8D12025BB0B8B0037FE4D /* RLMManagedSet.mm in Sources */, 5D659E8B1BE04556006515A0 /* RLMMigration.mm in Sources */, 5D659E8C1BE04556006515A0 /* RLMObject.mm in Sources */, 5D659E8D1BE04556006515A0 /* RLMObjectBase.mm in Sources */, 3F4F3ADB23F71C790048DB43 /* RLMObjectId.mm in Sources */, 5D659E8E1BE04556006515A0 /* RLMObjectSchema.mm in Sources */, 5D659E8F1BE04556006515A0 /* RLMObjectStore.mm in Sources */, 5D659E901BE04556006515A0 /* RLMObservation.mm in Sources */, 5D3E1A2F1C1FC6D5002913BA /* RLMPredicateUtil.mm in Sources */, 5D659E921BE04556006515A0 /* RLMProperty.mm in Sources */, 5D659E931BE04556006515A0 /* RLMQueryUtil.mm in Sources */, 5D659E941BE04556006515A0 /* RLMRealm.mm in Sources */, 5D659E951BE04556006515A0 /* RLMRealmConfiguration.mm in Sources */, 5D659E961BE04556006515A0 /* RLMRealmUtil.mm in Sources */, 5D659E971BE04556006515A0 /* RLMResults.mm in Sources */, 3F0BBB9929AFDA6600FEA7A7 /* RLMScheduler.mm in Sources */, 5D659E981BE04556006515A0 /* RLMSchema.mm in Sources */, CFDBC4B628803C7200EE80E6 /* RLMSectionedResults.mm in Sources */, CF986D2025AE3B090039D287 /* RLMSet.mm in Sources */, 5D659E8A1BE04556006515A0 /* RLMSwiftCollectionBase.mm in Sources */, 5D659E991BE04556006515A0 /* RLMSwiftSupport.m in Sources */, 3F1D90BC265C08F800593ABA /* RLMSwiftValueStorage.mm in Sources */, 3F67DB3E1E26D69C0024533D /* RLMThreadSafeReference.mm in Sources */, 5D659E9B1BE04556006515A0 /* RLMUtil.mm in Sources */, 0C5796A225643D7500744CAE /* RLMUUID.mm in Sources */, 3F1D8D35265B071000593ABA /* RLMValue.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FC71BE98C560021E04F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 5D660FF11BE98D670021E04F /* Aliases.swift in Sources */, 681EE33B25EE8E1400A9DEC5 /* AnyRealmValue.swift in Sources */, 3FE267D7264308680030F83C /* BasicTypes.swift in Sources */, 3FE267D5264308680030F83C /* CollectionAccess.swift in Sources */, 3F102CBD23DBC68300108FD2 /* Combine.swift in Sources */, 3FE267D6264308680030F83C /* ComplexTypes.swift in Sources */, 3F9F53D32718B5DA000EEB4A /* CustomPersistable.swift in Sources */, 3FB6ABD92416A27000E318C2 /* Decimal128.swift in Sources */, 3FC3F9172419B63200E27322 /* EmbeddedObject.swift in Sources */, 29B7FDF61C0DA6560023224E /* Error.swift in Sources */, AC7825B92ACD90BE007ABA4B /* Geospatial.swift in Sources */, 3F857A482769291800F9B9B1 /* KeyPathStrings.swift in Sources */, 5D1534B81CCFF545008976D7 /* LinkingObjects.swift in Sources */, 5D660FF21BE98D670021E04F /* List.swift in Sources */, 0C3BD4D325C1C5AB007CFDD3 /* Map.swift in Sources */, 5D660FF31BE98D670021E04F /* Migration.swift in Sources */, CFE9CE31265554FB00BF96D6 /* MutableSet.swift in Sources */, 3F3411A6273433B300EC9D25 /* ObjcBridgeable.swift in Sources */, 5D660FF41BE98D670021E04F /* Object.swift in Sources */, 3FB6ABD72416A26100E318C2 /* ObjectId.swift in Sources */, 681EE34725EE8E5600A9DEC5 /* ObjectiveCSupport+AnyRealmValue.swift in Sources */, 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */, 5D660FF51BE98D670021E04F /* ObjectSchema.swift in Sources */, 5D660FF61BE98D670021E04F /* Optional.swift in Sources */, 3FE267D8264308680030F83C /* Persistable.swift in Sources */, 3F149CCB2668112A00111D65 /* PersistedProperty.swift in Sources */, 3F857A49276A507200F9B9B1 /* Projection.swift in Sources */, 5D660FF71BE98D670021E04F /* Property.swift in Sources */, 3FE267D9264308680030F83C /* PropertyAccessors.swift in Sources */, ACF08B6726DD936200686CBC /* Query.swift in Sources */, 5D660FF81BE98D670021E04F /* Realm.swift in Sources */, 1AB605D31D495927007F53DE /* RealmCollection.swift in Sources */, 3F997773273E23D300AA9E13 /* RealmCollectionImpl.swift in Sources */, 5D660FFA1BE98D670021E04F /* RealmConfiguration.swift in Sources */, CFE9CE3326555BBD00BF96D6 /* RealmKeyedCollection.swift in Sources */, CF44461E26121C6800BAFDB4 /* RealmProperty.swift in Sources */, 5D660FFB1BE98D670021E04F /* Results.swift in Sources */, 68A7B91D2543538B00C703BC /* RLMSupport.swift in Sources */, 5D660FFC1BE98D670021E04F /* Schema.swift in Sources */, 3FE267DA264308680030F83C /* SchemaDiscovery.swift in Sources */, CF84D4F0281823B300005E27 /* SectionedResults.swift in Sources */, 5D660FFD1BE98D670021E04F /* SortDescriptor.swift in Sources */, 535EA9E225B0919800DBF3CD /* SwiftUI.swift in Sources */, 3F222C4E1E26F51300CA0713 /* ThreadSafeReference.swift in Sources */, 5D660FFE1BE98D670021E04F /* Util.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5D660FD41BE98C7C0021E04F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CFFC8CC8262310C800929608 /* AnyRealmValueTests.swift in Sources */, 3FCB1A7522A9B0A2003807FB /* CodableTests.swift in Sources */, 3FE2BE0323D8CAD1002860E9 /* CombineTests.swift in Sources */, E8AE7C261EA436F800CDFF9A /* CompactionTests.swift in Sources */, ACFF0EC728EC5ADB0097AEE0 /* CustomColumnNameTests.swift in Sources */, 3F40C613276D1B05007FEF1A /* CustomObjectCreationTests.swift in Sources */, 3F9F53D52718E8E6000EEB4A /* CustomPersistableTestObjects.swift in Sources */, CFFECBAA250646820010F585 /* Decimal128Tests.swift in Sources */, AC7825C22ACD917B007ABA4B /* GeospatialTests.swift in Sources */, CF0D04F1269365300038A058 /* KeyPathTests.swift in Sources */, 3FF3FFAF1F0D6D6400B84599 /* KVOTests.swift in Sources */, 5D6610161BE98D880021E04F /* ListTests.swift in Sources */, 3F1D8D77265B075100593ABA /* MapTests.swift in Sources */, 3F8824FD1E5E335000586B35 /* MigrationTests.swift in Sources */, AC5300722BD03D4A00BF5950 /* MixedCollectionTest.swift in Sources */, 3F4E0FF92654765C008B8C0B /* ModernKVOTests.swift in Sources */, 3F4E10102655CA33008B8C0B /* ModernObjectAccessorTests.swift in Sources */, 3FA5E94D266064C4008F1345 /* ModernObjectCreationTests.swift in Sources */, 3FB19069265ECF0C00DA7C76 /* ModernObjectTests.swift in Sources */, 3FB1906B265ED23300DA7C76 /* ModernTestObjects.swift in Sources */, CF986DE225AE3EC70039D287 /* MutableSetTests.swift in Sources */, 3F8824FE1E5E335000586B35 /* ObjectAccessorTests.swift in Sources */, 3F8824FF1E5E335000586B35 /* ObjectCreationTests.swift in Sources */, 3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */, CFFECBB525078EC40010F585 /* ObjectIdTests.swift in Sources */, 3F8825001E5E335000586B35 /* ObjectiveCSupportTests.swift in Sources */, 3F8825011E5E335000586B35 /* ObjectSchemaInitializationTests.swift in Sources */, 3F8825021E5E335000586B35 /* ObjectSchemaTests.swift in Sources */, 3F8825031E5E335000586B35 /* ObjectTests.swift in Sources */, 3F8825041E5E335000586B35 /* PerformanceTests.swift in Sources */, 3F2633C31E9D630000B32D30 /* PrimitiveListTests.swift in Sources */, CF040494263DF0AA00F9AEE0 /* PrimitiveMapTests.swift in Sources */, CF986DF625AE3EDF0039D287 /* PrimitiveMutableSetTests.swift in Sources */, 0CBF2DB927286FFD00635902 /* ProjectedCollectTests.swift in Sources */, 0CD1632826D3DF1D0027C49B /* ProjectionTests.swift in Sources */, 3F8825051E5E335000586B35 /* PropertyTests.swift in Sources */, CF46CC0226D931BA00DE450C /* QueryTests.swift in Sources */, 3F8825061E5E335000586B35 /* RealmCollectionTypeTests.swift in Sources */, 3F8825071E5E335000586B35 /* RealmConfigurationTests.swift in Sources */, 3F4E10112655CA33008B8C0B /* RealmPropertyTests.swift in Sources */, 3F8825081E5E335000586B35 /* RealmTests.swift in Sources */, 530BA61526DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */, 3F558C9622C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */, 3F558C9222C29A03002F0F30 /* RLMTestCase.m in Sources */, 3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */, 3F8825091E5E335000586B35 /* SchemaTests.swift in Sources */, CFDBC4C0288040E700EE80E6 /* SectionedResultsTests.swift in Sources */, 3F88250A1E5E335000586B35 /* SortDescriptorTests.swift in Sources */, 3F88250B1E5E335000586B35 /* SwiftLinkTests.swift in Sources */, 5D6610251BE98D880021E04F /* SwiftTestObjects.swift in Sources */, 535EAA7525B0B02B00DBF3CD /* SwiftUITests.swift in Sources */, 3F88250C1E5E335000586B35 /* SwiftUnicodeTests.swift in Sources */, 5D6610271BE98D880021E04F /* TestCase.swift in Sources */, 3F558C8A22C29A03002F0F30 /* TestUtils.mm in Sources */, 3FAF2D4129577100002EAC93 /* TestUtils.swift in Sources */, 3F90C1B22716169C0029000E /* TestValueFactory.swift in Sources */, 3F88250D1E5E335000586B35 /* ThreadSafeReferenceTests.swift in Sources */, 5DBEC9B11F719A9D001233EC /* Util.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8D89B9F1955FC6D00CF2B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E81A1FD51955FE0100FDED82 /* ArrayPropertyTests.m in Sources */, 3F7556751BE95A0C0058BC7E /* AsyncTests.mm in Sources */, E8DA16F81E81210D0055141C /* CompactionTests.m in Sources */, CFFECBAD250667B20010F585 /* Decimal128Tests.m in Sources */, CF052EFB25DEB671008EEF86 /* DictionaryPropertyTests.m in Sources */, 02E334C31A5F41C7009F8810 /* DynamicTests.m in Sources */, 021A88321AAFB5C800EEAC84 /* EncryptionTests.mm in Sources */, E81A1FDB1955FE0100FDED82 /* EnumeratorTests.m in Sources */, 027A4D2C1AB1012500AA46F9 /* InterprocessTests.m in Sources */, 3F1F47821B9612B300CD99A3 /* KVOTests.mm in Sources */, 5D432B8D1CC0713F00A610A9 /* LinkingObjectsTests.mm in Sources */, E81A1FDD1955FE0100FDED82 /* LinkTests.m in Sources */, 0207AB87195DFA15007EFB12 /* MigrationTests.mm in Sources */, 3F2E66641CA0BA11004761D5 /* NotificationTests.m in Sources */, 3FEB383F1E70AC8800F22712 /* ObjectCreationTests.mm in Sources */, CFFECBB0250690EA0010F585 /* ObjectIdTests.m in Sources */, E81A1FE11955FE0100FDED82 /* ObjectInterfaceTests.m in Sources */, 021A88361AAFB5CD00EEAC84 /* ObjectSchemaTests.m in Sources */, E81A1FE31955FE0100FDED82 /* ObjectTests.m in Sources */, 5D03FB1F1E0DAFBA007D53EA /* PredicateUtilTests.mm in Sources */, 3F572C971F2BDAB100F6C9AB /* PrimitiveArrayPropertyTests.m in Sources */, 0C86B33925E15B6000775FED /* PrimitiveDictionaryPropertyTests.m in Sources */, 3F1D8DCB265B077800593ABA /* PrimitiveRLMValuePropertyTests.m in Sources */, CF986D3125AE3BD40039D287 /* PrimitiveSetPropertyTests.m in Sources */, 02AFB4631A80343600E11938 /* PropertyTests.m in Sources */, E81A1FE71955FE0100FDED82 /* QueryTests.m in Sources */, C042A48D1B7522A900771ED2 /* RealmConfigurationTests.mm in Sources */, E81A1FEB1955FE0100FDED82 /* RealmTests.mm in Sources */, 02AFB4671A80343600E11938 /* ResultsTests.m in Sources */, 530BA61426DFA1CB008FC550 /* RLMChildProcessEnvironment.m in Sources */, 3F558C9322C29A03002F0F30 /* RLMMultiProcessTestCase.m in Sources */, 3FDCFEB619F6A8D3005E414A /* RLMSupport.swift in Sources */, 3F558C8F22C29A03002F0F30 /* RLMTestCase.m in Sources */, 297FBEFB1C19F696009D1118 /* RLMTestCaseUtils.swift in Sources */, 3F558C8B22C29A03002F0F30 /* RLMTestObjects.m in Sources */, 3F1D8DFF265B078200593ABA /* RLMValueTests.m in Sources */, 0207AB89195DFA15007EFB12 /* SchemaTests.mm in Sources */, CF25080E283B90F8007D66FE /* SectionedResultsTests.m in Sources */, CF986D3325AE3BD40039D287 /* SetPropertyTests.m in Sources */, 3FB4FA1819F5D2740020D53B /* SwiftArrayPropertyTests.swift in Sources */, 3FB4FA1919F5D2740020D53B /* SwiftArrayTests.swift in Sources */, 3FB4FA1A19F5D2740020D53B /* SwiftDynamicTests.swift in Sources */, 3FB4FA1B19F5D2740020D53B /* SwiftLinkTests.swift in Sources */, 3FB4FA1D19F5D2740020D53B /* SwiftObjectInterfaceTests.swift in Sources */, 3FB4FA1E19F5D2740020D53B /* SwiftPropertyTypeTest.swift in Sources */, 3FB4FA1F19F5D2740020D53B /* SwiftRealmTests.swift in Sources */, 0C9758BF264974660097B48D /* SwiftRLMDictionaryTests.swift in Sources */, 3FEC4A3F1BBB18D400F009C3 /* SwiftSchemaTests.swift in Sources */, CF986D7025AE3C550039D287 /* SwiftSetPropertyTests.swift in Sources */, CF986D8425AE3C5A0039D287 /* SwiftSetTests.swift in Sources */, 3FB4FA1719F5D2740020D53B /* SwiftTestObjects.swift in Sources */, 3FB4FA2019F5D2740020D53B /* SwiftUnicodeTests.swift in Sources */, 3F558C8722C29A03002F0F30 /* TestUtils.mm in Sources */, 3F572C941F2BDAAB00F6C9AB /* ThreadSafeReferenceTests.m in Sources */, E81A20021955FE0100FDED82 /* TransactionTests.m in Sources */, E8917598197A1B350068ACC6 /* UnicodeTests.m in Sources */, C0CDC0821B38DABA00C5716D /* UtilTests.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 3FC8BF35212B79F4001C2025 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3F1A5E711992EB7400F45F4C /* TestHost */; targetProxy = 3FC8BF34212B79F4001C2025 /* PBXContainerItemProxy */; }; 3FF5165226E96D2B00618280 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D660FCB1BE98C560021E04F /* RealmSwift */; targetProxy = 3FF5165126E96D2B00618280 /* PBXContainerItemProxy */; }; 534DF4D025B86F3A00655AE2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 53124AB725B71AF600771CE4 /* SwiftUITestHost */; targetProxy = 534DF4CF25B86F3A00655AE2 /* PBXContainerItemProxy */; }; 53BBF08D25B7436F00D225AD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D660FCB1BE98C560021E04F /* RealmSwift */; targetProxy = 53BBF08C25B7436F00D225AD /* PBXContainerItemProxy */; }; 5D6157021BE0A3A100A4BD3F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3F1A5E711992EB7400F45F4C /* TestHost */; targetProxy = 5D6157011BE0A3A100A4BD3F /* PBXContainerItemProxy */; }; 5D660FDF1BE98C7C0021E04F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D660FCB1BE98C560021E04F /* RealmSwift */; targetProxy = 5D660FDE1BE98C7C0021E04F /* PBXContainerItemProxy */; }; 5D66102C1BE98DF60021E04F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D659E7D1BE04556006515A0 /* Realm */; targetProxy = 5D66102B1BE98DF60021E04F /* PBXContainerItemProxy */; }; 5DD755D21BE05828002800DA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5D659E7D1BE04556006515A0 /* Realm */; targetProxy = 5DD755D11BE05828002800DA /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 3F1A5E8F1992EB7400F45F4C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D6156F71BE07B6B00A4BD3F /* TestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Debug; }; 3F1A5E901992EB7400F45F4C /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D6156F71BE07B6B00A4BD3F /* TestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Release; }; 3FEC91472A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3FEC91542A4B59A40044BFF5 /* Static.xcconfig */; buildSettings = { }; name = Static; }; 3FEC91482A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D659E761BE03E0D006515A0 /* Realm.xcconfig */; buildSettings = { }; name = Static; }; 3FEC91492A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */; buildSettings = { }; name = Static; }; 3FEC914A2A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5DD755E01BE05C19002800DA /* Tests.xcconfig */; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Static; }; 3FEC914B2A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FC01BE98BEF0021E04F /* Tests.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited) -D BUILDING_REALM_SWIFT_TESTS"; }; name = Static; }; 3FEC914D2A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Static; }; 3FEC914E2A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D6156F71BE07B6B00A4BD3F /* TestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Static; }; 3FEC91502A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Static; }; 3FEC91512A4B58BD0044BFF5 /* Static */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "\"-\""; CODE_SIGN_STYLE = Automatic; TEST_TARGET_NAME = SwiftUITestHost; }; name = Static; }; 53124ADC25B71AF700771CE4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Debug; }; 53124ADD25B71AF700771CE4 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53124A4F25B714EC00771CE4 /* SwiftUITestHost.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; SUPPORTS_MACCATALYST = YES; }; name = Release; }; 53124AE225B71AF700771CE4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "\"-\""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PROVISIONING_PROFILE_SPECIFIER = ""; TEST_TARGET_NAME = SwiftUITestHost; }; name = Debug; }; 53124AE325B71AF700771CE4 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53626A8C25D3172000D9515D /* SwiftUITests.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "\"-\""; CODE_SIGN_STYLE = Automatic; TEST_TARGET_NAME = SwiftUITestHost; }; name = Release; }; 5D659ED71BE04556006515A0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D659E761BE03E0D006515A0 /* Realm.xcconfig */; buildSettings = { }; name = Debug; }; 5D659ED81BE04556006515A0 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D659E761BE03E0D006515A0 /* Realm.xcconfig */; buildSettings = { }; name = Release; }; 5D660FD21BE98C560021E04F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */; buildSettings = { }; name = Debug; }; 5D660FD31BE98C560021E04F /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */; buildSettings = { }; name = Release; }; 5D660FE11BE98C7C0021E04F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FC01BE98BEF0021E04F /* Tests.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited) -D BUILDING_REALM_SWIFT_TESTS -D DEBUG"; }; name = Debug; }; 5D660FE21BE98C7C0021E04F /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D660FC01BE98BEF0021E04F /* Tests.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited) -D BUILDING_REALM_SWIFT_TESTS"; }; name = Release; }; ACB76B782AA9D9A600C59983 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; ACB76B792AA9D9A600C59983 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; ACB76B7A2AA9D9A600C59983 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Static; }; E83EAC7A1BED3D880085CCD2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E83EAC7B1BED3D880085CCD2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8D89BAC1955FC6D00CF2B9A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D659E6E1BE0398E006515A0 /* Debug.xcconfig */; buildSettings = { }; name = Debug; }; E8D89BAD1955FC6D00CF2B9A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D659E6F1BE0398E006515A0 /* Release.xcconfig */; buildSettings = { }; name = Release; }; E8D89BB21955FC6D00CF2B9A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5DD755E01BE05C19002800DA /* Tests.xcconfig */; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8D89BB31955FC6D00CF2B9A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5DD755E01BE05C19002800DA /* Tests.xcconfig */; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3F1A5E931992EB7400F45F4C /* Build configuration list for PBXNativeTarget "TestHost" */ = { isa = XCConfigurationList; buildConfigurations = ( 3F1A5E8F1992EB7400F45F4C /* Debug */, 3F1A5E901992EB7400F45F4C /* Release */, 3FEC914E2A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 53124ADB25B71AF700771CE4 /* Build configuration list for PBXNativeTarget "SwiftUITestHost" */ = { isa = XCConfigurationList; buildConfigurations = ( 53124ADC25B71AF700771CE4 /* Debug */, 53124ADD25B71AF700771CE4 /* Release */, 3FEC91502A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 53124AE125B71AF700771CE4 /* Build configuration list for PBXNativeTarget "SwiftUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 53124AE225B71AF700771CE4 /* Debug */, 53124AE325B71AF700771CE4 /* Release */, 3FEC91512A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 5D659ED61BE04556006515A0 /* Build configuration list for PBXNativeTarget "Realm" */ = { isa = XCConfigurationList; buildConfigurations = ( 5D659ED71BE04556006515A0 /* Debug */, 5D659ED81BE04556006515A0 /* Release */, 3FEC91482A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 5D660FD11BE98C560021E04F /* Build configuration list for PBXNativeTarget "RealmSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 5D660FD21BE98C560021E04F /* Debug */, 5D660FD31BE98C560021E04F /* Release */, 3FEC91492A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 5D660FE01BE98C7C0021E04F /* Build configuration list for PBXNativeTarget "RealmSwift Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 5D660FE11BE98C7C0021E04F /* Debug */, 5D660FE21BE98C7C0021E04F /* Release */, 3FEC914B2A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; ACB76B7B2AA9D9A600C59983 /* Build configuration list for PBXAggregateTarget "CI" */ = { isa = XCConfigurationList; buildConfigurations = ( ACB76B782AA9D9A600C59983 /* Debug */, ACB76B792AA9D9A600C59983 /* Release */, ACB76B7A2AA9D9A600C59983 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E83EAC7C1BED3D880085CCD2 /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { isa = XCConfigurationList; buildConfigurations = ( E83EAC7A1BED3D880085CCD2 /* Debug */, E83EAC7B1BED3D880085CCD2 /* Release */, 3FEC914D2A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8D89B921955FC6D00CF2B9A /* Build configuration list for PBXProject "Realm" */ = { isa = XCConfigurationList; buildConfigurations = ( E8D89BAC1955FC6D00CF2B9A /* Debug */, E8D89BAD1955FC6D00CF2B9A /* Release */, 3FEC91472A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8D89BB11955FC6D00CF2B9A /* Build configuration list for PBXNativeTarget "Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( E8D89BB21955FC6D00CF2B9A /* Debug */, E8D89BB31955FC6D00CF2B9A /* Release */, 3FEC914A2A4B58BD0044BFF5 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = E8D89B8F1955FC6D00CF2B9A /* Project object */; } ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/5D660FD71BE98C7C0021E04F.xcbaseline/6890B8D9-CE81-403E-8EF6-B95A367D65B5.plist ================================================ classNames SwiftPerformanceTests testCommitWriteTransaction() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.075572 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testCommitWriteTransactionWithCrossThreadNotification() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.10352 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testCommitWriteTransactionWithLocalNotification() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.077464 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testCountWhereQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.3073 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testCountWhereTableView() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.13184 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testCrossThreadSyncLatency() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.44978 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testDeleteAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.053093 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testEnumerateAndAccessAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.3791 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndAccessAllSlow() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.4184 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndAccessArrayProperty() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.3905 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndAccessArrayPropertySlow() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.537 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndAccessQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.76781 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndMutateAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.9894 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testEnumerateAndMutateQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.049 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testIndexedStringLookup() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.21403 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testInsertMultiple() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.24181 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testInsertMultipleLiteral() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.32753 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testInsertSingleLiteral() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.14083 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testLargeINQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.43999 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testManualDeletion() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.48855 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testQueryConstruction() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.1752 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testQueryDeletion() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.11546 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testRealmCreationCached() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.58361 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 testRealmCreationUncached() com.apple.XCTPerformanceMetric_WallClockTime testSortingAllObjects() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.20726 baselineIntegrationDisplayName May 12, 2016, 2:43:11 PM maxRegression 0.001 testUnindexedStringLookup() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.20652 baselineIntegrationDisplayName May 11, 2016, 2:03:24 PM maxRegression 0.001 ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/5D660FD71BE98C7C0021E04F.xcbaseline/B87A7896-8B70-4814-B20D-7CD723D62868.plist ================================================ classNames SwiftPerformanceTests testCommitWriteTransaction() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.26241 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testCommitWriteTransactionWithCrossThreadNotification() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.27074 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testCommitWriteTransactionWithLocalNotification() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.2707 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testCountWhereQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.069046 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testCountWhereTableView() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.05102 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testCrossThreadSyncLatency() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.4981 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testDeleteAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.034125 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testEnumerateAndAccessAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.43683 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndAccessAllSlow() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.47066 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndAccessArrayProperty() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.44294 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndAccessArrayPropertySlow() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.52273 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndAccessQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.2415 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndMutateAll() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.74033 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testEnumerateAndMutateQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.4148 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testIndexedStringLookup() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.099493 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testInsertMultiple() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.25401 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testInsertMultipleLiteral() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.29001 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testInsertSingleLiteral() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.49099 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testLargeINQuery() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.22316 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testManualDeletion() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.91235 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testQueryConstruction() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.076228 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testQueryDeletion() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.072784 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testRealmCreationCached() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.23321 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 testRealmCreationUncached() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 5.2582 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM testSortingAllObjects() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.06945 baselineIntegrationDisplayName May 12, 2016, 1:55:16 PM maxRegression 0.001 testUnindexedStringLookup() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.078901 baselineIntegrationDisplayName May 12, 2016, 1:22:28 PM maxRegression 0.001 ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/5D660FD71BE98C7C0021E04F.xcbaseline/Info.plist ================================================ runDestinationsByUUID 6890B8D9-CE81-403E-8EF6-B95A367D65B5 targetArchitecture armv7s targetDevice modelCode iPhone5,1 platformIdentifier com.apple.platform.iphoneos B87A7896-8B70-4814-B20D-7CD723D62868 targetArchitecture arm64 targetDevice modelCode iPad4,4 platformIdentifier com.apple.platform.iphoneos ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/E856D1DE195614A400FB2FCF.xcbaseline/2EB6396F-9FD1-4243-AA83-2D9F9D4DD919.plist ================================================ classNames PerformanceTests testArrayKVOIndexHandlingInsertCompact com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.44623 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testArrayKVOIndexHandlingInsertSparse com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.46951 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testArrayKVOIndexHandlingRemoveBackwards com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.25049 baselineIntegrationDisplayName May 11, 2016, 11:47:05 AM maxRegression 0.001 testArrayKVOIndexHandlingRemoveForward com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.28008 baselineIntegrationDisplayName May 11, 2016, 11:47:05 AM maxRegression 0.001 testCommitWriteTransaction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.25529 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testCommitWriteTransactionWithCrossThreadNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.3805 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testCommitWriteTransactionWithListNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.014152 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM testCommitWriteTransactionWithLocalNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.3356 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testCommitWriteTransactionWithResultsNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.024311 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM testCountWhereQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.068142 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testCountWhereTableView com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.079563 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testCrossThreadSyncLatency com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.5036 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testDeleteAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.057373 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testEnumerateAndAccessAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.021611 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testEnumerateAndAccessAllSlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.032911 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testEnumerateAndAccessArrayProperty com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.024657 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testEnumerateAndAccessArrayPropertySlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.029516 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testEnumerateAndAccessQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.013916 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testEnumerateAndMutateAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.085535 baselineIntegrationDisplayName May 11, 2016, 11:47:05 AM maxRegression 0.001 testEnumerateAndMutateQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.022362 baselineIntegrationDisplayName May 11, 2016, 11:47:05 AM maxRegression 0.001 testIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.064675 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testInsertMultiple com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.11286 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testInsertMultipleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.22709 baselineIntegrationDisplayName May 11, 2016, 11:47:05 AM maxRegression 0.001 testInsertSingleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.62334 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testLargeINQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.020886 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testManualDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.54088 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testQueryConstruction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.061729 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testQueryDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.099315 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testRealmCreationCached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.30414 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testRealmCreationUncached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 5.6515 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testRealmFileCreation com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.8914 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 testSortingAllObjects com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.017391 baselineIntegrationDisplayName May 12, 2016, 11:39:35 AM maxRegression 0.001 testUnIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.060121 baselineIntegrationDisplayName May 11, 2016, 11:11:55 AM maxRegression 0.001 ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/E856D1DE195614A400FB2FCF.xcbaseline/3AE81604-3FF9-49E2-A414-9B18BEEAE28F.plist ================================================ classNames PerformanceTests testArrayKVOIndexHandlingInsertCompact com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.32691 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testArrayKVOIndexHandlingInsertSparse com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.27606 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testArrayKVOIndexHandlingRemoveBackwards com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.24281 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testArrayKVOIndexHandlingRemoveForward com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.26714 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCommitWriteTransaction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.23385 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCommitWriteTransactionWithCrossThreadNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.1877 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCommitWriteTransactionWithListNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.014699 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCommitWriteTransactionWithLocalNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.1827 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCommitWriteTransactionWithResultsNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.025275 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCountWhereQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.069509 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCountWhereTableView com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.072526 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testCrossThreadSyncLatency com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.3507 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testDeleteAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.065435 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndAccessAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.022628 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndAccessAllSlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.025589 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndAccessArrayProperty com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.022196 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndAccessArrayPropertySlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.02866 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndAccessQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.012512 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndMutateAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.095107 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testEnumerateAndMutateQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.024364 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.061875 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testInsertMultiple com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.114 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testInsertMultipleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.22307 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testInsertSingleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.54683 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testLargeINQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.021874 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testManualDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.47608 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testQueryConstruction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.050903 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testQueryDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.085817 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testRealmCreationCached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.29618 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testRealmCreationUncached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 5.2568 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testRealmFileCreation com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.8215 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testSortingAllObjects com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.014483 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 testUnIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.061645 baselineIntegrationDisplayName May 11, 2016, 2:36:54 PM maxRegression 0.001 ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/E856D1DE195614A400FB2FCF.xcbaseline/AAC6BA1A-785D-4850-B3EC-68BC9F1D3EEF.plist ================================================ classNames PerformanceTests testArrayKVOIndexHandlingInsertCompact com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.33455 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testArrayKVOIndexHandlingInsertSparse com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.35906 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testArrayKVOIndexHandlingRemoveBackwards com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.57923 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testArrayKVOIndexHandlingRemoveForward com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.64841 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCommitWriteTransaction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.069065 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCommitWriteTransactionWithCrossThreadNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.36435 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCommitWriteTransactionWithListNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.056905 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCommitWriteTransactionWithLocalNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.35081 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCommitWriteTransactionWithResultsNotification com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.061962 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCountWhereQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.29435 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCountWhereTableView com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.27236 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testCrossThreadSyncLatency com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.47796 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testDeleteAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.11475 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndAccessAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.077348 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndAccessAllSlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.10176 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndAccessArrayProperty com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.079459 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndAccessArrayPropertySlow com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.10685 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndAccessQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.04714 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndMutateAll com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.11792 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testEnumerateAndMutateQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.017438 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.15732 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testInsertMultiple com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.19128 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testInsertMultipleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.28787 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testInsertSingleLiteral com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.23866 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testLargeINQuery com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.053538 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testManualDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.54791 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testQueryConstruction com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.15085 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testQueryDeletion com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.16626 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testRealmCreationCached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.80076 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testRealmCreationUncached com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 3.2179 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testRealmFileCreation com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.7743 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testSortingAllObjects com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.039965 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 testUnIndexedStringLookup com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.15588 baselineIntegrationDisplayName May 11, 2016, 2:16:48 PM maxRegression 0.001 ================================================ FILE: Realm.xcodeproj/xcshareddata/xcbaselines/E856D1DE195614A400FB2FCF.xcbaseline/Info.plist ================================================ runDestinationsByUUID 2EB6396F-9FD1-4243-AA83-2D9F9D4DD919 targetArchitecture arm64 targetDevice modelCode iPad4,4 platformIdentifier com.apple.platform.iphoneos AAC6BA1A-785D-4850-B3EC-68BC9F1D3EEF targetArchitecture armv7s targetDevice modelCode iPhone5,1 platformIdentifier com.apple.platform.iphoneos 3AE81604-3FF9-49E2-A414-9B18BEEAE28F targetArchitecture arm64 targetDevice modelCode iPhone6,2 platformIdentifier com.apple.platform.iphoneos ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/CI.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/Realm.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/RealmSwift.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/SwiftLint.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/SwiftUITestHost.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/SwiftUITests.xcscheme ================================================ ================================================ FILE: Realm.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme ================================================ ================================================ FILE: RealmSwift/Aliases.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm.Swift // These types don't change when wrapping in Swift // so we just typealias them to remove the 'RLM' prefix // MARK: Aliases /** `PropertyType` is an enum describing all property types supported in Realm models. For more information, see [Object Models and Schemas](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/model-data/object-models/). ### Primitive types * `Int` * `Bool` * `Float` * `Double` ### Object types * `String` * `Data` * `Date` * `Decimal128` * `ObjectId` ### Relationships: Array (in Swift, `List`) and `Object` types * `Object` * `Array` */ public typealias PropertyType = RLMPropertyType /** An opaque token which is returned from methods which subscribe to changes to a Realm. - see: `Realm.observe(_:)` */ public typealias NotificationToken = RLMNotificationToken /// :nodoc: public typealias ObjectBase = RLMObjectBase extension ObjectBase { internal func _observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { return RLMObjectBaseAddNotificationBlock(self, keyPaths, queue) { object, names, oldValues, newValues, error in assert(error == nil) block(.init(object: object as? T, names: names, oldValues: oldValues, newValues: newValues)) } } internal func _observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (T?) -> Void) -> NotificationToken { return RLMObjectBaseAddNotificationBlock(self, keyPaths, queue) { object, _, _, _, _ in block(object as? T) } } internal func _observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping () -> Void) -> NotificationToken { return RLMObjectBaseAddNotificationBlock(self, keyPaths, queue) { _, _, _, _, _ in block() } } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) internal func _observe( keyPaths: [String]? = nil, on actor: isolated A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { let token = RLMObjectNotificationToken() token.observe(self, keyPaths: keyPaths) { object, names, oldValues, newValues, error in assert(error == nil) actor.invokeIsolated(block, .init(object: object as? T, names: names, oldValues: oldValues, newValues: newValues)) } await withTaskCancellationHandler(operation: token.registrationComplete, onCancel: { token.invalidate() }) return token } } ================================================ FILE: RealmSwift/AnyRealmValue.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm /// A enum for storing and retrieving values associated with an `AnyRealmValue` property. /// `AnyRealmValue` can also store a collection (List, Dictionary) of `AnyRealmValue`, meaning that you can have /// nested collections inside a `AnyRealmValue`. public enum AnyRealmValue: Hashable { /// Represents `nil` case none /// An integer type. case int(Int) /// A boolean type. case bool(Bool) /// A floating point numeric type. case float(Float) /// A double numeric type. case double(Double) /// A string type. case string(String) /// A binary data type. case data(Data) /// A date type. case date(Date) /// A Realm Object type. case object(Object) /// An ObjectId type. case objectId(ObjectId) /// A Decimal128 type. case decimal128(Decimal128) /// A UUID type. case uuid(UUID) /// Dictionary type. case dictionary(Map) /// List type. case list(List) /// Returns an `Int` if that is what the stored value is, otherwise `nil`. public var intValue: Int? { guard case let .int(i) = self else { return nil } return i } /// Returns a `Bool` if that is what the stored value is, otherwise `nil`. public var boolValue: Bool? { guard case let .bool(b) = self else { return nil } return b } /// Returns a `Float` if that is what the stored value is, otherwise `nil`. public var floatValue: Float? { guard case let .float(f) = self else { return nil } return f } /// Returns a `Double` if that is what the stored value is, otherwise `nil`. public var doubleValue: Double? { guard case let .double(d) = self else { return nil } return d } /// Returns a `String` if that is what the stored value is, otherwise `nil`. public var stringValue: String? { guard case let .string(s) = self else { return nil } return s } /// Returns `Data` if that is what the stored value is, otherwise `nil`. public var dataValue: Data? { guard case let .data(d) = self else { return nil } return d } /// Returns a `Date` if that is what the stored value is, otherwise `nil`. public var dateValue: Date? { guard case let .date(d) = self else { return nil } return d } /// Returns an `ObjectId` if that is what the stored value is, otherwise `nil`. public var objectIdValue: ObjectId? { guard case let .objectId(o) = self else { return nil } return o } /// Returns a `Decimal128` if that is what the stored value is, otherwise `nil`. public var decimal128Value: Decimal128? { guard case let .decimal128(d) = self else { return nil } return d } /// Returns a `UUID` if that is what the stored value is, otherwise `nil`. public var uuidValue: UUID? { guard case let .uuid(u) = self else { return nil } return u } /// Returns the stored value as a Realm Object of a specific type. /// /// - Parameter objectType: The type of the Object to return. /// - Returns: A Realm Object of the supplied type if that is what the underlying value is, /// otherwise `nil` is returned. public func object(_ objectType: T.Type) -> T? { guard case let .object(o) = self else { return nil } return o as? T } /// Returns a `Map` if that is what the stored value is, otherwise `nil`. public var dictionaryValue: Map? { guard case let .dictionary(d) = self else { return nil } return d } /// Returns a `List` if that is what the stored value is, otherwise `nil`. public var listValue: List? { guard case let .list(l) = self else { return nil } return l } /// Returns a `DynamicObject` if the stored value is an `Object`, otherwise `nil`. /// /// Note: This allows access to an object stored in `AnyRealmValue` where you may not have /// the class information associated for it. For example if you are using Realm Sync and version 2 /// of your app sets an object into `AnyRealmValue` and that class does not exist in version 1 /// use this accessor to gain access to the object in the Realm. public var dynamicObject: DynamicObject? { guard case let .object(o) = self else { return nil } return unsafeBitCast(o, to: DynamicObject?.self) } /// Required for conformance to `AddableType` public init() { self = .none } /// Returns a `AnyRealmValue` storing a `Map`. /// /// - Parameter dictionary: A Swift's dictionary of `AnyRealmValue` values. /// - Returns: Returns an `AnyRealmValue` storing a `Map`. public static func fromDictionary(_ dictionary: Dictionary) -> AnyRealmValue { let map = Map() map.merge(dictionary, uniquingKeysWith: { $1 }) return AnyRealmValue.dictionary(map) } /// Returns a `AnyRealmValue` storing a `List`. /// /// - Parameter array: A Swift's array of `AnyRealmValue`. /// - Returns: Returns a `AnyRealmValue` storing a `List`. public static func fromArray(_ array: Array) -> AnyRealmValue { let list = List() list.append(objectsIn: array) return AnyRealmValue.list(list) } // MARK: - Hashable public func hash(into hasher: inout Hasher) { switch self { case let .int(i): hasher.combine(i) case let .bool(b): hasher.combine(b) case let .float(f): hasher.combine(f) case let .double(d): hasher.combine(d) case let .string(s): hasher.combine(s) case let .data(d): hasher.combine(d) case let .date(d): hasher.combine(d) case let .objectId(o): hasher.combine(o) case let .decimal128(d): hasher.combine(d) case let .uuid(u): hasher.combine(u) case let .object(o): hasher.combine(o) case .dictionary: hasher.combine(12) case .list: hasher.combine(13) case .none: hasher.combine(14) } } } ================================================ FILE: RealmSwift/Combine.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Combine import Realm import Realm.Private // MARK: - Identifiable /// A protocol which defines a default identity for Realm Objects /// /// Declaring your Object subclass as conforming to this protocol will supply /// a default implementation for `Identifiable`'s `id` which works for Realm /// Objects: /// /// // Automatically conforms to `Identifiable` /// class MyObjectType: Object, ObjectKeyIdentifiable { /// // ... /// } /// /// You can also manually conform to `Identifiable` if you wish, but note that /// using the object's memory address does *not* work for managed objects. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public protocol ObjectKeyIdentifiable: Identifiable { /// The stable identity of the entity associated with `self`. var id: UInt64 { get } } /// :nodoc: @available(*, deprecated, renamed: "ObjectKeyIdentifiable") @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public typealias ObjectKeyIdentifable = ObjectKeyIdentifiable @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension ObjectKeyIdentifiable where Self: ObjectBase { /// A stable identifier for this object. For managed Realm objects, this /// value will be the same for all object instances which refer to the same /// object (i.e. for which `Object.isSameObject(as:)` returns true). public var id: UInt64 { RLMObjectBaseGetCombineId(self) } } /// :nodoc: @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension ObjectKeyIdentifiable where Self: ProjectionObservable { /// A stable identifier for this projection. public var id: UInt64 { RLMObjectBaseGetCombineId(rootObject) } } // MARK: - Combine /// A type which can be passed to `valuePublisher()` or `changesetPublisher()`. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public protocol RealmSubscribable { /// :nodoc: func _observe(_ keyPaths: [String]?, on queue: DispatchQueue?, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self /// :nodoc: func _observe(_ keyPaths: [String]?, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Void } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Publisher { /// Freezes all Realm objects and collections emitted by the upstream publisher /// /// Freezing a Realm object makes it no longer live-update when writes are /// made to the Realm and makes it safe to pass freely between threads /// without using `.threadSafeReference()`. /// /// ``` /// // Get a publisher for a Results /// let cancellable = myResults.publisher /// // Convert to frozen Results /// .freeze() /// // Unlike live objects, frozen objects can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { frozenResults in /// // Do something with the frozen Results /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the objects which the upstream publisher publishes. public func freeze() -> Publishers.Map where Output: ThreadConfined, T == Output { return map { $0.freeze() } } /// Freezes all Realm object changesets emitted by the upstream publisher. /// /// Freezing a Realm object changeset makes the included object reference /// no longer live-update when writes are made to the Realm and makes it /// safe to pass freely between threads without using /// `.threadSafeReference()`. It also guarantees that the frozen object /// contained in the changeset will always match the property changes, which /// is not always the case when using thread-safe references. /// /// ``` /// // Get a changeset publisher for an object /// let cancellable = changesetPublisher(object) /// // Convert to frozen changesets /// .freeze() /// // Unlike live objects, frozen objects can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { changeset in /// // Do something with the frozen changeset /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the changesets /// which the upstream publisher publishes. public func freeze() -> Publishers.Map> where Output == ObjectChange { return map { if case .change(let object, let properties) = $0 { return .change(object.freeze(), properties) } return $0 } } /// Freezes all Realm collection changesets from the upstream publisher. /// /// Freezing a Realm collection changeset makes the included collection /// reference no longer live-update when writes are made to the Realm and /// makes it safe to pass freely between threads without using /// `.threadSafeReference()`. It also guarantees that the frozen collection /// contained in the changeset will always match the change information, /// which is not always the case when using thread-safe references. /// /// ``` /// // Get a changeset publisher for a collection /// let cancellable = myList.changesetPublisher /// // Convert to frozen changesets /// .freeze() /// // Unlike live objects, frozen objects can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { changeset in /// // Do something with the frozen changeset /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the changesets /// which the upstream publisher publishes. public func freeze() -> Publishers.Map> where Output == RealmCollectionChange { return map { switch $0 { case .initial(let collection): return .initial(collection.freeze()) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): return .update(collection.freeze(), deletions: deletions, insertions: insertions, modifications: modifications) case .error(let error): return .error(error) } } } /// Freezes all Realm sectioned results changesets from the upstream publisher. /// /// Freezing a Realm sectioned results changeset makes the included sectioned results /// reference no longer live-update when writes are made to the Realm and /// makes it safe to pass freely between threads without using /// `.threadSafeReference()`. It also guarantees that the frozen sectioned results /// contained in the changeset will always match the change information, /// which is not always the case when using thread-safe references. /// /// ``` /// // Get a changeset publisher for the sectioned results /// let cancellable = mySectionedResults.changesetPublisher /// // Convert to frozen changesets /// .freeze() /// // Unlike live objects, frozen objects can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { changeset in /// // Do something with the frozen changeset /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the changesets /// which the upstream publisher publishes. public func freeze() -> Publishers.Map> where Output == SectionedResultsChange { return map { switch $0 { case .initial(let collection): return .initial(collection.freeze()) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications, sectionsToInsert: let sectionsToInsert, sectionsToDelete: let sectionsToDelete): return .update(collection.freeze(), deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete) } } } /// Freezes all Realm collection changesets from the upstream publisher. /// /// Freezing a Realm collection changeset makes the included collection /// reference no longer live-update when writes are made to the Realm and /// makes it safe to pass freely between threads without using /// `.threadSafeReference()`. It also guarantees that the frozen collection /// contained in the changeset will always match the change information, /// which is not always the case when using thread-safe references. /// /// ``` /// // Get a changeset publisher for a collection /// let cancellable = myMap.changesetPublisher /// // Convert to frozen changesets /// .freeze() /// // Unlike live objects, frozen objects can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { changeset in /// // Do something with the frozen changeset /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the changesets /// which the upstream publisher publishes. public func freeze() -> Publishers.Map> where Output == RealmMapChange { return map { switch $0 { case .initial(let collection): return .initial(collection.freeze()) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): return .update(collection.freeze(), deletions: deletions, insertions: insertions, modifications: modifications) case .error(let error): return .error(error) } } } /// Freezes all Realm projection changesets emitted by the upstream publisher. /// /// Freezing a Realm projection changeset makes the included projection reference /// no longer live-update when writes are made to the Realm and makes it /// safe to pass freely between threads without using /// `.threadSafeReference()`. It also guarantees that the frozen projection /// contained in the changeset will always match the property changes, which /// is not always the case when using thread-safe references. /// /// ``` /// // Get a changeset publisher for an projection /// let cancellable = changesetPublisher(projection) /// // Convert to frozen changesets /// .freeze() /// // Unlike live projections, frozen projections can be sent to a concurrent queue /// .receive(on: DispatchQueue.global()) /// .sink { changeset in /// // Do something with the frozen changeset /// } /// ``` /// /// - returns: A publisher that publishes frozen copies of the changesets /// which the upstream publisher publishes. public func freeze() -> Publishers.Map> where Output == ObjectChange, T: ThreadConfined { return map { if case .change(let projection, let properties) = $0 { return .change(projection.freeze(), properties) } return $0 } } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: ThreadConfined { /// Enables passing thread-confined objects to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined objects must be proceeded by a call to /// `.threadSafeReference()`.The returned publisher handles the required /// logic to pass the thread-confined object to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the object to the main thread you can do: /// /// let cancellable = publisher(myObject) /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { object in /// // Do things with the object on the main thread /// } /// /// Calling this function on a publisher which emits frozen or unmanaged /// objects is unneccesary but is allowed. /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafe { RealmPublishers.MakeThreadSafe(self) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Publisher { /// Enables passing object changesets to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined objects must be proceeded by a call to /// `.threadSafeReference()`. The returned publisher handles the required /// logic to pass the thread-confined object to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the object changeset to the main thread you can do: /// /// let cancellable = changesetPublisher(myObject) /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { objectChange in /// // Do things with the object on the main thread /// } /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafeObjectChangeset where Output == ObjectChange { RealmPublishers.MakeThreadSafeObjectChangeset(self) } /// Enables passing projection changesets to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined projection must be proceeded by a call to /// `.threadSafeReference()`. The returned publisher handles the required /// logic to pass the thread-confined projection to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the projection changeset to the main thread you can do: /// /// let cancellable = changesetPublisher(myProjection) /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { projectionChange in /// // Do things with the projection on the main thread /// } /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafeObjectChangeset where Output == ObjectChange, T: ThreadConfined { RealmPublishers.MakeThreadSafeObjectChangeset(self) } /// Enables passing Realm collection changesets to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined objects must be proceeded by a call to /// `.threadSafeReference()`. The returned publisher handles the required /// logic to pass the thread-confined object to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the collection changeset to the main thread you can do: /// /// let cancellable = myCollection.changesetPublisher /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { collectionChange in /// // Do things with the collection on the main thread /// } /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafeCollectionChangeset where Output == RealmCollectionChange { RealmPublishers.MakeThreadSafeCollectionChangeset(self) } /// Enables passing Realm collection changesets to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined objects must be proceeded by a call to /// `.threadSafeReference()`. The returned publisher handles the required /// logic to pass the thread-confined object to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the collection changeset to the main thread you can do: /// /// let cancellable = myCollection.changesetPublisher /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { collectionChange in /// // Do things with the collection on the main thread /// } /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafeKeyedCollectionChangeset where Output == RealmMapChange { RealmPublishers.MakeThreadSafeKeyedCollectionChangeset(self) } /// Enables passing Realm sectioned results changesets to a different dispatch queue. /// /// Each call to `receive(on:)` on a publisher which emits Realm /// thread-confined objects must be proceeded by a call to /// `.threadSafeReference()`. The returned publisher handles the required /// logic to pass the thread-confined object to the new queue. Only serial /// dispatch queues are supported and using other schedulers will result in /// a fatal error. /// /// For example, to subscribe on a background thread, do some work there, /// then pass the collection changeset to the main thread you can do: /// /// let cancellable = mySectionedResults.changesetPublisher /// .subscribe(on: DispatchQueue(label: "background queue") /// .print() /// .threadSafeReference() /// .receive(on: DispatchQueue.main) /// .sink { sectionedResultsChange in /// // Do things with the sectioned results on the main thread /// } /// /// - returns: A publisher that supports `receive(on:)` for thread-confined objects. public func threadSafeReference() -> RealmPublishers.MakeThreadSafeSectionedResultsChangeset where Output == SectionedResultsChange { RealmPublishers.MakeThreadSafeSectionedResultsChangeset(self) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension RealmCollection where Self: RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } /// :nodoc: @available(*, deprecated, renamed: "collectionPublisher") public var publisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the collection each time the collection changes. public var collectionPublisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the collection each time the collection changes on the given property keyPaths. public func collectionPublisher(keyPaths: [String]?) -> RealmPublishers.Value { return RealmPublishers.Value(self, keyPaths: keyPaths) } /// A publisher that emits a collection changeset each time the collection changes. public var changesetPublisher: RealmPublishers.CollectionChangeset { RealmPublishers.CollectionChangeset(self) } /// A publisher that emits a collection changeset each time the collection changes on the given property keyPaths. public func changesetPublisher(keyPaths: [String]?) -> RealmPublishers.CollectionChangeset { return RealmPublishers.CollectionChangeset(self, keyPaths: keyPaths) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension RealmKeyedCollection where Self: RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } /// :nodoc: @available(*, deprecated, renamed: "collectionPublisher") public var publisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the collection each time the collection changes. public var collectionPublisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the collection each time the collection changes on the given property keyPaths. public func collectionPublisher(keyPaths: [String]?) -> RealmPublishers.Value { return RealmPublishers.Value(self, keyPaths: keyPaths) } /// A publisher that emits a collection changeset each time the collection changes. public var changesetPublisher: RealmPublishers.MapChangeset { RealmPublishers.MapChangeset(self) } /// A publisher that emits a collection changeset each time the collection changes on the given property keyPaths. public func changesetPublisher(keyPaths: [String]?) -> RealmPublishers.MapChangeset { return RealmPublishers.MapChangeset(self, keyPaths: keyPaths) } } /// Creates a publisher that emits the object each time the object changes. /// /// - precondition: The object must be a managed object which has not been invalidated. /// - parameter object: A managed object to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits the object each time it changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func valuePublisher(_ object: T, keyPaths: [String]? = nil) -> RealmPublishers.Value { RealmPublishers.Value(object, keyPaths: keyPaths) } /// Creates a publisher that emits the collection each time the collection changes. /// /// - precondition: The collection must be a managed collection which has not been invalidated. /// - parameter object: A managed collection to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits the collection each time it changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func valuePublisher(_ collection: T, keyPaths: [String]? = nil) -> RealmPublishers.Value { RealmPublishers.Value(collection, keyPaths: keyPaths) } /// Creates a publisher that emits the object each time the object changes. /// /// - precondition: The object must be a managed object which has not been invalidated. /// - parameter object: A managed object to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits the object each time it changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func valuePublisher(_ projection: T, keyPaths: [String]? = nil) -> RealmPublishers.Value { RealmPublishers.Value(projection, keyPaths: keyPaths) } /// Creates a publisher that emits an object changeset each time the object changes. /// /// - precondition: The object must be a managed object which has not been invalidated. /// - parameter object: A managed object to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits an object changeset each time the object changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func changesetPublisher(_ object: T, keyPaths: [String]? = nil) -> RealmPublishers.ObjectChangeset { precondition(object.realm != nil, "Only managed objects can be published") precondition(!object.isInvalidated, "Object is invalidated or deleted") return RealmPublishers.ObjectChangeset { queue, fn in object.observe(keyPaths: keyPaths, on: queue, fn) } } /// Creates a publisher that emits an object changeset each time the object changes. /// /// - precondition: The object must be a projection. /// - parameter projection: A projection of Realm Object to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits an object changeset each time the projection changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func changesetPublisher(_ projection: T, keyPaths: [String]? = nil) -> RealmPublishers.ObjectChangeset { precondition(projection.realm != nil, "Only managed objects can be published") precondition(!projection.isInvalidated, "Object is invalidated or deleted") return RealmPublishers.ObjectChangeset { queue, fn in projection.observe(keyPaths: keyPaths ?? [], on: queue, fn) } } /// Creates a publisher that emits a collection changeset each time the collection changes. /// /// - precondition: The collection must be a managed collection which has not been invalidated. /// - parameter object: A managed collection to observe. /// - parameter keyPaths: The publisher emits changes on these property keyPaths. If `nil` the publisher emits changes for every property. /// - returns: A publisher that emits a collection changeset each time the collection changes. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func changesetPublisher(_ collection: T, keyPaths: [String]? = nil) -> RealmPublishers.CollectionChangeset { RealmPublishers.CollectionChangeset(collection, keyPaths: keyPaths) } // MARK: - Realm @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Realm { /// A publisher that emits Void each time the object changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.RealmWillChange { return RealmPublishers.RealmWillChange(self) } } // MARK: - Object @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Object { /// A publisher that emits Void each time the object changes. /// /// Despite the name, this actually emits *after* the object has changed. public var objectWillChange: RealmPublishers.WillChange { return RealmPublishers.WillChange(self) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension EmbeddedObject { /// A publisher that emits Void each time the object changes. /// /// Despite the name, this actually emits *after* the embedded object has changed. public var objectWillChange: RealmPublishers.WillChange { return RealmPublishers.WillChange(self) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension ObjectBase: RealmSubscribable { /// :nodoc: public func _observe(_ keyPaths: [String]?, on queue: DispatchQueue?, _ subscriber: S) -> NotificationToken where S.Input: ObjectBase { return _observe(keyPaths: keyPaths, on: queue) { (object: S.Input?) in if let object = object { _ = subscriber.receive(object) } else { subscriber.receive(completion: .finished) } } } /// :nodoc: public func _observe(_ keyPaths: [String]?, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Void { return _observe(keyPaths: keyPaths, { _ = subscriber.receive() }) } } #if compiler(>=6) @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Object: @retroactive ObservableObject {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension EmbeddedObject: @retroactive ObservableObject {} #else @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Object: ObservableObject {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension EmbeddedObject: ObservableObject {} #endif // MARK: - List @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension List: ObservableObject, RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: - MutableSet @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension MutableSet: ObservableObject, RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: - Map @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Map: ObservableObject, RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: - LinkingObjects @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension LinkingObjects: RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: - Results @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Results: RealmSubscribable { /// A publisher that emits Void each time the collection changes. /// /// Despite the name, this actually emits *after* the collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: - Sectioned Results @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension SectionedResults: RealmSubscribable { /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self { return observe(keyPaths: keyPaths, on: queue) { change in switch change { case .initial(let collection): _ = subscriber.receive(collection) case .update(let collection, deletions: _, insertions: _, modifications: _, sectionsToInsert: _, sectionsToDelete: _): _ = subscriber.receive(collection) } } } /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, _ subscriber: S) -> NotificationToken where S.Input == Void { return observe(keyPaths: keyPaths, on: nil) { _ in _ = subscriber.receive() } } /// A publisher that emits Void each time the sectioned results collection changes. /// /// Despite the name, this actually emits *after* the sectioned results collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } /// A publisher that emits the sectioned results collection each time the sectioned results collection changes. public var collectionPublisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the sectioned results collection each time the sectioned results collection changes on the given property keyPaths. public func collectionPublisher(keyPaths: [String]?) -> RealmPublishers.Value { return RealmPublishers.Value(self, keyPaths: keyPaths) } /// A publisher that emits a sectioned results collection changeset each time the sectioned results collection changes. public var changesetPublisher: RealmPublishers.SectionedResultsChangeset { RealmPublishers.SectionedResultsChangeset(self) } /// A publisher that emits a sectioned results collection changeset each time the sectioned results collection changes on the given property keyPaths. public func changesetPublisher(keyPaths: [String]?) -> RealmPublishers.SectionedResultsChangeset { return RealmPublishers.SectionedResultsChangeset(self, keyPaths: keyPaths) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension ResultsSection: RealmSubscribable { /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self { return observe(keyPaths: keyPaths, on: queue) { change in switch change { case .initial(let collection): _ = subscriber.receive(collection) case .update(let collection, deletions: _, insertions: _, modifications: _, sectionsToInsert: _, sectionsToDelete: _): _ = subscriber.receive(collection) } } } /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, _ subscriber: S) -> NotificationToken where S.Input == Void { return observe(keyPaths: keyPaths, on: nil) { _ in _ = subscriber.receive() } } /// A publisher that emits Void each time the results section collection changes. /// /// Despite the name, this actually emits *after* the results section collection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } /// A publisher that emits the results section collection each time the results section collection changes. public var collectionPublisher: RealmPublishers.Value { RealmPublishers.Value(self) } /// A publisher that emits the results section collection each time the results section collection changes on the given property keyPaths. public func collectionPublisher(keyPaths: [String]?) -> RealmPublishers.Value { return RealmPublishers.Value(self, keyPaths: keyPaths) } /// A publisher that emits a results section collection changeset each time the results section collection changes. public var changesetPublisher: RealmPublishers.SectionChangeset { RealmPublishers.SectionChangeset(self) } /// A publisher that emits a results section collection changeset each time the results section collection changes on the given property keyPaths. public func changesetPublisher(keyPaths: [String]?) -> RealmPublishers.SectionChangeset { return RealmPublishers.SectionChangeset(self, keyPaths: keyPaths) } } // MARK: RealmCollection @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension RealmCollectionImpl { /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self { var col: Self? return collection.addNotificationBlock({ collection, _, _ in if col == nil, let collection = collection { col = self.collection === collection ? self : Self(collection: collection) } if let col = col { _ = subscriber.receive(col) } }, keyPaths: keyPaths, queue: queue) } /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, _ subscriber: S) -> NotificationToken where S.Input == Void { collection.addNotificationBlock({ _, _, _ in _ = subscriber.receive() }, keyPaths: keyPaths, queue: nil) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension AnyRealmCollection: RealmSubscribable {} // MARK: RealmKeyedCollection @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension RealmKeyedCollection { /// :nodoc: public func _observe(_ keyPaths: [String]?, on queue: DispatchQueue? = nil, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self { // FIXME: we could skip some pointless work in converting the changeset to the Swift type here return observe(keyPaths: keyPaths, on: queue) { change in switch change { case .initial(let collection): _ = subscriber.receive(collection) case .update(let collection, deletions: _, insertions: _, modifications: _): _ = subscriber.receive(collection) case .error(let error): fatalError("Unexpected error \(error)") } } } /// :nodoc: public func _observe(_ subscriber: S) -> NotificationToken where S.Input == Void { return observe(keyPaths: nil, on: nil) { _ in _ = subscriber.receive() } } /// :nodoc: public func _observe(_ keyPaths: [String]? = nil, _ subscriber: S) -> NotificationToken where S.Input == Void { return observe(keyPaths: keyPaths, on: nil) { _ in _ = subscriber.receive() } } } // MARK: Subscriptions /// A subscription which wraps a Realm notification. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @frozen public struct ObservationSubscription: Subscription { private var token: NotificationToken internal init(token: NotificationToken) { self.token = token } /// A unique identifier for identifying publisher streams. public var combineIdentifier: CombineIdentifier { return CombineIdentifier(token) } /// This function is not implemented. /// /// Realm publishers do not support backpressure and so this function does nothing. public func request(_ demand: Subscribers.Demand) { } /// Stop emitting values on this subscription. public func cancel() { token.invalidate() } } /// A subscription which wraps a Realm AsyncOpenTask. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @frozen public struct AsyncOpenSubscription: Subscription { private let task: Realm.AsyncOpenTask internal init(task: Realm.AsyncOpenTask) { self.task = task } /// A unique identifier for identifying publisher streams. public var combineIdentifier: CombineIdentifier { return CombineIdentifier(task.rlmTask) } /// This function is not implemented. /// /// Realm publishers do not support backpressure and so this function does nothing. public func request(_ demand: Subscribers.Demand) { } /// Stop emitting values on this subscription. public func cancel() { task.cancel() } } // MARK: Publishers /// Combine publishers for Realm types. /// /// You normally should not create any of these types directly, and should /// instead use the extension methods which create them. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public enum RealmPublishers { static private func realm(_ config: RLMRealmConfiguration, _ scheduler: S) -> Realm? { try? Realm(RLMRealm(configuration: config, queue: scheduler as? DispatchQueue)) } static private func realm(_ sourceRealm: Realm, _ scheduler: S) -> Realm? { return realm(sourceRealm.rlmRealm.configurationSharingSchema(), scheduler) } /// A publisher which emits Void each time the Realm is refreshed. /// /// Despite the name, this actually emits *after* the Realm is refreshed. @frozen public struct RealmWillChange: Publisher { /// This publisher cannot fail. public typealias Failure = Never /// This publisher emits Void. public typealias Output = Void private let realm: Realm internal init(_ realm: Realm) { self.realm = realm } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `RealmWillChangeWithToken` Publisher. public func saveToken(on object: T, for keyPath: WritableKeyPath) -> RealmWillChangeWithToken { return RealmWillChangeWithToken(realm, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.realm.observe { _, _ in _ = subscriber.receive() } subscriber.receive(subscription: ObservationSubscription(token: token)) } } /// :nodoc: public class RealmWillChangeWithToken: Publisher { /// This publisher cannot fail. public typealias Failure = Never /// This publisher emits Void. public typealias Output = Void internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private let realm: Realm private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath internal init(_ realm: Realm, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { self.realm = realm self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.realm.observe { _, _ in _ = subscriber.receive() } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } } /// A publisher which emits Void each time the object is mutated. /// /// Despite the name, this actually emits *after* the collection has changed. @frozen public struct WillChange: Publisher where Collection: ThreadConfined { /// This publisher cannot fail. public typealias Failure = Never /// This publisher emits Void. public typealias Output = Void private let collection: Collection internal init(_ collection: Collection) { self.collection = collection } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `WillChangeWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> WillChangeWithToken { return WillChangeWithToken(collection, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection._observe(nil, subscriber) subscriber.receive(subscription: ObservationSubscription(token: token)) } } /// A publisher which emits Void each time the object is mutated. /// /// Despite the name, this actually emits *after* the collection has changed. public class WillChangeWithToken: Publisher where Collection: ThreadConfined { /// This publisher cannot fail. public typealias Failure = Never /// This publisher emits Void. public typealias Output = Void internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private let object: Collection private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath internal init(_ object: Collection, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { self.object = object self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.object._observe(nil, subscriber) tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } } /// A publisher which emits an object or collection each time that object is mutated. @frozen public struct Value: Publisher where Subscribable: ThreadConfined { /// This publisher cannot actually fail and will change to Never in the future. public typealias Failure = Error /// This publisher emits the object or collection which it is publishing. public typealias Output = Subscribable private let subscribable: Subscribable private let keyPaths: [String]? private let queue: DispatchQueue? internal init(_ subscribable: Subscribable, keyPaths: [String]? = nil, queue: DispatchQueue? = nil) { precondition(subscribable.realm != nil, "Only managed objects can be published") self.subscribable = subscribable self.keyPaths = keyPaths self.queue = queue } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `ValueWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> ValueWithToken { return ValueWithToken(subscribable, queue, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { subscriber.receive(subscription: ObservationSubscription(token: self.subscribable._observe(keyPaths, on: queue, subscriber))) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> Value { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return Value(subscribable, keyPaths: keyPaths, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)`. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> RealmPublishers.Handover { return Handover(self, scheduler, self.subscribable.realm!) } } /// A publisher which emits an object or collection each time that object is mutated. public class ValueWithToken: Publisher where Subscribable: ThreadConfined { /// This publisher cannot actually fail and will change to Never in the future. public typealias Failure = Error /// This publisher emits the object or collection which it is publishing. public typealias Output = Subscribable internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private let object: Subscribable private let queue: DispatchQueue? private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath internal init(_ object: Subscribable, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { precondition(object.realm != nil, "Only managed objects can be published") self.object = object self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { let token = self.object._observe(nil, on: queue, subscriber) tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> ValueWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return ValueWithToken(object, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)`. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> Handover { return Handover(self, scheduler, self.object.realm!) } } /// A helper publisher used to support `receive(on:)` on Realm publishers. @frozen public struct Handover: Publisher where Upstream.Output: ThreadConfined { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let config: RLMRealmConfiguration private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S, _ realm: Realm) { self.config = realm.rlmRealm.configurationSharingSchema() self.upstream = upstream self.scheduler = scheduler } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler let config = self.config self.upstream .map { ThreadSafeReference(to: $0) } .receive(on: scheduler) .compactMap { realm(config, scheduler)?.resolve($0) } .receive(subscriber: subscriber) } } /// A publisher which makes `receive(on:)` work for streams of thread-confined objects /// /// Create using .threadSafeReference() @frozen public struct MakeThreadSafe: Publisher where Upstream.Output: ThreadConfined { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream internal init(_ upstream: Upstream) { self.upstream = upstream } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { self.upstream.receive(subscriber: subscriber) } /// Specifies the scheduler on which to receive elements from the publisher. /// /// This publisher converts each value emitted by the upstream /// publisher to a `ThreadSafeReference`, passes it to the target /// scheduler, and then converts back to the original type. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandover { DeferredHandover(self.upstream, scheduler) } } /// A publisher which delivers thread-confined values to a serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits thread-confined objects. @frozen public struct DeferredHandover: Publisher where Upstream.Output: ThreadConfined { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } private enum Handover { case object(_ object: Output) case tsr(_ tsr: ThreadSafeReference, config: RLMRealmConfiguration) } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { (obj: Output) -> Handover in guard let realm = obj.realm, !realm.isFrozen else { return .object(obj) } return .tsr(ThreadSafeReference(to: obj), config: realm.rlmRealm.configurationSharingSchema()) } .receive(on: scheduler) .compactMap { (handover: Handover) -> Output? in switch handover { case .object(let obj): return obj case .tsr(let tsr, let config): return realm(config, scheduler)?.resolve(tsr) } } .receive(subscriber: subscriber) } } /// A publisher which emits ObjectChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `objectChangeset()` function. @frozen public struct ObjectChangeset: Publisher { /// This publisher emits a ObjectChange indicating which object and /// which properties of that object have changed each time a Realm is /// refreshed after a write transaction which modifies the observed /// object. public typealias Output = ObjectChange /// This publisher reports error via the `.error` case of ObjectChange. public typealias Failure = Never @usableFromInline internal typealias Observe = (_ queue: DispatchQueue?, @escaping (Output) -> Void) -> NotificationToken private let observe: Observe private let queue: DispatchQueue? internal init(_ observe: @escaping Observe, queue: DispatchQueue? = nil) { self.observe = observe self.queue = queue } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `ObjectChangesetWithToken` Publisher. public func saveToken(on tokenParent: T, at keyPath: WritableKeyPath) -> ObjectChangesetWithToken { return ObjectChangesetWithToken(observe, queue, tokenParent, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = observe(self.queue) { change in switch change { case .change(let o, let properties): _ = subscriber.receive(.change(o, properties)) case .error(let error): _ = subscriber.receive(.error(error)) case .deleted: subscriber.receive(completion: .finished) } } subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> ObjectChangeset { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return ObjectChangeset(observe, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverObjectChangeset { DeferredHandoverObjectChangeset(self, scheduler) } } /// A publisher which emits ObjectChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `objectChangeset()` function. public class ObjectChangesetWithToken: Publisher { /// This publisher emits a ObjectChange indicating which object and /// which properties of that object have changed each time a Realm is /// refreshed after a write transaction which modifies the observed /// object. public typealias Output = ObjectChange /// This publisher reports error via the `.error` case of ObjectChange. public typealias Failure = Never internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath @usableFromInline internal typealias Observe = (_ queue: DispatchQueue?, @escaping (Output) -> Void) -> NotificationToken private let observe: Observe private let queue: DispatchQueue? internal init(_ observe: @escaping Observe, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { self.observe = observe self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = observe(self.queue) { change in switch change { case .change(let o, let properties): _ = subscriber.receive(.change(o, properties)) case .error(let error): _ = subscriber.receive(.error(error)) case .deleted: subscriber.receive(completion: .finished) } } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> ObjectChangesetWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return ObjectChangesetWithToken(observe, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverObjectChangeset { DeferredHandoverObjectChangeset(self, scheduler) } } /// A helper publisher created by calling `.threadSafeReference()` on a publisher which emits thread-confined values. @frozen public struct MakeThreadSafeObjectChangeset: Publisher where Upstream.Output == ObjectChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream internal init(_ upstream: Upstream) { self.upstream = upstream } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { self.upstream.receive(subscriber: subscriber) } /// Specifies the scheduler to deliver object changesets to. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)`. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverObjectChangeset { DeferredHandoverObjectChangeset(self.upstream, scheduler) } } /// A publisher which delivers thread-confined object changesets to a serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits `ObjectChange`. @frozen public struct DeferredHandoverObjectChangeset: Publisher where Upstream.Output == ObjectChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } private enum Handover { // .error and .change containing a frozen object can be delivered // without any handover case passthrough(_ change: ObjectChange) // .change containing a live object need to be wrapped in a TSR. // We also hold a reference to a pinned Realm to ensure that the // source version remains pinned and we can deliver the object at // the same version as the change information. case tsr(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference, _ properties: [PropertyChange]) } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { (change: Output) -> Handover in guard case .change(let obj, let properties) = change else { return .passthrough(change) } guard let realm = obj.realm, !realm.isFrozen else { return .passthrough(change) } return .tsr(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: obj), properties) } .receive(on: scheduler) .compactMap { (handover: Handover) -> Output? in switch handover { case .passthrough(let change): return change case .tsr(let pin, let tsr, let properties): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .change(resolved, properties) } return nil } } .receive(subscriber: subscriber) } } /// A publisher which emits RealmCollectionChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmCollection. @frozen public struct CollectionChangeset: Publisher { public typealias Output = RealmCollectionChange /// This publisher reports error via the `.error` case of RealmCollectionChange. public typealias Failure = Never private let collection: Collection private let keyPaths: [String]? private let queue: DispatchQueue? internal init(_ collection: Collection, keyPaths: [String]? = nil, queue: DispatchQueue? = nil) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.keyPaths = keyPaths self.queue = queue } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `CollectionChangesetWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> CollectionChangesetWithToken { return CollectionChangesetWithToken(collection, queue, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(keyPaths: self.keyPaths, on: self.queue) { change in _ = subscriber.receive(change) } subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> CollectionChangeset { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return CollectionChangeset(collection, keyPaths: self.keyPaths, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverCollectionChangeset { DeferredHandoverCollectionChangeset(self, scheduler) } } /// A publisher which emits RealmMapChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmCollection. @frozen public struct MapChangeset: Publisher { public typealias Output = RealmMapChange /// This publisher reports error via the `.error` case of RealmMapChange. public typealias Failure = Never private let collection: Collection private let keyPaths: [String]? private let queue: DispatchQueue? internal init(_ collection: Collection, keyPaths: [String]? = nil, queue: DispatchQueue? = nil) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.keyPaths = keyPaths self.queue = queue } /// Captures the `NotificationToken` produced by observing a Realm Collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `CollectionChangesetWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> MapChangesetWithToken { return MapChangesetWithToken(collection, queue, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(keyPaths: self.keyPaths, on: self.queue) { change in _ = subscriber.receive(change) } subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> MapChangeset { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return MapChangeset(collection, keyPaths: self.keyPaths, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverKeyedCollectionChangeset { DeferredHandoverKeyedCollectionChangeset(self, scheduler) } } /// A publisher which emits SectionedResultsChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmSectionedResult. @frozen public struct SectionedResultsChangeset: Publisher { public typealias Output = SectionedResultsChange /// This publisher reports error via the `.error` case of SectionedResultsChange. public typealias Failure = Never private let collection: Collection private let keyPaths: [String]? private let queue: DispatchQueue? internal init(_ collection: Collection, keyPaths: [String]? = nil, queue: DispatchQueue? = nil) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.keyPaths = keyPaths self.queue = queue } /// Captures the `NotificationToken` produced by observing the collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `SectionedResultsChangesetWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> SectionedResultsChangesetWithToken { return SectionedResultsChangesetWithToken(collection, queue, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(keyPaths: self.keyPaths, on: self.queue) { change in _ = subscriber.receive(change) } subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> SectionedResultsChangeset { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return SectionedResultsChangeset(collection, keyPaths: self.keyPaths, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverSectionedResultsChangeset { DeferredHandoverSectionedResultsChangeset(self, scheduler) } } /// A publisher which emits SectionedResultsChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmSectionedResult. @frozen public struct SectionChangeset: Publisher { public typealias Output = SectionedResultsChange /// This publisher reports error via the `.error` case of SectionedResultsChange. public typealias Failure = Never private let collection: Collection private let keyPaths: [String]? private let queue: DispatchQueue? internal init(_ collection: Collection, keyPaths: [String]? = nil, queue: DispatchQueue? = nil) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.keyPaths = keyPaths self.queue = queue } /// Captures the `NotificationToken` produced by observing a the collection. /// /// This allows you to do notification skipping when performing a `Realm.write(withoutNotifying:)`. You should use this call if you /// require to write to the Realm database and ignore this specific observation chain. /// The `NotificationToken` will be saved on the specified `KeyPath`from the observation block set up in `receive(subscriber:)`. /// /// - Parameters: /// - object: The object which the `NotificationToken` is written to. /// - keyPath: The KeyPath which the `NotificationToken` is written to. /// - Returns: A `SectionedResultsChangesetWithToken` Publisher. public func saveToken(on object: T, at keyPath: WritableKeyPath) -> SectionedResultsChangesetWithToken { return SectionedResultsChangesetWithToken(collection, queue, object, keyPath) } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(keyPaths: self.keyPaths, on: self.queue) { change in _ = subscriber.receive(change) } subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> SectionedResultsChangeset { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return SectionedResultsChangeset(collection, keyPaths: self.keyPaths, queue: queue) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverSectionedResultsChangeset { DeferredHandoverSectionedResultsChangeset(self, scheduler) } } /// A publisher which emits RealmCollectionChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmCollection. public class CollectionChangesetWithToken: Publisher { public typealias Output = RealmCollectionChange /// This publisher reports error via the `.error` case of RealmCollectionChange. public typealias Failure = Never internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath private let collection: Collection private let queue: DispatchQueue? internal init(_ collection: Collection, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(on: self.queue) { change in _ = subscriber.receive(change) } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> CollectionChangesetWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return CollectionChangesetWithToken(collection, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverCollectionChangeset { DeferredHandoverCollectionChangeset(self, scheduler) } } /// A publisher which emits SectionedResultsChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmSectionedResult. public class SectionedResultsChangesetWithToken: Publisher { public typealias Output = SectionedResultsChange /// This publisher reports error via the `.error` case of RealmCollectionChange. public typealias Failure = Never internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath private let collection: Collection private let queue: DispatchQueue? internal init(_ collection: Collection, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(on: self.queue) { change in _ = subscriber.receive(change) } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> SectionedResultsChangesetWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return SectionedResultsChangesetWithToken(collection, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverSectionedResultsChangeset { DeferredHandoverSectionedResultsChangeset(self, scheduler) } } /// A publisher which emits SectionedResultsChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmSectionedResult. public class SectionChangesetWithToken: Publisher { public typealias Output = SectionedResultsChange /// This publisher reports error via the `.error` case of RealmCollectionChange. public typealias Failure = Never internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath private let collection: Collection private let queue: DispatchQueue? internal init(_ collection: Collection, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(on: self.queue) { change in _ = subscriber.receive(change) } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> SectionedResultsChangesetWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return SectionedResultsChangesetWithToken(collection, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverSectionChangeset { DeferredHandoverSectionChangeset(self, scheduler) } } /// A publisher which emits RealmMapChange each time the observed object is modified /// /// `receive(on:)` and `subscribe(on:)` can be called directly on this /// publisher, and calling `.threadSafeReference()` is only required if /// there is an intermediate transform. If `subscribe(on:)` is used, it /// should always be the first operation in the pipeline. /// /// Create this publisher using the `changesetPublisher` property on RealmCollection. public class MapChangesetWithToken: Publisher { public typealias Output = RealmMapChange /// This publisher reports error via the `.error` case of RealmCollectionChange. public typealias Failure = Never internal typealias TokenParent = T internal typealias TokenKeyPath = WritableKeyPath private var tokenParent: TokenParent private var tokenKeyPath: TokenKeyPath private let collection: Collection private let queue: DispatchQueue? internal init(_ collection: Collection, _ queue: DispatchQueue? = nil, _ tokenParent: TokenParent, _ tokenKeyPath: TokenKeyPath) { precondition(collection.realm != nil, "Only managed collections can be published") self.collection = collection self.queue = queue self.tokenParent = tokenParent self.tokenKeyPath = tokenKeyPath } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Never, Output == S.Input { let token = self.collection.observe(on: self.queue) { change in _ = subscriber.receive(change) } tokenParent[keyPath: tokenKeyPath] = token subscriber.receive(subscription: ObservationSubscription(token: token)) } /// Specifies the scheduler on which to perform subscribe, cancel, and request operations. /// /// For Realm Publishers, this determines which queue the underlying /// change notifications are sent to. If `receive(on:)` is not used /// subsequently, it also will determine which queue elements received /// from the publisher are evaluated on. Currently only serial dispatch /// queues are supported, and the `options:` parameter is not /// supported. /// /// - parameter scheduler: The serial dispatch queue to perform the subscription on. /// - returns: A publisher which subscribes on the given scheduler. public func subscribe(on scheduler: S) -> MapChangesetWithToken { guard let queue = scheduler as? DispatchQueue else { fatalError("Cannot subscribe on scheduler \(scheduler): only serial dispatch queues are currently implemented.") } return MapChangesetWithToken(collection, queue, tokenParent, tokenKeyPath) } /// Specifies the scheduler on which to perform downstream operations. /// /// This differs from `subscribe(on:)` in how it is integrated with the /// autorefresh cycle. When using `subscribe(on:)`, the subscription is /// performed on the target scheduler and the publisher will emit the /// collection during the refresh. When using `receive(on:)`, the /// collection is then converted to a `ThreadSafeReference` and /// delivered to the target scheduler with no integration into the /// autorefresh cycle, meaning it may arrive some time after the /// refresh occurs. /// /// When in doubt, you probably want `subscribe(on:)` /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverKeyedCollectionChangeset { DeferredHandoverKeyedCollectionChangeset(self, scheduler) } } /// A helper publisher created by calling `.threadSafeReference()` on a /// publisher which emits `RealmCollectionChange`. @frozen public struct MakeThreadSafeCollectionChangeset: Publisher where Upstream.Output == RealmCollectionChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream internal init(_ upstream: Upstream) { self.upstream = upstream } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { self.upstream.receive(subscriber: subscriber) } /// Specifies the scheduler on which to receive elements from the publisher. /// /// This publisher converts each value emitted by the upstream /// publisher to a `ThreadSafeReference`, passes it to the target /// scheduler, and then converts back to the original type. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverCollectionChangeset { DeferredHandoverCollectionChangeset(self.upstream, scheduler) } } /// A helper publisher created by calling `.threadSafeReference()` on a /// publisher which emits `RealmMapChange`. @frozen public struct MakeThreadSafeKeyedCollectionChangeset: Publisher where Upstream.Output == RealmMapChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream internal init(_ upstream: Upstream) { self.upstream = upstream } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { self.upstream.receive(subscriber: subscriber) } /// Specifies the scheduler on which to receive elements from the publisher. /// /// This publisher converts each value emitted by the upstream /// publisher to a `ThreadSafeReference`, passes it to the target /// scheduler, and then converts back to the original type. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverKeyedCollectionChangeset { DeferredHandoverKeyedCollectionChangeset(self.upstream, scheduler) } } /// A helper publisher created by calling `.threadSafeReference()` on a /// publisher which emits `SectionedResultsChange`. @frozen public struct MakeThreadSafeSectionedResultsChangeset: Publisher where Upstream.Output == SectionedResultsChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream internal init(_ upstream: Upstream) { self.upstream = upstream } /// :nodoc: public func receive(subscriber: S) where S: Subscriber, S.Failure == Failure, Output == S.Input { self.upstream.receive(subscriber: subscriber) } /// Specifies the scheduler on which to receive elements from the publisher. /// /// This publisher converts each value emitted by the upstream /// publisher to a `ThreadSafeReference`, passes it to the target /// scheduler, and then converts back to the original type. /// /// - parameter scheduler: The serial dispatch queue to receive values on. /// - returns: A publisher which delivers values to the given scheduler. public func receive(on scheduler: S) -> DeferredHandoverSectionedResultsChangeset { DeferredHandoverSectionedResultsChangeset(self.upstream, scheduler) } } /// A publisher which delivers thread-confined collection changesets to a /// serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits `RealmCollectionChange`. @frozen public struct DeferredHandoverCollectionChangeset: Publisher where Upstream.Output == RealmCollectionChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } private enum Handover { // A collection change which does not contain a live object and so // can be delivered directly case passthrough(_ change: RealmCollectionChange) // The initial and update notifications for live collections need // to wrap the collection in a thread-safe reference and hold onto // a pinned Realm to ensure that the version which the change // information is for stays pinned until it's delivered. case initial(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference) case update(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference, deletions: [Int], insertions: [Int], modifications: [Int]) } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { (change: Output) -> Handover in switch change { case .initial(let collection): guard let realm = collection.realm, !realm.isFrozen else { return .passthrough(change) } return .initial(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection)) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): guard let realm = collection.realm, !realm.isFrozen else { return .passthrough(change) } return .update(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection), deletions: deletions, insertions: insertions, modifications: modifications) case .error: return .passthrough(change) } } .receive(on: scheduler) .compactMap { (handover: Handover) -> Output? in switch handover { case .passthrough(let change): return change case .initial(let pin, let tsr): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .initial(resolved) } return nil case .update(let pin, let tsr, deletions: let deletions, insertions: let insertions, modifications: let modifications): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .update(resolved, deletions: deletions, insertions: insertions, modifications: modifications) } return nil } } .receive(subscriber: subscriber) } } /// A publisher which delivers thread-confined `Map` changesets to a /// serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits `RealmMapChange`. @frozen public struct DeferredHandoverKeyedCollectionChangeset: Publisher where Upstream.Output == RealmMapChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } private enum Handover { // A collection change which does not contain a live object and so // can be delivered directly case passthrough(_ change: RealmMapChange) // The initial and update notifications for live collections need // to wrap the collection in a thread-safe reference and hold onto // a pinned Realm to ensure that the version which the change // information is for stays pinned until it's delivered. case initial(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference) case update(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference, deletions: [T.Key], insertions: [T.Key], modifications: [T.Key]) } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { (change: Output) -> Handover in switch change { case .initial(let collection): guard let realm = collection.realm, !realm.isFrozen else { return .passthrough(change) } return .initial(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection)) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): guard let realm = collection.realm, !realm.isFrozen else { return .passthrough(change) } return .update(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection), deletions: deletions, insertions: insertions, modifications: modifications) case .error: return .passthrough(change) } } .receive(on: scheduler) .compactMap { (handover: Handover) -> Output? in switch handover { case .passthrough(let change): return change case .initial(let pin, let tsr): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .initial(resolved) } return nil case .update(let pin, let tsr, deletions: let deletions, insertions: let insertions, modifications: let modifications): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .update(resolved, deletions: deletions, insertions: insertions, modifications: modifications) } return nil } } .receive(subscriber: subscriber) } } private enum SectionedHandover { // A collection change which does not contain a live object and so // can be delivered directly case passthrough(_ change: SectionedResultsChange) // The initial and update notifications for live collections need // to wrap the collection in a thread-safe reference and hold onto // a pinned Realm to ensure that the version which the change // information is for stays pinned until it's delivered. case initial(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference) case update(_ pin: RLMPinnedRealm, _ tsr: ThreadSafeReference, deletions: [IndexPath], insertions: [IndexPath], modifications: [IndexPath], sectionsToInsert: IndexSet, sectionsToDelete: IndexSet) init(_ change: SectionedResultsChange) { switch change { case .initial(let collection): guard let realm = collection.realm, !realm.isFrozen else { self = .passthrough(change) return } self = .initial(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection)) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications, sectionsToInsert: let sectionsToInsert, sectionsToDelete: let sectionsToDelete): guard let realm = collection.realm, !realm.isFrozen else { self = .passthrough(change) return } self = .update(RLMPinnedRealm(realm: realm.rlmRealm), ThreadSafeReference(to: collection), deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete) } } func resolve(_ scheduler: S) -> SectionedResultsChange? { switch self { case .passthrough(let change): return change case .initial(let pin, let tsr): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .initial(resolved) } return nil case .update(let pin, let tsr, deletions: let deletions, insertions: let insertions, modifications: let modifications, sectionsToInsert: let sectionsToInsert, sectionsToDelete: let sectionsToDelete): defer { pin.unpin() } if let resolved = realm(pin.configuration, scheduler)?.resolve(tsr) { return .update(resolved, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete) } return nil } } } // swiftlint:disable type_name /// A publisher which delivers thread-confined collection changesets to a /// serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits `RealmCollectionChange`. @frozen public struct DeferredHandoverSectionedResultsChangeset: Publisher where Upstream.Output == SectionedResultsChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { SectionedHandover($0) } .receive(on: scheduler) .compactMap { $0.resolve(scheduler) } .receive(subscriber: subscriber) } } // swiftlint:enable type_name /// A publisher which delivers thread-confined collection changesets to a /// serial dispatch queue. /// /// Create using `.threadSafeReference().receive(on: queue)` on a publisher /// that emits `SectionedResultsChange`. @frozen public struct DeferredHandoverSectionChangeset: Publisher where Upstream.Output == SectionedResultsChange { /// :nodoc: public typealias Failure = Upstream.Failure /// :nodoc: public typealias Output = Upstream.Output private let upstream: Upstream private let scheduler: S internal init(_ upstream: Upstream, _ scheduler: S) { self.upstream = upstream self.scheduler = scheduler } /// :nodoc: public func receive(subscriber: Sub) where Sub: Subscriber, Sub.Failure == Failure, Output == Sub.Input { let scheduler = self.scheduler self.upstream .map { SectionedHandover($0) } .receive(on: scheduler) .compactMap { $0.resolve(scheduler) } .receive(subscriber: subscriber) } } } ================================================ FILE: RealmSwift/CustomPersistable.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm // MARK: Public API /** A type which can be mapped to and from a type which Realm supports. To store types in a Realm which Realm doesn't natively support, declare the type as conforming to either CustomPersistable or FailableCustomPersistable. This requires defining an associatedtype named `PersistedType` which indicates what Realm type this type will be mapped to, an initializer taking the `PersistedType`, and a property which returns the appropriate `PersistedType`. For example, to make `URL` persistable: ``` // Not all strings are valid URLs, so this uses // FailableCustomPersistable to handle the case when the data // in the Realm isn't a valid URL. extension URL: FailableCustomPersistable { typealias PersistedType = String init?(persistedValue: String) { self.init(string: persistedValue) } var persistableValue: PersistedType { self.absoluteString } } ``` After doing this, you can define properties using URL: ``` class MyModel: Object { @Persisted var url: URL @Persisted var mapOfUrls: Map } ``` `PersistedType` can be any of the primitive types supported by Realm or an `EmbeddedObject` subclass. `EmbeddedObject` subclasses can be used if you need to store more than one piece of data for your mapped type. For example, to store `CGPoint`: ``` // Define the storage object. A type used for custom mappings // does not have to be used exclusively for custom mappings, // and more than one type can map to a single embedded object // type. class CGPointObject: EmbeddedObject { @Persisted var double: x @Persisted var double: y } // Define the mapping. This mapping isn't failable, as the // data stored in the Realm can always be interpreted as a // CGPoint. extension CGPoint: CustomPersistable { typealias PersistedType = CGPointObject init(persistedValue: CGPointObject) { self.init(x: persistedValue.x, y: persistedValue.y) } var persistableValue: PersistedType { CGPointObject(value: [x, y]) } } class PointModel: Object { // Note that types which are mapped to embedded objects do // not have to be optional (but can be). @Persisted var point: CGPoint @Persisted var line: List } ``` Queries are performed on the persisted type and not the custom persistable type. Values passed into queries can be of either the persisted type or custom persistable type. For custom persistable types which map to embedded objects, memberwise equality will be used. For examples, `realm.objects(PointModel.self).where { $0.point == CGPoint(x: 1, y: 2) }` is equivalent to `"point.x == 1 AND point.y == 2"`. */ public protocol CustomPersistable: _CustomPersistable { /// Construct an instance of this type from the persisted type. init(persistedValue: PersistedType) /// Construct an instance of the persisted type from this type. var persistableValue: PersistedType { get } } /** A type which can be mapped to and from a type which Realm supports. This protocol is identical to `CustomPersistable`, except with `init?(persistedValue:)` instead of `init(persistedValue:)`. FailableCustomPersistable types are force-unwrapped in non-Optional contexts, and collapsed to `nil` in Optional contexts. That is, if you have a value that can't be converted to a URL, reading a `@Persisted var url: URL` property will throw an unwrapped failed exception, and reading from `Persisted var url: URL?` will return `nil`. */ public protocol FailableCustomPersistable: _CustomPersistable { /// Construct an instance of the this type from the persisted type, /// returning nil if the conversion is not possible. /// /// This function must not return `nil` when given a default-initalized /// `PersistedType()`. init?(persistedValue: PersistedType) /// Construct an instance of the persisted type from this type. var persistableValue: PersistedType { get } } // MARK: - Implementation /// :nodoc: public protocol _CustomPersistable: _PersistableInsideOptional, _RealmCollectionValueInsideOptional {} /// :nodoc: extension _CustomPersistable { // _RealmSchemaDiscoverable /// :nodoc: public static var _rlmType: PropertyType { PersistedType._rlmType } /// :nodoc: public static var _rlmOptional: Bool { PersistedType._rlmOptional } /// :nodoc: public static var _rlmRequireObjc: Bool { false } /// :nodoc: public func _rlmPopulateProperty(_ prop: RLMProperty) { } /// :nodoc: public static func _rlmPopulateProperty(_ prop: RLMProperty) { prop.customMappingIsOptional = prop.optional if prop.type == .object && (!prop.collection || prop.dictionary) { prop.optional = true } PersistedType._rlmPopulateProperty(prop) } } extension CustomPersistable { // _Persistable /// :nodoc: public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self { return Self(persistedValue: PersistedType._rlmGetProperty(obj, key)) } /// :nodoc: public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Self? { return PersistedType._rlmGetPropertyOptional(obj, key).flatMap(Self.init) } /// :nodoc: public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Self) { PersistedType._rlmSetProperty(obj, key, value.persistableValue) } /// :nodoc: public static func _rlmSetAccessor(_ prop: RLMProperty) { if prop.customMappingIsOptional { prop.swiftAccessor = BridgedPersistedPropertyAccessor>.self } else if prop.optional { prop.swiftAccessor = CustomPersistablePropertyAccessor.self } else { prop.swiftAccessor = BridgedPersistedPropertyAccessor.self } } /// :nodoc: public static func _rlmDefaultValue() -> Self { Self(persistedValue: PersistedType._rlmDefaultValue()) } /// :nodoc: public func hash(into hasher: inout Hasher) { persistableValue.hash(into: &hasher) } } extension FailableCustomPersistable { // _Persistable /// :nodoc: public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self { let persistedValue = PersistedType._rlmGetProperty(obj, key) if let value = Self(persistedValue: persistedValue) { return value } throwRealmException("Failed to convert persisted value '\(persistedValue)' to type '\(Self.self)' in a non-optional context.") } /// :nodoc: public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Self? { return PersistedType._rlmGetPropertyOptional(obj, key).flatMap(Self.init) } /// :nodoc: public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Self) { PersistedType._rlmSetProperty(obj, key, value.persistableValue) } /// :nodoc: public static func _rlmSetAccessor(_ prop: RLMProperty) { if prop.customMappingIsOptional { prop.swiftAccessor = BridgedPersistedPropertyAccessor>.self } else if prop.optional { prop.swiftAccessor = CustomPersistablePropertyAccessor.self } else { prop.swiftAccessor = BridgedPersistedPropertyAccessor.self } } /// :nodoc: public static func _rlmDefaultValue() -> Self { if let value = Self(persistedValue: PersistedType._rlmDefaultValue()) { return value } throwRealmException("Failed to default construct a \(Self.self) using the default value for persisted type \(PersistedType.self). " + "This conversion must either succeed, the property must be optional, or you must explicitly specify a default value for the property.") } /// :nodoc: public func hash(into hasher: inout Hasher) { persistableValue.hash(into: &hasher) } } extension CustomPersistable { // _ObjcBridgeable /// :nodoc: public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { if let value = PersistedType._rlmFromObjc(value) { return Self(persistedValue: value) } if let value = value as? Self { return value } if !insideOptional && value is NSNull { return Self._rlmDefaultValue() } return nil } /// :nodoc: public var _rlmObjcValue: Any { persistableValue } } extension FailableCustomPersistable { // _ObjcBridgeable /// :nodoc: public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { if let value = PersistedType._rlmFromObjc(value) { return Self(persistedValue: value) } if let value = value as? Self { return value } return nil } /// :nodoc: public var _rlmObjcValue: Any { persistableValue } } ================================================ FILE: RealmSwift/Decimal128.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import Realm /** A 128-bit IEEE 754-2008 decimal floating point number. This type is similar to Swift's built-in Decimal type, but allocates bits differently, resulting in a different representable range. (NS)Decimal stores a significand of up to 38 digits long and an exponent from -128 to 127, while this type stores up to 34 digits of significand and an exponent from -6143 to 6144. */ @objc(RealmSwiftDecimal128) public final class Decimal128: RLMDecimal128, Decodable, @unchecked Sendable { /// Creates a new zero-initialized Decimal128. public override required init() { super.init() } /// Converts the given value to a Decimal128. /// /// The following types can be converted to Decimal128: /// - Int (of any size) /// - Float /// - Double /// - String /// - NSNumber /// - Decimal /// /// Passing a value with a type not in this list is a fatal error. Passing a string which cannot be parsed as a valid Decimal128 is a fatal error. /// /// - parameter value: The value to convert to a Decimal128. public override required init(value: Any) { super.init(value: value) } /// Converts the given number to a Decimal128. /// /// This initializer cannot fail and is never lossy. /// /// - parameter number: The number to convert to a Decimal128. public override required init(number: NSNumber) { super.init(number: number) } /// Parse the given string as a Decimal128. /// /// Strings which cannot be parsed as a Decimal128 return a value where `isNaN` is `true`. /// /// - parameter string: The string to parse. public override required init(string: String) { super.init(string: string) } /// Creates a new Decimal128 by decoding from the given decoder. /// /// This initializer throws an error if the decoder is invalid or does not decode to a value which can be converted to Decimal128. /// /// - Parameter decoder: The decoder to read data from. public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let strValue = try? container.decode(String.self) { super.init(string: strValue) } else if let intValue = try? container.decode(Int64.self) { super.init(number: intValue as NSNumber) } else if let doubleValue = try? container.decode(Double.self) { super.init(number: doubleValue as NSNumber) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot convert value to Decimal128") } } /// The minimum value for Decimal128 public static var min: Decimal128 { unsafeDowncast(__minimumDecimalNumber, to: Self.self) } /// The maximum value for Decimal128 public static var max: Decimal128 { unsafeDowncast(__maximumDecimalNumber, to: Self.self) } } extension Decimal128: Encodable { /// Encodes this Decimal128 to the given encoder. /// /// This function throws an error if the given encoder is unable to encode a string. /// /// - Parameter encoder: The encoder to write data to. public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(stringValue) } } extension Decimal128: ExpressibleByIntegerLiteral { /// Creates a new Decimal128 from the given integer literal. public convenience init(integerLiteral value: Int64) { self.init(number: value as NSNumber) } } extension Decimal128: ExpressibleByFloatLiteral { /// Creates a new Decimal128 from the given float literal. public convenience init(floatLiteral value: Double) { self.init(number: value as NSNumber) } } extension Decimal128: ExpressibleByStringLiteral { /// Creates a new Decimal128 from the given string literal. public convenience init(stringLiteral value: String) { self.init(string: value) } } extension Decimal128: Comparable { /// Returns a Boolean value indicating whether two decimal128 values are equal. /// /// - Parameters: /// - lhs: A Decimal128 value to compare. /// - rhs: Another Decimal128 value to compare. public static func == (lhs: Decimal128, rhs: Decimal128) -> Bool { lhs.isEqual(rhs) } /// Returns a Boolean value indicating whether the decimal128 value of the first /// argument is less than that of the second argument. /// /// - Parameters: /// - lhs: A Decimal128 value to compare. /// - rhs: Another Decimal128 value to compare. public static func < (lhs: Decimal128, rhs: Decimal128) -> Bool { lhs.isLessThan(rhs) } /// Returns a Boolean value indicating whether the decimal128 value of the first /// argument is less than or equal to that of the second argument. /// /// - Parameters: /// - lhs: A Decimal128 value to compare. /// - rhs: Another Decimal128 value to compare. public static func <= (lhs: Decimal128, rhs: Decimal128) -> Bool { lhs.isLessThanOrEqual(to: rhs) } /// Returns a Boolean value indicating whether the decimal128 value of the first /// argument is greater than or equal to that of the second argument. /// /// - Parameters: /// - lhs: A Decimal128 value to compare. /// - rhs: Another Decimal128 value to compare. public static func >= (lhs: Decimal128, rhs: Decimal128) -> Bool { lhs.isGreaterThanOrEqual(to: rhs) } /// Returns a Boolean value indicating whether the decimal128 value of the first /// argument is greater than that of the second argument. /// /// - Parameters: /// - lhs: A Decimal128 value to compare. /// - rhs: Another Decimal128 value to compare. public static func > (lhs: Decimal128, rhs: Decimal128) -> Bool { lhs.isGreaterThan(rhs) } } extension Decimal128: Numeric { /// Creates a new instance from the given integer, if it can be represented /// exactly. /// /// If the value passed as `source` is not representable exactly, the result /// is `nil`. In the following example, the constant `x` is successfully /// created from a value of `100`, while the attempt to initialize the /// constant `y` from `1_000` fails because the `Int8` type can represent /// `127` at maximum: /// /// - Parameter source: A value to convert to this type of integer. public convenience init?(exactly source: T) where T: BinaryInteger { self.init(value: source) } /// A type that can represent the absolute value of Decimal128 public typealias Magnitude = Decimal128 /// The magnitude of this Decimal128. public var magnitude: Magnitude { unsafeDowncast(self.__magnitude, to: Magnitude.self) } /// Adds two decimal128 values and produces their sum. /// /// - Parameters: /// - lhs: The first Decimal128 value to add. /// - rhs: The second Decimal128 value to add. public static func + (lhs: Decimal128, rhs: Decimal128) -> Decimal128 { unsafeDowncast(lhs.decimalNumber(byAdding: rhs), to: Decimal128.self) } /// Subtracts one Decimal128 value from another and produces their difference. /// /// - Parameters: /// - lhs: A Decimal128 value. /// - rhs: The Decimal128 value to subtract from `lhs`. public static func - (lhs: Decimal128, rhs: Decimal128) -> Decimal128 { unsafeDowncast(lhs.decimalNumber(bySubtracting: rhs), to: Decimal128.self) } /// Multiplies two Decimal128 values and produces their product. /// /// - Parameters: /// - lhs: The first value to multiply. /// - rhs: The second value to multiply. public static func * (lhs: Decimal128, rhs: Decimal128) -> Decimal128 { unsafeDowncast(lhs.decimalNumberByMultiplying(by: rhs), to: Decimal128.self) } /// Multiplies two values and stores the result in the left-hand-side /// variable. /// /// - Parameters: /// - lhs: The first value to multiply. /// - rhs: The second value to multiply. public static func *= (lhs: inout Decimal128, rhs: Decimal128) { lhs = lhs * rhs } /// Returns the quotient of dividing the first Decimal128 value by the second. /// /// - Parameters: /// - lhs: The Decimal128 value to divide. /// - rhs: The Decimal128 value to divide `lhs` by. `rhs` must not be zero. public static func / (lhs: Decimal128, rhs: Decimal128) -> Decimal128 { unsafeDowncast(lhs.decimalNumberByDividing(by: rhs), to: Decimal128.self) } } extension Decimal128 { /// A type that represents the distance between two values. public typealias Stride = Decimal128 /// Returns the distance from this Decimal128 to the given value, expressed as a /// stride. /// /// - Parameter other: The Decimal128 value to calculate the distance to. /// - Returns: The distance from this value to `other`. public func distance(to other: Decimal128) -> Decimal128 { unsafeDowncast(other.decimalNumber(bySubtracting: self), to: Decimal128.self) } /// Returns a Decimal128 that is offset the specified distance from this value. /// /// Use the `advanced(by:)` method in generic code to offset a value by a /// specified distance. If you're working directly with numeric values, use /// the addition operator (`+`) instead of this method. /// /// - Parameter n: The distance to advance this Decimal128. /// - Returns: A Decimal128 that is offset from this value by `n`. public func advanced(by n: Decimal128) -> Decimal128 { unsafeDowncast(decimalNumber(byAdding: n), to: Decimal128.self) } } extension Decimal128 { /// `true` if `self` is a signaling NaN, `false` otherwise. public var isSignaling: Bool { self.isNaN } /// `true` if `self` is a signaling NaN, `false` otherwise. public var isSignalingNaN: Bool { self.isSignaling } } ================================================ FILE: RealmSwift/EmbeddedObject.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import Realm import Realm.Private /** `EmbeddedObject` is a base class used to define embedded Realm model objects. Embedded objects work similarly to normal objects, but are owned by a single parent Object (which itself may be embedded). Unlike normal top-level objects, embedded objects cannot be directly created in or added to a Realm. Instead, they can only be created as part of a parent object, or by assigning an unmanaged object to a parent object's property. Embedded objects are automatically deleted when the parent object is deleted or when the parent is modified to no longer point at the embedded object, either by reassigning an Object property or by removing the embedded object from the List containing it. Embedded objects can only ever have a single parent object which links to them, and attempting to link to an existing managed embedded object will throw an exception. The property types supported on `EmbeddedObject` are the same as for `Object`, except for that embedded objects cannot link to top-level objects, so `Object` and `List` properties are not supported (`EmbeddedObject` and `List` *are*). Embedded objects cannot have primary keys or indexed properties. ```swift class Owner: Object { @Persisted var name: String @Persisted var dogs: List } class Dog: EmbeddedObject { @Persisted var name: String @Persisted var adopted: Bool @Persisted(originProperty: "dogs") var owner: LinkingObjects } ``` */ public typealias EmbeddedObject = RealmSwiftEmbeddedObject extension EmbeddedObject: _RealmCollectionValueInsideOptional { /// :nodoc: public static override func isEmbedded() -> Bool { true } // MARK: Initializers /** Creates an unmanaged instance of a Realm object. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an `Array` as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. An unmanaged embedded object can be added to a Realm by assigning it to a property of a managed object or by adding it to a managed List. - parameter value: The value used to populate the object. */ public convenience init(value: Any) { self.init() RLMInitializeWithValue(self, value, .partialPrivateShared()) } // MARK: Properties /// The Realm which manages the object, or `nil` if the object is unmanaged. public var realm: Realm? { if let rlmReam = RLMObjectBaseRealm(self) { return Realm(rlmReam) } return nil } /// The object schema which lists the managed properties for the object. public var objectSchema: ObjectSchema { return ObjectSchema(RLMObjectBaseObjectSchema(self)!) } /// Indicates if the object can no longer be accessed because it is now invalid. /// /// An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if /// `invalidate()` is called on that Realm. public override final var isInvalidated: Bool { return super.isInvalidated } /// A human-readable description of the object. open override var description: String { return super.description } /** WARNING: This is an internal helper method not intended for public use. It is not considered part of the public API. :nodoc: */ public override static func _getProperties() -> [RLMProperty] { ObjectUtil.getSwiftProperties(self) } // MARK: Object Customization /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. - warning: This function is only applicable to legacy property declarations using `@objc`. When using `@Persisted`, any properties not marked with `@Persisted` are automatically ignored. - returns: An array of property names to ignore. */ @objc open class func ignoredProperties() -> [String] { return [] } /** Override this method to specify a map of public-private property names. This will set a different persisted property name on the Realm, and allows using the public name for any operation with the property. (Ex: Queries, Sorting, ...). This very helpful if you need to map property names from your `Device Sync` JSON schema to local property names. ```swift class Person: EmbeddedObject { @Persisted var firstName: String @Persisted var birthDate: Date @Persisted var age: Int override class public func propertiesMapping() -> [String : String] { ["firstName": "first_name", "birthDate": "birth_date"] } } ``` - note: Only property that have a different column name have to be added to the properties mapping dictionary. - returns: A dictionary of public-private property names. */ @objc open override class func propertiesMapping() -> [String: String] { return [:] } /// :nodoc: @available(*, unavailable, renamed: "propertiesMapping", message: "`_realmColumnNames` private API is unavailable in our Swift SDK, please use the override `.propertiesMapping()` instead.") @objc open override class func _realmColumnNames() -> [String: String] { return [:] } // MARK: Key-Value Coding & Subscripting /// Returns or sets the value of the property with the given name. @objc open subscript(key: String) -> Any? { get { return RLMDynamicGetByName(self, key) } set { dynamicSet(object: self, key: key, value: newValue) } } // MARK: Notifications /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `List` and `Results`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { return _observe(on: queue, block) } #if compiler(<6) /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in await obj._observe(keyPaths: keyPaths, on: actor, block) } } /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #else /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in await obj._observe(keyPaths: keyPaths, on: actor, block) } } /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #endif // MARK: Dynamic list /** Returns a list of `DynamicObject`s for a given property name. - warning: This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use instance variables or cast the values returned from key-value coding. - parameter propertyName: The name of the property. - returns: A list of `DynamicObject`s. :nodoc: */ public func dynamicList(_ propertyName: String) -> List { let list = RLMDynamicGetByName(self, propertyName) as! RLMSwiftCollectionBase return List(collection: list._rlmCollection as! RLMArray) } // MARK: Comparison /** Returns whether two Realm objects are the same. Objects are considered the same if and only if they are both managed by the same Realm and point to the same underlying object in the database. - note: Equality comparison is implemented by `isEqual(_:)`. If the object type is defined with a primary key, `isEqual(_:)` behaves identically to this method. If the object type is not defined with a primary key, `isEqual(_:)` uses the `NSObject` behavior of comparing object identity. This method can be used to compare two objects for database equality whether or not their object type defines a primary key. - parameter object: The object to compare the receiver to. */ public func isSameObject(as object: EmbeddedObject?) -> Bool { return RLMObjectBaseAreEqual(self, object) } } extension EmbeddedObject: ThreadConfined { /** Indicates if this object is frozen. - see: `Object.freeze()` */ public var isFrozen: Bool { return realm?.isFrozen ?? false } /** Returns a frozen (immutable) snapshot of this object. The frozen copy is an immutable object which contains the same data as this object currently contains, but will not update when writes are made to the containing Realm. Unlike live objects, frozen objects can be accessed from any thread. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. - warning: This method can only be called on a managed object. */ public func freeze() -> Self { return realm!.freeze(self) } /** Returns a live (mutable) reference of this object. This method creates a managed accessor to a live copy of the same frozen object. Will return self if called on an already live object. */ public func thaw() -> Self? { return realm?.thaw(self) } } ================================================ FILE: RealmSwift/Error.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Realm extension Realm { /** Struct that describes the error codes within the Realm error domain. The values can be used to catch a variety of _recoverable_ errors, especially those happening when initializing a Realm instance. ```swift let realm: Realm? do { realm = try Realm() } catch Realm.Error.incompatibleLockFile { print("Realm Browser app may be attached to Realm on device?") } ``` */ public typealias Error = RLMError } extension Realm.Error { /// This error could be returned by completion block when no success and no error were produced public static let callFailed = Realm.Error(Realm.Error.fail, userInfo: [NSLocalizedDescriptionKey: "Call failed"]) /// The file URL which produced this error, or `nil` if not applicable public var fileURL: URL? { return (userInfo[NSFilePathErrorKey] as? String).flatMap(URL.init(fileURLWithPath:)) } } // MARK: Equatable #if compiler(>=6) extension Realm.Error: @retroactive Equatable {} #else extension Realm.Error: Equatable {} #endif ================================================ FILE: RealmSwift/Geospatial.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 CoreLocation import Realm /** A struct that represents the coordinates of a point formed by a latitude and a longitude value. * Latitude ranges between -90 and 90 degrees, inclusive. * Longitude ranges between -180 and 180 degrees, inclusive. Values outside this ranges will return nil when trying to create a `GeoPoint`. - note: There is no dedicated type to store Geospatial points, instead points should be stored as [GeoJson-shaped](https://www.mongodb.com/docs/manual/reference/geojson/) embedded object, as explained below. Geospatial queries (`geoWithin`) can only be executed in such a type of objects and will throw otherwise. Persisting geo points in Realm is currently done using duck-typing, which means that any model class with a specific **shape** can be queried as though it contained a geographical location. the following is required: * A String property with the value of **Point**: `@Persisted var type: String = "Point"`. * A List containing a Longitude/Latitude pair: `@Persisted private var coordinates: List`. The recommended approach is using an embedded object. ``` public class Location: EmbeddedObject { @Persisted private var coordinates: List @Persisted private var type: String = "Point" public var latitude: Double { return coordinates[1] } public var longitude: Double { return coordinates[0] } convenience init(_ latitude: Double, _ longitude: Double) { self.init() // Longitude comes first in the coordinates array of a GeoJson document coordinates.append(objectsIn: [longitude, latitude]) } } ``` - warning: This structure cannot be persisted and can only be used to build other geospatial shapes such as (`GeoBox`, `GeoPolygon` and `GeoCircle`). */ public typealias GeoPoint = RLMGeospatialPoint /** A class that represents a rectangle, that can be used in a geospatial `geoWithin`query. - warning: This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ public typealias GeoBox = RLMGeospatialBox public extension GeoBox { /// Initialize a `GeoBox`, with values for bottom left corner and top right corner. /// /// - Parameter bottomLeft: The bottom left corner of the rectangle. /// - Parameter topRight: The top right corner of the rectangle. convenience init?(bottomLeft: (Double, Double), topRight: (Double, Double)) { guard let bottomLeftPoint = GeoPoint(latitude: bottomLeft.0, longitude: bottomLeft.1), let topRightPoint = GeoPoint(latitude: topRight.0, longitude: topRight.1) else { return nil } self.init(bottomLeft: bottomLeftPoint, topRight: topRightPoint) } } /** A class that represents a polygon, that can be used in a geospatial `geoWithin`query. A `GeoPolygon` describes a shape conformed of and outer `Polygon`, called `outerRing`, and 0 or more inner `Polygon`s, called `holes`, which represents an unlimited number of internal holes inside the outer `Polygon`. A `Polygon` describes a shape conformed by at least three segments, where the last and the first `GeoPoint` must be the same to indicate a closed polygon (meaning you need at least 4 points to define a polygon). Inner holes in a `GeoPolygon` must be entirely inside the outer ring A `hole` has the following restrictions: - Holes may not cross, i.e. the boundary of a hole may not intersect both the interior and the exterior of any other hole. - Holes may not share edges, i.e. if a hole contains and edge AB, the no other hole may contain it. - Holes may share vertices, however no vertex may appear twice in a single hole. - No hole may be empty. - Only one nesting is allowed. - warning: This class cannot be persisted and can only be use within a geospatial `geoWithin` query. - warning: Altitude is not used in any of the query calculations. */ public typealias GeoPolygon = RLMGeospatialPolygon public extension GeoPolygon { /// Initialize a `GeoPolygon`, with values for bottom left corner and top right corner. /// /// Returns `nil` if the `GeoPoints` representing a polygon (outer ring or holes), don't have at least 4 points. /// Returns `nil` if the first and the last `GeoPoint` in a polygon are not the same. /// /// - Parameter outerRing: The polygon's external (outer) ring. /// - Parameter holes: The holes (if any) in the polygon. convenience init?(outerRing: [(Double, Double)], holes: [[(Double, Double)]] = []) { let outerRingPoints = outerRing.compactMap(GeoPoint.init) let holesPoints = holes.map { $0.compactMap(GeoPoint.init) } guard outerRing.count == outerRingPoints.count, zip(holes, holesPoints).allSatisfy({ $0.count == $1.count }) else { return nil } self.init(outerRing: outerRingPoints, holes: holesPoints) } /// Initialize a `GeoPolygon`, with values for bottom left corner and top right corner. /// /// Returns `nil` if the `GeoPoints` representing a polygon (outer ring or holes), don't have at least 4 points. /// Returns `nil` if the first and the last `GeoPoint` in a polygon are not the same. /// /// - Parameter outerRing: The polygon's external (outer) ring. /// - Parameter holes: The holes (if any) in the polygon. convenience init?(outerRing: [(Double, Double)], holes: [(Double, Double)]...) { self.init(outerRing: outerRing, holes: holes.map { $0 }) } } /** This structure is a helper to represent/convert a distance. It can be used in geospatial queries like those represented by a `GeoCircle` - warning: This structure cannot be persisted and can only be used to build other geospatial shapes */ public typealias Distance = RLMDistance /** A class that represents a circle, that can be used in a geospatial `geoWithin`query. - warning: This class cannot be persisted and can only be use within a geospatial `geoWithin` query. */ public typealias GeoCircle = RLMGeospatialCircle public extension GeoCircle { /// Initialize a `GeoCircle`, from its center and radius in radians. /// /// - Parameter center: Center of the circle. /// - Parameter radiusInRadians: The radius of the circle in radians. convenience init?(center: (Double, Double), radiusInRadians: Double) { guard let centerPoint = GeoPoint(latitude: center.0, longitude: center.1) else { return nil } self.init(center: centerPoint, radiusInRadians: radiusInRadians) } /// Initialize a `GeoCircle`, from its center and radius. /// /// - Parameter center: Center of the circle. /// - Parameter radius: Radius of the circle. convenience init?(center: (Double, Double), radius: Distance) { guard let centerPoint = GeoPoint(latitude: center.0, longitude: center.1) else { return nil } self.init(center: centerPoint, radius: radius) } } ================================================ FILE: RealmSwift/Impl/BasicTypes.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private // MARK: - Property Types extension Int: SchemaDiscoverable { public static var _rlmType: PropertyType { .int } } extension Int8: SchemaDiscoverable { public static var _rlmType: PropertyType { .int } } extension Int16: SchemaDiscoverable { public static var _rlmType: PropertyType { .int } } extension Int32: SchemaDiscoverable { public static var _rlmType: PropertyType { .int } } extension Int64: SchemaDiscoverable { public static var _rlmType: PropertyType { .int } } extension Bool: SchemaDiscoverable { public static var _rlmType: PropertyType { .bool } } extension Float: SchemaDiscoverable { public static var _rlmType: PropertyType { .float } } extension Double: SchemaDiscoverable { public static var _rlmType: PropertyType { .double } } extension String: SchemaDiscoverable { public static var _rlmType: PropertyType { .string } } extension Data: SchemaDiscoverable { public static var _rlmType: PropertyType { .data } } extension ObjectId: SchemaDiscoverable { public static var _rlmType: PropertyType { .objectId } } extension Decimal128: SchemaDiscoverable { public static var _rlmType: PropertyType { .decimal128 } } extension Date: SchemaDiscoverable { public static var _rlmType: PropertyType { .date } } extension UUID: SchemaDiscoverable { public static var _rlmType: PropertyType { .UUID } } extension AnyRealmValue: SchemaDiscoverable { public static var _rlmType: PropertyType { .any } public static func _rlmPopulateProperty(_ prop: RLMProperty) { if prop.optional { var type = "AnyRealmValue" if prop.array { type = "List" } else if prop.set { type = "MutableSet" } else if prop.dictionary { type = "Map" } throwRealmException("\(type) property '\(prop.name)' must not be marked as optional: nil values are represented as AnyRealmValue.none") } } } extension NSString: SchemaDiscoverable { public static var _rlmType: PropertyType { .string } } extension NSData: SchemaDiscoverable { public static var _rlmType: PropertyType { .data } } extension NSDate: SchemaDiscoverable { public static var _rlmType: PropertyType { .date } } // MARK: - Modern property getters/setters private protocol _Int: BinaryInteger, _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable { } extension _Int { @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self { return Self(RLMGetSwiftPropertyInt64(obj, key)) } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Self? { var gotValue = false let ret = RLMGetSwiftPropertyInt64Optional(obj, key, &gotValue) return gotValue ? Self(ret) : nil } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Self) { RLMSetSwiftPropertyInt64(obj, key, Int64(value)) } } extension Int: _Int { public typealias PersistedType = Int } extension Int8: _Int { public typealias PersistedType = Int8 } extension Int16: _Int { public typealias PersistedType = Int16 } extension Int32: _Int { public typealias PersistedType = Int32 } extension Int64: _Int { public typealias PersistedType = Int64 } extension Bool: _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable { public typealias PersistedType = Bool @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Bool { return RLMGetSwiftPropertyBool(obj, key) } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Bool? { var gotValue = false let ret = RLMGetSwiftPropertyBoolOptional(obj, key, &gotValue) return gotValue ? ret : nil } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Bool) { RLMSetSwiftPropertyBool(obj, key, (value)) } } extension Float: _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = Float @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Float { return RLMGetSwiftPropertyFloat(obj, key) } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Float? { var gotValue = false let ret = RLMGetSwiftPropertyFloatOptional(obj, key, &gotValue) return gotValue ? ret : nil } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Float) { RLMSetSwiftPropertyFloat(obj, key, (value)) } } extension Double: _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = Double @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Double { return RLMGetSwiftPropertyDouble(obj, key) } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Double? { var gotValue = false let ret = RLMGetSwiftPropertyDoubleOptional(obj, key, &gotValue) return gotValue ? ret : nil } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Double) { RLMSetSwiftPropertyDouble(obj, key, (value)) } } extension String: _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable { public typealias PersistedType = String @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> String { return RLMGetSwiftPropertyString(obj, key)! } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> String? { return RLMGetSwiftPropertyString(obj, key) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: String) { RLMSetSwiftPropertyString(obj, key, value) } } extension Data: _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = Data @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Data { return RLMGetSwiftPropertyData(obj, key)! } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Data? { return RLMGetSwiftPropertyData(obj, key) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Data) { RLMSetSwiftPropertyData(obj, key, value) } } extension ObjectId: _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable { public typealias PersistedType = ObjectId @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> ObjectId { return RLMGetSwiftPropertyObjectId(obj, key) as! ObjectId } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> ObjectId? { return RLMGetSwiftPropertyObjectId(obj, key).flatMap(failableStaticBridgeCast) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: ObjectId) { RLMSetSwiftPropertyObjectId(obj, key, (value)) } public static func _rlmDefaultValue() -> ObjectId { return Self.generate() } } extension Decimal128: _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = Decimal128 @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Decimal128 { return RLMGetSwiftPropertyDecimal128(obj, key) as! Decimal128 } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Decimal128? { return RLMGetSwiftPropertyDecimal128(obj, key).flatMap(failableStaticBridgeCast) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Decimal128) { RLMSetSwiftPropertyDecimal128(obj, key, value) } } extension Date: _PersistableInsideOptional, _DefaultConstructible, _Indexable { public typealias PersistedType = Date @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Date { return RLMGetSwiftPropertyDate(obj, key)! } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Date? { return RLMGetSwiftPropertyDate(obj, key) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Date) { RLMSetSwiftPropertyDate(obj, key, value) } } extension UUID: _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable { public typealias PersistedType = UUID @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> UUID { return RLMGetSwiftPropertyUUID(obj, key)! } @inlinable public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> UUID? { return RLMGetSwiftPropertyUUID(obj, key) } @inlinable public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: UUID) { RLMSetSwiftPropertyUUID(obj, key, value) } } extension AnyRealmValue: _Persistable, _DefaultConstructible { public typealias PersistedType = AnyRealmValue @inlinable public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> AnyRealmValue { return ObjectiveCSupport.convert(value: RLMGetSwiftPropertyAny(obj, key)) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: AnyRealmValue) { RLMSetSwiftPropertyAny(obj, key, value._rlmObjcValue as! RLMValue) } public static func _rlmSetAccessor(_ prop: RLMProperty) { prop.swiftAccessor = BridgedPersistedPropertyAccessor.self } } ================================================ FILE: RealmSwift/Impl/CollectionAccess.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm private func isSameCollection(_ lhs: RLMCollection, _ rhs: Any) -> Bool { // Managed isEqual checks if they're backed by the same core field, so it does exactly what we need if lhs.realm != nil { return lhs.isEqual(rhs) } // For unmanaged we want to check if the backing collection is the same instance if let rhs = rhs as? RLMSwiftCollectionBase { return lhs === rhs._rlmCollection } return lhs === rhs as AnyObject } internal protocol MutableRealmCollection { func assign(_ value: Any) // Unmanaged collection properties need a reference to their parent object for // KVO to work because the mutation is done via the collection object but the // observation is on the parent. func setParent(_ object: RLMObjectBase, _ property: RLMProperty) } extension List: MutableRealmCollection { func assign(_ value: Any) { guard !isSameCollection(_rlmCollection, value) else { return } RLMAssignToCollection(_rlmCollection, value) } func setParent(_ object: RLMObjectBase, _ property: RLMProperty) { rlmArray.setParent(object, property: property) } } extension MutableSet: MutableRealmCollection { func assign(_ value: Any) { guard !isSameCollection(_rlmCollection, value) else { return } RLMAssignToCollection(_rlmCollection, value) } func setParent(_ object: RLMObjectBase, _ property: RLMProperty) { rlmSet.setParent(object, property: property) } } extension Map: MutableRealmCollection { func assign(_ value: Any) { guard !isSameCollection(_rlmCollection, value) else { return } rlmDictionary.setDictionary(value) } func setParent(_ object: RLMObjectBase, _ property: RLMProperty) { rlmDictionary.setParent(object, property: property) } } ================================================ FILE: RealmSwift/Impl/ComplexTypes.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private extension Object: SchemaDiscoverable, _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = Object public static var _rlmType: PropertyType { .object } public static func _rlmPopulateProperty(_ prop: RLMProperty) { if !prop.optional && !prop.collection { throwRealmException("Object property '\(prop.name)' must be marked as optional.") } if prop.optional && prop.array { throwRealmException("List<\(className())> property '\(prop.name)' must not be marked as optional.") } if prop.optional && prop.set { throwRealmException("MutableSet<\(className())> property '\(prop.name)' must not be marked as optional.") } if !prop.optional && prop.dictionary { throwRealmException("Map property '\(prop.name)' must be marked as optional.") } prop.objectClassName = className() } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Self { fatalError("Non-optional Object properties are not allowed.") } public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: UInt16) -> Self? { // FIXME: gives Assertion failed: (LocalSelf && "no local self metadata"), function getLocalSelfMetadata, file /src/swift-source/swift/lib/IRGen/GenHeap.cpp, line 1686. // return RLMGetSwiftPropertyObject(obj, key).map(dynamicBridgeCast) if let value = RLMGetSwiftPropertyObject(obj, key) { return (value as! Self) } return nil } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: Object) { RLMSetSwiftPropertyObject(obj, key, value) } } extension EmbeddedObject: SchemaDiscoverable, _PersistableInsideOptional, _DefaultConstructible { public typealias PersistedType = EmbeddedObject public static var _rlmType: PropertyType { .object } public static func _rlmPopulateProperty(_ prop: RLMProperty) { Object._rlmPopulateProperty(prop) prop.objectClassName = className() } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Self { if let value = RLMGetSwiftPropertyObject(obj, key) { return value as! Self } return Self() } public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: UInt16) -> Self? { if let value = RLMGetSwiftPropertyObject(obj, key) { return (value as! Self) } return nil } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: EmbeddedObject) { RLMSetSwiftPropertyObject(obj, key, value) } } extension List: _RealmSchemaDiscoverable, SchemaDiscoverable where Element: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { Element._rlmType } public static var _rlmOptional: Bool { Element._rlmOptional } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { prop.array = true prop.swiftAccessor = ListAccessor.self Element._rlmPopulateProperty(prop) } } extension List: _HasPersistedType, _Persistable, _DefaultConstructible where Element: _Persistable { public typealias PersistedType = List public static var _rlmRequiresCaching: Bool { true } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Self { return Self(collection: RLMGetSwiftPropertyArray(obj, key)) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: List) { let array = RLMGetSwiftPropertyArray(obj, key) if array.isEqual(value.rlmArray) { return } array.removeAllObjects() array.addObjects(value.rlmArray) } public static func _rlmSetAccessor(_ prop: RLMProperty) { prop.swiftAccessor = PersistedListAccessor.self } } extension MutableSet: _RealmSchemaDiscoverable, SchemaDiscoverable where Element: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { Element._rlmType } public static var _rlmOptional: Bool { Element._rlmOptional } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { prop.set = true prop.swiftAccessor = SetAccessor.self Element._rlmPopulateProperty(prop) } } extension MutableSet: _HasPersistedType, _Persistable, _DefaultConstructible where Element: _Persistable { public typealias PersistedType = MutableSet public static var _rlmRequiresCaching: Bool { true } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Self { return Self(collection: RLMGetSwiftPropertySet(obj, key)) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: MutableSet) { let set = RLMGetSwiftPropertySet(obj, key) if set.isEqual(value.rlmSet) { return } set.removeAllObjects() set.addObjects(value.rlmSet) } public static func _rlmSetAccessor(_ prop: RLMProperty) { prop.swiftAccessor = PersistedSetAccessor.self } } extension Map: _RealmSchemaDiscoverable, SchemaDiscoverable where Value: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { Value._rlmType } public static var _rlmOptional: Bool { Value._rlmOptional } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { prop.dictionary = true prop.swiftAccessor = MapAccessor.self prop.dictionaryKeyType = Key._rlmType Value._rlmPopulateProperty(prop) } } extension Map: _HasPersistedType, _Persistable, _DefaultConstructible where Value: _Persistable { public typealias PersistedType = Map public static var _rlmRequiresCaching: Bool { true } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Self { return Self(objc: RLMGetSwiftPropertyMap(obj, key)) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: Map) { let map = RLMGetSwiftPropertyMap(obj, key) if map.isEqual(value.rlmDictionary) { return } map.removeAllObjects() map.addEntries(fromDictionary: value.rlmDictionary) } public static func _rlmSetAccessor(_ prop: RLMProperty) { prop.swiftAccessor = PersistedMapAccessor.self } } extension LinkingObjects: SchemaDiscoverable { public static var _rlmType: PropertyType { .linkingObjects } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { prop.array = true prop.objectClassName = Element.className() prop.swiftAccessor = LinkingObjectsAccessor.self if prop.linkOriginPropertyName == nil { throwRealmException("LinkingObjects<\(prop.objectClassName!)> property '\(prop.name)' must set the origin property name with @Persisted(originProperty: \"name\").") } } public func _rlmPopulateProperty(_ prop: RLMProperty) { prop.linkOriginPropertyName = self.propertyName } } @available(*, deprecated) extension RealmOptional: SchemaDiscoverable, _RealmSchemaDiscoverable where Value: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { Value._rlmType } public static var _rlmOptional: Bool { true } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { Value._rlmPopulateProperty(prop) prop.swiftAccessor = RealmOptionalAccessor.self } } extension LinkingObjects: _HasPersistedType, _Persistable where Element: _Persistable { public typealias PersistedType = Self public static func _rlmDefaultValue() -> Self { fatalError("LinkingObjects properties must set the origin property name") } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> LinkingObjects { let prop = RLMObjectBaseObjectSchema(obj)!.computedProperties[Int(key)] return Self(propertyName: prop.name, handle: RLMLinkingObjectsHandle(object: obj, property: prop)) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: LinkingObjects) { fatalError("LinkingObjects properties are read-only") } public static func _rlmSetAccessor(_ prop: RLMProperty) { prop.swiftAccessor = PersistedLinkingObjectsAccessor.self } } extension Optional: SchemaDiscoverable, _RealmSchemaDiscoverable where Wrapped: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { Wrapped._rlmType } public static var _rlmOptional: Bool { true } public static func _rlmPopulateProperty(_ prop: RLMProperty) { Wrapped._rlmPopulateProperty(prop) } } extension Optional: _HasPersistedType where Wrapped: _HasPersistedType { public typealias PersistedType = Wrapped.PersistedType? } extension Optional: _Persistable where Wrapped: _PersistableInsideOptional { public static func _rlmDefaultValue() -> Self { return .none } public static func _rlmGetProperty(_ obj: ObjectBase, _ key: UInt16) -> Wrapped? { return Wrapped._rlmGetPropertyOptional(obj, key) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: UInt16, _ value: Wrapped?) { if let value = value { Wrapped._rlmSetProperty(obj, key, value) } else { RLMSetSwiftPropertyNil(obj, key) } } public static func _rlmSetAccessor(_ prop: RLMProperty) { Wrapped._rlmSetAccessor(prop) } } extension Optional: _PrimaryKey where Wrapped: _Persistable, Wrapped.PersistedType: _PrimaryKey {} extension Optional: _Indexable where Wrapped: _Persistable, Wrapped.PersistedType: _Indexable {} extension RealmProperty: _RealmSchemaDiscoverable, SchemaDiscoverable { public static var _rlmType: PropertyType { Value._rlmType } public static var _rlmOptional: Bool { Value._rlmOptional } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { Value._rlmPopulateProperty(prop) prop.swiftAccessor = RealmPropertyAccessor.self } } extension RawRepresentable where RawValue: _RealmSchemaDiscoverable { public static var _rlmType: PropertyType { RawValue._rlmType } public static var _rlmOptional: Bool { RawValue._rlmOptional } public static var _rlmRequireObjc: Bool { false } public func _rlmPopulateProperty(_ prop: RLMProperty) { } public static func _rlmPopulateProperty(_ prop: RLMProperty) { RawValue._rlmPopulateProperty(prop) } } extension RawRepresentable where Self: _PersistableInsideOptional, RawValue: _PersistableInsideOptional { public typealias PersistedType = RawValue public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self { return Self(rawValue: RawValue._rlmGetProperty(obj, key))! } public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Self? { return RawValue._rlmGetPropertyOptional(obj, key).flatMap(Self.init) } public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Self) { RawValue._rlmSetProperty(obj, key, value.rawValue) } public static func _rlmSetAccessor(_ prop: RLMProperty) { if prop.optional { prop.swiftAccessor = BridgedPersistedPropertyAccessor>.self } else { prop.swiftAccessor = BridgedPersistedPropertyAccessor.self } } } ================================================ FILE: RealmSwift/Impl/KeyPathStrings.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm.Private /** Gets the components of a given key path as a string. - warning: Objects that declare properties with the old `@objc dynamic` syntax are not fully supported by this function, and it is recommended that you use `@Persisted` to declare your properties if you wish to use this function to its full benefit. Example: ``` let name = ObjectBase._name(for: \Person.dogs[0].name) // "dogs.name" // Note that the above KeyPath expression is only supported with properties declared // with `@Persisted`. let nested = ObjectBase._name(for: \Person.address.city.zip) // "address.city.zip" ``` */ public func _name(for keyPath: PartialKeyPath) -> String { return name(for: keyPath) } /** Gets the components of a given key path as a string. - warning: Objects that declare properties with the old `@objc dynamic` syntax are not fully supported by this function, and it is recommended that you use `@Persisted` to declare your properties if you wish to use this function to its full benefit. Example: ``` let name = PersonProjection._name(for: \PersonProjection.dogs[0].name) // "dogs.name" // Note that the above KeyPath expression is only supported with properties declared // with `@Persisted`. let nested = ObjectBase._name(for: \Person.address.city.zip) // "address.city.zip" ``` */ public func _name(for keyPath: PartialKeyPath) -> String where T: Projection { return name(for: keyPath) } private func name(for keyPath: PartialKeyPath) -> String { if let name = keyPath._kvcKeyPathString { return name } let names = NSMutableArray() let value = T.keyPathRecorder(with: names)[keyPath: keyPath] if let collection = value as? PropertyNameConvertible, let propertyInfo = collection.propertyInformation, propertyInfo.isLegacy { names.add(propertyInfo.key) } if let storage = value as? RLMSwiftValueStorage { names.add(RLMSwiftValueStorageGetPropertyName(storage)) } return names.componentsJoined(by: ".") } /// Create a valid element for a collection, as a keypath recorder if that type supports it. internal func elementKeyPathRecorder( for type: T.Type, with lastAccessedNames: NSMutableArray) -> T { if let type = type as? KeypathRecorder.Type { return type.keyPathRecorder(with: lastAccessedNames) as! T } return T._rlmDefaultValue() } // MARK: - Implementation /// Protocol which allows a collection to produce its property name internal protocol PropertyNameConvertible { /// A mutable array referenced from the enclosing parent that contains the last accessed property names. var lastAccessedNames: NSMutableArray? { get set } /// `key` is the property name for this collection. /// `isLegacy` will be true if the property is declared with old property syntax. var propertyInformation: (key: String, isLegacy: Bool)? { get } } internal protocol KeypathRecorder { // Return an instance of Self which is initialized for keypath recording // using the given target array. static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> Self } extension Optional: KeypathRecorder where Wrapped: KeypathRecorder { internal static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> Self { return Wrapped.keyPathRecorder(with: lastAccessedNames) } } extension ObjectBase: KeypathRecorder { internal static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> Self { let obj = Self() obj.lastAccessedNames = lastAccessedNames let objectSchema = ObjectSchema(RLMObjectBaseObjectSchema(obj)!) (objectSchema.rlmObjectSchema.properties + objectSchema.rlmObjectSchema.computedProperties) .map { (prop: $0, accessor: $0.swiftAccessor) } .forEach { $0.accessor?.observe($0.prop, on: obj) } return obj } } extension Projection: KeypathRecorder { internal static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> Self { let obj = Self(projecting: PersistedType()) obj.rootObject.lastAccessedNames = lastAccessedNames let objectSchema = ObjectSchema(RLMObjectBaseObjectSchema(obj.rootObject)!) (objectSchema.rlmObjectSchema.properties + objectSchema.rlmObjectSchema.computedProperties) .map { (prop: $0, accessor: $0.swiftAccessor) } .forEach { $0.accessor?.observe($0.prop, on: obj.rootObject) } return obj } } extension _DefaultConstructible { internal static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> Self { let obj = Self() if var obj = obj as? PropertyNameConvertible { obj.lastAccessedNames = lastAccessedNames } return obj } } extension List: KeypathRecorder where Element: _Persistable {} extension List: PropertyNameConvertible { var propertyInformation: (key: String, isLegacy: Bool)? { return (key: rlmArray.propertyKey, isLegacy: rlmArray.isLegacyProperty) } } extension Map: KeypathRecorder where Value: _Persistable {} extension Map: PropertyNameConvertible { var propertyInformation: (key: String, isLegacy: Bool)? { return (key: rlmDictionary.propertyKey, isLegacy: rlmDictionary.isLegacyProperty) } } extension MutableSet: KeypathRecorder where Element: _Persistable {} extension MutableSet: PropertyNameConvertible { var propertyInformation: (key: String, isLegacy: Bool)? { return (key: rlmSet.propertyKey, isLegacy: rlmSet.isLegacyProperty) } } extension LinkingObjects: KeypathRecorder where Element: _Persistable { static func keyPathRecorder(with lastAccessedNames: NSMutableArray) -> LinkingObjects { var obj = Self(propertyName: "", handle: nil) obj.lastAccessedNames = lastAccessedNames return obj } } extension LinkingObjects: PropertyNameConvertible { var propertyInformation: (key: String, isLegacy: Bool)? { guard let handle = handle else { return nil } return (key: handle._propertyKey, isLegacy: handle._isLegacyProperty) } } ================================================ FILE: RealmSwift/Impl/ObjcBridgeable.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm /// A type which can be bridged to and from Objective-C. /// /// Do not use this protocol or the functions it adds directly. public protocol _ObjcBridgeable { static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? var _rlmObjcValue: Any { get } } /// A type where the default logic suffices for bridging and we don't need to do anything special. internal protocol DefaultObjcBridgeable: _ObjcBridgeable {} extension DefaultObjcBridgeable { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { value as? Self } public var _rlmObjcValue: Any { self } } /// A type which needs custom logic, but doesn't care if it's being bridged inside an Optional internal protocol BuiltInObjcBridgeable: _ObjcBridgeable { static func _rlmFromObjc(_ value: Any) -> Self? } extension BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { return _rlmFromObjc(value) } } extension Bool: DefaultObjcBridgeable {} extension Int: DefaultObjcBridgeable {} extension Double: DefaultObjcBridgeable {} extension Date: DefaultObjcBridgeable {} extension String: DefaultObjcBridgeable {} extension Data: DefaultObjcBridgeable {} extension ObjectId: DefaultObjcBridgeable {} extension UUID: DefaultObjcBridgeable {} extension NSNumber: DefaultObjcBridgeable {} extension NSDate: DefaultObjcBridgeable {} extension ObjectBase: BuiltInObjcBridgeable { public class func _rlmFromObjc(_ value: Any) -> Self? { if let value = value as? Self { return value } if Self.self === DynamicObject.self, let object = value as? ObjectBase { // Without `as AnyObject` this will produce a warning which incorrectly // claims it could be replaced with `unsafeDowncast()` return unsafeBitCast(object as AnyObject, to: Self.self) } return nil } public var _rlmObjcValue: Any { self } } // `NSNumber as? T` coerces values which can't be exact represented for some // types and fails for others. We want to always coerce, for backwards // compatibility if nothing else. extension Float: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? NSNumber)?.floatValue } public var _rlmObjcValue: Any { return NSNumber(value: self) } } extension Int8: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? NSNumber)?.int8Value } public var _rlmObjcValue: Any { // Promote to Int before boxing as otherwise 0 and 1 will get treated // as Bool instead. return NSNumber(value: Int16(self)) } } extension Int16: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? NSNumber)?.int16Value } public var _rlmObjcValue: Any { return NSNumber(value: self) } } extension Int32: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? NSNumber)?.int32Value } public var _rlmObjcValue: Any { return NSNumber(value: self) } } extension Int64: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? NSNumber)?.int64Value } public var _rlmObjcValue: Any { return NSNumber(value: self) } } extension Optional: BuiltInObjcBridgeable, _ObjcBridgeable where Wrapped: _ObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { // ?? here gives the nonsensical error "Left side of nil coalescing operator '??' has non-optional type 'Wrapped?', so the right side is never used" if let value = Wrapped._rlmFromObjc(value, insideOptional: true) { return .some(value) } // We have a double-optional here and need to explicitly specify that we // successfully converted to `nil`, as opposed to failing to bridge. return .some(Self.none) } public var _rlmObjcValue: Any { if let value = self { return value._rlmObjcValue } return NSNull() } } extension Decimal128: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Decimal128? { if let value = value as? Decimal128 { return .some(value) } if let number = value as? NSNumber { return Decimal128(number: number) } if let str = value as? String { return Decimal128(string: str) } return .none } public var _rlmObjcValue: Any { return self } } extension AnyRealmValue: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { if let any = value as? Self { return any } if let any = value as? RLMValue { return ObjectiveCSupport.convert(value: any) } return Self?.none // We need to explicitly say which .none we want here } public var _rlmObjcValue: Any { return ObjectiveCSupport.convert(value: self) ?? NSNull() } } // MARK: - Collections extension Map: BuiltInObjcBridgeable { public var _rlmObjcValue: Any { _rlmCollection } public static func _rlmFromObjc(_ value: Any) -> Self? { (value as? RLMCollection).map(Self.init(collection:)) } } extension RealmCollectionImpl { public var _rlmObjcValue: Any { self.collection } public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { (value as? RLMCollection).map(Self.init(collection:)) } } extension LinkingObjects: _ObjcBridgeable {} extension Results: _ObjcBridgeable {} extension AnyRealmCollection: _ObjcBridgeable {} extension List: _ObjcBridgeable {} extension MutableSet: _ObjcBridgeable {} extension SectionedResults: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { (value as? RLMSectionedResults).map(Self.init(rlmSectionedResult:)) } public var _rlmObjcValue: Any { self.collection } } extension ResultsSection: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { (value as? RLMSection).map(Self.init(rlmSectionedResult:)) } public var _rlmObjcValue: Any { self.collection } } extension RLMSwiftCollectionBase { public static func == (lhs: RLMSwiftCollectionBase, rhs: RLMSwiftCollectionBase) -> Bool { return lhs.isEqual(rhs) } } #if compiler(>=6) extension RLMSwiftCollectionBase: @retroactive Equatable {} #else extension RLMSwiftCollectionBase: Equatable {} #endif extension Projection: BuiltInObjcBridgeable { public static func _rlmFromObjc(_ value: Any) -> Self? { return (value as? Root).map(Self.init(projecting:)) } public var _rlmObjcValue: Any { self.rootObject } } public protocol _PossiblyAggregateable: _ObjcBridgeable { associatedtype PersistedType } extension NSDate: _PossiblyAggregateable {} extension NSNumber: _PossiblyAggregateable {} ================================================ FILE: RealmSwift/Impl/Persistable.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private // An opaque identifier for each property on a class. Happens to currently be // the property's index in the object schema, but that's not something that any // of the Swift code should rely on. In the future it may make sense to change // this to the ColKey. public typealias PropertyKey = UInt16 // A tag protocol used in schema discovery to find @Persisted properties internal protocol DiscoverablePersistedProperty: _RealmSchemaDiscoverable {} public protocol _HasPersistedType: _ObjcBridgeable { // The type which is actually stored in the Realm. This is Self for types // we support directly, but may be a different type for enums and mapped types. associatedtype PersistedType: _ObjcBridgeable } // These two types need PersistedType for collection aggregate functions but // aren't persistable or valid collection types extension NSNumber: _HasPersistedType { public typealias PersistedType = NSNumber } extension NSDate: _HasPersistedType { public typealias PersistedType = NSDate } // A type which can be stored by the @Persisted property wrapper public protocol _Persistable: _RealmSchemaDiscoverable, _HasPersistedType where PersistedType: _Persistable, PersistedType.PersistedType.PersistedType == PersistedType.PersistedType { // Read a value of this type from the target object static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self // Set a value of this type on the target object static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: Self) // Set the swiftAccessor for this type if the default PersistedPropertyAccessor // is not suitable. static func _rlmSetAccessor(_ prop: RLMProperty) // Do the values of this type need to be cached on the Persisted? static var _rlmRequiresCaching: Bool { get } // Get the zero/empty/nil value for this type. Used to supply a default // when the user does not declare one in their model. static func _rlmDefaultValue() -> Self } extension _Persistable { public static var _rlmRequiresCaching: Bool { false } } // A type which can appear inside Optional in a @Persisted property public protocol _PersistableInsideOptional: _Persistable where PersistedType: _PersistableInsideOptional { // Read an optional value of this type from the target object static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> Self? } extension _PersistableInsideOptional { public static func _rlmSetAccessor(_ prop: RLMProperty) { if prop.optional { prop.swiftAccessor = PersistedPropertyAccessor>.self } else { prop.swiftAccessor = PersistedPropertyAccessor.self } } } // Default definition of _rlmDefaultValue used by everything exception for // Optional, which requires doing Optional.none rather than Optional(). public protocol _DefaultConstructible { init() } extension _Persistable where Self: _DefaultConstructible { public static func _rlmDefaultValue() -> Self { .init() } } ================================================ FILE: RealmSwift/Impl/PropertyAccessors.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private // Get a pointer to the given property's ivar on the object. This is similar to // object_getIvar() but returns a pointer to the value rather than the value. @_transparent private func ptr(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutableRawPointer { return Unmanaged.passUnretained(obj).toOpaque().advanced(by: property.swiftIvar) } // MARK: - Legacy Property Accessors internal class ListAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> List { return ptr(property, obj).assumingMemoryBound(to: List.self).pointee } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent)._rlmCollection = RLMManagedArray(parent: parent, property: property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).rlmArray.setParent(parent, property: property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent) } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).assign(value) } } internal class SetAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> MutableSet { return ptr(property, obj).assumingMemoryBound(to: MutableSet.self).pointee } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent)._rlmCollection = RLMManagedSet(parent: parent, property: property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).rlmSet.setParent(parent, property: property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent) } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).assign(value) } } internal class MapAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> Map { return ptr(property, obj).assumingMemoryBound(to: Map.self).pointee } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent)._rlmCollection = RLMManagedDictionary(parent: parent, property: property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).rlmDictionary.setParent(parent, property: property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent) } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).assign(value) } } internal class LinkingObjectsAccessor: RLMManagedPropertyAccessor where Element: RealmCollectionValue { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer> { return ptr(property, obj).assumingMemoryBound(to: LinkingObjects.self) } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).pointee.handle = RLMLinkingObjectsHandle(object: parent, property: property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { if parent.lastAccessedNames != nil { bound(property, parent).pointee.handle = RLMLinkingObjectsHandle(object: parent, property: property) } } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent).pointee } } @available(*, deprecated) internal class RealmOptionalAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> RealmOptional { return ptr(property, obj).assumingMemoryBound(to: RealmOptional.self).pointee } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { RLMInitializeManagedSwiftValueStorage(bound(property, parent), parent, property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { RLMInitializeUnmanagedSwiftValueStorage(bound(property, parent), parent, property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { let value = bound(property, parent).value return value._rlmObjcValue } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).value = Value._rlmFromObjc(value) } } internal class RealmPropertyAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> RealmProperty { return ptr(property, obj).assumingMemoryBound(to: RealmProperty.self).pointee } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { RLMInitializeManagedSwiftValueStorage(bound(property, parent), parent, property) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { RLMInitializeUnmanagedSwiftValueStorage(bound(property, parent), parent, property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent).value._rlmObjcValue } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).value = Value._rlmFromObjc(value)! } } // MARK: - Modern Property Accessors internal class PersistedPropertyAccessor: RLMManagedPropertyAccessor { fileprivate static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer> { return ptr(property, obj).assumingMemoryBound(to: Persisted.self) } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).pointee.initialize(parent, key: PropertyKey(property.index)) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).pointee.observe(parent, property: property) } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent).pointee.get(parent) } @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { guard let v = T._rlmFromObjc(value) else { throwRealmException("Could not convert value '\(value)' to type '\(T.self)'.") } bound(property, parent).pointee.set(parent, value: v) } } internal class PersistedListAccessor: PersistedPropertyAccessor> { @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).pointee.get(parent).assign(value) } // When promoting an existing object to managed we want to promote the existing // Swift collection object if it exists @objc override class func promote(_ property: RLMProperty, on parent: RLMObjectBase) { let key = PropertyKey(property.index) if let existing = bound(property, parent).pointee.initializeCollection(parent, key: key) { existing._rlmCollection = RLMGetSwiftPropertyArray(parent, key) } } } internal class PersistedSetAccessor: PersistedPropertyAccessor> { @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).pointee.get(parent).assign(value) } @objc override class func promote(_ property: RLMProperty, on parent: RLMObjectBase) { let key = PropertyKey(property.index) if let existing = bound(property, parent).pointee.initializeCollection(parent, key: key) { existing._rlmCollection = RLMGetSwiftPropertyArray(parent, key) } } } internal class PersistedMapAccessor: PersistedPropertyAccessor> { @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { bound(property, parent).pointee.get(parent).assign(value) } @objc override class func promote(_ property: RLMProperty, on parent: RLMObjectBase) { let key = PropertyKey(property.index) if let existing = bound(property, parent).pointee.initializeCollection(parent, key: key) { existing._rlmCollection = RLMGetSwiftPropertyMap(parent, PropertyKey(property.index)) } } } internal class PersistedLinkingObjectsAccessor: RLMManagedPropertyAccessor { private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer>> { return ptr(property, obj).assumingMemoryBound(to: Persisted>.self) } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { bound(property, parent).pointee.initialize(parent, key: PropertyKey(property.index)) } @objc override class func observe(_ property: RLMProperty, on parent: RLMObjectBase) { if parent.lastAccessedNames != nil { bound(property, parent).pointee.observe(parent, property: property) } } @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent).pointee.get(parent) } } // Dynamic getters return the Swift type for Collections, and the obj-c type // for enums and AnyRealmValue. This difference is probably a mistake but it's // a breaking change to adjust. internal class BridgedPersistedPropertyAccessor: PersistedPropertyAccessor { @objc override class func get(_ property: RLMProperty, on parent: RLMObjectBase) -> Any { return bound(property, parent).pointee.get(parent)._rlmObjcValue } } internal class CustomPersistablePropertyAccessor: BridgedPersistedPropertyAccessor { @objc override class func set(_ property: RLMProperty, on parent: RLMObjectBase, to value: Any) { if coerceToNil(value) == nil { super.set(property, on: parent, to: T._rlmDefaultValue()) } else { super.set(property, on: parent, to: value) } } } ================================================ FILE: RealmSwift/Impl/RealmCollectionImpl.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm // RealmCollectionImpl implements all of the RealmCollection protocol except for // description and single-element subscript. description actually varies between // the collection wrappers, and Sequence infers its associated types from subscript, // so moving that here requires defining those explicitly in each collection. // // The functions don't need to be documented here because Xcode/DocC inherit // the documentation from the RealmCollection protocol definition, and jazzy // excludes this file entirely. internal protocol RealmCollectionImpl: RealmCollection where Index == Int, SubSequence == Slice, Iterator == RLMIterator { var collection: RLMCollection { get } init(collection: RLMCollection) } extension RealmCollectionImpl { public var realm: Realm? { collection.realm.map(Realm.init) } public var isInvalidated: Bool { collection.isInvalidated } public var count: Int { Int(collection.count) } public subscript(bounds: Range) -> SubSequence { return SubSequence(base: self, bounds: bounds) } public var first: Element? { return collection.firstObject!().map(staticBridgeCast) } public var last: Element? { return collection.lastObject!().map(staticBridgeCast) } public func objects(at indexes: IndexSet) -> [Element] { guard let r = collection.objects!(at: indexes) else { throwRealmException("Indexes for collection are out of bounds.") } return r.map(staticBridgeCast) } public func index(of object: Element) -> Int? { if let indexOf = collection.index(of:) { return notFoundToNil(index: indexOf(staticBridgeCast(fromSwift: object) as AnyObject)) } fatalError("Collection does not support index(of:)") } public func index(matching predicate: NSPredicate) -> Int? { if let indexMatching = collection.indexOfObject(with:) { return notFoundToNil(index: indexMatching(predicate)) } fatalError("Collection does not support index(matching:)") } public func filter(_ predicate: NSPredicate) -> Results { return Results(collection.objects(with: predicate)) } public func sorted(by sortDescriptors: S) -> Results where S.Iterator.Element == SortDescriptor { return Results(collection.sortedResults(using: sortDescriptors.map { $0.rlmSortDescriptorValue })) } public func distinct(by keyPaths: S) -> Results where S.Iterator.Element == String { return Results(collection.distinctResults(usingKeyPaths: Array(keyPaths))) } public func min(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return collection.min(ofProperty: property).map(staticBridgeCast) } public func max(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return collection.max(ofProperty: property).map(staticBridgeCast) } public func sum(ofProperty property: String) -> T where T.PersistedType: AddableType { return staticBridgeCast(fromObjectiveC: collection.sum(ofProperty: property)) } public func average(ofProperty property: String) -> T? where T.PersistedType: AddableType { return collection.average(ofProperty: property).map(staticBridgeCast) } public func value(forKey key: String) -> Any? { return collection.value(forKey: key) } public func value(forKeyPath keyPath: String) -> Any? { return collection.value(forKeyPath: keyPath) } public func setValue(_ value: Any?, forKey key: String) { return collection.setValue(value, forKey: key) } public func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (RealmCollectionChange) -> Void) -> NotificationToken { // We want to pass the same object instance to the change callback each time. // If the callback is being called on the source thread the instance should // be `self`, but if it's on a different thread it needs to be a new Swift // wrapper for the obj-c type, which we'll construct the first time the // callback is called. var col: Self? func wrapped(collection: RLMCollection?, change: RLMCollectionChange?, error: Error?) { if col == nil, let collection = collection { col = self.collection === collection ? self : Self(collection: collection) } block(.init(value: col, change: change, error: error)) } return collection.addNotificationBlock(wrapped, keyPaths: keyPaths, queue: queue) } #if compiler(<6) @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [String]?, on actor: A, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } #else @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [String]?, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } #endif public var isFrozen: Bool { return collection.isFrozen } public func freeze() -> Self { return Self(collection: collection.freeze()) } public func thaw() -> Self? { return Self(collection: collection.thaw()) } public func sectioned(sortDescriptors: [SortDescriptor], _ keyBlock: @escaping ((Element) -> Key)) -> SectionedResults { if sortDescriptors.isEmpty { throwRealmException("There must be at least one SortDescriptor when using SectionedResults.") } let sectionedResults = collection.sectionedResults(using: sortDescriptors.map(ObjectiveCSupport.convert)) { value in return keyBlock(Element._rlmFromObjc(value)!)._rlmObjcValue as? RLMValue } return SectionedResults(rlmSectionedResult: sectionedResults) } } // A helper protocol which lets us check for Optional in where clauses public protocol OptionalProtocol { associatedtype Wrapped func _rlmInferWrappedType() -> Wrapped } extension Optional: OptionalProtocol { public func _rlmInferWrappedType() -> Wrapped { return self! } } // `with(object, on: actor) { object, actor in ... }` hands the object over // to the given actor and then invokes the callback within the actor. #if compiler(<6) @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor internal func with( _ value: Value, on actor: A, _ block: @Sendable @escaping (isolated A, Value) async throws -> NotificationToken ) async rethrows -> NotificationToken { if value.realm == nil { fatalError("Change notifications are only supported for managed objects") } let tsr = ThreadSafeReference(to: value) let config = Unchecked(wrappedValue: value.realm!.rlmRealm.configurationSharingSchema()) return try await actor.invoke { actor in if Task.isCancelled { return nil } let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let realm = Realm(try! RLMRealm(configuration: config.wrappedValue, confinedTo: scheduler)) guard let value = tsr.resolve(in: realm) else { return nil } // This is safe but 5.10's sendability checking can't prove it // nonisolated(unsafe) can't be applied to a let in guard so we need // a second variable nonisolated(unsafe) let v = value return try await block(actor, v) } ?? NotificationToken() } #else @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) internal func with( _ value: Value, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, Value) async throws -> NotificationToken? ) async rethrows -> NotificationToken { if value.realm == nil { fatalError("Change notifications are only supported for managed objects") } let tsr = ThreadSafeReference(to: value) nonisolated(unsafe) let config = value.realm!.rlmRealm.configurationSharingSchema() return try await actor.invoke { actor in if Task.isCancelled { return nil } let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let realm = Realm(try! RLMRealm(configuration: config, confinedTo: scheduler)) guard let value = tsr.resolve(in: realm) else { return nil } return try await block(actor, value) } ?? NotificationToken() } #endif ================================================ FILE: RealmSwift/Impl/SchemaDiscovery.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm import Realm.Private // A type which we can get the runtime schema information from public protocol _RealmSchemaDiscoverable { // The Realm property type associated with this type static var _rlmType: PropertyType { get } static var _rlmOptional: Bool { get } // Does this type require @objc for legacy declarations? Not used for modern // declarations as no types use @objc. static var _rlmRequireObjc: Bool { get } // Set any fields of the property applicable to this type other than type/optional. // There are both static and non-static versions of this function because // some times need data from an instance (e.g. LinkingObjects, where the // source property name is runtime data and not part of the type), while // wrappers like Optional need to be able to recur to the wrapped type // without creating an instance of that. func _rlmPopulateProperty(_ prop: RLMProperty) 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 } public static var _rlmRequireObjc: Bool { true } public func _rlmPopulateProperty(_ prop: RLMProperty) { } public static func _rlmPopulateProperty(_ prop: RLMProperty) { } } extension RLMProperty { internal convenience init(name: String, value: _RealmSchemaDiscoverable) { let valueType = Swift.type(of: value) self.init() self.name = name self.type = valueType._rlmType self.optional = valueType._rlmOptional value._rlmPopulateProperty(self) valueType._rlmPopulateProperty(self) if valueType._rlmRequireObjc { self.updateAccessors() } } /// Exposed for Macros. /// Important: Keep args in same order & default value as `@Persisted` property wrapper @_spi(RealmSwiftPrivate) public convenience init( 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] { let columnNames: [String: String] = type(of: object).propertiesMapping() return Mirror(reflecting: object).children.compactMap { prop in guard let label = prop.label else { return nil } guard let value = prop.value as? DiscoverablePersistedProperty else { return nil } let property = RLMProperty(name: label, value: value) property.swiftIvar = ivar_getOffset(class_getInstanceVariable(type(of: object), label)!) property.columnName = columnNames[property.name] return property } } // If the property is a storage property for a lazy Swift property, return // the base property name (e.g. `foo.storage` becomes `foo`). Otherwise, nil. private func baseName(forLazySwiftProperty name: String) -> String? { // A Swift lazy var shows up as two separate children on the reflection tree: // one named 'x', and another that is optional and is named "$__lazy_storage_$_propName" if let storageRange = name.range(of: "$__lazy_storage_$_", options: [.anchored]) { return String(name[storageRange.upperBound...]) } return nil } private func getLegacyProperties(_ object: ObjectBase, _ cls: ObjectBase.Type) -> [RLMProperty] { let indexedProperties: Set let ignoredPropNames: Set let columnNames: [String: String] = type(of: object).propertiesMapping() if let realmObject = object as? Object { indexedProperties = Set(type(of: realmObject).indexedProperties()) ignoredPropNames = Set(type(of: realmObject).ignoredProperties()) } else if let realmEmbeddedObject = object as? EmbeddedObject { indexedProperties = Set() ignoredPropNames = Set(type(of: realmEmbeddedObject).ignoredProperties()) } else { indexedProperties = Set() ignoredPropNames = Set() } return Mirror(reflecting: object).children.filter { (prop: Mirror.Child) -> Bool in guard let label = prop.label else { return false } if ignoredPropNames.contains(label) { return false } if let lazyBaseName = baseName(forLazySwiftProperty: label) { if ignoredPropNames.contains(lazyBaseName) { return false } throwRealmException("Lazy managed property '\(lazyBaseName)' is not allowed on a Realm Swift object" + " class. Either add the property to the ignored properties list or make it non-lazy.") } return true }.compactMap { prop in guard let label = prop.label else { return nil } var rawValue = prop.value if let value = rawValue as? RealmEnum { rawValue = value._rlmObjcValue } guard let value = rawValue as? _RealmSchemaDiscoverable else { if class_getProperty(cls, label) != nil { throwRealmException("Property \(cls).\(label) is declared as \(type(of: prop.value)), which is not a supported managed Object property type. If it is not supposed to be a managed property, either add it to `ignoredProperties()` or do not declare it as `@objc dynamic`. See https://www.mongodb.com/docs/realm-sdks/swift/latest/Classes/Object.html for more information.") } if prop.value is RealmOptionalProtocol { throwRealmException("Property \(cls).\(label) has unsupported RealmOptional type \(type(of: prop.value)). Extending RealmOptionalType with custom types is not currently supported. ") } return nil } RLMValidateSwiftPropertyName(label) let valueType = type(of: value) let property = RLMProperty(name: label, value: value) property.indexed = indexedProperties.contains(property.name) property.columnName = columnNames[property.name] if let objcProp = class_getProperty(cls, label) { var count: UInt32 = 0 let attrs = property_copyAttributeList(objcProp, &count)! defer { free(attrs) } var computed = true for i in 0.. [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() let props = getModernProperties(object) if props.count > 0 { return props } return getLegacyProperties(object, cls) } internal class ObjectUtil { private static let runOnce: Void = { RLMSetSwiftBridgeCallback { (value: Any) -> Any? in // `as AnyObject` required on iOS <= 13; it will compile but silently // fail to cast otherwise if let value = value as AnyObject as? _ObjcBridgeable { return value._rlmObjcValue } return nil } }() internal class func getSwiftProperties(_ cls: RLMObjectBase.Type) -> [RLMProperty] { _ = ObjectUtil.runOnce return getProperties(cls) } } ================================================ FILE: RealmSwift/LinkingObjects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 Foundation import Realm /** `LinkingObjects` is an auto-updating container type. It represents zero or more objects that are linked to its owning model object through a property relationship. `LinkingObjects` can be queried with the same predicates as `List` and `Results`. `LinkingObjects` always reflects the current state of the Realm on the current thread, including during write transactions on the current thread. The one exception to this is when using `for...in` enumeration, which will always enumerate over the linking objects that were present when the enumeration is begun, even if some of them are deleted or modified to no longer link to the target object during the enumeration. `LinkingObjects` can only be used as a property on `Object` models. */ @frozen public struct LinkingObjects: RealmCollectionImpl { // MARK: Initializers /** Creates an instance of a `LinkingObjects`. This initializer should only be called when declaring a property on a Realm model. - parameter type: The type of the object owning the property the linking objects should refer to. - parameter propertyName: The property name of the property the linking objects should refer to. */ public init(fromType _: Element.Type, property propertyName: String) { self.propertyName = propertyName } /// A human-readable description of the objects represented by the linking objects. public var description: String { if realm == nil { var this = self return withUnsafePointer(to: &this) { return "LinkingObjects<\(Element.className())> <\($0)> (\n\n)" } } return RLMDescriptionWithMaxDepth("LinkingObjects", collection, RLMDescriptionMaxDepth) } // MARK: Object Retrieval /** Returns the object at the given `index`. - parameter index: The index. */ public subscript(index: Int) -> Element { if let lastAccessedNames = lastAccessedNames { return Element.keyPathRecorder(with: lastAccessedNames) } throwForNegativeIndex(index) return collection[UInt(index)] as! Element } // MARK: Equatable public static func == (lhs: LinkingObjects, rhs: LinkingObjects) -> Bool { lhs.collection.isEqual(rhs.collection) } // MARK: Implementation internal init(propertyName: String, handle: RLMLinkingObjectsHandle?) { self.propertyName = propertyName self.handle = handle } internal init(collection: RLMCollection) { self.propertyName = "" self.handle = RLMLinkingObjectsHandle(linkingObjects: collection as! RLMResults) } internal var collection: RLMCollection { return handle?.results ?? RLMResults.emptyDetached() } internal var propertyName: String internal var handle: RLMLinkingObjectsHandle? internal var lastAccessedNames: NSMutableArray? /// :nodoc: public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } } ================================================ FILE: RealmSwift/List.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import Realm.Private /** `List` is the container type in Realm used to define to-many relationships. Like Swift's `Array`, `List` is a generic type that is parameterized on the type it stores. This can be either an `Object` subclass or one of the following types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `String`, `Data`, `Date`, `Decimal128`, and `ObjectId` (and their optional versions) Unlike Swift's native collections, `List`s are reference types, and are only immutable if the Realm that manages them is opened as read-only. Lists can be filtered and sorted with the same predicates as `Results`. */ public final class List: RLMSwiftCollectionBase, RealmCollectionImpl { internal var lastAccessedNames: NSMutableArray? internal var rlmArray: RLMArray { unsafeDowncast(collection, to: RLMArray.self) } internal var collection: RLMCollection { _rlmCollection } // MARK: Initializers /// Creates a `List` that holds Realm model objects of type `Element`. public override init() { super.init() } /// :nodoc: public override init(collection: RLMCollection) { super.init(collection: collection) } // MARK: Object Retrieval /** Returns the object at the given index (get), or replaces the object at the given index (set). - warning: You can only set an object during a write transaction. - parameter index: The index of the object to retrieve or replace. */ public subscript(position: Int) -> Element { get { if let lastAccessedNames = lastAccessedNames { return elementKeyPathRecorder(for: Element.self, with: lastAccessedNames) } throwForNegativeIndex(position) return staticBridgeCast(fromObjectiveC: _rlmCollection.object(at: UInt(position))) } set { throwForNegativeIndex(position) rlmArray.replaceObject(at: UInt(position), with: staticBridgeCast(fromSwift: newValue) as AnyObject) } } // MARK: KVC /** Returns an `Array` containing the results of invoking `valueForKey(_:)` using `key` on each of the collection's objects. */ @nonobjc public func value(forKey key: String) -> [AnyObject] { return rlmArray.value(forKeyPath: key)! as! [AnyObject] } /** Returns an `Array` containing the results of invoking `valueForKeyPath(_:)` using `keyPath` on each of the collection's objects. - parameter keyPath: The key path to the property whose values are desired. */ @nonobjc public func value(forKeyPath keyPath: String) -> [AnyObject] { return rlmArray.value(forKeyPath: keyPath) as! [AnyObject] } // MARK: Mutation /** Appends the given object to the end of the list. If the object is managed by a different Realm than the receiver, a copy is made and added to the Realm managing the receiver. - warning: This method may only be called during a write transaction. - parameter object: An object. */ public func append(_ object: Element) { rlmArray.add(staticBridgeCast(fromSwift: object) as AnyObject) } /** Appends the objects in the given sequence to the end of the list. - warning: This method may only be called during a write transaction. */ public func append(objectsIn objects: S) where S.Iterator.Element == Element { for obj in objects { rlmArray.add(staticBridgeCast(fromSwift: obj) as AnyObject) } } /** Inserts an object at the given index. - warning: This method may only be called during a write transaction. - warning: This method will throw an exception if called with an invalid index. - parameter object: An object. - parameter index: The index at which to insert the object. */ public func insert(_ object: Element, at index: Int) { throwForNegativeIndex(index) rlmArray.insert(staticBridgeCast(fromSwift: object) as AnyObject, at: UInt(index)) } /** Removes an object at the given index. The object is not removed from the Realm that manages it. - warning: This method may only be called during a write transaction. - warning: This method will throw an exception if called with an invalid index. - parameter index: The index at which to remove the object. */ public func remove(at index: Int) { throwForNegativeIndex(index) rlmArray.removeObject(at: UInt(index)) } /** Removes all objects from the list. The objects are not removed from the Realm that manages them. - warning: This method may only be called during a write transaction. */ public func removeAll() { rlmArray.removeAllObjects() } /** Replaces an object at the given index with a new object. - warning: This method may only be called during a write transaction. - warning: This method will throw an exception if called with an invalid index. - parameter index: The index of the object to be replaced. - parameter object: An object. */ public func replace(index: Int, object: Element) { throwForNegativeIndex(index) rlmArray.replaceObject(at: UInt(index), with: staticBridgeCast(fromSwift: object) as AnyObject) } /** Moves the object at the given source index to the given destination index. - warning: This method may only be called during a write transaction. - warning: This method will throw an exception if called with invalid indices. - parameter from: The index of the object to be moved. - parameter to: index to which the object at `from` should be moved. */ public func move(from: Int, to: Int) { throwForNegativeIndex(from) throwForNegativeIndex(to) rlmArray.moveObject(at: UInt(from), to: UInt(to)) } /** Exchanges the objects in the list at given indices. - warning: This method may only be called during a write transaction. - warning: This method will throw an exception if called with invalid indices. - parameter index1: The index of the object which should replace the object at index `index2`. - parameter index2: The index of the object which should replace the object at index `index1`. */ public func swapAt(_ index1: Int, _ index2: Int) { throwForNegativeIndex(index1, parameterName: "index1") throwForNegativeIndex(index2, parameterName: "index2") rlmArray.exchangeObject(at: UInt(index1), withObjectAt: UInt(index2)) } @objc static func _unmanagedCollection() -> RLMArray { if let type = Element.self as? ObjectBase.Type { return RLMArray(objectClassName: type.className()) } if let type = Element.PersistedType.self as? ObjectBase.Type { return RLMArray(objectClassName: type.className()) } if let type = Element.PersistedType.self as? _RealmSchemaDiscoverable.Type { return RLMArray(objectType: type._rlmType, optional: type._rlmOptional) } fatalError("Collections of projections must be used with @Projected.") } /// :nodoc: @objc public override static func _backingCollectionType() -> AnyClass { RLMManagedArray.self } // Printable requires a description property defined in Swift (and not obj-c), // and it has to be defined as override, which can't be done in a // generic class. /// Returns a human-readable description of the objects contained in the List. @objc public override var description: String { return descriptionWithMaxDepth(RLMDescriptionMaxDepth) } @objc private func descriptionWithMaxDepth(_ depth: UInt) -> String { return RLMDescriptionWithMaxDepth("List", _rlmCollection, depth) } } extension List { /** Replace the given `subRange` of elements with `newElements`. - parameter subrange: The range of elements to be replaced. - parameter newElements: The new elements to be inserted into the List. */ public func replaceSubrange(_ subrange: R, with newElements: C) where C.Iterator.Element == Element, R: RangeExpression, List.Index == R.Bound { let subrange = subrange.relative(to: self) for _ in subrange.lowerBound.. /** Returns the objects at the given range (get), or replaces the objects at the given range with new objects (set). - warning: Objects may only be set during a write transaction. - parameter index: The index of the object to retrieve or replace. */ public subscript(bounds: Range) -> SubSequence { get { return SubSequence(base: self, bounds: bounds) } set { replaceSubrange(bounds.lowerBound..(contentsOf newElements: C, at i: Int) where C.Iterator.Element == Element { var currentIndex = i for item in newElements { insert(item, at: currentIndex) currentIndex += 1 } } /** Removes objects from the list at the given range. - warning: This method may only be called during a write transaction. */ public func removeSubrange(_ boundsExpression: R) where R: RangeExpression, List.Index == R.Bound { let bounds = boundsExpression.relative(to: self) for _ in bounds { remove(at: bounds.lowerBound) } } /// :nodoc: public func remove(atOffsets offsets: IndexSet) { for offset in offsets.reversed() { remove(at: offset) } } /// :nodoc: public func move(fromOffsets offsets: IndexSet, toOffset destination: Int) { for offset in offsets { var d = destination if destination > offset { d = destination - 1 } move(from: offset, to: d) } } /// :nodoc: public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } } // MARK: - Codable extension List: Decodable where Element: Decodable { public convenience init(from decoder: Decoder) throws { self.init() var container = try decoder.unkeyedContainer() while !container.isAtEnd { append(try container.decode(Element.self)) } } } extension List: Encodable where Element: Encodable {} ================================================ FILE: RealmSwift/Map.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm import Realm.Private /// :nodoc: public protocol _MapKey: Hashable, _ObjcBridgeable { static var _rlmType: RLMPropertyType { get } } extension String: _MapKey { } /** Map is a key-value storage container used to store supported Realm types. Map is a generic type that is parameterized on the type it stores. This can be either an Object subclass or one of the following types: Bool, Int, Int8, Int16, Int32, Int64, Float, Double, String, Data, Date, Decimal128, and ObjectId (and their optional versions) Map only supports `String` as a key. Realm disallows the use of `.` or `$` characters within a dictionary key. Unlike Swift's native collections, `Map`s is a reference types, and are only immutable if the Realm that manages them is opened as read-only. A Map can be filtered and sorted with the same predicates as `Results`. */ public final class Map: RLMSwiftCollectionBase { // MARK: Properties /// Contains the last accessed property names when tracing the key path. internal var lastAccessedNames: NSMutableArray? /// The Realm which manages the map, or `nil` if the map is unmanaged. public var realm: Realm? { return _rlmCollection.realm.map { Realm($0) } } /// Indicates if the map can no longer be accessed. public var isInvalidated: Bool { return _rlmCollection.isInvalidated } /// Returns all of the keys in this map. public var keys: [Key] { return rlmDictionary.allKeys.map(staticBridgeCast) } /// Returns all of the values in this map. public var values: [Value] { return rlmDictionary.allValues.map(staticBridgeCast) } // MARK: Initializers /// Creates a `Map` that holds Realm model objects of type `Value`. public override init() { super.init() } /// :nodoc: public override init(collection: RLMCollection) { super.init(collection: collection) } internal init(objc rlmDictionary: RLMDictionary) { super.init(collection: rlmDictionary) } // MARK: Count /// Returns the number of key-value pairs in this map. @objc public var count: Int { return Int(_rlmCollection.count) } // MARK: Mutation /** Updates the value stored in the map for the given key, or adds a new key-value pair if the key does not exist. - Note: If the value being added to the map is an unmanaged object and the map is managed then that unmanaged object will be added to the Realm. - warning: This method may only be called during a write transaction. - parameter value: a value's key path predicate. - parameter forKey: The direction to sort in. */ public func updateValue(_ value: Value, forKey key: Key) { rlmDictionary[objcKey(from: key)] = staticBridgeCast(fromSwift: value) as AnyObject } /** Merges the given dictionary into this map, using a combining closure to determine the value for any duplicate keys. If `dictionary` contains a key which is already present in this map, `combine` will be called with the value currently in the map and the value in the dictionary. The value returned by the closure will be stored in the map for that key. - Note: If a value being added to the map is an unmanaged object and the map is managed then that unmanaged object will be added to the Realm. - warning: This method may only be called on managed Maps during a write transaction. - parameter dictionary: The dictionary to merge into this map. - parameter combine: A closure that takes the current and new values for any duplicate keys. The closure returns the desired value for the final map. */ public func merge(_ sequence: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S: Sequence, S.Element == (key: Key, value: Value) { for (key, value) in sequence { let key = objcKey(from: key) var selectedValue: Value if let existing = rlmDictionary[key] { selectedValue = try combine(staticBridgeCast(fromObjectiveC: existing), value) } else { selectedValue = value } rlmDictionary[key] = staticBridgeCast(fromSwift: selectedValue) as AnyObject } } /** Removes the given key and its associated object, only if the key exists in the map. If the key does not exist, the map will not be modified. - warning: This method may only be called during a write transaction. */ public func removeObject(for key: Key) { rlmDictionary.removeObject(forKey: objcKey(from: key)) } /** Removes all objects from the map. The objects are not removed from the Realm that manages them. - warning: This method may only be called during a write transaction. */ public func removeAll() { rlmDictionary.removeAllObjects() } /** Returns the value for a given key, or sets a value for a key should the subscript be used for an assign. - Note:If the value being added to the map is an unmanaged object and the map is managed then that unmanaged object will be added to the Realm. - Note:If the value being assigned for a key is `nil` then that key will be removed from the map. - warning: This method may only be called during a write transaction. - parameter key: The key. */ public subscript(key: Key) -> Value? { get { if let lastAccessedNames = lastAccessedNames { return ((Value.self as! KeypathRecorder.Type).keyPathRecorder(with: lastAccessedNames) as! Value) } return rlmDictionary[objcKey(from: key)].map(staticBridgeCast) } set { if newValue == nil { rlmDictionary.removeObject(forKey: key as AnyObject) } else { rlmDictionary[objcKey(from: key)] = staticBridgeCast(fromSwift: newValue) as AnyObject } } } /** Returns a type of `AnyObject` for a specified key if it exists in the map. - parameter key: The key to the property whose values are desired. */ @objc public func object(forKey key: AnyObject) -> AnyObject? { return rlmDictionary.object(forKey: key as AnyObject) } // MARK: KVC /** Returns a type of `Value` for a specified key if it exists in the map. Note that when using key-value coding, the key must be a string. - parameter key: The key to the property whose values are desired. */ @nonobjc public func value(forKey key: String) -> AnyObject? { return rlmDictionary.value(forKey: key as AnyObject) .map(dynamicBridgeCast) } /** Returns a type of `Value` for a specified key if it exists in the map. - parameter keyPath: The key to the property whose values are desired. */ @nonobjc public func value(forKeyPath keyPath: String) -> AnyObject? { return rlmDictionary.value(forKeyPath: keyPath) .map(dynamicBridgeCast) } /** Adds a given key-value pair to the map or updates a given key should it already exist. - warning: This method can only be called during a write transaction. - parameter value: The object value. - parameter key: The name of the property whose value should be set on each object. */ public func setValue(_ value: Any?, forKey key: String) { rlmDictionary.setValue(value, forKey: key) } // MARK: Filtering /** Returns a `Results` containing all matching values in the map with the given predicate. - Note: This will return the values in the map, and not the key-value pairs. - parameter predicate: The predicate with which to filter the values. */ public func filter(_ predicate: NSPredicate) -> Results { return Results(rlmDictionary.objects(with: predicate)) } /** Returns a `Results` containing all matching values in the map with the given query. - Note: This should only be used with classes using the `@Persistable` property declaration. - Usage: ``` myMap.where { ($0.fooCol > 5) && ($0.barCol == "foobar") } ``` - Note: See ``Query`` for more information on what query operations are available. - parameter isIncluded: The query closure with which to filter the objects. */ public func `where`(_ isIncluded: ((Query) -> Query)) -> Results { return filter(isIncluded(Query()).predicate) } /** Returns a Boolean value indicating whether the Map contains the key-value pair satisfies the given predicate - parameter where: a closure that test if any key-pair of the given map represents the match. */ public func contains(where predicate: @escaping (_ key: Key, _ value: Value) -> Bool) -> Bool { var found = false rlmDictionary.enumerateKeysAndObjects { (k, v, shouldStop) in if predicate(staticBridgeCast(fromObjectiveC: k), staticBridgeCast(fromObjectiveC: v)) { found = true shouldStop.pointee = true } } return found } // MARK: Sorting /** Returns a `Results` containing the objects in the map, but sorted. Objects are sorted based on their values. For example, to sort a map of `Date`s from newest to oldest based, you might call `dates.sorted(ascending: true)`. - parameter ascending: The direction to sort in. */ public func sorted(ascending: Bool = true) -> Results { return sorted(byKeyPath: "self", ascending: ascending) } /** Returns a `Results` containing the objects in the map, but sorted. Objects are sorted based on the values of the given key path. For example, to sort a map of `Student`s from youngest to oldest based on their `age` property, you might call `students.sorted(byKeyPath: "age", ascending: true)`. - warning: Dictionaries may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - parameter keyPath: The key path to sort by. - parameter ascending: The direction to sort in. */ public func sorted(byKeyPath keyPath: String, ascending: Bool = true) -> Results { return sorted(by: [SortDescriptor(keyPath: keyPath, ascending: ascending)]) } /** Returns a `Results` containing the objects in the map, but sorted. - warning: Map's may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - see: `sorted(byKeyPath:ascending:)` */ public func sorted(by sortDescriptors: S) -> Results where S.Iterator.Element == SortDescriptor { return Results(_rlmCollection.sortedResults(using: sortDescriptors.map { $0.rlmSortDescriptorValue })) } // MARK: Aggregate Operations /** Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the map is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ public func min(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return rlmDictionary.min(ofProperty: property).map(staticBridgeCast) } /** Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the map is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ public func max(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return rlmDictionary.max(ofProperty: property).map(staticBridgeCast) } /** Returns the sum of the given property for objects in the collection, or `nil` if the map is empty. - warning: Only names of properties of a type conforming to the `AddableType` protocol can be used. - parameter property: The name of a property conforming to `AddableType` to calculate sum on. */ public func sum(ofProperty property: String) -> T where T.PersistedType: AddableType { return staticBridgeCast(fromObjectiveC: rlmDictionary.sum(ofProperty: property)) } /** Returns the average value of a given property over all the objects in the collection, or `nil` if the map is empty. - warning: Only a property whose type conforms to the `AddableType` protocol can be specified. - parameter property: The name of a property whose values should be summed. */ public func average(ofProperty property: String) -> T? where T.PersistedType: AddableType { return rlmDictionary.average(ofProperty: property).map(staticBridgeCast) } // MARK: Notifications /** Registers a block to be called each time the map changes. The block will be asynchronously called with the initial map, and then called again after each write transaction which changes either any of the keys or values in the map. The `change` parameter that is passed to the block reports, in the form of keys within the map, which of the key-value pairs were added, removed, or modified during each write transaction. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let myStringMap = myObject.stringMap print("myStringMap.count: \(myStringMap?.count)") // => 0 let token = myStringMap.observe { changes in switch changes { case .initial(let myStringMap): // Will print "myStringMap.count: 1" print("myStringMap.count: \(myStringMap.count)") print("Dog Name: \(myStringMap["nameOfDog"])") // => "Rex" break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { myStringMap["nameOfDog"] = "Rex" } ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the Map. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (RealmMapChange) -> Void) -> NotificationToken { var col: Map? let wrapped = { (collection: RLMDictionary?, change: RLMDictionaryChange?, error: Error?) in if col == nil, let collection = collection { col = collection === self._rlmCollection ? self : Self(objc: collection) } block(.fromObjc(value: col, change: change, error: error)) } return rlmDictionary.addNotificationBlock(wrapped, keyPaths: keyPaths, queue: queue) } #if compiler(<6) /** Registers a block to be called each time the map changes. The block will be asynchronously called on the actor with the initial map, and then called again after each write transaction which changes either which keys are present in the map or the values of any of the objects. The `change` parameter that is passed to the block reports, in the form of keys within the map, which of the key-value pairs were added, removed, or modified during each write transaction. Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection, and changes are only reported for writes which occur after the initial notification is delivered. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: ["name"], on: actor) { actor, changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // No longer possible and left for backwards compatibility } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the Map. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, RealmMapChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } /** Registers a block to be called each time the map changes. The block will be asynchronously called on the actor with the initial map, and then called again after each write transaction which changes either which keys are present in the map or the values of any of the objects. The `change` parameter that is passed to the block reports, in the form of keys within the map, which of the key-value pairs were added, removed, or modified during each write transaction. Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection, and changes are only reported for writes which occur after the initial notification is delivered. The block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: [\.name], on: actor) { actor, changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // No longer possible and left for backwards compatibility } } ``` - If the observed key path were `[\.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the Map. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, RealmMapChange) -> Void ) async -> NotificationToken where Value: OptionalProtocol, Value.Wrapped: ObjectBase { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #else /** Registers a block to be called each time the map changes. The block will be asynchronously called on the actor with the initial map, and then called again after each write transaction which changes either which keys are present in the map or the values of any of the objects. The `change` parameter that is passed to the block reports, in the form of keys within the map, which of the key-value pairs were added, removed, or modified during each write transaction. Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection, and changes are only reported for writes which occur after the initial notification is delivered. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: ["name"], on: actor) { actor, changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // No longer possible and left for backwards compatibility } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the Map. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, RealmMapChange) -> Void ) async -> NotificationToken { nonisolated(unsafe) let collection = self return await with(collection, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } /** Registers a block to be called each time the map changes. The block will be asynchronously called on the actor with the initial map, and then called again after each write transaction which changes either which keys are present in the map or the values of any of the objects. The `change` parameter that is passed to the block reports, in the form of keys within the map, which of the key-value pairs were added, removed, or modified during each write transaction. Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection, and changes are only reported for writes which occur after the initial notification is delivered. The block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: [\.name], on: actor) { actor, changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // No longer possible and left for backwards compatibility } } ``` - If the observed key path were `[\.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the Map. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, RealmMapChange) -> Void ) async -> NotificationToken where Value: OptionalProtocol, Value.Wrapped: ObjectBase { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #endif // MARK: Frozen Objects /** Indicates if the `Map` is frozen. Frozen `Map`s are immutable and can be accessed from any thread. Frozen `Map`s are created by calling `-freeze` on a managed live `Map`. Unmanaged `Map`s are never frozen. */ public var isFrozen: Bool { return _rlmCollection.isFrozen } /** Returns a frozen (immutable) snapshot of a `Map`. The frozen copy is an immutable `Map` which contains the same data as this `Map` currently contains, but will not update when writes are made to the containing Realm. Unlike live `Map`s, frozen `Map`s can be accessed from any thread. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - warning: This method may only be called on a managed `Map`. - warning: Holding onto a frozen `Map` for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `RLMRealmConfiguration.maximumNumberOfActiveVersions` for more information. */ public func freeze() -> Map { Map(objc: rlmDictionary.freeze()) } /** Returns a live version of this frozen `Map`. This method resolves a reference to a live copy of the same frozen `Map`. If called on a live `Map`, will return itself. */ public func thaw() -> Map? { Map(objc: rlmDictionary.thaw()) } @objc static func _unmanagedCollection() -> RLMDictionary { if let type = Value.self as? HasClassName.Type ?? Value.PersistedType.self as? HasClassName.Type { return RLMDictionary(objectClassName: type.className(), keyType: Key._rlmType) } if let type = Value.self as? _RealmSchemaDiscoverable.Type { return RLMDictionary(objectType: type._rlmType, optional: type._rlmOptional, keyType: Key._rlmType) } fatalError("Collections of projections must be used with @Projected.") } /// :nodoc: @objc public override static func _backingCollectionType() -> AnyClass { RLMManagedDictionary.self } /** Returns a human-readable description of the objects contained in the Map. */ @objc public override var description: String { return descriptionWithMaxDepth(RLMDescriptionMaxDepth) } @objc private func descriptionWithMaxDepth(_ depth: UInt) -> String { return RLMDictionaryDescriptionWithMaxDepth("Map", rlmDictionary, depth) } internal var rlmDictionary: RLMDictionary { _rlmCollection as! RLMDictionary } private func objcKey(from swiftKey: Key) -> AnyObject { return swiftKey as AnyObject } } // MARK: - Codable extension Map: Decodable where Key: Decodable, Value: Decodable { public convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.singleValueContainer() for (key, value) in try container.decode([Key: Value].self) { self[key] = value } } } extension Map: Encodable where Key: Encodable, Value: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.reduce(into: [Key: Value]()) { map, element in map[element.key] = element.value }) } } // MARK: Sequence Support extension Map: Sequence { /// Returns a `RLMMapIterator` that yields successive elements in the `Map`. public func makeIterator() -> RLMKeyValueIterator { return RLMKeyValueIterator(collection: rlmDictionary) } } // MARK: - Notifications // NEXT-MAJOR: remove this and make RealmCollectionChange get the key type from the collection /** A `RealmMapChange` value encapsulates information about changes to dictionaries that are reported by Realm notifications. */ @frozen public enum RealmMapChange { /** `.initial` indicates that the initial run of the query has completed (if applicable), and the collection can now be used without performing any blocking work. */ case initial(Collection) /** `.update` indicates that a write transaction has been committed which either changed which keys are in the collection, or the values of the objects for those keys in the collection, and/or modified one or more of the objects in the collection. - parameter deletions: The keys in the previous version of the collection which were removed from this one. - parameter insertions: The keys in the new collection which were added in this version. - parameter modifications: The keys of the objects in the new collection which were modified in this version. */ case update(Collection, deletions: [Collection.Key], insertions: [Collection.Key], modifications: [Collection.Key]) /** Errors can no longer occur. This case is unused and will be removed in the next major version. */ case error(Error) static func fromObjc(value: Collection?, change: RLMDictionaryChange?, error: Error?) -> RealmMapChange { if let error = error { return .error(error) } if let change = change { return .update(value!, deletions: change.deletions as! [Collection.Key], insertions: change.insertions as! [Collection.Key], modifications: change.modifications as! [Collection.Key]) } return .initial(value!) } } // MARK: - RealmKeyedCollection Conformance extension Map: RealmKeyedCollection { } // MARK: - MapIndex /// Container type which holds the offset of the element in the Map. public struct MapIndex { /// The position of the element in the Map. public var offset: UInt } // MARK: - SingleMapEntry /// Container for holding a single key-value entry in a Map. This is used where a tuple cannot be expressed as a generic argument. public struct SingleMapEntry: _RealmMapValue, Hashable { /// :nodoc: public static func == (lhs: SingleMapEntry, rhs: SingleMapEntry) -> Bool { return lhs.value == rhs.value } /// :nodoc: public func hash(into hasher: inout Hasher) { hasher.combine(key) } /// The key for this Map entry. public var key: Self.Key /// The value for this Map entry. public var value: Self.Value } private protocol HasClassName { static func className() -> String } extension ObjectBase: HasClassName {} extension Optional: HasClassName where Wrapped: ObjectBase { static func className() -> String { Wrapped.className() } } ================================================ FILE: RealmSwift/Migration.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import Realm.Private /** The type of a migration block used to migrate a Realm. - parameter migration: A `Migration` object used to perform the migration. The migration object allows you to enumerate and alter any existing objects which require migration. - parameter oldSchemaVersion: The schema version of the Realm being migrated. */ public typealias MigrationBlock = @Sendable (_ migration: Migration, _ oldSchemaVersion: UInt64) -> Void /// An object class used during migrations. public typealias MigrationObject = DynamicObject /** A block type which provides both the old and new versions of an object in the Realm. Object properties can only be accessed using subscripting. - parameter oldObject: The object from the original Realm (read-only). - parameter newObject: The object from the migrated Realm (read-write). */ public typealias MigrationObjectEnumerateBlock = (_ oldObject: MigrationObject?, _ newObject: MigrationObject?) -> Void /** Returns the schema version for a Realm at a given local URL. - parameter fileURL: Local URL to a Realm file. - parameter encryptionKey: 64-byte key used to encrypt the file, or `nil` if it is unencrypted. - throws: An `NSError` that describes the problem. */ public func schemaVersionAtURL(_ fileURL: URL, encryptionKey: Data? = nil) throws -> UInt64 { var error: NSError? let version = RLMRealm.__schemaVersion(at: fileURL, encryptionKey: encryptionKey, error: &error) guard version != RLMNotVersioned else { throw error! } return version } extension Realm { /** Performs the given Realm configuration's migration block on a Realm at the given path. This method is called automatically when opening a Realm for the first time and does not need to be called explicitly. You can choose to call this method to control exactly when and how migrations are performed. - parameter configuration: The Realm configuration used to open and migrate the Realm. */ public static func performMigration(for configuration: Realm.Configuration = Realm.Configuration.defaultConfiguration) throws { try RLMRealm.performMigration(for: configuration.rlmConfiguration) } } /** `Migration` instances encapsulate information intended to facilitate a schema migration. A `Migration` instance is passed into a user-defined `MigrationBlock` block when updating the version of a Realm. This instance provides access to the old and new database schemas, the objects in the Realm, and provides functionality for modifying the Realm during the migration. */ public typealias Migration = RLMMigration extension Migration { // MARK: Properties /// The old schema, describing the Realm before applying a migration. public var oldSchema: Schema { return Schema(__oldSchema) } /// The new schema, describing the Realm after applying a migration. public var newSchema: Schema { return Schema(__newSchema) } // MARK: Altering Objects During a Migration /** Enumerates all the objects of a given type in this Realm, providing both the old and new versions of each object. Properties on an object can be accessed using subscripting. - parameter objectClassName: The name of the `Object` class to enumerate. - parameter block: The block providing both the old and new versions of an object in this Realm. */ public func enumerateObjects(ofType typeName: String, _ block: MigrationObjectEnumerateBlock) { __enumerateObjects(typeName) { oldObject, newObject in block(unsafeBitCast(oldObject, to: MigrationObject.self), unsafeBitCast(newObject, to: MigrationObject.self)) } } /** Creates and returns an `Object` of type `className` in the Realm being migrated. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an `Array` as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. - parameter className: The name of the `Object` class to create. - parameter value: The value used to populate the created object. - returns: The newly created object. */ @discardableResult public func create(_ typeName: String, value: Any = [Any]()) -> MigrationObject { return unsafeBitCast(__createObject(typeName, withValue: value), to: MigrationObject.self) } /** Deletes an object from a Realm during a migration. It is permitted to call this method from within the block passed to `enumerate(_:block:)`. - parameter object: An object to be deleted from the Realm being migrated. */ public func delete(_ object: MigrationObject) { __delete(object.unsafeCastToRLMObject()) } /** Deletes the data for the class with the given name. All objects of the given class will be deleted. If the `Object` subclass no longer exists in your program, any remaining metadata for the class will be removed from the Realm file. - parameter objectClassName: The name of the `Object` class to delete. - returns: A Boolean value indicating whether there was any data to delete. */ @discardableResult public func deleteData(forType typeName: String) -> Bool { return __deleteData(forClassName: typeName) } /** Renames a property of the given class from `oldName` to `newName`. - parameter className: The name of the class whose property should be renamed. This class must be present in both the old and new Realm schemas. - parameter oldName: The old column name for the property to be renamed. There must not be a property with this name in the class as defined by the new Realm schema. - parameter newName: The new column name for the property to be renamed. There must not be a property with this name in the class as defined by the old Realm schema. */ public func renameProperty(onType typeName: String, from oldName: String, to newName: String) { __renameProperty(forClass: typeName, oldName: oldName, newName: newName) } } ================================================ FILE: RealmSwift/MutableSet.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import Realm import Realm.Private /** `MutableSet` is the container type in Realm used to define to-many relationships with distinct values as objects. Like Swift's `Set`, `MutableSet` is a generic type that is parameterized on the type it stores. This can be either an `Object` subclass or one of the following types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `String`, `Data`, `Date`, `Decimal128`, and `ObjectId` (and their optional versions) Unlike Swift's native collections, `MutableSet`s are reference types, and are only immutable if the Realm that manages them is opened as read-only. MutableSet's can be filtered and sorted with the same predicates as `Results`. */ public final class MutableSet: RLMSwiftCollectionBase, RealmCollectionImpl { internal var lastAccessedNames: NSMutableArray? internal var rlmSet: RLMSet { unsafeDowncast(_rlmCollection, to: RLMSet.self) } internal var collection: RLMCollection { _rlmCollection } // MARK: Initializers /// Creates a `MutableSet` that holds Realm model objects of type `Element`. public override init() { super.init() } /// :nodoc: public override init(collection: RLMCollection) { super.init(collection: collection) } // MARK: KVC /** Returns an `Array` containing the results of invoking `valueForKey(_:)` using `key` on each of the collection's objects. */ @nonobjc public func value(forKey key: String) -> [AnyObject] { return (rlmSet.value(forKeyPath: key)! as! NSSet).allObjects as [AnyObject] } // MARK: Object Retrieval /** - warning: Ordering is not guaranteed on a MutableSet. Subscripting is implement convenience should not be relied on. */ public subscript(position: Int) -> Element { if let lastAccessedNames = lastAccessedNames { return elementKeyPathRecorder(for: Element.self, with: lastAccessedNames) } throwForNegativeIndex(position) return staticBridgeCast(fromObjectiveC: rlmSet.object(at: UInt(position))) } // MARK: Filtering /** Returns a Boolean value indicating whether the Set contains the given object. - parameter object: The element to find in the MutableSet. */ public func contains(_ object: Element) -> Bool { return rlmSet.contains(staticBridgeCast(fromSwift: object) as AnyObject) } /** Returns a Boolean value that indicates whether this set is a subset of the given set. - Parameter object: Another MutableSet to compare. */ public func isSubset(of possibleSuperset: MutableSet) -> Bool { return rlmSet.isSubset(of: possibleSuperset.rlmSet) } /** Returns a Boolean value that indicates whether this set intersects with another given set. - Parameter object: Another MutableSet to compare. */ public func intersects(_ otherSet: MutableSet) -> Bool { return rlmSet.intersects(otherSet.rlmSet) } // MARK: Mutation /** Inserts an object to the set if not already present. - warning: This method may only be called during a write transaction. - parameter object: An object. */ public func insert(_ object: Element) { rlmSet.add(staticBridgeCast(fromSwift: object) as AnyObject) } /** Inserts the given sequence of objects into the set if not already present. - warning: This method may only be called during a write transaction. */ public func insert(objectsIn objects: S) where S.Iterator.Element == Element { for obj in objects { rlmSet.add(staticBridgeCast(fromSwift: obj) as AnyObject) } } /** Removes an object in the set if present. The object is not removed from the Realm that manages it. - warning: This method may only be called during a write transaction. - parameter object: The object to remove. */ public func remove(_ object: Element) { rlmSet.remove(staticBridgeCast(fromSwift: object) as AnyObject) } /** Removes all objects from the set. The objects are not removed from the Realm that manages them. - warning: This method may only be called during a write transaction. */ public func removeAll() { rlmSet.removeAllObjects() } /** Mutates the set in place with the elements that are common to both this set and the given sequence. - warning: This method may only be called during a write transaction. - parameter other: Another set. */ public func formIntersection(_ other: MutableSet) { rlmSet.intersect(other.rlmSet) } /** Mutates the set in place and removes the elements of the given set from this set. - warning: This method may only be called during a write transaction. - parameter other: Another set. */ public func subtract(_ other: MutableSet) { rlmSet.minus(other.rlmSet) } /** Inserts the elements of the given sequence into the set. - warning: This method may only be called during a write transaction. - parameter other: Another set. */ public func formUnion(_ other: MutableSet) { rlmSet.union(other.rlmSet) } @objc static func _unmanagedCollection() -> RLMSet { if let type = Element.self as? ObjectBase.Type { return RLMSet(objectClassName: type.className()) } if let type = Element.self as? _RealmSchemaDiscoverable.Type { return RLMSet(objectType: type._rlmType, optional: type._rlmOptional) } fatalError("Collections of projections must be used with @Projected.") } /// :nodoc: @objc public override static func _backingCollectionType() -> AnyClass { RLMManagedSet.self } // Printable requires a description property defined in Swift (and not obj-c), // and it has to be defined as override, which can't be done in a // generic class. /// Returns a human-readable description of the objects contained in the MutableSet. @objc public override var description: String { return descriptionWithMaxDepth(RLMDescriptionMaxDepth) } @objc private func descriptionWithMaxDepth(_ depth: UInt) -> String { return RLMDescriptionWithMaxDepth("MutableSet", rlmSet, depth) } /// :nodoc: public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } /// :nodoc: public func index(of object: Element) -> Int? { fatalError("index(of:) is not available on MutableSet") } /// :nodoc: public func index(matching predicate: NSPredicate) -> Int? { fatalError("index(matching:) is not available on MutableSet") } /// :nodoc: public func index(matching isIncluded: ((Query) -> Query)) -> Int? { fatalError("index(matching:) is not available on MutableSet") } } // MARK: - Codable extension MutableSet: Decodable where Element: Decodable { public convenience init(from decoder: Decoder) throws { self.init() var container = try decoder.unkeyedContainer() while !container.isAtEnd { insert(try container.decode(Element.self)) } } } extension MutableSet: Encodable where Element: Encodable {} ================================================ FILE: RealmSwift/Object.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import Realm.Private /** `Object` is a class used to define Realm model objects. In Realm you define your model classes by subclassing `Object` and adding properties to be managed. You then instantiate and use your custom subclasses instead of using the `Object` class directly. ```swift class Dog: Object { @Persisted var name: String @Persisted var adopted: Bool @Persisted var siblings: List } ``` ### Supported property types - `String` - `Int`, `Int8`, `Int16`, `Int32`, `Int64` - `Float` - `Double` - `Bool` - `Date` - `Data` - `Decimal128` - `ObjectId` - `UUID` - `AnyRealmValue` - Any RawRepresentable enum whose raw type is a legal property type. Enums must explicitly be marked as conforming to `PersistableEnum`. - `Object` subclasses, to model many-to-one relationships - `EmbeddedObject` subclasses, to model owning one-to-one relationships All of the types above may also be `Optional`, with the exception of `AnyRealmValue`. `Object` and `EmbeddedObject` subclasses *must* be Optional. In addition to individual values, three different collection types are supported: - `List`: an ordered mutable collection similar to `Array`. - `MutableSet`: an unordered uniquing collection similar to `Set`. - `Map`: an unordered key-value collection similar to `Dictionary`. The Element type of collections may be any of the supported non-collection property types listed above. Collections themselves may not be Optional, but the values inside them may be, except for lists and sets of `Object` or `EmbeddedObject` subclasses. Finally, `LinkingObjects` properties can be used to track which objects link to this one. All properties which should be stored by Realm must be explicitly marked with `@Persisted`. Any properties not marked with `@Persisted` will be ignored entirely by Realm, and may be of any type. ### Querying You can retrieve all objects of a given type from a Realm by calling the `objects(_:)` instance method. ### Relationships See our [Swift guide](https://docs.mongodb.com/realm/sdk/swift/fundamentals/relationships/) for more details. */ public typealias Object = RealmSwiftObject extension Object: _RealmCollectionValueInsideOptional { // MARK: Initializers /** Creates an unmanaged instance of a Realm object. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an `Array` as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. Call `add(_:)` on a `Realm` instance to add an unmanaged object into that Realm. - parameter value: The value used to populate the object. */ public convenience init(value: Any) { self.init() RLMInitializeWithValue(self, value, .partialPrivateShared()) } // MARK: Properties /// The Realm which manages the object, or `nil` if the object is unmanaged. public var realm: Realm? { if let rlmReam = RLMObjectBaseRealm(self) { return Realm(rlmReam) } return nil } /// The object schema which lists the managed properties for the object. public var objectSchema: ObjectSchema { return ObjectSchema(RLMObjectBaseObjectSchema(self)!) } /// Indicates if the object can no longer be accessed because it is now invalid. /// /// An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if /// `invalidate()` is called on that Realm. This property is key-value observable. @objc dynamic open override var isInvalidated: Bool { return super.isInvalidated } /// A human-readable description of the object. open override var description: String { return super.description } /** WARNING: This is an internal helper method not intended for public use. It is not considered part of the public API. :nodoc: */ public override static func _getProperties() -> [RLMProperty] { ObjectUtil.getSwiftProperties(self) } // MARK: Object Customization /** Override this method to specify the name of a property to be used as the primary key. Only properties of types `String`, `Int`, `ObjectId` and `UUID` can be designated as the primary key. Primary key properties enforce uniqueness for each value whenever the property is set, which incurs minor overhead. Indexes are created automatically for primary key properties. - warning: This function is only applicable to legacy property declarations using `@objc`. When using `@Persisted`, use `@Persisted(primaryKey: true)` instead. - returns: The name of the property designated as the primary key, or `nil` if the model has no primary key. */ @objc open class func primaryKey() -> String? { return nil } /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. - warning: This function is only applicable to legacy property declarations using `@objc`. When using `@Persisted`, any properties not marked with `@Persisted` are automatically ignored. - returns: An array of property names to ignore. */ @objc open class func ignoredProperties() -> [String] { return [] } /** Returns an array of property names for properties which should be indexed. Only string, integer, boolean, `Date`, and `NSDate` properties are supported. - warning: This function is only applicable to legacy property declarations using `@objc`. When using `@Persisted`, use `@Persisted(indexed: true)` instead. - returns: An array of property names. */ @objc open class func indexedProperties() -> [String] { return [] } /** Override this method to specify a map of public-private property names. This will set a different persisted property name on the Realm, and allows using the public name for any operation with the property. (Ex: Queries, Sorting, ...). This very helpful if you need to map property names from your `Device Sync` JSON schema to local property names. ```swift class Person: Object { @Persisted var firstName: String @Persisted var birthDate: Date @Persisted var age: Int override class public func propertiesMapping() -> [String : String] { ["firstName": "first_name", "birthDate": "birth_date"] } } ``` - note: Only property that have a different column name have to be added to the properties mapping dictionary. - note: In a migration block, when enumerating an old property with a public/private name, you will have to use the old column name instead of the public one to retrieve the property value. ```swift let migrationBlock = { migration, oldSchemaVersion in migration.enumerateObjects(ofType: "Person", { oldObj, newObj in let oldPropertyValue = oldObj!["first_name"] as! String // Use this value in migration }) } ``` This has to be done as well when renaming a property. ```swift let migrationBlock = { migration, oldSchemaVersion in migration.renameProperty(onType: "Person", from: "first_name", to: "complete_name") } ``` - returns: A dictionary of public-private property names. */ @objc open override class func propertiesMapping() -> [String: String] { return [:] } /// :nodoc: @available(*, unavailable, renamed: "propertiesMapping", message: "`_realmColumnNames` private API is unavailable in our Swift SDK, please use the override `.propertiesMapping()` instead.") @objc open override class func _realmColumnNames() -> [String: String] { return [:] } // MARK: Key-Value Coding & Subscripting /// Returns or sets the value of the property with the given name. @objc open subscript(key: String) -> Any? { get { RLMDynamicGetByName(self, key) } set { dynamicSet(object: self, key: key, value: newValue) } } // MARK: Notifications /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object first-level properties of the object. `Object` notifications are shallow by default, any nested property modification will not trigger a notification, unless the key path to that property is specified. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var adopted: Bool @Persisted var siblings: List } // ... where `dog` is a managed Dog object. dog.observe(keyPaths: ["adopted"], { changes in // ... }) ``` - The above notification block fires for changes to the `adopted` property, but not for any changes made to `name`. - If the observed key path were `["siblings"]`, then any insertion, deletion, or modification to the `siblings` list will trigger the block. A change to `someSibling.name` would not trigger the block (where `someSibling` is an element contained in `siblings`) - If the observed key path were `["siblings.name"]`, then any insertion or deletion to the `siblings` list would trigger the block. For objects contained in the `siblings` list, only modifications to their `name` property will trigger the block. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `List` and `Results`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { _observe(keyPaths: keyPaths, on: queue, block) } /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object first-level properties of the object. `Object` notifications are shallow by default, any nested property modification will not trigger a notification, unless the key path to that property is specified. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, i ```swift class Dog: Object { @Persisted var name: String @Persisted var adopted: Bool @Persisted var siblings: List } // ... where `dog` is a managed Dog object. dog.observe(keyPaths: [\Dog.adopted], { changes in // ... }) ``` - The above notification block fires for changes to the `adopted` property, but not for any changes made to `name`. - If the observed key path were `[\Dog.siblings]`, then any insertion, deletion, or modification to the `siblings` list will trigger the block. A change to `someSibling.name` would not trigger the block (where `someSibling` is an element contained in `siblings`) - If the observed key path were `[\Dog.siblings.name]`, then any insertion or deletion to the `siblings` list would trigger the block. For objects contained in the `siblings` list, only modifications to their `name` property will trigger the block. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `List` and `Results`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { _observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } #if compiler(<6) /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in await obj._observe(keyPaths: keyPaths, on: actor, block) } } /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #else /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in await obj._observe(keyPaths: keyPaths, on: actor, block) } } /** Registers a block to be called each time the object changes. The block will be asynchronously called on the given actor's executor after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor which can be safely used on that actor along with information about what changed. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. By default, only direct changes to the object's properties will produce notifications, and not changes to linked objects. Note that this is different from collection change notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths will produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter actor: The actor to isolate notifications to. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #endif // MARK: Dynamic list /** Returns a list of `DynamicObject`s for a given property name. - warning: This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use instance variables or cast the values returned from key-value coding. - parameter propertyName: The name of the property. - returns: A list of `DynamicObject`s. :nodoc: */ public func dynamicList(_ propertyName: String) -> List { if let dynamic = self as? DynamicObject { return dynamic[propertyName] as! List } let list = RLMDynamicGetByName(self, propertyName) as! RLMSwiftCollectionBase return List(collection: list._rlmCollection as! RLMArray) } // MARK: Dynamic set /** Returns a set of `DynamicObject`s for a given property name. - warning: This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use instance variables or cast the values returned from key-value coding. - parameter propertyName: The name of the property. - returns: A set of `DynamicObject`s. :nodoc: */ public func dynamicMutableSet(_ propertyName: String) -> MutableSet { if let dynamic = self as? DynamicObject { return dynamic[propertyName] as! MutableSet } let set = RLMDynamicGetByName(self, propertyName) as! RLMSwiftCollectionBase return MutableSet(collection: set._rlmCollection as! RLMSet) } // MARK: Dynamic map /** Returns a map of `DynamicObject`s for a given property name. - warning: This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use instance variables or cast the values returned from key-value coding. - parameter propertyName: The name of the property. - returns: A map with a given key type with `DynamicObject` as the value. :nodoc: */ public func dynamicMap(_ propertyName: String) -> Map { if let dynamic = self as? DynamicObject { return dynamic[propertyName] as! Map } let base = RLMDynamicGetByName(self, propertyName) as! RLMSwiftCollectionBase return Map(objc: base._rlmCollection as! RLMDictionary) } // MARK: Comparison /** Returns whether two Realm objects are the same. Objects are considered the same if and only if they are both managed by the same Realm and point to the same underlying object in the database. - note: Equality comparison is implemented by `isEqual(_:)`. If the object type is defined with a primary key, `isEqual(_:)` behaves identically to this method. If the object type is not defined with a primary key, `isEqual(_:)` uses the `NSObject` behavior of comparing object identity. This method can be used to compare two objects for database equality whether or not their object type defines a primary key. - parameter object: The object to compare the receiver to. */ public func isSameObject(as object: Object?) -> Bool { return RLMObjectBaseAreEqual(self, object) } } extension Object: ThreadConfined { /** Indicates if this object is frozen. - see: `Object.freeze()` */ public var isFrozen: Bool { return realm?.isFrozen ?? false } /** Returns a frozen (immutable) snapshot of this object. The frozen copy is an immutable object which contains the same data as this object currently contains, but will not update when writes are made to the containing Realm. Unlike live objects, frozen objects can be accessed from any thread. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. - warning: This method can only be called on a managed object. */ public func freeze() -> Self { guard let realm = realm else { throwRealmException("Unmanaged objects cannot be frozen.") } return realm.freeze(self) } /** Returns a live (mutable) reference of this object. This method creates a managed accessor to a live copy of the same frozen object. Will return self if called on an already live object. */ public func thaw() -> Self? { guard let realm = realm else { throwRealmException("Unmanaged objects cannot be thawed.") } return realm.thaw(self) } } /** Information about a specific property which changed in an `Object` change notification. */ @frozen public struct PropertyChange { /** The name of the property which changed. */ public let name: String /** Value of the property before the change occurred. This is not supplied if the change happened on the same thread as the notification and for `List` properties. For object properties this will give the object which was previously linked to, but that object will have its new values and not the values it had before the changes. This means that `previousValue` may be a deleted object, and you will need to check `isInvalidated` before accessing any of its properties. */ public let oldValue: Any? /** The value of the property after the change occurred. This is not supplied for `List` properties and will always be nil. */ public let newValue: Any? } /** Information about the changes made to an object which is passed to `Object`'s notification blocks. */ @frozen public enum ObjectChange { /** Errors can no longer occur. This case is unused and will be removed in the next major version. */ case error(_ error: NSError) /** One or more of the properties of the object have been changed. */ case change(_: T, _: [PropertyChange]) /// The object has been deleted from the Realm. case deleted internal init(object: T?, names: [String]?, oldValues: [Any]?, newValues: [Any]?) { guard let names = names, let newValues = newValues, let object = object else { self = .deleted return } self = .change(object, (0.. Any? { get { let value = RLMDynamicGetByName(self, key).flatMap(coerceToNil) if let array = value as? RLMArray { return list(from: array) } if let set = value as? RLMSet { return mutableSet(from: set) } if let dictionary = value as? RLMDictionary { return map(from: dictionary) } return value } set(value) { RLMDynamicValidatedSet(self, key, value) } } public subscript(dynamicMember member: String) -> Any? { get { self[member] } set(value) { self[member] = value } } /// :nodoc: public override func value(forUndefinedKey key: String) -> Any? { self[key] } /// :nodoc: public override func setValue(_ value: Any?, forUndefinedKey key: String) { self[key] = value } /// :nodoc: public override static func shouldIncludeInDefaultSchema() -> Bool { false } override public static func sharedSchema() -> RLMObjectSchema? { nil } private func list(from array: RLMArray) -> Any { switch array.type { case .int: return array.isOptional ? List(collection: array) : List(collection: array) case .double: return array.isOptional ? List(collection: array) : List(collection: array) case .float: return array.isOptional ? List(collection: array) : List(collection: array) case .decimal128: return array.isOptional ? List(collection: array) : List(collection: array) case .bool: return array.isOptional ? List(collection: array) : List(collection: array) case .UUID: return array.isOptional ? List(collection: array) : List(collection: array) case .string: return array.isOptional ? List(collection: array) : List(collection: array) case .data: return array.isOptional ? List(collection: array) : List(collection: array) case .date: return array.isOptional ? List(collection: array) : List(collection: array) case .any: return List(collection: array) case .linkingObjects: throwRealmException("Unsupported migration type of 'LinkingObjects' for type 'List'.") case .objectId: return array.isOptional ? List(collection: array) : List(collection: array) case .object: return List(collection: array) } } private func mutableSet(from set: RLMSet) -> Any { switch set.type { case .int: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .double: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .float: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .decimal128: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .bool: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .UUID: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .string: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .data: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .date: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .any: return MutableSet(collection: set) case .linkingObjects: throwRealmException("Unsupported migration type of 'LinkingObjects' for type 'MutableSet'.") case .objectId: return set.isOptional ? MutableSet(collection: set) : MutableSet(collection: set) case .object: return MutableSet(collection: set) } } private func map(from dictionary: RLMDictionary) -> Any { switch dictionary.type { case .int: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .double: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .float: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .decimal128: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .bool: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .UUID: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .string: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .data: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .date: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .any: return Map(objc: dictionary) case .linkingObjects: throwRealmException("Unsupported migration type of 'LinkingObjects' for type 'Map'.") case .objectId: return dictionary.isOptional ? Map(objc: dictionary) : Map(objc: dictionary) case .object: return Map(objc: dictionary) } } } /** An enum type which can be stored on a Realm Object. Only `@objc` enums backed by an Int can be stored on a Realm object, and the enum type must explicitly conform to this protocol. For example: ``` @objc enum MyEnum: Int, RealmEnum { case first = 1 case second = 2 case third = 7 } class MyModel: Object { @objc dynamic enumProperty = MyEnum.first let optionalEnumProperty = RealmOptional() } ``` */ public protocol RealmEnum: RealmOptionalType, _RealmSchemaDiscoverable { } // MARK: - Implementation /// :nodoc: public extension RealmEnum where Self: RawRepresentable, Self.RawValue: _RealmSchemaDiscoverable & _ObjcBridgeable { var _rlmObjcValue: Any { rawValue._rlmObjcValue } static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Self? { if let value = value as? Self { return value } if let value = value as? RawValue { return Self(rawValue: value) } return nil } static func _rlmPopulateProperty(_ prop: RLMProperty) { RawValue._rlmPopulateProperty(prop) } static var _rlmType: PropertyType { RawValue._rlmType } } internal func dynamicSet(object: ObjectBase, key: String, value: Any?) { let bridgedValue: Any? if let v1 = value, let v2 = v1 as AnyObject as? _ObjcBridgeable { bridgedValue = v2._rlmObjcValue } else { bridgedValue = value } if RLMObjectBaseRealm(object) == nil { object.setValue(bridgedValue, forKey: key) } else { RLMDynamicValidatedSet(object, key, bridgedValue) } } ================================================ FILE: RealmSwift/ObjectId.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import Realm /** A 12-byte (probably) unique object identifier. ObjectIds are similar to a GUID or a UUID, and can be used to uniquely identify objects without a centralized ID generator. An ObjectID consists of: 1. A 4 byte timestamp measuring the creation time of the ObjectId in seconds since the Unix epoch. 2. A 5 byte random value 3. A 3 byte counter, initialized to a random value. ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order. */ @objc(RealmSwiftObjectId) public final class ObjectId: RLMObjectId, Decodable, @unchecked Sendable { // MARK: Initializers /// Creates a new zero-initialized ObjectId. public override required init() { super.init() } // swiftlint:disable unneeded_override /// Creates a new randomly-initialized ObjectId. public override static func generate() -> ObjectId { super.generate() } // swiftlint:enable unneeded_override /// Creates a new ObjectId from the given 24-byte hexadecimal string. /// /// Throws if the string is not 24 characters or contains any characters other than 0-9a-fA-F. /// - Parameter string: The string to parse. public override required init(string: String) throws { try super.init(string: string) } /// Creates a new ObjectId using the given date, machine identifier, process identifier. /// /// - Parameters: /// - timestamp: A timestamp as NSDate. /// - machineId: The machine identifier. /// - processId: The process identifier. public required init(timestamp: Date, machineId: Int, processId: Int) { super.init(timestamp: timestamp, machineIdentifier: Int32(machineId), processIdentifier: Int32(processId)) } /// Creates a new ObjectId from the given 24-byte hexadecimal static string. /// /// Aborts if the string is not 24 characters or contains any characters other than 0-9a-fA-F. Use the initializer which takes a String to handle invalid strings at runtime. public required init(_ str: StaticString) { // swiftlint:disable:next optional_data_string_conversion try! super.init(string: str.withUTF8Buffer { String(decoding: $0, as: UTF8.self) }) } /// Creates a new ObjectId by decoding from the given decoder. /// /// This initializer throws an error if reading from the decoder fails, or /// if the data read is corrupted or otherwise invalid. /// /// - Parameter decoder: The decoder to read data from. public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() try super.init(string: container.decode(String.self)) } } extension ObjectId: Encodable { /// Encodes this ObjectId into the given encoder. /// /// This function throws an error if the given encoder is unable to encode a string. /// /// - Parameter encoder: The encoder to write data to. public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(stringValue) } } extension ObjectId: Comparable { /// Returns a Boolean value indicating whether the value of the first /// argument is less than that of the second argument. /// /// - Parameters: /// - lhs: An ObjectId value to compare. /// - rhs: Another ObjectId value to compare. public static func < (lhs: ObjectId, rhs: ObjectId) -> Bool { lhs.isLessThan(rhs) } /// Returns a Boolean value indicating whether the ObjectId of the first /// argument is less than or equal to that of the second argument. /// /// - Parameters: /// - lhs: An ObjectId value to compare. /// - rhs: Another ObjectId value to compare. public static func <= (lhs: ObjectId, rhs: ObjectId) -> Bool { lhs.isLessThanOrEqual(to: rhs) } /// Returns a Boolean value indicating whether the ObjectId of the first /// argument is greater than or equal to that of the second argument. /// /// - Parameters: /// - lhs: An ObjectId value to compare. /// - rhs: Another ObjectId value to compare. public static func >= (lhs: ObjectId, rhs: ObjectId) -> Bool { lhs.isGreaterThanOrEqual(to: rhs) } /// Returns a Boolean value indicating whether the ObjectId of the first /// argument is greater than that of the second argument. /// /// - Parameters: /// - lhs: An ObjectId value to compare. /// - rhs: Another ObjectId value to compare. public static func > (lhs: ObjectId, rhs: ObjectId) -> Bool { lhs.isGreaterThan(rhs) } } ================================================ FILE: RealmSwift/ObjectSchema.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import Realm.Private /** This class represents Realm model object schemas. When using Realm, `ObjectSchema` instances allow performing migrations and introspecting the database's schema. Object schemas map to tables in the core database. */ @frozen public struct ObjectSchema: CustomStringConvertible { // MARK: Properties internal let rlmObjectSchema: RLMObjectSchema /** An array of `Property` instances representing the managed properties of a class described by the schema. - see: `Property` */ public var properties: [Property] { return rlmObjectSchema.properties.map { Property($0) } } /// The name of the class the schema describes. public var className: String { return rlmObjectSchema.className } /// The object class the schema describes. public var objectClass: AnyClass { return rlmObjectSchema.objectClass } /// Whether this object is embedded. public var isEmbedded: Bool { return rlmObjectSchema.isEmbedded } /// Whether this object is asymmetric. public var isAsymmetric: Bool { return rlmObjectSchema.isAsymmetric } /// The property which serves as the primary key for the class the schema describes, if any. public var primaryKeyProperty: Property? { if let rlmProperty = rlmObjectSchema.primaryKeyProperty { return Property(rlmProperty) } return nil } /// A human-readable description of the properties contained in the object schema. public var description: String { return rlmObjectSchema.description } // MARK: Initializers internal init(_ rlmObjectSchema: RLMObjectSchema) { self.rlmObjectSchema = rlmObjectSchema } // MARK: Property Retrieval /// Returns the property with the given name, if it exists. public subscript(propertyName: String) -> Property? { if let rlmProperty = rlmObjectSchema[propertyName] { return Property(rlmProperty) } return nil } } // MARK: Equatable extension ObjectSchema: Equatable { /// Returns whether the two object schemas are equal. public static func == (lhs: ObjectSchema, rhs: ObjectSchema) -> Bool { return lhs.rlmObjectSchema.isEqual(to: rhs.rlmObjectSchema) } } ================================================ FILE: RealmSwift/ObjectiveCSupport+AnyRealmValue.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm public extension ObjectiveCSupport { /// Convert an object boxed in `AnyRealmValue` to its /// Objective-C representation. /// - Parameter value: The AnyRealmValue with the object. /// - Returns: Conversion of `value` to its Objective-C representation. static func convert(value: AnyRealmValue?) -> RLMValue? { switch value { case let .int(i): return i as NSNumber case let .bool(b): return b as NSNumber case let .float(f): return f as NSNumber case let .double(d): return d as NSNumber case let .string(s): return s as NSString case let .data(d): return d as NSData case let .date(d): return d as NSDate case let .objectId(o): return o as RLMObjectId case let .decimal128(o): return o as RLMDecimal128 case let .uuid(u): return u as NSUUID case let .object(o): return o case let .dictionary(d): return d.rlmDictionary case let .list(l): return l.rlmArray default: return nil } } /// Takes an RLMValue, converts it to its Swift type and /// stores it in `AnyRealmValue`. /// - Parameter value: The RLMValue. /// - Returns: The converted RLMValue type as an AnyRealmValue enum. static func convert(value: RLMValue?) -> AnyRealmValue { guard let value = value else { return .none } switch value.rlm_anyValueType { case RLMAnyValueType.int: guard let val = value as? NSNumber else { return .none } return .int(val.intValue) case RLMAnyValueType.bool: guard let val = value as? NSNumber else { return .none } return .bool(val.boolValue) case RLMAnyValueType.float: guard let val = value as? NSNumber else { return .none } return .float(val.floatValue) case RLMAnyValueType.double: guard let val = value as? NSNumber else { return .none } return .double(val.doubleValue) case RLMAnyValueType.string: guard let val = value as? String else { return .none } return .string(val) case RLMAnyValueType.data: guard let val = value as? Data else { return .none } return .data(val) case RLMAnyValueType.date: guard let val = value as? Date else { return .none } return .date(val) case RLMAnyValueType.objectId: guard let val = value as? ObjectId else { return .none } return .objectId(val) case RLMAnyValueType.decimal128: guard let val = value as? Decimal128 else { return .none } return .decimal128(val) case RLMAnyValueType.UUID: guard let val = value as? UUID else { return .none } return .uuid(val) case RLMAnyValueType.object: guard let val = value as? Object else { return .none } return .object(val) case RLMAnyValueType.dictionary: guard let val = value as? RLMDictionary else { return .none } let d = Map(objc: val) return AnyRealmValue.dictionary(d) case RLMAnyValueType.list: guard let val = value as? RLMArray else { return .none } return AnyRealmValue.list(List(collection: val)) default: return .none } } } ================================================ FILE: RealmSwift/ObjectiveCSupport.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Realm /** `ObjectiveCSupport` is a class providing methods for Swift/Objective-C interoperability. With `ObjectiveCSupport` you can either retrieve the internal ObjC representations of the Realm objects, or wrap ObjC Realm objects with their Swift equivalents. Use this to provide public APIs that support both platforms. :nodoc: **/ @frozen public struct ObjectiveCSupport { /// Convert a `Results` to a `RLMResults`. public static func convert(object: Results) -> RLMResults { return object.collection as! RLMResults } /// Convert a `RLMResults` to a `Results`. public static func convert(object: RLMResults) -> Results { return Results(object) } /// Convert a `List` to a `RLMArray`. public static func convert(object: List) -> RLMArray { return object.rlmArray } /// Convert a `MutableSet` to a `RLMSet`. public static func convert(object: MutableSet) -> RLMSet { return object.rlmSet } /// Convert a `RLMArray` to a `List`. public static func convert(object: RLMArray) -> List { return List(collection: object) } /// Convert a `RLMSet` to a `MutableSet`. public static func convert(object: RLMSet) -> MutableSet { return MutableSet(collection: object) } /// Convert a `Map` to a `RLMDictionary`. public static func convert(object: Map) -> RLMDictionary { return object.rlmDictionary } /// Convert a `RLMDictionary` to a `Map`. public static func convert(object: RLMDictionary) -> Map { return Map(objc: object) } /// Convert a `LinkingObjects` to a `RLMResults`. public static func convert(object: LinkingObjects) -> RLMResults { return object.collection as! RLMResults } /// Convert a `RLMLinkingObjects` to a `Results`. public static func convert(object: RLMLinkingObjects) -> Results { return Results(object) } /// Convert a `Realm` to a `RLMRealm`. public static func convert(object: Realm) -> RLMRealm { return object.rlmRealm } /// Convert a `RLMRealm` to a `Realm`. public static func convert(object: RLMRealm) -> Realm { return Realm(object) } /// Convert a `Migration` to a `RLMMigration`. @available(*, deprecated, message: "This function is now redundant") public static func convert(object: Migration) -> RLMMigration { return object } /// Convert a `ObjectSchema` to a `RLMObjectSchema`. public static func convert(object: ObjectSchema) -> RLMObjectSchema { return object.rlmObjectSchema } /// Convert a `RLMObjectSchema` to a `ObjectSchema`. public static func convert(object: RLMObjectSchema) -> ObjectSchema { return ObjectSchema(object) } /// Convert a `Property` to a `RLMProperty`. public static func convert(object: Property) -> RLMProperty { return object.rlmProperty } /// Convert a `RLMProperty` to a `Property`. public static func convert(object: RLMProperty) -> Property { return Property(object) } /// Convert a `Realm.Configuration` to a `RLMRealmConfiguration`. public static func convert(object: Realm.Configuration) -> RLMRealmConfiguration { return object.rlmConfiguration } /// Convert a `RLMRealmConfiguration` to a `Realm.Configuration`. public static func convert(object: RLMRealmConfiguration) -> Realm.Configuration { return .fromRLMRealmConfiguration(object) } /// Convert a `Schema` to a `RLMSchema`. public static func convert(object: Schema) -> RLMSchema { return object.rlmSchema } /// Convert a `RLMSchema` to a `Schema`. public static func convert(object: RLMSchema) -> Schema { return Schema(object) } /// Convert a `SortDescriptor` to a `RLMSortDescriptor`. public static func convert(object: SortDescriptor) -> RLMSortDescriptor { return object.rlmSortDescriptorValue } /// Convert a `RLMSortDescriptor` to a `SortDescriptor`. public static func convert(object: RLMSortDescriptor) -> SortDescriptor { return SortDescriptor(keyPath: object.keyPath, ascending: object.ascending) } /// Convert a `RLMShouldCompactOnLaunchBlock` to a Realm Swift compact block. @preconcurrency public static func convert(object: @escaping RLMShouldCompactOnLaunchBlock) -> @Sendable (Int, Int) -> Bool { return { totalBytes, usedBytes in return object(UInt(totalBytes), UInt(usedBytes)) } } /// Convert a Realm Swift compact block to a `RLMShouldCompactOnLaunchBlock`. @preconcurrency public static func convert(object: @Sendable @escaping (Int, Int) -> Bool) -> RLMShouldCompactOnLaunchBlock { return { totalBytes, usedBytes in return object(Int(totalBytes), Int(usedBytes)) } } } ================================================ FILE: RealmSwift/Optional.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Realm /// A protocol describing types that can parameterize a `RealmOptional`. public protocol RealmOptionalType: _ObjcBridgeable { } public extension RealmOptionalType { /// :nodoc: static func className() -> String { return "" } } extension Int: RealmOptionalType {} extension Int8: RealmOptionalType {} extension Int16: RealmOptionalType {} extension Int32: RealmOptionalType {} extension Int64: RealmOptionalType {} extension Float: RealmOptionalType {} extension Double: RealmOptionalType {} extension Bool: RealmOptionalType {} /** A `RealmOptional` instance represents an optional value for types that can't be directly declared as `@objc` in Swift, such as `Int`, `Float`, `Double`, and `Bool`. To change the underlying value stored by a `RealmOptional` instance, mutate the instance's `value` property. */ @available(*, deprecated, renamed: "RealmProperty", message: "RealmOptional has been deprecated, use RealmProperty instead.") public final class RealmOptional: RLMSwiftValueStorage { /// The value the optional represents. public var value: Value? { get { return RLMGetSwiftValueStorage(self).map(staticBridgeCast) } set { RLMSetSwiftValueStorage(self, newValue.map(staticBridgeCast)) } } /** Creates a `RealmOptional` instance encapsulating the given default value. - parameter value: The value to store in the optional, or `nil` if not specified. */ public init(_ value: Value? = nil) { super.init() self.value = value } } @available(*, deprecated, message: "RealmOptional has been deprecated, use RealmProperty instead.") extension RealmOptional: Equatable where Value: Equatable { public static func == (lhs: RealmOptional, rhs: RealmOptional) -> Bool { return lhs.value == rhs.value } } @available(*, deprecated, message: "RealmOptional has been deprecated, use RealmProperty instead.") extension RealmOptional: Codable where Value: Codable, Value: _RealmSchemaDiscoverable { public convenience init(from decoder: Decoder) throws { self.init() // `try decoder.singleValueContainer().decode(Value?.self)` incorrectly // rejects null values: https://bugs.swift.org/browse/SR-7404 self.value = try decoder.decodeOptional(Value?.self) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value) } } internal protocol RealmOptionalProtocol { } @available(*, deprecated, message: "RealmOptional has been deprecated, use RealmProperty instead.") extension RealmOptional: RealmOptionalProtocol { } internal extension Decoder { func decodeOptional(_ type: T.Type) throws -> T where T: Decodable { let container = try singleValueContainer() if container.decodeNil() { if let type = T.self as? _ObjcBridgeable.Type, let value = type._rlmFromObjc(NSNull()) { return value as! T } throw DecodingError.typeMismatch(T.self, .init(codingPath: self.codingPath, debugDescription: "Cannot convert nil to \(T.self)")) } return try container.decode(T.self) } } ================================================ FILE: RealmSwift/PersistedProperty.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private /// @Persisted is used to declare properties on Object subclasses which should be /// managed by Realm. /// /// Example of usage: /// ``` /// class MyModel: Object { /// // A basic property declaration. A property with no /// // default value supplied will default to `nil` for /// // Optional types, zero for numeric types, false for Bool, /// // an empty string/data, and a new random value for UUID /// // and ObjectID. /// @Persisted var basicIntProperty: Int /// /// // Custom default values can be specified with the /// // standard Swift syntax /// @Persisted var intWithCustomDefault: Int = 5 /// /// // Properties can be indexed by passing `indexed: true` /// // to the initializer. /// @Persisted(indexed: true) var indexedString: String /// /// // Properties can set as the class's primary key by /// // passing `primaryKey: true` to the initializer /// @Persisted(primaryKey: true) var _id: ObjectId /// /// // List and set properties should always be declared /// // with `: List` rather than `= List()` /// @Persisted var listProperty: List /// @Persisted var setProperty: MutableSet /// /// // LinkingObjects properties require setting the source /// // object link property name in the initializer /// @Persisted(originProperty: "outgoingLink") /// var incomingLinks: LinkingObjects /// /// // Properties which are not marked with @Persisted will /// // be ignored entirely by Realm. /// var ignoredProperty = true /// } /// ``` /// /// Int, Bool, String, ObjectId and Date properties can be indexed by passing /// `indexed: true` to the initializer. Indexing a property improves the /// performance of equality queries on that property, at the cost of slightly /// worse write performance. No other operations currently use the index. /// /// A property can be set as the class's primary key by passing `primaryKey: true` /// to the initializer. Compound primary keys are not supported, and setting /// more than one property as the primary key will throw an exception at /// runtime. Only Int, String, UUID and ObjectID properties can be made the /// primary key. The primary key property can only be mutated on unmanaged objects, /// and mutating it on an object which has been added to a Realm will throw an /// exception. /// /// Properties can optionally be given a default value using the standard Swift /// syntax. If no default value is given, a value will be generated on first /// access: `nil` for all Optional types, zero for numeric types, false for /// Bool, an empty string/data, and a new random value for UUID and ObjectID. /// List and MutableSet properties *should not* be defined by setting them to a /// default value of an empty List/MutableSet. Doing so will work, but will /// result in worse performance when accessing objects managed by a Realm. /// Similarly, ObjectID properties *should not* be initialized to /// `ObjectID.generate()`, as doing so will result in extra ObjectIDs being /// generated and then discarded when reading from a Realm. /// /// If a class has at least one @Persisted property, all other properties will be /// ignored by Realm. This means that they will not be persisted and will not /// be usable in queries and other operations such as sorting and aggregates /// which require a managed property. /// /// @Persisted cannot be used anywhere other than as a property on an Object or /// EmbeddedObject subclass and trying to use it in other places will result in /// runtime errors. @propertyWrapper public struct Persisted { private var storage: PropertyStorage /// :nodoc: @available(*, unavailable, message: "@Persisted can only be used as a property on a Realm object") public var wrappedValue: Value { // The static subscript below is called instead of this when the property // wrapper is used on an ObjectBase subclass, which is the only thing we support. get { fatalError("called wrappedValue getter") } // swiftlint:disable:next unused_setter_value set { fatalError("called wrappedValue setter") } } /// Declares a property which is lazily initialized to the type's default value. public init() { storage = .unmanagedNoDefault(indexed: false, primary: false) } /// Declares a property which defaults to the given value. public init(wrappedValue value: Value) { storage = .unmanaged(value: value, indexed: false, primary: false) } /// :nodoc: public static subscript( _enclosingInstance observed: EnclosingSelf, wrapped wrappedKeyPath: ReferenceWritableKeyPath, storage storageKeyPath: ReferenceWritableKeyPath ) -> Value { get { return observed[keyPath: storageKeyPath].get(observed) } set { observed[keyPath: storageKeyPath].set(observed, value: newValue) } } // Called via RLMInitializeSwiftAccessor() to initialize the wrapper on a // newly created managed accessor object. internal mutating func initialize(_ object: ObjectBase, key: PropertyKey) { storage = .managed(key: key) } // Collection types use this instead of the above because when promoting a // unmanaged object to a managed object we want to reuse the existing collection // object if it exists. Currently it always will exist because we read the // value of the property first, but there's a potential optimization to // skip initializing it on that read. internal mutating func initializeCollection(_ object: ObjectBase, key: PropertyKey) -> Value? { if case let .unmanaged(value, _, _) = storage { storage = .managedCached(value: value, key: key) return value } if case let .unmanagedObserved(value, _) = storage { storage = .managedCached(value: value, key: key) return value } storage = .managed(key: key) return nil } internal mutating func get(_ object: ObjectBase) -> Value { switch storage { case let .unmanaged(value, _, _): return value case .unmanagedNoDefault: let value = Value._rlmDefaultValue() storage = .unmanaged(value: value) return value case let .unmanagedObserved(value, key): if let lastAccessedNames = object.lastAccessedNames { let name: String if Value._rlmType == .linkingObjects { name = RLMObjectBaseObjectSchema(object)!.computedProperties[Int(key)].name } else { name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name } lastAccessedNames.add(name) if let type = Value.self as? KeypathRecorder.Type { return type.keyPathRecorder(with: lastAccessedNames) as! Value } return Value._rlmDefaultValue() } return value case let .managed(key): let v = Value._rlmGetProperty(object, key) if Value._rlmRequiresCaching { // Collection types are initialized once and stored on the // object rather than on every access. Non-collection types // cannot be cached without some mechanism for knowing when to // reread them which we don't currently have. storage = .managedCached(value: v, key: key) } return v case let .managedCached(value, _): return value } } internal mutating func set(_ object: ObjectBase, value: Value) { if value is MutableRealmCollection { (get(object) as! MutableRealmCollection).assign(value) return } switch storage { case let .unmanagedObserved(_, key): let name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name object.willChangeValue(forKey: name) storage = .unmanagedObserved(value: value, key: key) object.didChangeValue(forKey: name) case .managed(let key), .managedCached(_, let key): Value._rlmSetProperty(object, key, value) case .unmanaged, .unmanagedNoDefault: storage = .unmanaged(value: value, indexed: false, primary: false) } } // Initialize an unmanaged property for observation internal mutating func observe(_ object: ObjectBase, property: RLMProperty) { let value: Value switch storage { case let .unmanaged(v, _, _): value = v case .unmanagedNoDefault: value = Value._rlmDefaultValue() case .unmanagedObserved, .managed, .managedCached: return } // Mutating a collection triggers a KVO notification on the parent, so // we need to ensure that the collection has a pointer to its parent. if let value = value as? MutableRealmCollection { value.setParent(object, property) } storage = .unmanagedObserved(value: value, key: PropertyKey(property.index)) } } extension Persisted: Decodable where Value: Decodable { public init(from decoder: Decoder) throws { storage = .unmanaged(value: try decoder.decodeOptional(Value.self), indexed: false, primary: false) } } extension Persisted: Encodable where Value: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch storage { case .unmanaged(let value, _, _): try container.encode(value) case .unmanagedObserved(let value, _): try container.encode(value) case .unmanagedNoDefault: try container.encode(Value._rlmDefaultValue()) default: // We need a reference to the parent object to be able to read from // a managed property. There's probably a way to do this with some // sort of custom adapter that keeps track of the current parent // at each level of recursion, but it's not trivial. throw EncodingError.invalidValue(self, .init(codingPath: encoder.codingPath, debugDescription: "Only unmanaged Realm objects can be encoded using automatic Codable synthesis. You must explicitly define encode(to:) on your model class to support managed Realm objects.")) } } } /// :nodoc: /// Protocol for a PropertyWrapper to properly handle Coding when the wrappedValue is Optional public protocol OptionalCodingWrapper { associatedtype WrappedType: ExpressibleByNilLiteral init(wrappedValue: WrappedType) } /// :nodoc: extension KeyedDecodingContainer { // This is used to override the default decoding behaviour for OptionalCodingWrapper to allow a value to avoid a missing key Error public func decode(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T where T: Decodable, T: OptionalCodingWrapper { return try decodeIfPresent(T.self, forKey: key) ?? T(wrappedValue: nil) } } extension Persisted: OptionalCodingWrapper where Value: ExpressibleByNilLiteral { } /** An enum type which can be used with @Persisted and Realm Collections. Persisting an enum in Realm requires that it have a raw value and that the raw value by a type which Realm can store. The enum also has to be explicitly marked as conforming to this protocol as Swift does not let us do so implicitly. ``` enum IntEnum: Int, PersistableEnum { case first = 1 case second = 2 case third = 7 } enum StringEnum: String, PersistableEnum { case first = "a" case second = "b" case third = "g" } ``` If the Realm contains a value which is not a valid member of the enum (such as if it was written by a different sync client which disagrees on which values are valid), optional enum properties will return `nil`, and non-optional properties will abort the process. */ public protocol PersistableEnum: _PersistableInsideOptional, RawRepresentable, CaseIterable, RealmEnum, _RealmCollectionValueInsideOptional, MinMaxType, Comparable where RawValue: Comparable { } extension PersistableEnum { /// :nodoc: public init() { self = Self.allCases.first! } /// :nodoc: public static func < (lhs: Self, rhs: Self) -> Bool { return lhs.rawValue < rhs.rawValue } /// :nodoc: public static func _rlmDefaultValue() -> Self { Self.allCases.first! } } /// A type which can be indexed. /// /// This protocol is merely a tag and declaring additional types as conforming /// to it will simply result in runtime errors rather than compile-time errors. @_marker public protocol _Indexable {} extension Persisted where Value.PersistedType: _Indexable { /// Declares an indexed property which is lazily initialized to the type's default value. public init(indexed: Bool) { storage = .unmanagedNoDefault(indexed: indexed) } /// Declares an indexed property which defaults to the given value. public init(wrappedValue value: Value, indexed: Bool) { storage = .unmanaged(value: value, indexed: indexed) } } /// A type which can be made the primary key of an object. /// /// This protocol is merely a tag and declaring additional types as conforming /// to it will simply result in runtime errors rather than compile-time errors. @_marker public protocol _PrimaryKey {} extension Persisted where Value.PersistedType: _PrimaryKey { /// Declares the primary key property which is lazily initialized to the type's default value. public init(primaryKey: Bool) { storage = .unmanagedNoDefault(primary: primaryKey) } /// Declares the primary key property which defaults to the given value. public init(wrappedValue value: Value, primaryKey: Bool) { storage = .unmanaged(value: value, primary: primaryKey) } } // Constraining the LinkingObjects initializer to only LinkingObjects require // doing so via a protocol which only that type conforms to. /// :nodoc: public protocol LinkingObjectsProtocol { init(fromType: Element.Type, property: String) associatedtype Element } extension Persisted where Value: LinkingObjectsProtocol { /// Declares a LinkingObjects property with the given origin property name. /// /// - param originProperty: The name of the property on the linking object type which links to this object. public init(originProperty: String) { self.init(wrappedValue: Value(fromType: Value.Element.self, property: originProperty)) } } extension LinkingObjects: LinkingObjectsProtocol {} // MARK: - Implementation /// :nodoc: extension Persisted: DiscoverablePersistedProperty where Value: _Persistable { public static var _rlmType: PropertyType { Value._rlmType } public static var _rlmOptional: Bool { Value._rlmOptional } public static var _rlmRequireObjc: Bool { false } public static func _rlmPopulateProperty(_ prop: RLMProperty) { // The label reported by Mirror has an underscore prefix added to it // as it's the actual storage rather than the compiler-magic getter/setter prop.name = String(prop.name.dropFirst()) Value._rlmPopulateProperty(prop) Value._rlmSetAccessor(prop) } public func _rlmPopulateProperty(_ prop: RLMProperty) { switch storage { case let .unmanaged(value, indexed, primary): value._rlmPopulateProperty(prop) prop.indexed = indexed || primary prop.isPrimary = primary case let .unmanagedNoDefault(indexed, primary): prop.indexed = indexed || primary prop.isPrimary = primary default: fatalError() } } } // The actual storage for modern properties on objects. // // A newly created @Persisted will be either .unmanaged or .unmanagedNoDefault // depending on whether the user supplied a default value with `= value` when // defining the property. .unmanagedNoDefault turns into .unmanaged the first // time the property is read from, using a default value generated for the type. // If an unmanaged object is observed, that specific property is switched to // .unmanagedObserved so that the property can look up its name in the setter. // // When a new managed accessor is created, all properties are set to .managed. // When an existing unmanaged object is added to a Realm, existing non-collection // properties are set to .unmanaged, and collections are set to .managedCached, // reusing the existing instance of the collection (which are themselves promoted // to managed). // // The indexed and primary members of the unmanaged cases are used only for // schema discovery and are not always preserved once the Persisted is actually // used for anything. private enum PropertyStorage { // An unmanaged value. This is used as the initial state if the user did // supply a default value, or if an unmanaged property is read or written // (but not observed). case unmanaged(value: T, indexed: Bool = false, primary: Bool = false) // The property is unmanaged and does not yet have a value. This state is // used if the user does not supply a default value in their model definition // and will be converted to the zero/empty value for the type when this // property is first used. case unmanagedNoDefault(indexed: Bool = false, primary: Bool = false) // The property is unmanaged and the parent object has (or previously had) // KVO observers, so we performed the additional initialization to set the // property key on each property. We do not track indexed/primary in this // state because those are needed only for schema discovery. An unmanaged // property never transitions from this state back to .unmanaged. case unmanagedObserved(value: T, key: PropertyKey) // The property is managed and so only needs to store the key to get/set // the value on the parent object. case managed(key: PropertyKey) // The property is managed and is storing a value which will be returned each // time. This is used only for collection properties, which are themselves // live objects and so only need to be created once. Caching them is both a // performance optimization (creating them involves a few memory allocations) // and is required for KVO to work correctly. case managedCached(value: T, key: PropertyKey) } ================================================ FILE: RealmSwift/PrivacyInfo.xcprivacy ================================================ NSPrivacyTrackingDomains NSPrivacyCollectedDataTypes NSPrivacyAccessedAPITypes NSPrivacyAccessedAPITypeReasons C617.1 NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons E174.1 NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryDiskSpace NSPrivacyTracking ================================================ FILE: RealmSwift/Projection.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Realm import Realm.Private import Combine private protocol AnyProjected { var projectedKeyPath: AnyKeyPath { get } } // MARK: Projection /// ``@Projected`` is used to declare properties on ``Projection`` protocols which should be /// managed by Realm. /// /// Example of usage: /// ```swift /// public class Person: Object { /// @Persisted var firstName = "" /// @Persisted var lastName = "" /// @Persisted var address: Address? /// @Persisted var friends: List /// @Persisted var reviews: List /// } /// /// class PersonProjection: Projection { /// @Projected(\Person.firstName) var firstName /// @Projected(\Person.lastName.localizedUppercase) var lastNameCaps /// @Projected(\Person.address.city) var homeCity /// @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection /// } /// /// let people: Results = realm.objects(PersonProjection.self) /// ``` @propertyWrapper public struct Projected: AnyProjected { fileprivate var _projectedKeyPath: KeyPath var projectedKeyPath: AnyKeyPath { _projectedKeyPath } /// :nodoc: @available(*, unavailable, message: "@Persisted can only be used as a property on a Realm object") public var wrappedValue: Value { // The static subscript below is called instead of this when the property // wrapper is used on an ObjectBase subclass, which is the only thing we support. get { fatalError("called wrappedValue getter") } // swiftlint:disable:next unused_setter_value set { fatalError("called wrappedValue setter") } } /// :nodoc: public static subscript>( _enclosingInstance observed: EnclosingSelf, wrapped wrappedKeyPath: ReferenceWritableKeyPath, storage storageKeyPath: ReferenceWritableKeyPath ) -> Value { get { let storage = observed[keyPath: storageKeyPath] return observed.rootObject[keyPath: storage._projectedKeyPath] } set { guard let keyPath = observed[keyPath: storageKeyPath].projectedKeyPath as? WritableKeyPath else { preconditionFailure("KeyPath is not writable") } var obj = observed.rootObject obj[keyPath: keyPath] = newValue } } /// Declares a property which is lazily initialized to the type's default value. public init(_ projectedKeyPath: KeyPath) { self._projectedKeyPath = projectedKeyPath } } // MARK: ProjectionObservable /** A type erased Projection. ProjectionObservable is a Combine publisher */ public protocol ProjectionObservable: AnyObject, ThreadConfined { /// The Projection's underlying type - a child of Realm `Object` or `EmbeddedObject`. associatedtype Root: ObjectBase /// The object being projected var rootObject: Root { get } /// :nodoc: init(projecting object: Root) } /// ``Projection`` is a light weight model of the original Realm ``Object`` or ``EmbeddedObject``. /// You can use `Projection` as a view model to minimize boilerplate. /// /// Example of usage: /// ```swift /// public class Person: Object { /// @Persisted var firstName = "" /// @Persisted var lastName = "" /// @Persisted var address: Address? /// @Persisted var friends: List /// @Persisted var reviews: List /// } /// /// public class Address: EmbeddedObject { /// @Persisted var city: String = "" /// @Persisted var country = "" /// } /// /// class PersonProjection: Projection { /// @Projected(\Person.firstName) var firstName /// @Projected(\Person.lastName.localizedUppercase) /// var lastNameCaps /// @Projected(\Person.address.city) var homeCity /// @Projected(\Person.friends.projectTo.firstName) /// var friendsFirstName: ProjectedCollection /// } /// ``` /// /// ### Supported property types /// /// Projection can transform the original `@Persisted` properties in several ways: /// - `Passthrough` - `Projection`'s property will have same name and type as original object. See `PersonProjection.firstName`. /// - `Rename` - Projection's property will have same type as original object just with the new name. /// - `Keypath resolution` - you can access the certain properties of the projected `Object`. See `PersonProjection.lastNameCaps` and `PersonProjection.homeCity`. /// - `Collection mapping` - `List` and `MutableSet`of `Object`s or `EmbeddedObject`s can be projected as a collection of primitive values. /// See `PersonProjection.friendsFirstName`. /// - `Exclusion` - all properties of the original Realm object that were not defined in the projection model will be excluded from projection. /// Any changes happened on those properties will not trigger a change notification for the `Projection`. /// You still can access the original `Object` or `EmbeddedObject` and observe notifications directly on it. /// - note: each `@Persisted` property can be `@Projected` in different ways in the same Projection class. /// Each `Object` or `EmbeddedObject` can have sevaral projections of same or different classes at once. /// /// ### Querying /// /// You can retrieve all Projections of a given type from a Realm by calling the `objects(_:)` of Realm or `init(projecting:)` /// of Projection's class: /// /// ```swift /// let projections = realm.object(PersonProjection.self) /// let personObject = realm.create(Person.self) /// let singleProjection = PersonProjection(projecting: personObject) /// ``` open class Projection: RealmCollectionValue, ProjectionObservable { /// :nodoc: public typealias PersistedType = Root /// The object being projected public let rootObject: Root /** Create a new projection. - parameter object: The object to project. */ public required init(projecting object: Root) { self.rootObject = object // Eagerly initialize the schema to ensure we report errors at a sensible time _ = schema } /// :nodoc: public static func == (lhs: Projection, rhs: Projection) -> Bool { RLMObjectBaseAreEqual(lhs.rootObject, rhs.rootObject) } /// :nodoc: public func hash(into hasher: inout Hasher) { let hashVal = rootObject.hashValue hasher.combine(hashVal) } /// :nodoc: open var description: String { return """ \(type(of: self))<\(type(of: rootObject))> <\(Unmanaged.passUnretained(rootObject).toOpaque())> { \t\(schema.map { "\t\(String($0.label))(\\.\($0.originPropertyKeyPathString)) = \(rootObject[keyPath: $0.projectedKeyPath]!);" }.joined(separator: "\n")) } """ } /// :nodoc: public static func _rlmDefaultValue() -> Self { fatalError() } } extension ProjectionObservable { /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: ["name"], { changes in // ... }) ``` - The above notification block fires for changes to the `Person.firstName` property of the the projection's underlying `Person` Object, but not for any changes made to `Person.lastName` or `Person.friends` list. - The notification block fires for changes of `PersonProjection.name` property, but not for another projection's property change. - If the observed key path were `["firstFriendsName"]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `List` and `Results`, there is no "initial" callback made after you add a new notification block. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - warning: For projected properties where the original property has the same root property name, this will trigger a `PropertyChange` for each of the Projected properties even though the change only corresponds to one of them. For the following `Projection` object ```swift class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.address.country) originCountry @Projected(\Person.address.phone.number) mobile } let token = projectedPerson.observe { changes in if case .change(_, let propertyChanges) = changes { propertyChanges[0].newValue as? String, "Winterfell" // Will notify the new value propertyChanges[1].newValue as? String, "555-555-555" // Will notify with the current value, which hasn't change. } }) try realm.write { person.address.country = "Winterfell" } ``` - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { let kps: [String] = schema.map(\.originPropertyKeyPathString) // If we're observing on a different queue, we need a projection which // wraps an object confined to that queue. We'll lazily create it the // first time the observation block is called. We can't create it now // as we're probably not on the queue. If we aren't observing on a queue, // we can just use ourself rather than allocating a new object var projection: Self? if queue == nil { projection = self } let schema = self.schema return RLMObjectBaseAddNotificationBlock(rootObject, kps, queue) { object, names, oldValues, newValues, error in assert(error == nil) // error is no longer used guard let names = names, let newValues = newValues else { block(.deleted) return } if projection == nil { projection = Self(projecting: object as! Self.Root) } // Mapping the old values to the projected values requires assigning // them to an object and then reading from the projected key path var unmanagedRoot: Self.Root? if let oldValues = oldValues { unmanagedRoot = Self.Root() for i in 0.. Bool = { prop in if prop.originPropertyKeyPathString.components(separatedBy: ".").first != names[i] { return false } guard let keyPaths, !keyPaths.isEmpty else { return true } // This will allow us to notify `PropertyChange`s associated only to the keyPaths passed by the user, instead of any Property which has the same root as the notified one. return keyPaths.contains(prop.originPropertyKeyPathString) } for property in schema.filter(filter) { // If the root is marked as modified this will build a `PropertyChange` for each of the Projection properties with the same original root, even if there is no change on their value. var changeOldValue: Any? if oldValues != nil { changeOldValue = unmanagedRoot![keyPath: property.projectedKeyPath] } let changedNewValue = object[keyPath: property.projectedKeyPath] projectedChanges.append(.init(name: property.label, oldValue: changeOldValue, newValue: changedNewValue)) } } // keypath filtering means this should never actually be empty if !projectedChanges.isEmpty { block(.change(projection!, projectedChanges)) } } } /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: [\PersonProjection.name], { changes in // ... }) ``` - The above notification block fires for changes to the `firstName` property of the Object, but not for any changes made to `lastName` or `friends` list. - If the observed key path were `[\PersonProjection.firstFriendsName]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `List` and `Results`, there is no "initial" callback made after you add a new notification block. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (ObjectChange) -> Void) -> NotificationToken { observe(keyPaths: map(keyPaths: keyPaths), on: queue, block) } #if compiler(<6) /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called on the actor after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: ["name"], { changes in // ... }) ``` - The above notification block fires for changes to the `Person.firstName` property of the the projection's underlying `Person` Object, but not for any changes made to `Person.lastName` or `Person.friends` list. - The notification block fires for changes of `PersonProjection.name` property, but not for another projection's property change. - If the observed key path were `["firstFriendsName"]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. Unlike with Collection notifications, there is no "Initial" notification and there is no gap between when this function returns and when changes will first be captured. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in obj.observe(keyPaths: keyPaths, on: nil) { (change: ObjectChange) in actor.invokeIsolated(block, change) } } } /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called on the actor after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: [\PersonProjection.name], { changes in // ... }) ``` - The above notification block fires for changes to the `Person.firstName` property of the the projection's underlying `Person` Object, but not for any changes made to `Person.lastName` or `Person.friends` list. - The notification block fires for changes of `PersonProjection.name` property, but not for another projection's property change. - If the observed key path were `[\.firstFriendsName]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. Unlike with Collection notifications, there is no "Initial" notification and there is no gap between when this function returns and when changes will first be captured. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: map(keyPaths: keyPaths), on: actor, block) } fileprivate var schema: [ProjectionProperty] { projectionSchemaCache.schema(for: self) } private func map(keyPaths: [PartialKeyPath]) -> [String]? { if keyPaths.isEmpty { return nil } let names = NSMutableArray() let root = Root.keyPathRecorder(with: names) let projection = Self(projecting: root) return keyPaths.map { names.removeAllObjects() _ = projection[keyPath: $0] return names.componentsJoined(by: ".") } } #else /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called on the actor after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: ["name"], { changes in // ... }) ``` - The above notification block fires for changes to the `Person.firstName` property of the the projection's underlying `Person` Object, but not for any changes made to `Person.lastName` or `Person.friends` list. - The notification block fires for changes of `PersonProjection.name` property, but not for another projection's property change. - If the observed key path were `["firstFriendsName"]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. Unlike with Collection notifications, there is no "Initial" notification and there is no gap between when this function returns and when changes will first be captured. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, obj in obj.observe(keyPaths: keyPaths, on: nil) { (change: ObjectChange) in actor.invokeIsolated(block, change) } } } /** Registers a block to be called each time the projection's underlying object changes. The block will be asynchronously called on the actor after each write transaction which deletes the underlying object or modifies any of the projected properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in different processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. If no key paths are given, the block will be executed on any insertion, modification, or deletion for all projected properties, including projected properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted public var friends: List } class PersonProjection: Projection { @Projected(\Person.firstName) var name @Projected(\Person.lastName.localizedUppercase) var lastNameCaps @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } let token = projectedPerson.observe(keyPaths: [\PersonProjection.name], { changes in // ... }) ``` - The above notification block fires for changes to the `Person.firstName` property of the the projection's underlying `Person` Object, but not for any changes made to `Person.lastName` or `Person.friends` list. - The notification block fires for changes of `PersonProjection.name` property, but not for another projection's property change. - If the observed key path were `[\.firstFriendsName]`, then any insertion, deletion, or modification of the `firstName` of the `friends` list will trigger the block. A change to `someFriend.lastName` would not trigger the block (where `someFriend` is an element contained in `friends`) Notifications are delivered to a function isolated to the given actor, on that actors executor. If the actor is performing blocking work, multiple notifications may be coalesced into a single notification. Unlike with Collection notifications, there is no "Initial" notification and there is no gap between when this function returns and when changes will first be captured. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any projected property change on the object. String key paths which do not correspond to a valid projected property will throw an exception. - parameter actor: The actor which notifications should be delivered on. The block is passed this actor as an isolated parameter, allowing you to access the actor synchronously from within the callback. - parameter block: The block to call with information about changes to the object. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) public func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, ObjectChange) -> Void ) async -> NotificationToken { await observe(keyPaths: map(keyPaths: keyPaths), on: actor, block) } fileprivate var schema: [ProjectionProperty] { projectionSchemaCache.schema(for: self) } private func map(keyPaths: [PartialKeyPath]) -> [String]? { if keyPaths.isEmpty { return nil } let names = NSMutableArray() let root = Root.keyPathRecorder(with: names) let projection = Self(projecting: root) return keyPaths.map { names.removeAllObjects() _ = projection[keyPath: $0] return names.componentsJoined(by: ".") } } #endif } /** Information about a specific property which changed in an `Object` change notification. */ @frozen public struct ProjectedPropertyChange { /** The name of the property which changed. */ public let name: String /** Value of the property before the change occurred. This is not supplied if the change happened on the same thread as the notification and for `List` properties. For object properties this will give the object which was previously linked to, but that object will have its new values and not the values it had before the changes. This means that `previousValue` may be a deleted object, and you will need to check `isInvalidated` before accessing any of its properties. */ public let oldValue: Any? /** The value of the property after the change occurred. This is not supplied for `List` properties and will always be nil. */ public let newValue: Any? } // MARK: Notifications @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public extension Projection { /// :nodoc: func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?) { rootObject.addObserver(observer, forKeyPath: keyPath, options: options, context: context) } /// :nodoc: func removeObserver(_ observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutableRawPointer?) { rootObject.removeObserver(observer, forKeyPath: keyPath, context: context) } /// :nodoc: func removeObserver(_ observer: NSObject, forKeyPath keyPath: String) { rootObject.removeObserver(observer, forKeyPath: keyPath) } } // MARK: ThreadConfined extension Projection: ThreadConfined where Root: ThreadConfined { /** The Realm which manages the object, or `nil` if the object is unmanaged. Note: Projection can be instantiated for the managed objects only therefore realm will never be nil. Unmanaged objects are not confined to a thread and cannot be passed to methods expecting a `ThreadConfined` object. */ public var realm: Realm? { rootObject.realm } /// Indicates if the object can no longer be accessed because it is now invalid. public var isInvalidated: Bool { return rootObject.isInvalidated } /** Indicates if the object is frozen. Frozen objects are not confined to their source thread. Forming a `ThreadSafeReference` to a frozen object is allowed, but is unlikely to be useful. */ public var isFrozen: Bool { return realm?.isFrozen ?? false } /** Returns a frozen snapshot of this object. Unlike normal Realm live objects, the frozen copy can be read from any thread, and the values read will never update to reflect new writes to the Realm. Frozen collections can be queried like any other Realm collection. Frozen objects cannot be mutated, and cannot be observed for change notifications. Unmanaged Realm objects cannot be frozen. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ public func freeze() -> Self { let frozenObject = rootObject.freeze() return Self(projecting: frozenObject) } /** Returns a live (mutable) reference of this object. Will return self if called on an already live object. */ public func thaw() -> Self? { if let thawedObject = rootObject.thaw() { return Self(projecting: thawedObject) } return nil } } // MARK: - RealmSubscribable @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension ProjectionObservable { /// :nodoc: public func _observe(_ keyPaths: [String]?, on queue: DispatchQueue?, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Self { return observe(keyPaths: keyPaths ?? [], on: queue) { (change: ObjectChange) in switch change { case .change(let projection, _): _ = subscriber.receive(projection) case .deleted: subscriber.receive(completion: .finished) case .error(let error): fatalError("Unexpected error \(error)") } } } /// :nodoc: public func _observe(_ keyPaths: [String]?, _ subscriber: S) -> NotificationToken where S: Subscriber, S.Input == Void { return observe(keyPaths: [PartialKeyPath](), { _ in _ = subscriber.receive() }) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Projection: ObservableObject, RealmSubscribable where Root: ThreadConfined { /// A publisher that emits Void each time the projection changes. /// /// Despite the name, this actually emits *after* the projection has changed. public var objectWillChange: RealmPublishers.WillChange { RealmPublishers.WillChange(self) } } // MARK: Implementation private struct ProjectionProperty: @unchecked Sendable { let projectedKeyPath: AnyKeyPath let originPropertyKeyPathString: String let label: String } // A subset of OSAllocatedUnfairLock, which requires iOS 16 internal final class AllocatedUnfairLock: @unchecked Sendable { private var value: Value private let impl: os_unfair_lock_t = .allocate(capacity: 1) init(_ value: Value) { impl.initialize(to: os_unfair_lock()) self.value = value } func withLock(_ body: (inout Value) -> R) -> R { os_unfair_lock_lock(impl) let ret = body(&value) os_unfair_lock_unlock(impl) return ret } } // A property wrapper which unsafely disables concurrency checking for a property // This is required when a property is guarded by something which concurrency // checking doesn't understand (i.e. a lock instead of an actor) @usableFromInline @propertyWrapper internal struct Unchecked: @unchecked Sendable { public var wrappedValue: Wrapped public init(wrappedValue: Wrapped) { self.wrappedValue = wrappedValue } public init(_ wrappedValue: Wrapped) { self.wrappedValue = wrappedValue } } private final class ProjectionSchemaCache: @unchecked Sendable { private static let schema = AllocatedUnfairLock([ObjectIdentifier: [ProjectionProperty]]()) fileprivate func schema(for obj: T) -> [ProjectionProperty] { let identifier = ObjectIdentifier(type(of: obj)) if let schema = Self.schema.withLock({ $0[identifier] }) { return schema } var properties = [ProjectionProperty]() for child in Mirror(reflecting: obj).children { guard let label = child.label?.dropFirst() else { continue } guard let projected = child.value as? AnyProjected else { continue } let originPropertyLabel = _name(for: projected.projectedKeyPath as! PartialKeyPath) guard !originPropertyLabel.isEmpty else { throwRealmException("@Projected property '\(label)' must be a part of Realm object") } properties.append(.init(projectedKeyPath: projected.projectedKeyPath, originPropertyKeyPathString: originPropertyLabel, label: String(label))) } let p = properties Self.schema.withLock { // This might overwrite a schema generated by a different thread // if we happened to do the initialization on multiple threads at // once, but if so that's fine. $0[identifier] = p } return properties } } private let projectionSchemaCache = ProjectionSchemaCache() ================================================ FILE: RealmSwift/Property.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm /** `Property` instances represent properties managed by a Realm in the context of an object schema. Such properties may be persisted to a Realm file or computed from other data in the Realm. When using Realm, property instances allow performing migrations and introspecting the database's schema. Property instances map to columns in the core database. */ @frozen public struct Property: CustomStringConvertible { // MARK: Properties internal let rlmProperty: RLMProperty /// The name of the property. public var name: String { return rlmProperty.name } /// The column name of the property in the database. This will be the same as the property name when no /// private name is provided on the property mapping. public var columnName: String { return rlmProperty.columnName ?? name } /// The type of the property. public var type: PropertyType { return rlmProperty.type } /// Indicates whether this property is an array of the property type. public var isArray: Bool { return rlmProperty.array } /// Indicates whether this property is a set of the property type. public var isSet: Bool { return rlmProperty.set } /// Indicates whether this property is a dictionary of the property type. public var isMap: Bool { return rlmProperty.dictionary } /// Indicates whether this property is indexed. public var isIndexed: Bool { return rlmProperty.indexed } /// Indicates whether this property is optional. (Note that certain numeric types must be wrapped in a /// `RealmOptional` instance in order to be declared as optional.) public var isOptional: Bool { return rlmProperty.optional } /// For `Object` and `List` properties, the name of the class of object stored in the property. public var objectClassName: String? { return rlmProperty.objectClassName } /// A human-readable description of the property object. public var description: String { return rlmProperty.description } // MARK: Initializers internal init(_ rlmProperty: RLMProperty) { self.rlmProperty = rlmProperty } } // MARK: Equatable extension Property: Equatable { /// Returns whether the two properties are equal. public static func == (lhs: Property, rhs: Property) -> Bool { return lhs.rlmProperty.isEqual(to: rhs.rlmProperty) } } ================================================ FILE: RealmSwift/Query.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm import Realm.Private /// Enum representing an option for `String` queries. public struct StringOptions: OptionSet, Sendable { /// :doc: public let rawValue: Int8 /// :doc: public init(rawValue: Int8) { self.rawValue = rawValue } /// A case-insensitive search. public static let caseInsensitive = StringOptions(rawValue: 1) /// Query ignores diacritic marks. public static let diacriticInsensitive = StringOptions(rawValue: 2) } /** `Query` is a class used to create type-safe query predicates. With `Query` you are given the ability to create Swift style query expression that will then be constructed into an `NSPredicate`. The `Query` class should not be instantiated directly and should be only used as a parameter within a closure that takes a query expression as an argument. Example: ```swift public func where(_ query: ((Query) -> Query)) -> Results ``` You would then use the above function like so: ```swift let results = realm.objects(Person.self).query { $0.name == "Foo" || $0.name == "Bar" && $0.age >= 21 } ``` ## Supported predicate types ### Prefix - NOT `!` ```swift let results = realm.objects(Person.self).query { !$0.dogsName.contains("Fido") || !$0.name.contains("Foo") } ``` ### Comparisions - Equals `==` - Not Equals `!=` - Greater Than `>` - Less Than `<` - Greater Than or Equal `>=` - Less Than or Equal `<=` - Between `.contains(_ range:)` ### Collections - IN `.contains(_ element:)` - Between `.contains(_ range:)` ### Map - @allKeys `.keys` - @allValues `.values` ### Compound - AND `&&` - OR `||` ### Collection Aggregation - @avg `.avg` - @min `.min` - @max `.max` - @sum `.sum` - @count `.count` ```swift let results = realm.objects(Person.self).query { !$0.dogs.age.avg >= 0 || !$0.dogsAgesArray.avg >= 0 } ``` ### Other - NOT `!` - Subquery `($0.fooList.intCol >= 5).count > n` */ @dynamicMemberLookup public struct Query { /// This initaliser should be used from callers who require queries on primitive collections. /// - Parameter isPrimitive: True if performing a query on a primitive collection. internal init(isPrimitive: Bool = false) { if isPrimitive { node = .keyPath(["self"], options: [.isCollection]) } else { node = .keyPath([], options: []) } } private let node: QueryNode /** The `Query` struct works by compounding `QueryNode`s together in a tree structure. Each part of a query expression will be represented by one of the below static methods. For example in the simple expression `stringCol == 'Foo'`: The first static method that will be called from inside the query closure is `subscript(dynamicMember member: KeyPath)` this will extract the `stringCol` keypath. The last static method to be called in this expression is `func == (_ lhs: Query, _ rhs: V)` where the lhs is a `Query` which holds the `QueryNode` keyPath for `stringCol`. The rhs will be expressed as a constant in `QueryNode` and a tree will be built to represent an equals comparison. To build the tree we will do: ``` Query(.comparison(operator: .equal, lhs.node, .constant(rhs), options: [])) ``` This sets the comparison node as the root node for the expression and the new `Query` struct will be returned. When it comes time to build the predicate string with its arguments call `_constructPredicate()`. This will recursively traverse the tree and build the NSPredicate compatible string. */ private init(_ node: QueryNode) { self.node = node } private func appendKeyPath(_ keyPath: String, options: KeyPathOptions) -> QueryNode { if case let .keyPath(kp, ops) = node { return .keyPath(kp + [keyPath], options: ops.union(options)) } else if case .mapSubscript = node { throwRealmException("Cannot apply key path to Map subscripts.") } throwRealmException("Cannot apply a keypath to \(buildPredicate(node))") } private func buildCollectionAggregateKeyPath(_ aggregate: String) -> QueryNode { if case let .keyPath(kp, options) = node { var keyPaths = kp if keyPaths.count > 1 { keyPaths.insert(aggregate, at: 1) } else { keyPaths.append(aggregate) } return .keyPath(keyPaths, options: [options.subtracting(.requiresAny)]) } throwRealmException("Cannot apply a keypath to \(buildPredicate(node))") } private func keyPathErasingAnyPrefix(appending keyPath: String? = nil) -> QueryNode { if case let .keyPath(kp, o) = node { if let keyPath = keyPath { return .keyPath(kp + [keyPath], options: [o.subtracting(.requiresAny)]) } return .keyPath(kp, options: [o.subtracting(.requiresAny)]) } throwRealmException("Cannot apply a keypath to \(buildPredicate(node))") } private func anySubscript(appending key: CollectionSubscript) -> QueryNode { if case .keyPath = node { return .mapAnySubscripts(keyPathErasingAnyPrefix(), keys: [key]) } else if case let .mapAnySubscripts(kp, keys) = node { var tmpKeys = keys tmpKeys.append(key) return .mapAnySubscripts(kp, keys: tmpKeys) } throwRealmException("Cannot add subscript to \(buildPredicate(node))") } // MARK: Comparable /// :nodoc: public static func == (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .equal, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func == (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .equal, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func != (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .notEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func != (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .notEqual, lhs.node, rhs.node, options: [])) } // MARK: In /// Checks if the value is present in the collection. public func `in`(_ collection: U) -> Query where U.Element == T { .init(.comparison(operator: .in, node, .constant(collection), options: [])) } // MARK: Subscript /// :nodoc: public subscript(dynamicMember member: KeyPath) -> Query where T: ObjectBase { .init(appendKeyPath(_name(for: member), options: [])) } /// :nodoc: public subscript(dynamicMember member: KeyPath) -> Query where T: ObjectBase { .init(appendKeyPath(_name(for: member), options: [.isCollection, .requiresAny])) } /// :nodoc: public subscript(dynamicMember member: KeyPath) -> Query where T: ObjectBase { .init(appendKeyPath(_name(for: member), options: [.isCollection, .requiresAny])) } // MARK: Query Construction /// For testing purposes only. Do not use directly. public static func _constructForTesting() -> Query { return Query() } /// Constructs an NSPredicate compatible string with its accompanying arguments. /// - Note: This is for internal use only and is exposed for testing purposes. public func _constructPredicate() -> (String, [Any]) { return buildPredicate(node) } /// Creates an NSPredicate compatible string. /// - Returns: A tuple containing the predicate string and an array of arguments. /// Creates an NSPredicate from the query expression. internal var predicate: NSPredicate { let predicate = _constructPredicate() return NSPredicate(format: predicate.0, argumentArray: predicate.1) } } // MARK: Numerics extension Query where T: _HasPersistedType, T.PersistedType: _QueryNumeric { /// :nodoc: public static func > (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .greaterThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func > (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .greaterThan, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func >= (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .greaterThanEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func >= (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .greaterThanEqual, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func < (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .lessThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func < (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .lessThan, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func <= (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .lessThanEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func <= (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .lessThanEqual, lhs.node, rhs.node, options: [])) } } // MARK: Compound extension Query where T == Bool { /// :nodoc: public static prefix func ! (_ query: Query) -> Query { .init(.not(query.node)) } /// :nodoc: public static func && (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .and, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func || (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .or, lhs.node, rhs.node, options: [])) } } // MARK: Mixed extension Query where T == AnyRealmValue { /// :nodoc: public subscript(position: Int) -> Query { .init(anySubscript(appending: .index(position))) } /// :nodoc: public subscript(key: String) -> Query { .init(anySubscript(appending: .key(key))) } /// Query all indexes or keys in a mixed nested collecttion. public var any: Query { .init(anySubscript(appending: .all)) } } // MARK: OptionalProtocol extension Query where T: OptionalProtocol { /// :nodoc: public subscript(dynamicMember member: KeyPath) -> Query where T.Wrapped: ObjectBase { .init(appendKeyPath(_name(for: member), options: [])) } } // MARK: RealmCollection extension Query where T: RealmCollection { /// :nodoc: public subscript(dynamicMember member: KeyPath) -> Query where T.Element: ObjectBase { .init(appendKeyPath(_name(for: member), options: [])) } /// Query the count of the objects in the collection. public var count: Query { .init(keyPathErasingAnyPrefix(appending: "@count")) } } extension Query where T: RealmCollection { /// Checks if an element exists in this collection. public func contains(_ value: T.Element) -> Query { .init(.comparison(operator: .in, .constant(value), keyPathErasingAnyPrefix(), options: [])) } /// Checks if any elements contained in the given array are present in the collection. public func containsAny(in collection: U) -> Query where U.Element == T.Element { .init(.comparison(operator: .in, node, .constant(collection), options: [])) } } extension Query where T: RealmCollection, T.Element: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThanEqual, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } } extension Query where T: RealmCollection, T.Element: OptionalProtocol, T.Element.Wrapped: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThanEqual, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } } extension Query where T: RealmCollection { /// :nodoc: public static func == (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .equal, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func != (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .notEqual, lhs.node, .constant(rhs), options: [])) } } extension Query where T: RealmCollection, T.Element.PersistedType: _QueryNumeric { /// :nodoc: public static func > (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .greaterThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func >= (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .greaterThanEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func < (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .lessThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func <= (_ lhs: Query, _ rhs: T.Element) -> Query { .init(.comparison(operator: .lessThanEqual, lhs.node, .constant(rhs), options: [])) } /// Returns the minimum value in the collection. public var min: Query { .init(keyPathErasingAnyPrefix(appending: "@min")) } /// Returns the maximum value in the collection. public var max: Query { .init(keyPathErasingAnyPrefix(appending: "@max")) } /// Returns the average in the collection. public var avg: Query { .init(keyPathErasingAnyPrefix(appending: "@avg")) } /// Returns the sum of all the values in the collection. public var sum: Query { .init(keyPathErasingAnyPrefix(appending: "@sum")) } } // MARK: RealmKeyedCollection extension Query where T: RealmKeyedCollection { /// Checks if any elements contained in the given array are present in the map's values. public func containsAny(in collection: U) -> Query where U.Element == T.Value { .init(.comparison(operator: .in, node, .constant(collection), options: [])) } /// Checks if an element exists in this collection. public func contains(_ value: T.Value) -> Query { .init(.comparison(operator: .in, .constant(value), keyPathErasingAnyPrefix(), options: [])) } /// Allows a query over all values in the Map. public var values: Query { .init(appendKeyPath("@allValues", options: [])) } /// :nodoc: public subscript(member: T.Key) -> Query { .init(.mapSubscript(keyPathErasingAnyPrefix(), key: member)) } } extension Query where T: RealmKeyedCollection, T.Key == String { /// Allows a query over all keys in the `Map`. public var keys: Query { .init(appendKeyPath("@allKeys", options: [])) } } extension Query where T: RealmKeyedCollection, T.Value: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThanEqual, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } } extension Query where T: RealmKeyedCollection, T.Value: OptionalProtocol, T.Value.Wrapped: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, keyPathErasingAnyPrefix(appending: "@min"), .constant(range.lowerBound), options: []), .comparison(operator: .lessThanEqual, keyPathErasingAnyPrefix(appending: "@max"), .constant(range.upperBound), options: []), options: [])) } } extension Query where T: RealmKeyedCollection, T.Value.PersistedType: _QueryNumeric { /// Returns the minimum value in the keyed collection. public var min: Query { .init(keyPathErasingAnyPrefix(appending: "@min")) } /// Returns the maximum value in the keyed collection. public var max: Query { .init(keyPathErasingAnyPrefix(appending: "@max")) } /// Returns the average in the keyed collection. public var avg: Query { .init(keyPathErasingAnyPrefix(appending: "@avg")) } /// Returns the sum of all the values in the keyed collection. public var sum: Query { .init(keyPathErasingAnyPrefix(appending: "@sum")) } } extension Query where T: RealmKeyedCollection { /// Returns the count of all the values in the keyed collection. public var count: Query { .init(keyPathErasingAnyPrefix(appending: "@count")) } } // MARK: - PersistableEnum extension Query where T: PersistableEnum, T.RawValue: _RealmSchemaDiscoverable { /// Query on the rawValue of the Enum rather than the Enum itself. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query { .init(node) } } extension Query where T: OptionalProtocol, T.Wrapped: PersistableEnum, T.Wrapped.RawValue: _RealmSchemaDiscoverable { /// Query on the rawValue of the Enum rather than the Enum itself. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query { .init(node) } } // The actual collection type returned in these doesn't matter because it's // only used to constrain the set of operations available, and the collections // all have the same operations. extension Query where T: RealmCollection, T.Element: PersistableEnum, T.Element.RawValue: RealmCollectionValue { /// Query on the rawValue of the Enums in the collection rather than the Enums themselves. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query> { .init(node) } } extension Query where T: RealmKeyedCollection, T.Value: PersistableEnum, T.Value.RawValue: RealmCollectionValue { /// Query on the rawValue of the Enums in the collection rather than the Enums themselves. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query> { .init(node) } } extension Query where T: RealmCollection, T.Element: OptionalProtocol, T.Element.Wrapped: PersistableEnum, T.Element.Wrapped.RawValue: _RealmCollectionValueInsideOptional { /// Query on the rawValue of the Enums in the collection rather than the Enums themselves. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query> { .init(node) } } extension Query where T: RealmKeyedCollection, T.Value: OptionalProtocol, T.Value.Wrapped: PersistableEnum, T.Value.Wrapped.RawValue: _RealmCollectionValueInsideOptional { /// Query on the rawValue of the Enums in the collection rather than the Enums themselves. /// /// This can be used to write queries which can be expressed on the /// RawValue but not the enum. For example, this lets you query for /// `.starts(with:)` on a string enum where the prefix is not a member of /// the enum. public var rawValue: Query> { .init(node) } } // MARK: - CustomPersistable extension Query where T: _HasPersistedType { /// Query on the persistableValue of the value rather than the value itself. /// /// This can be used to write queries which can be expressed on the /// persisted type but not on the type itself, such as range queries /// on the persistable value or to query for values which can't be /// converted to the mapped type. /// /// For types which don't conform to PersistableEnum, CustomPersistable or /// FailableCustomPersistable this doesn't do anything useful. public var persistableValue: Query { .init(node) } } // The actual collection type returned in these doesn't matter because it's // only used to constrain the set of operations available, and the collections // all have the same operations. extension Query where T: RealmCollection { /// Query on the persistableValue of the values in the collection rather /// than the values themselves. /// /// This can be used to write queries which can be expressed on the /// persisted type but not on the type itself, such as range queries /// on the persistable value or to query for values which can't be /// converted to the mapped type. /// /// For types which don't conform to PersistableEnum, CustomPersistable or /// FailableCustomPersistable this doesn't do anything useful. public var persistableValue: Query> { .init(node) } } extension Query where T: RealmKeyedCollection { /// Query on the persistableValue of the values in the collection rather /// than the values themselves. /// /// This can be used to write queries which can be expressed on the /// persisted type but not on the type itself, such as range queries /// on the persistable value or to query for values which can't be /// converted to the mapped type. /// /// For types which don't conform to PersistableEnum, CustomPersistable or /// FailableCustomPersistable this doesn't do anything useful. public var persistableValue: Query> { .init(node) } } // MARK: _QueryNumeric extension Query where T: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, node, .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, node, .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.between(node, lowerBound: .constant(range.lowerBound), upperBound: .constant(range.upperBound))) } } // MARK: _QueryString extension Query where T: _HasPersistedType, T.PersistedType: _QueryString { /** Checks for all elements in this collection that equal the given value. `?` and `*` are allowed as wildcard characters, where `?` matches 1 character and `*` matches 0 or more characters. - parameter value: value used. - parameter caseInsensitive: `true` if it is a case-insensitive search. */ public func like(_ value: T, caseInsensitive: Bool = false) -> Query { .init(.comparison(operator: .like, node, .constant(value), options: caseInsensitive ? [.caseInsensitive] : [])) } /** Checks for all elements in this collection that equal the given value. `?` and `*` are allowed as wildcard characters, where `?` matches 1 character and `*` matches 0 or more characters. - parameter value: value used. - parameter caseInsensitive: `true` if it is a case-insensitive search. */ public func like(_ column: Query, caseInsensitive: Bool = false) -> Query { .init(.comparison(operator: .like, node, column.node, options: caseInsensitive ? [.caseInsensitive] : [])) } } // MARK: _QueryBinary extension Query where T: _HasPersistedType, T.PersistedType: _QueryBinary { /** Checks for all elements in this collection that contains the given value. - parameter value: value used. - parameter options: A Set of options used to evaluate the search query. */ public func contains(_ value: T, options: StringOptions = []) -> Query { .init(.comparison(operator: .contains, node, .constant(value), options: options)) } /** Compares that this column contains a value in another column. - parameter column: The other column. - parameter options: A Set of options used to evaluate the search query. */ public func contains(_ column: Query, options: StringOptions = []) -> Query where U: _Persistable, U.PersistedType: _QueryBinary { .init(.comparison(operator: .contains, node, column.node, options: options)) } /** Checks for all elements in this collection that starts with the given value. - parameter value: value used. - parameter options: A Set of options used to evaluate the search query. */ public func starts(with value: T, options: StringOptions = []) -> Query { .init(.comparison(operator: .beginsWith, node, .constant(value), options: options)) } /** Compares that this column starts with a value in another column. - parameter column: The other column. - parameter options: A Set of options used to evaluate the search query. */ public func starts(with column: Query, options: StringOptions = []) -> Query { .init(.comparison(operator: .beginsWith, node, column.node, options: options)) } /** Checks for all elements in this collection that ends with the given value. - parameter value: value used. - parameter options: A Set of options used to evaluate the search query. */ public func ends(with value: T, options: StringOptions = []) -> Query { .init(.comparison(operator: .endsWith, node, .constant(value), options: options)) } /** Compares that this column ends with a value in another column. - parameter column: The other column. - parameter options: A Set of options used to evaluate the search query. */ public func ends(with column: Query, options: StringOptions = []) -> Query { .init(.comparison(operator: .endsWith, node, column.node, options: options)) } /** Checks for all elements in this collection that equals the given value. - parameter value: value used. - parameter options: A Set of options used to evaluate the search query. */ public func equals(_ value: T, options: StringOptions = []) -> Query { .init(.comparison(operator: .equal, node, .constant(value), options: options)) } /** Compares that this column is equal to the value in another given column. - parameter column: The other column. - parameter options: A Set of options used to evaluate the search query. */ public func equals(_ column: Query, options: StringOptions = []) -> Query { .init(.comparison(operator: .equal, node, column.node, options: options)) } /** Checks for all elements in this collection that are not equal to the given value. - parameter value: value used. - parameter options: A Set of options used to evaluate the search query. */ public func notEquals(_ value: T, options: StringOptions = []) -> Query { .init(.comparison(operator: .notEqual, node, .constant(value), options: options)) } /** Compares that this column is not equal to the value in another given column. - parameter column: The other column. - parameter options: A Set of options used to evaluate the search query. */ public func notEquals(_ column: Query, options: StringOptions = []) -> Query { .init(.comparison(operator: .notEqual, node, column.node, options: options)) } /// :nodoc: public static func > (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .greaterThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func > (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .greaterThan, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func >= (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .greaterThanEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func >= (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .greaterThanEqual, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func < (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .lessThan, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func < (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .lessThan, lhs.node, rhs.node, options: [])) } /// :nodoc: public static func <= (_ lhs: Query, _ rhs: T) -> Query { .init(.comparison(operator: .lessThanEqual, lhs.node, .constant(rhs), options: [])) } /// :nodoc: public static func <= (_ lhs: Query, _ rhs: Query) -> Query { .init(.comparison(operator: .lessThanEqual, lhs.node, rhs.node, options: [])) } } extension Query where T: OptionalProtocol, T.Wrapped: Comparable { /// Checks for all elements in this collection that are within a given range. public func contains(_ range: Range) -> Query { .init(.comparison(operator: .and, .comparison(operator: .greaterThanEqual, node, .constant(range.lowerBound), options: []), .comparison(operator: .lessThan, node, .constant(range.upperBound), options: []), options: [])) } /// Checks for all elements in this collection that are within a given range. public func contains(_ range: ClosedRange) -> Query { .init(.between(node, lowerBound: .constant(range.lowerBound), upperBound: .constant(range.upperBound))) } } // MARK: Subquery extension Query where T == Bool { /// Completes a subquery expression. /// - Usage: /// ``` /// (($0.myCollection.age >= 21) && ($0.myCollection.siblings == 4))).count >= 5 /// ``` /// - Note: /// Do not mix collections within a subquery expression. It is /// only permitted to reference a single collection per each subquery. public var count: Query { .init(.subqueryCount(node)) } } // MARK: Keypath Collection Aggregates /** You can use only use aggregates in numeric types where the root keypath is a collection. ```swift let results = realm.objects(Person.self).query { !$0.dogs.age.avg >= 0 } ``` Where `dogs` is an array of objects. */ extension Query where T: _HasPersistedType, T.PersistedType: _QueryNumeric { /// Returns the minimum value of the objects in the collection based on the keypath. public var min: Query { Query(buildCollectionAggregateKeyPath("@min")) } /// Returns the maximum value of the objects in the collection based on the keypath. public var max: Query { Query(buildCollectionAggregateKeyPath("@max")) } /// Returns the average of the objects in the collection based on the keypath. public var avg: Query { Query(buildCollectionAggregateKeyPath("@avg")) } /// Returns the sum of the objects in the collection based on the keypath. public var sum: Query { Query(buildCollectionAggregateKeyPath("@sum")) } } public extension Query where T: OptionalProtocol, T.Wrapped: EmbeddedObject { /** Use `geoWithin` function to filter objects whose location points lie within a certain area, using a Geospatial shape (`GeoBox`, `GeoPolygon` or `GeoCircle`). - note: There is no dedicated type to store Geospatial points, instead points should be stored as [GeoJson-shaped](https://www.mongodb.com/docs/manual/reference/geojson/) embedded object. Geospatial queries (`geoWithin`) can only be executed in such a type of objects and will throw otherwise. - see: `GeoPoint` */ func geoWithin(_ value: U) -> Query { .init(.geoWithin(node, .constant(value))) } } /// Tag protocol for all numeric types. public protocol _QueryNumeric: _RealmSchemaDiscoverable { } extension Int: _QueryNumeric { } extension Int8: _QueryNumeric { } extension Int16: _QueryNumeric { } extension Int32: _QueryNumeric { } extension Int64: _QueryNumeric { } extension Float: _QueryNumeric { } extension Double: _QueryNumeric { } extension Decimal128: _QueryNumeric { } extension Date: _QueryNumeric { } extension AnyRealmValue: _QueryNumeric { } extension Optional: _QueryNumeric where Wrapped: _Persistable, Wrapped.PersistedType: _QueryNumeric { } /// Tag protocol for all types that are compatible with `String`. public protocol _QueryString: _QueryBinary { } extension String: _QueryString { } extension Optional: _QueryString where Wrapped: _Persistable, Wrapped.PersistedType: _QueryString { } /// Tag protocol for all types that are compatible with `Binary`. public protocol _QueryBinary { } extension Data: _QueryBinary { } extension Optional: _QueryBinary where Wrapped: _Persistable, Wrapped.PersistedType: _QueryBinary { } // MARK: QueryNode - private indirect enum QueryNode { enum Operator: String { case or = "||" case and = "&&" case equal = "==" case notEqual = "!=" case lessThan = "<" case lessThanEqual = "<=" case greaterThan = ">" case greaterThanEqual = ">=" case `in` = "IN" case contains = "CONTAINS" case beginsWith = "BEGINSWITH" case endsWith = "ENDSWITH" case like = "LIKE" } case not(_ child: QueryNode) case constant(_ value: Any?) case keyPath(_ value: [String], options: KeyPathOptions) case comparison(operator: Operator, _ lhs: QueryNode, _ rhs: QueryNode, options: StringOptions) case between(_ lhs: QueryNode, lowerBound: QueryNode, upperBound: QueryNode) case subqueryCount(_ child: QueryNode) case mapSubscript(_ keyPath: QueryNode, key: Any) case mapAnySubscripts(_ keyPath: QueryNode, keys: [CollectionSubscript]) case geoWithin(_ keyPath: QueryNode, _ value: QueryNode) } private enum CollectionSubscript { case index(Int) case key(String) case all } private func buildPredicate(_ root: QueryNode, subqueryCount: Int = 0) -> (String, [Any]) { let formatStr = NSMutableString() let arguments = NSMutableArray() var subqueryCounter = subqueryCount func buildExpression(_ lhs: QueryNode, _ op: String, _ rhs: QueryNode, prefix: String? = nil) { if case let .keyPath(_, lhsOptions) = lhs, case let .keyPath(_, rhsOptions) = rhs, lhsOptions.contains(.isCollection), rhsOptions.contains(.isCollection) { throwRealmException("Comparing two collection columns is not permitted.") } formatStr.append("(") if let prefix = prefix { formatStr.append(prefix) } build(lhs) formatStr.append(" \(op) ") build(rhs) formatStr.append(")") } func buildCompoundExpression(_ lhs: QueryNode, _ op: String, _ rhs: QueryNode, prefix: String? = nil) { if let prefix = prefix { formatStr.append(prefix) } formatStr.append("(") build(lhs, isNewNode: true) formatStr.append(" \(op) ") build(rhs, isNewNode: true) formatStr.append(")") } func buildBetween(_ lowerBound: QueryNode, _ upperBound: QueryNode) { formatStr.append(" BETWEEN {") build(lowerBound) formatStr.append(", ") build(upperBound) formatStr.append("}") } func buildBool(_ node: QueryNode, isNot: Bool = false) { if case let .keyPath(kp, _) = node { formatStr.append(kp.joined(separator: ".")) formatStr.append(" == \(isNot ? "false" : "true")") } } func strOptions(_ options: StringOptions) -> String { if options == [] { return "" } return "[\(options.contains(.caseInsensitive) ? "c" : "")\(options.contains(.diacriticInsensitive) ? "d" : "")]" } func build(_ node: QueryNode, prefix: String? = nil, isNewNode: Bool = false) { switch node { case .constant(let value): formatStr.append("%@") arguments.add(value ?? NSNull()) case .keyPath(let kp, let options): if isNewNode { buildBool(node) return } if options.contains(.requiresAny) { formatStr.append("ANY ") } formatStr.append(kp.joined(separator: ".")) case .not(let child): if case .keyPath = child, isNewNode { buildBool(child, isNot: true) return } build(child, prefix: "NOT ") case .comparison(operator: let op, let lhs, let rhs, let options): switch op { case .and, .or: buildCompoundExpression(lhs, op.rawValue, rhs, prefix: prefix) default: buildExpression(lhs, "\(op.rawValue)\(strOptions(options))", rhs, prefix: prefix) } case .between(let lhs, let lowerBound, let upperBound): formatStr.append("(") build(lhs) buildBetween(lowerBound, upperBound) formatStr.append(")") case .subqueryCount(let inner): subqueryCounter += 1 let (collectionName, node) = SubqueryRewriter.rewrite(inner, subqueryCounter) formatStr.append("SUBQUERY(\(collectionName), $col\(subqueryCounter), ") build(node) formatStr.append(").@count") case .mapSubscript(let keyPath, let key): build(keyPath) formatStr.append("[%@]") arguments.add(key) case .mapAnySubscripts(let keyPath, let keys): build(keyPath) for key in keys { switch key { case .index(let index): formatStr.append("[%@]") arguments.add(index) case .key(let key): formatStr.append("[%@]") arguments.add(key) case .all: formatStr.append("[%K]") arguments.add("#any") } } case .geoWithin(let keyPath, let value): buildExpression(keyPath, QueryNode.Operator.in.rawValue, value, prefix: nil) } } build(root, isNewNode: true) return (formatStr as String, (arguments as! [Any])) } private struct KeyPathOptions: OptionSet { let rawValue: Int8 init(rawValue: RawValue) { self.rawValue = rawValue } static let isCollection = KeyPathOptions(rawValue: 1) static let requiresAny = KeyPathOptions(rawValue: 2) } private struct SubqueryRewriter { private var collectionName: String? private var counter: Int private mutating func rewrite(_ node: QueryNode) -> QueryNode { switch node { case .keyPath(let kp, let options): if options.contains(.isCollection) { precondition(kp.count > 0) collectionName = kp[0] var copy = kp copy[0] = "$col\(counter)" return .keyPath(copy, options: [.isCollection]) } return node case .not(let child): return .not(rewrite(child)) case .comparison(operator: let op, let lhs, let rhs, options: let options): return .comparison(operator: op, rewrite(lhs), rewrite(rhs), options: options) case .between(let lhs, let lowerBound, let upperBound): return .between(rewrite(lhs), lowerBound: rewrite(lowerBound), upperBound: rewrite(upperBound)) case .subqueryCount(let inner): return .subqueryCount(inner) case .constant: return node case .mapSubscript: throwRealmException("Subqueries do not support map subscripts.") case .mapAnySubscripts: throwRealmException("Subqueries do not support AnyRealmValue subscripts.") case .geoWithin(let keyPath, let value): return .geoWithin(keyPath, value) } } static fileprivate func rewrite(_ node: QueryNode, _ counter: Int) -> (String, QueryNode) { var rewriter = SubqueryRewriter(counter: counter) let rewritten = rewriter.rewrite(node) guard let collectionName = rewriter.collectionName else { throwRealmException("Subqueries must contain a keypath starting with a collection.") } return (collectionName, rewritten) } } ================================================ FILE: RealmSwift/Realm.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Realm.Private /// The Id of the asynchronous transaction. public typealias AsyncTransactionId = RLMAsyncTransactionId /** A `Realm` instance (also referred to as "a Realm") represents a Realm database. Realms can either be stored on disk (see `init(path:)`) or in memory (see `Configuration`). `Realm` instances are cached internally, and constructing equivalent `Realm` objects (for example, by using the same path or identifier) produces limited overhead. If you specifically want to ensure a `Realm` instance is destroyed (for example, if you wish to open a Realm, check some property, and then possibly delete the Realm file and re-open it), place the code which uses the Realm within an `autoreleasepool {}` and ensure you have no other strong references to it. - warning: Non-frozen `RLMRealm` instances are thread-confined and cannot be shared across threads or dispatch queues. Trying to do so will cause an exception to be thrown. You must obtain an instance of `RLMRealm` on each thread or queue you want to interact with the Realm on. Realms can be confined to a dispatch queue rather than the thread they are opened on by explicitly passing in the queue when obtaining the `RLMRealm` instance. If this is not done, trying to use the same instance in multiple blocks dispatch to the same queue may fail as queues are not always run on the same thread. */ @frozen public struct Realm { // MARK: Properties /// The `Schema` used by the Realm. public var schema: Schema { return Schema(rlmRealm.schema) } /// The `Configuration` value that was used to create the `Realm` instance. public var configuration: Configuration { return Configuration.fromRLMRealmConfiguration(rlmRealm.configuration) } /// Indicates if the Realm contains any objects. public var isEmpty: Bool { return rlmRealm.isEmpty } // MARK: Initializers /** Obtains an instance of the default Realm. The default Realm is persisted as *default.realm* under the *Documents* directory of your Application on iOS, and in your application's *Application Support* directory on OS X. The default Realm is created using the default `Configuration`, which can be changed by setting the `Realm.Configuration.defaultConfiguration` property to a new value. - parameter queue: An optional dispatch queue to confine the Realm to. If given, this Realm instance can be used from within blocks dispatched to the given queue rather than on the current thread. - throws: An `NSError` if the Realm could not be initialized. */ public init(queue: DispatchQueue? = nil) throws { _ = Realm.initMainActor let rlmRealm = try RLMRealm(configuration: RLMRealmConfiguration.rawDefault(), queue: queue) self.init(rlmRealm) } /** Obtains a `Realm` instance with the given configuration. - parameter configuration: A configuration value to use when creating the Realm. - parameter queue: An optional dispatch queue to confine the Realm to. If given, this Realm instance can be used from within blocks dispatched to the given queue rather than on the current thread. - throws: An `NSError` if the Realm could not be initialized. */ public init(configuration: Configuration, queue: DispatchQueue? = nil) throws { _ = Realm.initMainActor let rlmRealm = try RLMRealm(configuration: configuration.rlmConfiguration, queue: queue) self.init(rlmRealm) } /** Obtains a `Realm` instance persisted at a specified file URL. - parameter fileURL: The local URL of the file the Realm should be saved at. - throws: An `NSError` if the Realm could not be initialized. */ public init(fileURL: URL) throws { _ = Realm.initMainActor let configuration = RLMRealmConfiguration.default() configuration.fileURL = fileURL self.init(try RLMRealm(configuration: configuration)) } private static let initMainActor: Void = { if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { RLMSetMainActor(MainActor.shared) } }() // MARK: Async /** Asynchronously open a Realm and deliver it to a block on the given queue. Opening a Realm asynchronously will perform all work needed to get the Realm to a usable state (such as running potentially time-consuming migrations) on a background thread before dispatching to the given queue. In addition, synchronized Realms wait for all remote content available at the time the operation began to be downloaded and available locally. The Realm passed to the callback function is confined to the callback queue as if `Realm(configuration:queue:)` was used. - parameter configuration: A configuration object to use when opening the Realm. - parameter callbackQueue: The dispatch queue on which the callback should be run. - parameter callback: A callback block. If the Realm was successfully opened, an it will be passed in as an argument. Otherwise, a `Swift.Error` describing what went wrong will be passed to the block instead. - returns: A task object which can be used to observe or cancel the async open. */ @discardableResult public static func asyncOpen(configuration: Realm.Configuration = .defaultConfiguration, callbackQueue: DispatchQueue = .main, callback: @escaping (Result) -> Void) -> AsyncOpenTask { return AsyncOpenTask(rlmTask: RLMRealm.asyncOpen(with: configuration.rlmConfiguration, callbackQueue: callbackQueue, callback: { rlmRealm, error in if let realm = rlmRealm.flatMap(Realm.init) { callback(.success(realm)) } else { callback(.failure(error ?? Realm.Error.callFailed)) } })) } /** A task object which can be used to observe or cancel an async open. When a synchronized Realm is opened asynchronously, the latest state of the Realm is downloaded from the server before the completion callback is invoked. This task object can be used to observe the state of the download or to cancel it. This should be used instead of trying to observe the download via the sync session as the sync session itself is created asynchronously, and may not exist yet when Realm.asyncOpen() returns. */ @frozen public struct AsyncOpenTask { internal let rlmTask: RLMAsyncOpenTask /** Cancel the asynchronous open. Any download in progress will be cancelled, and the completion block for this async open will never be called. If multiple async opens on the same Realm are happening concurrently, all other opens will fail with the error "operation cancelled". */ public func cancel() { rlmTask.cancel() } } // MARK: Transactions /** Performs actions contained within the given block inside a write transaction. If the block throws an error, the transaction will be canceled and any changes made before the error will be rolled back. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `write` from `Realm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `write` updates the `Realm` instance to the latest Realm version, as if `refresh()` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. You can skip notifiying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this function must be for notifications for this Realm which were added on the same thread as the write transaction is being performed on. Notifications for different threads cannot be skipped using this method. - parameter tokens: An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. - parameter block: The block containing actions to perform. - returns: The value returned from the block, if any. - warning: This function is not safe to call from async functions, which should use ``asyncWrite`` instead. - throws: An `NSError` if the transaction could not be completed successfully. If `block` throws, the function throws the propagated `ErrorType` instead. */ @discardableResult public func write(withoutNotifying tokens: [NotificationToken] = [], _ block: (() throws -> Result)) throws -> Result { beginWrite() let ret: Result do { ret = try block() } catch let error { if isInWriteTransaction { cancelWrite() } throw error } if isInWriteTransaction { try commitWrite(withoutNotifying: tokens) } return ret } /** Begins a write transaction on the Realm. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `beginWrite` from `Realm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `beginWrite` updates the `Realm` instance to the latest Realm version, as if `refresh()` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. It is rarely a good idea to have write transactions span multiple cycles of the run loop, but if you do wish to do so you will need to ensure that the Realm participating in the write transaction is kept alive until the write transaction is committed. - warning: This function is not safe to call from async functions, which should use ``asyncWrite`` instead. */ public func beginWrite() { rlmRealm.beginWriteTransaction() } /** Commits all write operations in the current write transaction, and ends the transaction. After saving the changes and completing the write transaction, all notification blocks registered on this specific `Realm` instance are called synchronously. Notification blocks for `Realm` instances on other threads and blocks registered for any Realm collection (including those on the current thread) are scheduled to be called synchronously. You can skip notifiying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this function must be for notifications for this Realm which were added on the same thread as the write transaction is being performed on. Notifications for different threads cannot be skipped using this method. - warning: This method may only be called during a write transaction. - parameter tokens: An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. - throws: An `NSError` if the transaction could not be written due to running out of disk space or other i/o errors. */ public func commitWrite(withoutNotifying tokens: [NotificationToken] = []) throws { try rlmRealm.commitWriteTransactionWithoutNotifying(tokens) } /** Reverts all writes made in the current write transaction and ends the transaction. This rolls back all objects in the Realm to the state they were in at the beginning of the write transaction, and then ends the transaction. This restores the data for deleted objects, but does not revive invalidated object instances. Any `Object`s which were added to the Realm will be invalidated rather than becoming unmanaged. Given the following code: ```swift let oldObject = objects(ObjectType).first! let newObject = ObjectType() realm.beginWrite() realm.add(newObject) realm.delete(oldObject) realm.cancelWrite() ``` Both `oldObject` and `newObject` will return `true` for `isInvalidated`, but re-running the query which provided `oldObject` will once again return the valid object. KVO observers on any objects which were modified during the transaction will be notified about the change back to their initial values, but no other notifcations are produced by a cancelled write transaction. This function is applicable regardless of how a write transaction was started. Notably it can be called from inside a block passed to ``write`` or ``writeAsync``. - warning: This method may only be called during a write transaction. */ public func cancelWrite() { rlmRealm.cancelWriteTransaction() } /** Indicates whether the Realm is currently in a write transaction. - warning: Do not simply check this property and then start a write transaction whenever an object needs to be created, updated, or removed. Doing so might cause a large number of write transactions to be created, degrading performance. Instead, always prefer performing multiple updates during a single transaction. */ public var isInWriteTransaction: Bool { return rlmRealm.inWriteTransaction } // MARK: Asynchronous Transactions /** Asynchronously performs actions contained within the given block inside a write transaction. The write transaction is begun asynchronously as if calling `beginAsyncWrite`, and by default the transaction is committed asynchronously after the block completes. You can also explicitly call `commitWrite` or `cancelWrite` from within the block to synchronously commit or cancel the write transaction. Returning without one of these calls is equivalent to calling `commitWrite`. @param block The block containing actions to perform. @param completionBlock A block which will be called on the source thread or queue once the commit has either completed or failed with an error. @return An id identifying the asynchronous transaction which can be passed to `cancelAsyncWrite` prior to the block being called to cancel the pending invocation of the block. */ @discardableResult public func writeAsync(_ block: @escaping () -> Void, onComplete: ((Swift.Error?) -> Void)? = nil) -> AsyncTransactionId { return beginAsyncWrite { block() commitAsyncWrite(onComplete) } } /** Begins an asynchronous write transaction. This function asynchronously begins a write transaction on a background thread, and then invokes the block on the original thread or queue once the transaction has begun. Unlike `beginWrite`, this does not block the calling thread if another thread is current inside a write transaction, and will always return immediately. Multiple calls to this function (or the other functions which perform asynchronous write transactions) will queue the blocks to be called in the same order as they were queued. This includes calls from inside a write transaction block, which unlike with synchronous transactions are allowed. @param asyncWriteBlock The block containing actions to perform inside the write transaction. `asyncWriteBlock` should end by calling `commitAsyncWrite` or `commitWrite`. Returning without one of these calls is equivalent to calling `cancelAsyncWrite`. @return An id identifying the asynchronous transaction which can be passed to `cancelAsyncWrite` prior to the block being called to cancel the pending invocation of the block. */ @discardableResult public func beginAsyncWrite(_ asyncWriteBlock: @escaping () -> Void) -> AsyncTransactionId { return rlmRealm.beginAsyncWriteTransaction { asyncWriteBlock() } } /** Asynchronously commits a write transaction. The call returns immediately allowing the caller to proceed while the I/O is performed on a dedicated background thread. This can be used regardless of if the write transaction was begun with `beginWrite` or `beginAsyncWrite`. @param onComplete A block which will be called on the source thread or queue once the commit has either completed or failed with an error. @param allowGrouping If `true`, multiple sequential calls to `commitAsyncWrite` may be batched together and persisted to stable storage in one group. This improves write performance, particularly when the individual transactions being batched are small. In the event of a crash or power failure, either all of the grouped transactions will be lost or none will, rather than the usual guarantee that data has been persisted as soon as a call to commit has returned. @return An id identifying the asynchronous transaction commit can be passed to `cancelAsyncWrite` prior to the completion block being called to cancel the pending invocation of the block. Note that this does *not* cancel the commit itself. */ @discardableResult public func commitAsyncWrite(allowGrouping: Bool = false, _ onComplete: ((Swift.Error?) -> Void)? = nil) -> AsyncTransactionId { return rlmRealm.commitAsyncWriteTransaction(onComplete, allowGrouping: allowGrouping) } /** Cancels a queued block for an asynchronous transaction. This can cancel a block passed to either an asynchronous begin or an asynchronous commit. Canceling a begin cancels that transaction entirely, while canceling a commit merely cancels the invocation of the completion callback, and the commit will still happen. Transactions can only be canceled before the block is invoked, and calling `cancelAsyncWrite` from within the block is a no-op. @param AsyncTransactionId A transaction id from either `beginAsyncWrite` or `commitAsyncWrite`. */ public func cancelAsyncWrite(_ asyncTransactionId: AsyncTransactionId) throws { rlmRealm.cancelAsyncTransaction(asyncTransactionId) } /** Indicates if the Realm is currently performing async write operations. This becomes `true` following a call to `beginAsyncWrite`, `commitAsyncWrite`, or `writeAsync`, and remains so until all scheduled async write work has completed. - warning: If this is `true`, closing or invalidating the Realm will block until scheduled work has completed. */ public var isPerformingAsynchronousWriteOperations: Bool { return rlmRealm.isPerformingAsynchronousWriteOperations } // MARK: Adding and Creating objects /** What to do when an object being added to or created in a Realm has a primary key that already exists. */ @frozen public enum UpdatePolicy: Int { /** Throw an exception. This is the default when no policy is specified for `add()` or `create()`. This behavior is the same as passing `update: false` to `add()` or `create()`. */ case error = 1 /** Overwrite only properties in the existing object which are different from the new values. This results in change notifications reporting only the properties which changed, and influences the sync merge logic. If few or no of the properties are changing this will be faster than .all and reduce how much data has to be written to the Realm file. If all of the properties are changing, it may be slower than .all (but will never result in *more* data being written). */ case modified = 3 /** Overwrite all properties in the existing object with the new values, even if they have not changed. This results in change notifications reporting all properties as changed, and influences the sync merge logic. This behavior is the same as passing `update: true` to `add()` or `create()`. */ case all = 2 } /// :nodoc: @available(*, unavailable, message: "Pass .error, .modified or .all rather than a boolean. .error is equivalent to false and .all is equivalent to true.") public func add(_ object: Object, update: Bool) { fatalError() } /** Adds an unmanaged object to this Realm. If an object with the same primary key already exists in this Realm, it is updated with the property values from this object as specified by the `UpdatePolicy` selected. The update policy must be `.error` for objects with no primary key. Adding an object to a Realm will also add all child relationships referenced by that object (via `Object` and `List` properties). Those objects must also be valid objects to add to this Realm, and the value of the `update:` parameter is propagated to those adds. The object to be added must either be an unmanaged object or a valid object which is already managed by this Realm. Adding an object already managed by this Realm is a no-op, while adding an object which is managed by another Realm or which has been deleted from any Realm (i.e. one where `isInvalidated` is `true`) is an error. To copy a managed object from one Realm to another, use `create()` instead. - warning: This method may only be called during a write transaction. - parameter object: The object to be added to this Realm. - parameter update: What to do if an object with the same primary key already exists. Must be `.error` for objects without a primary key. */ public func add(_ object: Object, update: UpdatePolicy = .error) { if update != .error && object.objectSchema.primaryKeyProperty == nil { throwRealmException("'\(object.objectSchema.className)' does not have a primary key and can not be updated") } RLMAddObjectToRealm(object, rlmRealm, RLMUpdatePolicy(rawValue: UInt(update.rawValue))!) } /// :nodoc: @available(*, unavailable, message: "Pass .error, .modified or .all rather than a boolean. .error is equivalent to false and .all is equivalent to true.") public func add(_ objects: S, update: Bool) where S.Iterator.Element: Object { fatalError() } /** Adds all the objects in a collection into the Realm. - see: `add(_:update:)` - warning: This method may only be called during a write transaction. - parameter objects: A sequence which contains objects to be added to the Realm. - parameter update: How to handle without a primary key. - parameter update: How to handle objects in the collection with a primary key that already exists in this Realm. Must be `.error` for object types without a primary key. */ public func add(_ objects: S, update: UpdatePolicy = .error) where S.Iterator.Element: Object { for obj in objects { add(obj, update: update) } } /** Creates a Realm object with a given value, adding it to the Realm and returning it. The `value` argument can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each managed property. Do not pass in a `LinkingObjects` instance, either by itself or as a member of a collection. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. If the object type does not have a primary key or no object with the specified primary key already exists, a new object is created in the Realm. If an object already exists in the Realm with the specified primary key and the update policy is `.modified` or `.all`, the existing object will be updated and a reference to that object will be returned. If the object is being updated, all properties defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `value(forKey:)` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value, or (if you are passing in an instance of an `Object` subclass) setting the corresponding property on `value` to nil. - warning: This method may only be called during a write transaction. - parameter type: The type of the object to create. - parameter value: The value used to populate the object. - parameter update: What to do if an object with the same primary key already exists. Must be `.error` for object types without a primary key. - returns: The newly created object. */ @discardableResult public func create(_ type: T.Type, value: Any = [String: Any](), update: UpdatePolicy = .error) -> T { if update != .error { RLMVerifyHasPrimaryKey(type) } let typeName = (type as Object.Type).className() return unsafeDowncast(RLMCreateObjectInRealmWithValue(rlmRealm, typeName, value, RLMUpdatePolicy(rawValue: UInt(update.rawValue))!), to: type) } /** This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use the typed method `create(_:value:update:)`. Creates or updates an object with the given class name and adds it to the `Realm`, populating the object with the given value. The `value` argument can be a Realm object, a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an `Array` containing one element for each managed property. Do not pass in a `LinkingObjects` instance, either by itself or as a member of a collection. If the `value` argument is an array, all properties must be present, valid and in the same order as the properties defined in the model. If the object type does not have a primary key or no object with the specified primary key already exists, a new object is created in the Realm. If an object already exists in the Realm with the specified primary key and the update policy is `.modified` or `.all`, the existing object will be updated and a reference to that object will be returned. If the object is being updated, all properties defined in its schema will be set by copying from `value` using key-value coding. If the `value` argument does not respond to `value(forKey:)` for a given property name (or getter name, if defined), that value will remain untouched. Nullable properties on the object can be set to nil by using `NSNull` as the updated value, or (if you are passing in an instance of an `Object` subclass) setting the corresponding property on `value` to nil. - warning: This method can only be called during a write transaction. - parameter className: The class name of the object to create. - parameter value: The value used to populate the object. - parameter update: What to do if an object with the same primary key already exists. Must be `.error` for object types without a primary key. - returns: The created object. :nodoc: */ @discardableResult public func dynamicCreate(_ typeName: String, value: Any = [String: Any](), update: UpdatePolicy = .error) -> DynamicObject { if update != .error && schema[typeName]?.primaryKeyProperty == nil { throwRealmException("'\(typeName)' does not have a primary key and can not be updated") } return noWarnUnsafeBitCast(RLMCreateObjectInRealmWithValue(rlmRealm, typeName, value, RLMUpdatePolicy(rawValue: UInt(update.rawValue))!), to: DynamicObject.self) } // MARK: Deleting objects /** Deletes an object from the Realm. Once the object is deleted it is considered invalidated. - warning: This method may only be called during a write transaction. - parameter object: The object to be deleted. */ public func delete(_ object: ObjectBase) { rlmRealm.delete(object.unsafeCastToRLMObject()) } /** Deletes zero or more objects from the Realm. Do not pass in a slice to a `Results` or any other auto-updating Realm collection type (for example, the type returned by the Swift `suffix(_:)` standard library method). Instead, make a copy of the objects to delete using `Array()`, and pass that instead. Directly passing in a view into an auto-updating collection may result in 'index out of bounds' exceptions being thrown. - warning: This method may only be called during a write transaction. - parameter objects: The objects to be deleted. This can be a `List`, `Results`, or any other Swift `Sequence` whose elements are `Object`s (subject to the caveats above). */ public func delete(_ objects: S) where S.Iterator.Element: ObjectBase { for obj in objects { delete(obj) } } /** Deletes zero or more objects from the Realm. - warning: This method may only be called during a write transaction. - parameter objects: A list of objects to delete. :nodoc: */ public func delete(_ objects: List) { rlmRealm.deleteObjects(objects._rlmCollection) } /** Deletes zero or more objects from the Realm. - warning: This method may only be called during a write transaction. - parameter objects: A map of objects to delete. :nodoc: */ public func delete(_ map: Map) { rlmRealm.deleteObjects(map._rlmCollection) } /** Deletes zero or more objects from the Realm. - warning: This method may only be called during a write transaction. - parameter objects: A `Results` containing the objects to be deleted. :nodoc: */ public func delete(_ objects: Results) { rlmRealm.deleteObjects(objects.collection) } /** Deletes all objects from the Realm. - warning: This method may only be called during a write transaction. */ public func deleteAll() { rlmRealm.deleteAllObjects() } // MARK: Object Retrieval /** Returns all objects of the given type stored in the Realm. - parameter type: The type of the objects to be returned. - returns: A `Results` containing the objects. */ public func objects(_ type: Element.Type) -> Results { return Results(RLMGetObjects(rlmRealm, type.className(), nil)) } /** This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use the typed method `objects(type:)`. Returns all objects for a given class name in the Realm. - parameter typeName: The class name of the objects to be returned. - returns: All objects for the given class name as dynamic objects :nodoc: */ public func dynamicObjects(_ typeName: String) -> Results { return Results(RLMGetObjects(rlmRealm, typeName, nil)) } /** Retrieves the single instance of a given object type with the given primary key from the Realm. This method requires that `primaryKey()` be overridden on the given object class. - see: `Object.primaryKey()` - parameter type: The type of the object to be returned. - parameter key: The primary key of the desired object. - returns: An object of type `type`, or `nil` if no instance with the given primary key exists. */ public func object(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? { return unsafeBitCast(RLMGetObject(rlmRealm, (type as Object.Type).className(), dynamicBridgeCast(fromSwift: key)) as! RLMObjectBase?, to: Optional.self) } /** This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use the typed method `objectForPrimaryKey(_:key:)`. Get a dynamic object with the given class name and primary key. Returns `nil` if no object exists with the given class name and primary key. This method requires that `primaryKey()` be overridden on the given subclass. - see: Object.primaryKey() - warning: This method is useful only in specialized circumstances. - parameter className: The class name of the object to be returned. - parameter key: The primary key of the desired object. - returns: An object of type `DynamicObject` or `nil` if an object with the given primary key does not exist. :nodoc: */ public func dynamicObject(ofType typeName: String, forPrimaryKey key: Any) -> DynamicObject? { return unsafeBitCast(RLMGetObject(rlmRealm, typeName, key) as! RLMObjectBase?, to: Optional.self) } // MARK: Notifications /** Adds a notification handler for changes made to this Realm, and returns a notification token. Notification handlers are called after each write transaction is committed, independent of the thread or process. Handler blocks are called on the same thread that they were added on, and may only be added on threads which are currently within a run loop. Unless you are specifically creating and running a run loop on a background thread, this will normally only be the main thread. Notifications can't be delivered as long as the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - parameter block: A block which is called to process Realm notifications. It receives the following parameters: `notification`: the incoming notification; `realm`: the Realm for which the notification occurred. - returns: A token which must be held for as long as you wish to continue receiving change notifications. */ public func observe(_ block: @escaping NotificationBlock) -> NotificationToken { return rlmRealm.addNotificationBlock { rlmNotification, _ in switch rlmNotification { case RLMNotification.DidChange: block(.didChange, self) case RLMNotification.RefreshRequired: block(.refreshRequired, self) default: fatalError("Unhandled notification type: \(rlmNotification)") } } } // MARK: Autorefresh and Refresh /** Set this property to `true` to automatically update this Realm when changes happen in other threads. If set to `true` (the default), changes made on other threads will be reflected in this Realm on the next cycle of the run loop after the changes are committed. If set to `false`, you must manually call `refresh()` on the Realm to update it to get the latest data. Note that by default, background threads do not have an active run loop and you will need to manually call `refresh()` in order to update to the latest version, even if `autorefresh` is set to `true`. Even with this property enabled, you can still call `refresh()` at any time to update the Realm before the automatic refresh would occur. Notifications are sent when a write transaction is committed whether or not automatic refreshing is enabled. Disabling `autorefresh` on a `Realm` without any strong references to it will not have any effect, and `autorefresh` will revert back to `true` the next time the Realm is created. This is normally irrelevant as it means that there is nothing to refresh (as managed `Object`s, `List`s, and `Results` have strong references to the `Realm` that manages them), but it means that setting `autorefresh = false` in `application(_:didFinishLaunchingWithOptions:)` and only later storing Realm objects will not work. Defaults to `true`. */ public var autorefresh: Bool { get { return rlmRealm.autorefresh } nonmutating set { rlmRealm.autorefresh = newValue } } /** Updates the Realm and outstanding objects managed by the Realm to point to the most recent data and deliver any applicable notifications. By default Realms will automatically refresh in a more efficient way than is possible with this function. This function should be avoided when possible. - warning: This function is not safe to call from async functions, which should use ``asyncRefresh`` instead. - returns: Whether there were any updates for the Realm. Note that `true` may be returned even if no data actually changed. */ @discardableResult public func refresh() -> Bool { return rlmRealm.refresh() } // MARK: Frozen Realms /// Returns if this Realm is frozen. public var isFrozen: Bool { return rlmRealm.isFrozen } /** Returns a frozen (immutable) snapshot of this Realm. A frozen Realm is an immutable snapshot view of a particular version of a Realm's data. Unlike normal Realm instances, it does not live-update to reflect writes made to the Realm, and can be accessed from any thread. Writing to a frozen Realm is not allowed, and attempting to begin a write transaction will throw an exception. All objects and collections read from a frozen Realm will also be frozen. - warning: Holding onto a frozen Realm for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ public func freeze() -> Realm { return isFrozen ? self : Realm(rlmRealm.freeze()) } /** Returns a live (mutable) reference of this Realm. All objects and collections read from the returned Realm reference will no longer be frozen. Will return self if called on a Realm that is not already frozen. */ public func thaw() -> Realm { return isFrozen ? Realm(rlmRealm.thaw()) : self } /** Returns a frozen (immutable) snapshot of the given object. The frozen copy is an immutable object which contains the same data as the given object currently contains, but will not update when writes are made to the containing Realm. Unlike live objects, frozen objects can be accessed from any thread. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ public func freeze(_ obj: T) -> T { return RLMObjectFreeze(obj) as! T } /** Returns a live (mutable) reference of this object. This method creates a managed accessor to a live copy of the same frozen object. Will return self if called on an already live object. */ public func thaw(_ obj: T) -> T? { return RLMObjectThaw(obj) as? T } /** Returns a frozen (immutable) snapshot of the given collection. The frozen copy is an immutable collection which contains the same data as the given collection currently contains, but will not update when writes are made to the containing Realm. Unlike live collections, frozen collections can be accessed from any thread. - warning: This method cannot be called during a write transaction, or when the Realm is read-only. - warning: Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ public func freeze(_ collection: Collection) -> Collection { return collection.freeze() } // MARK: Invalidation /** Invalidates all `Object`s, `Results`, `LinkingObjects`, and `List`s managed by the Realm. A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need. All `Object`, `Results` and `List` instances obtained from this `Realm` instance on the current thread are invalidated. `Object`s and `Array`s cannot be used. `Results` will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm. Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. */ public func invalidate() { rlmRealm.invalidate() } // MARK: File Management /** Writes a compacted and optionally encrypted copy of the Realm to the given local URL. The destination file cannot already exist. Note that if this method is called from within a write transaction, the *current* data is written, not the data from the point when the previous write transaction was committed. - parameter fileURL: Local URL to save the Realm to. - parameter encryptionKey: Optional 64-byte encryption key to encrypt the new file with. - throws: An `NSError` if the copy could not be written. */ public func writeCopy(toFile fileURL: URL, encryptionKey: Data? = nil) throws { try rlmRealm.writeCopy(to: fileURL, encryptionKey: encryptionKey) } /** Writes a copy of the Realm to a given location specified by a given configuration. If the configuration supplied is derived from a `User` then this Realm will be copied with sync functionality enabled. The destination file cannot already exist. - parameter configuration: A Realm Configuration. - throws: An `NSError` if the copy could not be written. */ public func writeCopy(configuration: Realm.Configuration) throws { try rlmRealm.writeCopy(for: configuration.rlmConfiguration) } /** Checks if the Realm file for the given configuration exists locally on disk. For non-synchronized, non-in-memory Realms, this is equivalent to `FileManager.default.fileExists(atPath:)`. For synchronized Realms, it takes care of computing the actual path on disk based on the server, virtual path, and user as is done when opening the Realm. @param config A Realm configuration to check the existence of. @return true if the Realm file for the given configuration exists on disk, false otherwise. */ public static func fileExists(for config: Configuration) -> Bool { return RLMRealm.fileExists(for: config.rlmConfiguration) } /** Deletes the local Realm file and associated temporary files for the given configuration. This deletes the ".realm", ".note" and ".management" files which would be created by opening the Realm with the given configuration. It does not delete the ".lock" file (which contains no persisted data and is recreated from scratch every time the Realm file is opened). The Realm must not be currently open on any thread or in another process. If it is, this will throw the error .alreadyOpen. Attempting to open the Realm on another thread while the deletion is happening will block, and then create a new Realm and open that afterwards. If the Realm already does not exist this will return `false`. @param config A Realm configuration identifying the Realm to be deleted. @return true if any files were deleted, false otherwise. */ public static func deleteFiles(for config: Configuration) throws -> Bool { return try RLMRealm.deleteFiles(for: config.rlmConfiguration) } // MARK: Internal internal var rlmRealm: RLMRealm internal init(_ rlmRealm: RLMRealm) { self.rlmRealm = rlmRealm } } // MARK: Equatable extension Realm: Equatable { /// Returns whether two `Realm` instances are equal. public static func == (lhs: Realm, rhs: Realm) -> Bool { return lhs.rlmRealm == rhs.rlmRealm } } // MARK: Notifications extension Realm { /// A notification indicating that changes were made to a Realm. @frozen public enum Notification: String { /** This notification is posted when the data in a Realm has changed. `didChange` is posted after a Realm has been refreshed to reflect a write transaction, This can happen when an autorefresh occurs, `refresh()` is called, after an implicit refresh from `write(_:)`/`beginWrite()`, or after a local write transaction is committed. */ case didChange = "RLMRealmDidChangeNotification" /** This notification is posted when a write transaction has been committed to a Realm on a different thread for the same file. It is not posted if `autorefresh` is enabled, or if the Realm is refreshed before the notification has a chance to run. Realms with autorefresh disabled should normally install a handler for this notification which calls `refresh()` after doing some work. Refreshing the Realm is optional, but not refreshing the Realm may lead to large Realm files. This is because an extra copy of the data must be kept for the stale Realm. */ case refreshRequired = "RLMRealmRefreshRequiredNotification" } } /// The type of a block to run for notification purposes when the data in a Realm is modified. public typealias NotificationBlock = (_ notification: Realm.Notification, _ realm: Realm) -> Void @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Realm { /** Obtains a `Realm` instance with the given configuration, possibly asynchronously. By default this simply returns the Realm instance exactly as if the synchronous initializer was used. It optionally can instead open the Realm asynchronously, performing all work needed to get the Realm to a usable state on a background thread. For local Realms, this means that migrations will be run in the background, and for synchronized Realms all data will be downloaded from the server before the Realm is returned. - parameter configuration: A configuration object to use when opening the Realm. all data from the server. - throws: An `NSError` if the Realm could not be initialized. - returns: An open Realm. */ @MainActor public init(configuration: Realm.Configuration = .defaultConfiguration) async throws { let scheduler = RLMScheduler.dispatchQueue(.main) let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, actor: MainActor.shared) self = Realm(rlmRealm.wrappedValue) } /** Asynchronously obtains a `Realm` instance isolated to the given Actor. Opening a Realm with an actor isolates the Realm to that actor. Rather than being confined to the specific thread which the Realm was opened on, the Realm can instead only be used from within that actor or functions isolated to that actor. Isolating a Realm to an actor also enables using ``asyncWrite`` and ``asyncRefresh``. All initialization work to prepare the Realm for work, such as creating, migrating, or compacting the file on disk, and waiting for synchronized Realms to download the latest data from the server is done on a background thread and does not block the calling executor. When using actor-isolated Realms, enabling struct concurrency checking (`SWIFT_STRICT_CONCURRENCY=complete` in Xcode) and runtime data race detection (by passing `-Xfrontend -enable-actor-data-race-checks` to the compiler) is strongly recommended. - parameter configuration: A configuration object to use when opening the Realm. - parameter actor: The actor to confine this Realm to. The actor can be either a local actor or a global actor. The calling function does not need to be isolated to the actor passed in, but if it is not it will not be able to use the returned Realm. - throws: An `NSError` if the Realm could not be initialized. `CancellationError` if the task is cancelled. - returns: An open Realm. */ public init(configuration: Realm.Configuration = .defaultConfiguration, actor: A) async throws { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: await actor.verifier()) let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, actor: actor) self = Realm(rlmRealm.wrappedValue) } #if compiler(>=6) /** Asynchronously obtains a `Realm` instance isolated to the current Actor. Opening a Realm with an actor isolates the Realm to that actor. Rather than being confined to the specific thread which the Realm was opened on, the Realm can instead only be used from within that actor or functions isolated to that actor. Isolating a Realm to an actor also enables using ``asyncWrite`` and ``asyncRefresh``. All initialization work to prepare the Realm for work, such as creating, migrating, or compacting the file on disk, and waiting for synchronized Realms to download the latest data from the server is done on a background thread and does not block the calling executor. - parameter configuration: A configuration object to use when opening the Realm. - parameter downloadBeforeOpen: When opening the Realm should first download all data from the server. - throws: An `NSError` if the Realm could not be initialized. `CancellationError` if the task is cancelled. - returns: An open Realm. */ public static func open(configuration: Realm.Configuration = .defaultConfiguration, _isolation actor: isolated any Actor = #isolation) async throws -> Realm { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let rlmRealm = try await openRealm(configuration: configuration, scheduler: scheduler, actor: actor) return Realm(rlmRealm.wrappedValue) } #endif #if compiler(<6) /** Performs actions contained within the given block inside a write transaction. This function differs from synchronous ``write`` in that it suspends the calling task while waiting for its turn to write rather than blocking the thread. In addition, the actual i/o to write data to disk is done by a background worker thread. For small writes, using this function on the main thread may block the main thread for less time than manually dispatching the write to a background thread. If the block throws an error, the transaction will be canceled and any changes made before the error will be rolled back. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `write` from `Realm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `asyncWrite` updates the `Realm` instance to the latest Realm version, as if `asyncRefresh()` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. You can skip notifying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this function must be for notifications for this Realm which were added on the same actor as the write transaction is being performed on. Notifications for different threads cannot be skipped using this method. - parameter tokens: An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. - parameter block: The block containing actions to perform. - returns: The value returned from the block, if any. - throws: An `NSError` if the transaction could not be completed successfully. `CancellationError` if the task is cancelled. If `block` throws, the function throws the propagated `ErrorType` instead. */ @discardableResult @_unsafeInheritExecutor public func asyncWrite(_ block: (() throws -> Result)) async throws -> Result { guard let actor = rlmRealm.actor as? Actor else { fatalError("asyncWrite() can only be called on main thread or actor-isolated Realms") } return try await withoutActuallyEscaping(block) { block in try await Self.asyncWrite(actor: actor, realm: Unchecked(rlmRealm), Unchecked(block)).wrappedValue } } private static func asyncWrite(actor: isolated any Actor, realm: Unchecked, _ block: Unchecked<(() throws -> Result)>) async throws -> Unchecked { let realm = realm.wrappedValue let write = realm.beginAsyncWrite() await withTaskCancellationHandler { await write.wait() } onCancel: { actor.invoke { write.complete(true) } } let ret: Result do { try Task.checkCancellation() ret = try block.wrappedValue() } catch { if realm.inWriteTransaction { realm.cancelWriteTransaction() } throw error } if realm.inWriteTransaction { try await realm.commitAsyncWrite(withGrouping: false) } return Unchecked(ret) } /** Updates the Realm and outstanding objects managed by the Realm to point to the most recent data and deliver any applicable notifications. This function should be used instead of synchronous ``refresh`` in async functions, as it suspends the calling task (if required) rather than blocking. - warning: This function is only supported for main thread and actor-isolated Realms. - returns: Whether there were any updates for the Realm. Note that `true` may be returned even if no data actually changed. */ @discardableResult @_unsafeInheritExecutor public func asyncRefresh() async -> Bool { guard rlmRealm.actor is Actor else { fatalError("asyncRefresh() can only be called on main thread or actor-isolated Realms") } guard let task = RLMRealmRefreshAsync(rlmRealm) else { return false } return await withTaskCancellationHandler { await task.wait() } onCancel: { task.complete(false) } } #else // compiler(<6) /** Performs actions contained within the given block inside a write transaction. This function differs from synchronous ``write`` in that it suspends the calling task while waiting for its turn to write rather than blocking the thread. In addition, the actual i/o to write data to disk is done by a background worker thread. For small writes, using this function on the main thread may block the main thread for less time than manually dispatching the write to a background thread. If the block throws an error, the transaction will be canceled and any changes made before the error will be rolled back. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `write` from `Realm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `asyncWrite` updates the `Realm` instance to the latest Realm version, as if `asyncRefresh()` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. You can skip notifying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this function must be for notifications for this Realm which were added on the same actor as the write transaction is being performed on. Notifications for different threads cannot be skipped using this method. - parameter tokens: An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. - parameter block: The block containing actions to perform. - returns: The value returned from the block, if any. - throws: An `NSError` if the transaction could not be completed successfully. `CancellationError` if the task is cancelled. If `block` throws, the function throws the propagated `ErrorType` instead. */ @discardableResult public func asyncWrite(_isolation actor: isolated any Actor = #isolation, _ block: (() throws -> Result)) async throws -> Result { guard rlmRealm.actor != nil else { fatalError("asyncWrite() can only be called on main thread or actor-isolated Realms") } let realm = rlmRealm let write = realm.beginAsyncWrite() await withTaskCancellationHandler { await write.wait() } onCancel: { actor.invoke { write.complete(true) } } let ret: Result do { try Task.checkCancellation() ret = try block() } catch { if realm.inWriteTransaction { realm.cancelWriteTransaction() } throw error } if realm.inWriteTransaction { let error = await withCheckedContinuation { continuation in realm.commitAsyncWrite(withGrouping: false) { error in continuation.resume(returning: error) } } if let error { throw error } } return ret } /** Updates the Realm and outstanding objects managed by the Realm to point to the most recent data and deliver any applicable notifications. This function should be used instead of synchronous ``refresh`` in async functions, as it suspends the calling task (if required) rather than blocking. - warning: This function is only supported for main thread and actor-isolated Realms. - returns: Whether there were any updates for the Realm. Note that `true` may be returned even if no data actually changed. */ @discardableResult public func asyncRefresh(_isolation: isolated any Actor = #isolation) async -> Bool { guard rlmRealm.actor != nil else { fatalError("asyncRefresh() can only be called on main thread or actor-isolated Realms") } guard let task = RLMRealmRefreshAsync(rlmRealm) else { return false } return await withTaskCancellationHandler { await task.wait() } onCancel: { task.complete(false) } } #endif // compiler(<6) } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) private func openRealm(configuration: Realm.Configuration, scheduler: RLMScheduler, actor: isolated A ) async throws -> Unchecked { let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let rlmConfiguration = configuration.rlmConfiguration // If we already have a cached Realm for this actor, just reuse it // If this Realm is open but with a different scheduler, open it synchronously. // The overhead of dispatching to a different thread and back is more expensive // than the fast path of obtaining a new instance for an already open Realm. var realm = RLMGetCachedRealm(rlmConfiguration, scheduler) if realm == nil, let cachedRealm = RLMGetAnyCachedRealm(rlmConfiguration) { try withExtendedLifetime(cachedRealm) { realm = try RLMRealm(configuration: rlmConfiguration, confinedTo: scheduler) } } if let realm = realm { return Unchecked(realm) } // We're doing the first open and hitting the expensive path, so do an async // open on a background thread let task = RLMAsyncOpenTask(configuration: rlmConfiguration, confinedTo: scheduler) do { try await task.waitWithCancellationHandler() let realm = task.localRealm! task.localRealm = nil return Unchecked(realm) } catch { // Check if the task was cancelled and if so replace the error // with reporting cancellation try Task.checkCancellation() throw error } } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) private protocol TaskWithCancellation: Sendable { func waitWithCancellationHandler() async throws func wait() async throws func cancel() } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) extension TaskWithCancellation { func waitWithCancellationHandler() async throws { do { try await withTaskCancellationHandler { try await wait() } onCancel: { cancel() } } catch { // Check if the task was cancelled and if so replace the error // with reporting cancellation try Task.checkCancellation() throw error } } } extension RLMAsyncOpenTask: TaskWithCancellation {} @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) internal extension Actor { func verifier() -> (@Sendable () -> Void) { #if compiler(>=5.10) // This was made backdeployable in Xcode 15.3 return { self.preconditionIsolated() } #else // When possible use the official API for actor checking if #available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, *) { return { self.preconditionIsolated() } } // This exploits a hole in Swift's type system to construct a function // which is isolated to the current actor, and then casts away that // information. This results in runtime warnings/aborts if it's called // from outside the actor when actor data race checking is enabled. let fn: () -> Void = { _ = self } return unsafeBitCast(fn, to: (@Sendable () -> Void).self) #endif } // Asynchronously invoke the given block on the actor. This takes a // non-sendable function because the function is invoked on the same actor // it was defined on, and just goes through some hops in between. nonisolated func invoke(_ fn: @escaping () -> Void) { let fn = unsafeBitCast(fn, to: (@Sendable () -> Void).self) Task { await doInvoke(fn) } } private func doInvoke(_ fn: @Sendable () -> Void) { fn() } // A helper to invoke a regular isolated sendable function with this actor func invoke(_ fn: @Sendable (isolated Self) async throws -> T) async rethrows -> T { try await fn(self) } } /** Objects which can be fetched from the Realm - Object or Projection */ public protocol RealmFetchable: RealmCollectionValue { /// :nodoc: static func className() -> String } /// :nodoc: extension Object: RealmFetchable {} /// :nodoc: extension Projection: RealmFetchable { /// :nodoc: public static func className() -> String { return Root.className() } } /** `Logger` is used for creating your own custom logging logic. You can define your own logger creating an instance of `Logger` and define the log function which will be invoked whenever there is a log message. ```swift let logger = Logger(level: .all) { level, message in print("Realm Log - \(level): \(message)") } ``` Set this custom logger as you default logger using `Logger.shared`. ```swift Logger.shared = inMemoryLogger ``` - note: By default default log threshold level is `.info`, and logging strings are output to Apple System Logger. */ public typealias Logger = RLMLogger extension Logger { /** Log a message to the supplied level. ```swift let logger = Logger(level: .info, logFunction: { level, message in print("Realm Log - \(level): \(message)") }) logger.log(level: .info, message: "Info DB: Database opened succesfully") ``` - parameter level: The log level for the message. - parameter message: The message to log. */ internal func log(level: LogLevel, message: String) { self.logLevel(level, message: message) } } ================================================ FILE: RealmSwift/RealmCollection.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm /** An iterator for a `RealmCollection` instance. */ @frozen public struct RLMIterator: IteratorProtocol { private var generatorBase: NSFastEnumerationIterator init(collection: RLMCollection) { generatorBase = NSFastEnumerationIterator(collection) } /// Advance to the next element and return it, or `nil` if no next element exists. public mutating func next() -> Element? { guard let next = generatorBase.next() else { return nil } return staticBridgeCast(fromObjectiveC: next) as Element } } /// :nodoc: public protocol _RealmMapValue { /// The key of this element. associatedtype Key: _MapKey /// The value of this element. associatedtype Value: RealmCollectionValue } /** An iterator for a `RealmKeyedCollection` instance. */ @frozen public struct RLMMapIterator: IteratorProtocol { private var generatorBase: NSFastEnumerationIterator private var collection: RLMDictionary init(collection: RLMDictionary) { self.collection = collection generatorBase = NSFastEnumerationIterator(collection) } /// Advance to the next element and return it, or `nil` if no next element exists. public mutating func next() -> Element? { let next = generatorBase.next() if let next = next as? Element.Key { let key: Element.Key = next if let val = collection[key as AnyObject].map(Element.Value._rlmFromObjc(_:)), let val { return SingleMapEntry(key: key, value: val) as? Element } } return nil } } /** An iterator for `Map` which produces `(key: Key, value: Value)` pairs for each entry in the map. */ @frozen public struct RLMKeyValueIterator: IteratorProtocol { private var generatorBase: NSFastEnumerationIterator private var collection: RLMDictionary public typealias Element = (key: Key, value: Value) init(collection: RLMDictionary) { self.collection = collection generatorBase = NSFastEnumerationIterator(collection) } /// Advance to the next element and return it, or `nil` if no next element exists. public mutating func next() -> Element? { let next = generatorBase.next() if let key = next as? Key, let value = collection[key as AnyObject].map(Value._rlmFromObjc(_:)), let value { return (key: key, value: value) } return nil } } /** A `RealmCollectionChange` value encapsulates information about changes to collections that are reported by Realm notifications. The change information is available in two formats: a simple array of row indices in the collection for each type of change, and an array of index paths in a requested section suitable for passing directly to `UITableView`'s batch update methods. The arrays of indices in the `.update` case follow `UITableView`'s batching conventions, and can be passed as-is to a table view's batch update functions after being converted to index paths. For example, for a simple one-section table view, you can do the following: ```swift self.notificationToken = results.observe { changes in switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI self.tableView.reloadData() break case .update(_, let deletions, let insertions, let modifications): // Query results have changed, so apply them to the TableView self.tableView.beginUpdates() self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.endUpdates() break case .error(let err): // An error occurred while opening the Realm file on the background worker thread fatalError("\(err)") break } } ``` */ @frozen public enum RealmCollectionChange { /** `.initial` indicates that the initial run of the query has completed (if applicable), and the collection can now be used without performing any blocking work. */ case initial(CollectionType) /** `.update` indicates that a write transaction has been committed which either changed which objects are in the collection, and/or modified one or more of the objects in the collection. All three of the change arrays are always sorted in ascending order. - parameter deletions: The indices in the previous version of the collection which were removed from this one. - parameter insertions: The indices in the new collection which were added in this version. - parameter modifications: The indices of the objects which were modified in the previous version of this collection. */ case update(CollectionType, deletions: [Int], insertions: [Int], modifications: [Int]) /** Errors can no longer occur. This case is unused and will be removed in the next major version. */ case error(Error) init(value: CollectionType?, change: RLMCollectionChange?, error: Error?) { if let error = error { self = .error(error) } else if let change = change { self = .update(value!, deletions: forceCast(change.deletions, to: [Int].self), insertions: forceCast(change.insertions, to: [Int].self), modifications: forceCast(change.modifications, to: [Int].self)) } else { self = .initial(value!) } } } private func forceCast(_ from: A, to type: U.Type) -> U { return from as! U } /// A type which can be stored in a Realm List, MutableSet, Map, or Results. /// /// Declaring additional types as conforming to this protocol will not make them /// actually work. Most of the logic for how to store values in Realm is not /// implemented in Swift and there is currently no extension mechanism for /// supporting more types. public protocol RealmCollectionValue: Hashable, _HasPersistedType where PersistedType: RealmCollectionValue { // Get the zero/empty/nil value for this type. Used to supply a default // when the user does not declare one in their model. /// :nodoc: static func _rlmDefaultValue() -> Self } /// A type which can appear in a Realm collection inside an Optional. /// /// :nodoc: public protocol _RealmCollectionValueInsideOptional: RealmCollectionValue where PersistedType: _RealmCollectionValueInsideOptional {} extension Int: _RealmCollectionValueInsideOptional {} extension Int8: _RealmCollectionValueInsideOptional {} extension Int16: _RealmCollectionValueInsideOptional {} extension Int32: _RealmCollectionValueInsideOptional {} extension Int64: _RealmCollectionValueInsideOptional {} extension Float: _RealmCollectionValueInsideOptional {} extension Double: _RealmCollectionValueInsideOptional {} extension Bool: _RealmCollectionValueInsideOptional {} extension String: _RealmCollectionValueInsideOptional {} extension Date: _RealmCollectionValueInsideOptional {} extension Data: _RealmCollectionValueInsideOptional {} extension Decimal128: _RealmCollectionValueInsideOptional {} extension ObjectId: _RealmCollectionValueInsideOptional {} extension UUID: _RealmCollectionValueInsideOptional {} extension AnyRealmValue: RealmCollectionValue {} extension Optional: RealmCollectionValue where Wrapped: _RealmCollectionValueInsideOptional { public static func _rlmDefaultValue() -> Self { return .none } } /// :nodoc: public protocol RealmCollectionBase: RandomAccessCollection, LazyCollectionProtocol, CustomStringConvertible, ThreadConfined where Element: RealmCollectionValue { // This typealias was needed with Swift 3.1. It no longer is, but remains // just in case someone was depending on it typealias ElementType = Element } // MARK: - RealmCollection protocol /** A homogenous collection of `Object`s which can be retrieved, filtered, sorted, and operated upon. */ public protocol RealmCollection: RealmCollectionBase, Equatable where Iterator == RLMIterator { // MARK: Properties /// The Realm which manages the collection, or `nil` for unmanaged collections. var realm: Realm? { get } /** Indicates if the collection can no longer be accessed. The collection can no longer be accessed if `invalidate()` is called on the `Realm` that manages the collection. */ var isInvalidated: Bool { get } /// The number of objects in the collection. var count: Int { get } /// A human-readable description of the objects contained in the collection. var description: String { get } // MARK: Object Retrieval /// Returns the first object in the collection, or `nil` if the collection is empty. var first: Element? { get } /// Returns the last object in the collection, or `nil` if the collection is empty. var last: Element? { get } // MARK: Index Retrieval /** Returns the index of an object in the collection, or `nil` if the object is not present. - parameter object: An object. */ func index(of object: Element) -> Int? /** Returns the index of the first object matching the predicate, or `nil` if no objects match. This is only applicable to ordered collections, and will abort if the collection is unordered. - parameter predicate: The predicate to use to filter the objects. */ func index(matching predicate: NSPredicate) -> Int? /** Returns the index of the first object matching the predicate, or `nil` if no objects match. This is only applicable to ordered collections, and will abort if the collection is unordered. - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments. */ func index(matching predicateFormat: String, _ args: Any...) -> Int? // MARK: Object Retrieval /** Returns an array containing the objects in the collection at the indexes specified by a given index set. - warning: Throws if an index supplied in the IndexSet is out of bounds. - parameter indexes: The indexes in the collection to select objects from. */ func objects(at indexes: IndexSet) -> [Element] // MARK: Filtering /** Returns a `Results` containing all objects matching the given predicate in the collection. - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments. */ func filter(_ predicateFormat: String, _ args: Any...) -> Results /** Returns a `Results` containing all objects matching the given predicate in the collection. - parameter predicate: The predicate to use to filter the objects. */ func filter(_ predicate: NSPredicate) -> Results // MARK: Sorting /** Returns a `Results` containing the objects in the collection, but sorted. - warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - see: `sorted(byKeyPath:ascending:)` - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. */ func sorted(by sortDescriptors: S) -> Results where S.Iterator.Element == SortDescriptor /** Returns a `Results` containing distinct objects based on the specified key paths. - parameter keyPaths: The key paths to distinct on. */ func distinct(by keyPaths: S) -> Results where S.Iterator.Element == String // MARK: Aggregate Operations /** Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ func min(ofProperty property: String) -> T? where T.PersistedType: MinMaxType /** Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ func max(ofProperty property: String) -> T? where T.PersistedType: MinMaxType /** Returns the sum of the given property for objects in the collection, or `nil` if the collection is empty. - warning: Only names of properties of a type conforming to the `AddableType` protocol can be used. - parameter property: The name of a property conforming to `AddableType` to calculate sum on. */ func sum(ofProperty property: String) -> T where T.PersistedType: AddableType /** Returns the average value of a given property over all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `AddableType` protocol can be specified. - parameter property: The name of a property whose values should be summed. */ func average(ofProperty property: String) -> T? where T.PersistedType: AddableType // MARK: Key-Value Coding /** Returns an `Array` containing the results of invoking `valueForKey(_:)` with `key` on each of the collection's objects. - parameter key: The name of the property whose values are desired. */ func value(forKey key: String) -> Any? /** Returns an `Array` containing the results of invoking `valueForKeyPath(_:)` with `keyPath` on each of the collection's objects. - parameter keyPath: The key path to the property whose values are desired. */ func value(forKeyPath keyPath: String) -> Any? /** Invokes `setValue(_:forKey:)` on each of the collection's objects using the specified `value` and `key`. - warning: This method may only be called during a write transaction. - parameter value: The object value. - parameter key: The name of the property whose value should be set on each object. */ func setValue(_ value: Any?, forKey key: String) // MARK: Notifications /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial results, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) print("dogs.count: \(dogs?.count)") // => 0 let token = dogs.observe { changes in switch changes { case .initial(let dogs): // Will print "dogs.count: 1" print("dogs.count: \(dogs.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let token = dogs.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (RealmCollectionChange) -> Void) -> NotificationToken #if compiler(<6) /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil` or empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe(keyPaths: [String]?, on actor: A, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken #else /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil` or empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe(keyPaths: [String]?, on actor: A, _isolation: isolated (any Actor)?, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken #endif // MARK: Frozen Objects /// Returns true if this collection is frozen var isFrozen: Bool { get } /** Returns a frozen (immutable) snapshot of this collection. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live collections, frozen collections can be accessed from any thread. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - warning: Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ func freeze() -> Self /** Returns a live (mutable) version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ func thaw() -> Self? /** Sorts this collection from a given array of sort descriptors and performs sectioning via a user defined callback, returning the result as an instance of `SectionedResults`. - parameter sortDescriptors: An array of `SortDescriptor`s to sort by. - parameter keyBlock: A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. - note: The primary sort descriptor must be responsible for determining the section key. - returns: An instance of `SectionedResults`. */ func sectioned(sortDescriptors: [SortDescriptor], _ keyBlock: @escaping ((Element) -> Key)) -> SectionedResults } // MARK: - Codable extension RealmCollection where Element: Encodable { /// Encodes the contents of this collection into the given encoder. /// - parameter encoder The encoder to write data to. public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() for value in self { try container.encode(value) } } } // MARK: - Type-safe queries public extension RealmCollection { /** Returns the index of the first object matching the query, or `nil` if no objects match. This is only applicable to ordered collections, and will abort if the collection is unordered. - Note: This should only be used with classes using the `@Persistable` property declaration. - Usage: ``` obj.index(matching: { $0.fooCol < 456 }) ``` - Note: See ``Query`` for more information on what query operations are available. - parameter isIncluded: The query closure to use to filter the objects. */ func index(matching isIncluded: ((Query) -> Query)) -> Int? where Element: _RealmSchemaDiscoverable { let isPrimitive = Element._rlmType != .object return index(matching: isIncluded(Query(isPrimitive: isPrimitive)).predicate) } /** Returns a `Results` containing all objects matching the given query in the collection. - Note: This should only be used with classes using the `@Persistable` property declaration. - Usage: ``` myCol.where { ($0.fooCol > 5) && ($0.barCol == "foobar") } ``` - Note: See ``Query`` for more information on what query operations are available. - parameter isIncluded: The query closure to use to filter the objects. */ func `where`(_ isIncluded: ((Query) -> Query)) -> Results { return filter(isIncluded(Query()).predicate) } } // MARK: Collection protocol public extension RealmCollection { /// The position of the first element in a non-empty collection. /// Identical to endIndex in an empty collection. var startIndex: Int { 0 } /// The collection's "past the end" position. /// endIndex is not a valid argument to subscript, and is always reachable from startIndex by /// zero or more applications of successor(). var endIndex: Int { count } /// Returns the position immediately after the given index. /// - parameter i: A valid index of the collection. `i` must be less than `endIndex`. func index(after i: Int) -> Int { return i + 1 } /// Returns the position immediately before the given index. /// - parameter i: A valid index of the collection. `i` must be greater than `startIndex`. func index(before i: Int) -> Int { return i - 1 } } // MARK: - Aggregation /** Extension for RealmCollections where the Value is of an Object type that enables aggregatable operations. */ public extension RealmCollection where Element: ObjectBase { /** Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter keyPath: The keyPath of a property whose minimum value is desired. */ func min(of keyPath: KeyPath) -> T? where T.PersistedType: MinMaxType { min(ofProperty: _name(for: keyPath)) } /** Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter keyPath: The keyPath of a property whose minimum value is desired. */ func max(of keyPath: KeyPath) -> T? where T.PersistedType: MinMaxType { max(ofProperty: _name(for: keyPath)) } /** Returns the sum of the given property for objects in the collection, or `nil` if the collection is empty. - warning: Only names of properties of a type conforming to the `AddableType` protocol can be used. - parameter keyPath: The keyPath of a property conforming to `AddableType` to calculate sum on. */ func sum(of keyPath: KeyPath) -> T where T.PersistedType: AddableType { sum(ofProperty: _name(for: keyPath)) } /** Returns the average value of a given property over all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `AddableType` protocol can be specified. - parameter keyPath: The keyPath of a property whose values should be summed. */ func average(of keyPath: KeyPath) -> T? where T.PersistedType: AddableType { average(ofProperty: _name(for: keyPath)) } /** Sorts and sections this collection from a given property key path, returning the result as an instance of `SectionedResults`. For every unique value retrieved from the keyPath a section key will be generated. - parameter keyPath: The property key path to sort & section on. - parameter ascending: The direction to sort in. - returns: An instance of `SectionedResults`. */ func sectioned(by keyPath: KeyPath, ascending: Bool = true) -> SectionedResults where Element: ObjectBase { return sectioned(sortDescriptors: [.init(keyPath: _name(for: keyPath), ascending: ascending)], { return $0[keyPath: keyPath] }) } /** Sorts and sections this collection from a given property key path, returning the result as an instance of `SectionedResults`. For every unique value retrieved from the keyPath a section key will be generated. - parameter keyPath: The property key path to sort & section on. - parameter sortDescriptors: An array of `SortDescriptor`s to sort by. - note: The primary sort descriptor must be responsible for determining the section key. - returns: An instance of `SectionedResults`. */ func sectioned(by keyPath: KeyPath, sortDescriptors: [SortDescriptor]) -> SectionedResults where Element: ObjectBase { guard let sortDescriptor = sortDescriptors.first else { throwRealmException("Can not section Results with empty sortDescriptor parameter.") } let keyPathString = _name(for: keyPath) if keyPathString != sortDescriptor.keyPath { throwRealmException("The section key path must match the primary sort descriptor.") } return sectioned(sortDescriptors: sortDescriptors, { $0[keyPath: keyPath] }) } /** Sorts this collection from a given array of `SortDescriptor`'s and performs sectioning via a user defined callback function. - parameter block: A callback which is invoked on each element in the collection. This callback is to return the section key for the element in the collection. - parameter sortDescriptors: An array of `SortDescriptor`s to sort by. - note: The primary sort descriptor must be responsible for determining the section key. - returns: An instance of `SectionedResults`. */ func sectioned(by block: @escaping ((Element) -> Key), sortDescriptors: [SortDescriptor]) -> SectionedResults where Element: ObjectBase { return sectioned(sortDescriptors: sortDescriptors, block) } } public extension RealmCollection where Element.PersistedType: MinMaxType { /** Returns the minimum (lowest) value of the collection, or `nil` if the collection is empty. */ func min() -> Element? { return min(ofProperty: "self") } /** Returns the maximum (highest) value of the collection, or `nil` if the collection is empty. */ func max() -> Element? { return max(ofProperty: "self") } } public extension RealmCollection where Element.PersistedType: AddableType { /** Returns the sum of the values in the collection, or `nil` if the collection is empty. */ func sum() -> Element { return sum(ofProperty: "self") } /** Returns the average of all of the values in the collection. */ func average() -> T? where T.PersistedType: AddableType { return average(ofProperty: "self") } } // MARK: Sort and distinct /** Extension for RealmCollections where the Value is of an Object type that enables sortable operations. */ public extension RealmCollection where Element: KeypathSortable { /** Returns a `Results` containing the objects in the collection, but sorted. Objects are sorted based on the values of the given key path. For example, to sort a collection of `Student`s from youngest to oldest based on their `age` property, you might call `students.sorted(byKeyPath: "age", ascending: true)`. - warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - parameter keyPath: The key path to sort by. - parameter ascending: The direction to sort in. */ func sorted(byKeyPath keyPath: String, ascending: Bool = true) -> Results { sorted(by: [SortDescriptor(keyPath: keyPath, ascending: ascending)]) } /** Returns a `Results` containing the objects in the collection, but sorted. Objects are sorted based on the values of the given key path. For example, to sort a collection of `Student`s from youngest to oldest based on their `age` property, you might call `students.sorted(byKeyPath: "age", ascending: true)`. - warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - parameter keyPath: The key path to sort by. - parameter ascending: The direction to sort in. */ func sorted(by keyPath: KeyPath, ascending: Bool = true) -> Results where T.PersistedType: SortableType, Element: ObjectBase { sorted(by: [SortDescriptor(keyPath: keyPath, ascending: ascending)]) } /** Returns a `Results` containing distinct objects based on the specified key paths - parameter keyPaths: The key paths used produce distinct results */ func distinct(by keyPaths: S) -> Results where S.Iterator.Element == PartialKeyPath, Element: ObjectBase { return distinct(by: keyPaths.map(_name(for:))) } } public extension RealmCollection where Element.PersistedType: SortableType { /** Returns a `Results` containing the objects in the collection, but sorted. Objects are sorted based on their values. For example, to sort a collection of `Date`s from neweset to oldest based, you might call `dates.sorted(ascending: true)`. - parameter ascending: The direction to sort in. */ func sorted(ascending: Bool = true) -> Results { sorted(by: [SortDescriptor(keyPath: "self", ascending: ascending)]) } /** Returns a `Results` containing the distinct values in the collection. */ func distinct() -> Results { return distinct(by: ["self"]) } } // MARK: - Sectioned Results on primitives public extension RealmCollection { /** Sorts this collection in ascending or descending order and performs sectioning via a user defined callback function. - parameter block: A callback which is invoked on each element in the collection. This callback is to return the section key for the element in the collection. - parameter ascending: The direction to sort in. - returns: An instance of `SectionedResults`. */ func sectioned(by block: @escaping ((Element) -> Key), ascending: Bool = true) -> SectionedResults { sectioned(sortDescriptors: [.init(keyPath: "self", ascending: ascending)], block) } } // MARK: - NSPredicate builders public extension RealmCollection { /** Returns the index of the first object matching the given predicate, or `nil` if no objects match. - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments. */ func index(matching predicateFormat: String, _ args: Any...) -> Int? { return index(matching: NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args))) } /** Returns a `Results` containing all objects matching the given predicate in the collection. - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments. */ func filter(_ predicateFormat: String, _ args: Any...) -> Results { return filter(NSPredicate(format: predicateFormat, argumentArray: unwrapOptionals(in: args))) } } // MARK: - Observation public extension RealmCollection { /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial results, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) print("dogs.count: \(dogs?.count)") // => 0 let token = dogs.observe { changes in switch changes { case .initial(let dogs): // Will print "dogs.count: 1" print("dogs.count: \(dogs.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let token = dogs.observe(keyPaths: [\Dog.name]) { changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `[\Dog.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\Dog.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (RealmCollectionChange) -> Void) -> NotificationToken { return self.observe(keyPaths: keyPaths, on: queue, block) } #if compiler(<6) /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil` or empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe(keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken { await self.observe(keyPaths: keyPaths, on: actor, block) } #else /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil` or empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe(keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken { await self.observe(keyPaths: keyPaths, on: actor, _isolation: _isolation, block) } #endif } public extension RealmCollection where Element: ObjectBase { /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial results, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) print("dogs.count: \(dogs?.count)") // => 0 let token = dogs.observe { changes in switch changes { case .initial(let dogs): // Will print "dogs.count: 1" print("dogs.count: \(dogs.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let token = dogs.observe(keyPaths: [\Dog.name]) { changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `[\Dog.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\Dog.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (RealmCollectionChange) -> Void) -> NotificationToken { return self.observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } #if compiler(<6) /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: [\.name], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `[\.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe(keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #else /** Registers a block to be called each time the collection changes. The block will be asynchronously called with an initial version of the collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `actor` parameter passed to the block is the actor which you pass to this function. This parameter is required to isolate the callback to the actor. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified after the previous notification. The `collection` field in the change enum will be isolated to the requested actor, and is safe to use within that actor only. See the ``RealmCollectionChange`` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. Once the initial notification is delivered, the collection will be fully evaluated and up-to-date, and accessing it will never perform any blocking work. This guarantee holds only as long as you do not perform a write transaction on the same actor as notifications are being delivered to. If you do, accessing the collection before the next notification is delivered may need to rerun the query. Notifications are delivered to the given actor's executor. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection: any writes which occur before the initial notification is delivered may not produce change notifications. Adding, removing or assigning objects in the collection always produces a notification. By default, modifying the objects which a collection links to (and the objects which those objects link to, if applicable) will also report that index in the collection as being modified. If a non-empty array of keypaths is provided, then only modifications to those keypaths will mark the object as modified. For example: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } let dogs = realm.objects(Dog.self) let token = await dogs.observe(keyPaths: [\.name], on: myActor) { actor, changes in switch changes { case .initial(let dogs): // Query has finished running and dogs can not be used without blocking case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the collection is modified // - when an element is inserted or removed from the collection. // This block is not triggered: // - when a value other than name is modified on one of the elements. case .error: // Can no longer happen but is left for backwards compatiblity } } ``` - If the observed key path were `[\.toys.brand]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `[\.toys]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If empty, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter actor: The actor to isolate the notifications to. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe(keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, RealmCollectionChange) -> Void) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } #endif } extension RealmCollection { /** Sorts and sections this collection from a given property key path, returning the result as an instance of `SectionedResults`. For every unique value retrieved from the keyPath a section key will be generated. - parameter keyPath: The property key path to sort on. - parameter ascending: The direction to sort in. - returns: An instance of `SectionedResults`. */ public func sectioned(by keyPath: KeyPath, ascending: Bool = true) -> SectionedResults where Element: Projection { let keyPathString = _name(for: keyPath) return sectioned(sortDescriptors: [.init(keyPath: keyPathString, ascending: ascending)], { return $0[keyPath: keyPath] }) } /** Sorts and sections this collection from a given property key path, returning the result as an instance of `SectionedResults`. For every unique value retrieved from the keyPath a section key will be generated. - parameter keyPath: The property key path to sort on. - parameter sortDescriptors: An array of `SortDescriptor`s to sort by. - note: The primary sort descriptor must be responsible for determining the section key. - returns: An instance of `SectionedResults`. */ public func sectioned(by keyPath: KeyPath, sortDescriptors: [SortDescriptor]) -> SectionedResults where Element: Projection { guard let sortDescriptor = sortDescriptors.first else { throwRealmException("Can not section Results with empty sortDescriptor parameter.") } let keyPathString = _name(for: keyPath) if keyPathString != sortDescriptor.keyPath { throwRealmException("The section key path must match the primary sort descriptor.") } return sectioned(sortDescriptors: sortDescriptors, { $0[keyPath: keyPath] }) } /** Sorts this collection from a given array of sort descriptors and performs sectioning from a user defined callback, returning the result as an instance of `SectionedResults`. - parameter block: A callback which is invoked on each element in the Results collection. This callback is to return the section key for the element in the collection. - parameter sortDescriptors: An array of `SortDescriptor`s to sort by. - note: The primary sort descriptor must be responsible for determining the section key. - returns: An instance of `SectionedResults`. */ public func sectioned(by block: @escaping ((Element) -> Key), sortDescriptors: [SortDescriptor]) -> SectionedResults where Element: Projection { return sectioned(sortDescriptors: sortDescriptors, block) } } /** A type-erased `RealmCollection`. Instances of `RealmCollection` forward operations to an opaque underlying collection having the same `Element` type. This type can be used to write non-generic code which can operate on or store multiple types of Realm collections. It does not have any runtime overhead over using the original collection directly. */ @frozen public struct AnyRealmCollection: RealmCollectionImpl { internal let collection: RLMCollection internal var lastAccessedNames: NSMutableArray? internal init(collection: RLMCollection) { self.collection = collection } /// Creates an `AnyRealmCollection` wrapping `base`. public init(_ base: C) where C.Element == Element { self.collection = base._rlmObjcValue as! RLMCollection } /** Returns the object at the given `index`. - parameter index: The index. */ public subscript(position: Int) -> Element { throwForNegativeIndex(position) return staticBridgeCast(fromObjectiveC: collection.object(at: UInt(position))) } /// A human-readable description of the objects represented by the linking objects. public var description: String { return RLMDescriptionWithMaxDepth("AnyRealmCollection", collection, RLMDescriptionMaxDepth) } public static func == (lhs: AnyRealmCollection, rhs: AnyRealmCollection) -> Bool { lhs.collection.isEqual(rhs.collection) } /// :nodoc: public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } } extension AnyRealmCollection: Encodable where Element: Encodable {} /** ProjectedCollection is a special type of collection for Projection's properties which should be used when you want to project a `List` of Realm Objects to a list of values. You don't need to instantiate this type manually. Use it by calling `projectTo` on a `List` property: ```swift class PersistedListObject: Object { @Persisted public var people: List } class ListProjection: Projection { @Projected(\PersistedListObject.people.projectTo.firstName) var strings: ProjectedCollection } ``` */ public struct ProjectedCollection: RandomAccessCollection, CustomStringConvertible, ThreadConfined where Element: RealmCollectionValue { public typealias Index = Int /** Returns the index of the first object in the list matching the predicate, or `nil` if no objects match. - parameter predicate: The predicate with which to filter the objects. */ public func index(matching predicate: NSPredicate) -> Int? { backingCollection.index(matching: predicate) } /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial results, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift class Person: Object { @Persisted var dogs: List } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogsNames: ProjectedCollection } // ... let dogsNames = personProjection.dogsNames print("dogsNames.count: \(dogsNames?.count)") // => 0 let token = dogsNames.observe { changes in switch changes { case .initial(let dogsNames): // Will print "dogsNames.count: 1" print("dogsNames.count: \(dogsNames.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(on queue: DispatchQueue?, _ block: @escaping (RealmCollectionChange>) -> Void) -> NotificationToken { backingCollection.observe(on: queue, { switch $0 { case .initial(let collection): block(.initial(Self(collection.collection, keyPath: keyPath, propertyName: propertyName))) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): block(.update(Self(collection.collection, keyPath: keyPath, propertyName: propertyName), deletions: deletions, insertions: insertions, modifications: modifications)) case .error(let error): block(.error(error)) } }) } /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial results, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift class Person: Object { @Persisted var dogs: List } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogNames: ProjectedCollection } // ... let dogNames = personProjection.dogNames print("dogNames.count: \(dogNames?.count)") // => 0 let token = dogs.observe { changes in switch changes { case .initial(let dogNames): // Will print "dogNames.count: 1" print("dogNames.count: \(dogNames.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Person: Object { @Persisted var dogs: List } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogNames: ProjectedCollection } // ... let dogNames = personProjection.dogNames let token = dogNames.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let dogNames): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` Changes to any other value that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ public func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (RealmCollectionChange>) -> Void) -> NotificationToken { backingCollection.observe(keyPaths: keyPaths, on: queue) { switch $0 { case .initial(let collection): block(.initial(Self(collection.collection, keyPath: keyPath, propertyName: propertyName))) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): block(.update(Self(collection.collection, keyPath: keyPath, propertyName: propertyName), deletions: deletions, insertions: insertions, modifications: modifications)) case .error(let error): block(.error(error)) } } } /** Returns the object at the given index (get), or replaces the object at the given index (set). - warning: You can only set an object during a write transaction. - parameter index: The index of the object to retrieve or replace. */ public subscript(position: Int) -> Element { get { backingCollection[position][keyPath: keyPath] as! Element } set { backingCollection[position].setValue(newValue, forKeyPath: propertyName) } } /// The position of the first element in a non-empty collection. /// Identical to endIndex in an empty collection. public var startIndex: Int { backingCollection.startIndex } /// The collection's "past the end" position. /// endIndex is not a valid argument to subscript, and is always reachable from startIndex by /// zero or more applications of successor(). public var endIndex: Int { backingCollection.endIndex } /// The Realm which manages the object. public var realm: Realm? { backingCollection.realm } /// Indicates if the collection can no longer be accessed. public var isInvalidated: Bool { backingCollection.isInvalidated } /// A human-readable description of the object. public var description: String { var description = "ProjectedCollection<\(Element.self)> {\n" for (i, v) in self.enumerated() { description += "\t[\(i)] \(v)\n" } return description + "}" } /** Returns the index of an object in the linking objects, or `nil` if the object is not present. - parameter object: The object whose index is being queried. */ public func index(of object: Element) -> Int? { return backingCollection.map { $0[keyPath: self.keyPath] as! Element }.firstIndex(of: object) } public var isFrozen: Bool { backingCollection.isFrozen } public func freeze() -> Self { Self(backingCollection.freeze().collection, keyPath: keyPath, propertyName: propertyName) } public func thaw() -> Self? { guard let backingCollection = backingCollection.thaw() else { return nil } return Self(backingCollection.collection, keyPath: keyPath, propertyName: propertyName) } private let backingCollection: AnyRealmCollection private let keyPath: AnyKeyPath private let propertyName: String init(_ collection: RLMCollection, keyPath: AnyKeyPath, propertyName: String) { self.backingCollection = AnyRealmCollection(collection: collection) self.keyPath = keyPath self.propertyName = propertyName } } /** `CollectionElementMapper` transforms the actual collection objects into a `ProjectedCollection`. For example: ```swift class Person: Object { @Persisted var dogs: List } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogNames: ProjectedCollection } ``` In this code the `Person`'s dogs list will be prijected to the list of dogs names via `projectTo` */ @dynamicMemberLookup public struct CollectionElementMapper where Element: ObjectBase & RealmCollectionValue { let collection: RLMCollection /// :nodoc: public subscript(dynamicMember member: KeyPath) -> ProjectedCollection { ProjectedCollection(collection, keyPath: member, propertyName: _name(for: member)) } } extension List where Element: ObjectBase & RealmCollectionValue { /** `projectTo` will map the original `List` of `Objects` or `List` of `EmbeddedObjects` in to `ProjectedCollection`. For example: ```swift class Person: Object { @Persisted var dogs: List } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogNames: ProjectedCollection } ``` In this code the `Person`'s dogs list will be prijected to the list of dogs names via `projectTo` */ public var projectTo: CollectionElementMapper { CollectionElementMapper(collection: collection) } } extension MutableSet where Element: ObjectBase & RealmCollectionValue { /** `MutableSetElementMapper` transforms the actual `MutableSet` of `Objects` or `MutableSet` of `EmbeddedObjects` in to `ProjectedCollection`. For example: ```swift class Person: Object { @Persisted var dogs: MutableSet } class PersonProjection: Projection { @Projected(\Person.dogs.projectTo.name) var dogNames: ProjectedCollection } ``` In this code the `Person`'s dogs set will be prijected to the projected set of dogs names via `projectTo` Note: This is not the actual *set* data type therefore projected elements can contain duplicates. */ public var projectTo: CollectionElementMapper { CollectionElementMapper(collection: collection) } } ================================================ FILE: RealmSwift/RealmConfiguration.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Realm.Private extension Realm { /** A `Configuration` instance describes the different options used to create an instance of a Realm. `Configuration` instances are just plain Swift structs. Unlike `Realm`s and `Object`s, they can be freely shared between threads as long as you do not mutate them. Creating configuration values for class subsets (by setting the `objectClasses` property) can be expensive. Because of this, you will normally want to cache and reuse a single configuration value for each distinct configuration rather than creating a new value each time you open a Realm. */ @frozen public struct Configuration: Sendable { // MARK: Default Configuration /** The default `Configuration` used to create Realms when no configuration is explicitly specified (i.e. `Realm()`) */ public static var defaultConfiguration: Configuration { get { return fromRLMRealmConfiguration(RLMRealmConfiguration.default()) } set { RLMRealmConfiguration.setDefault(newValue.rlmConfiguration) } } // MARK: Initialization /** Creates a `Configuration` which can be used to create new `Realm` instances. - note: The `fileURL`, and `inMemoryIdentifier`, parameters are mutually exclusive. Only set one of them, or none if you wish to use the default file URL. - parameter fileURL: The local URL to the Realm file. - parameter inMemoryIdentifier: A string used to identify a particular in-memory Realm. - parameter encryptionKey: An optional 64-byte key to use to encrypt the data. - parameter readOnly: Whether the Realm is read-only (must be true for read-only files). - parameter schemaVersion: The current schema version. - parameter migrationBlock: The block which migrates the Realm to the current version. - parameter deleteRealmIfMigrationNeeded: If `true`, recreate the Realm file with the provided schema if a migration is required. - parameter shouldCompactOnLaunch: A block called when opening a Realm for the first time during the life of a process to determine if it should be compacted before being returned to the user. It is passed the total file size (data + free space) and the total bytes used by data in the file. Return `true ` to indicate that an attempt to compact the file should be made. The compaction will be skipped if another process is accessing it. - parameter objectTypes: The subset of `Object` and `EmbeddedObject` subclasses persisted in the Realm. - parameter seedFilePath: The path to the realm file that will be copied to the fileURL when opened for the first time. */ @preconcurrency public init(fileURL: URL? = URL(fileURLWithPath: RLMRealmPathForFile("default.realm"), isDirectory: false), inMemoryIdentifier: String? = nil, encryptionKey: Data? = nil, readOnly: Bool = false, schemaVersion: UInt64 = 0, migrationBlock: MigrationBlock? = nil, deleteRealmIfMigrationNeeded: Bool = false, shouldCompactOnLaunch: (@Sendable (Int, Int) -> Bool)? = nil, objectTypes: [ObjectBase.Type]? = nil, seedFilePath: URL? = nil) { self.fileURL = fileURL if let inMemoryIdentifier = inMemoryIdentifier { self.inMemoryIdentifier = inMemoryIdentifier } self.encryptionKey = encryptionKey self.readOnly = readOnly self.schemaVersion = schemaVersion self.migrationBlock = migrationBlock self.deleteRealmIfMigrationNeeded = deleteRealmIfMigrationNeeded self.shouldCompactOnLaunch = shouldCompactOnLaunch self.objectTypes = objectTypes self.seedFilePath = seedFilePath } // MARK: Configuration Properties /// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier`. public var fileURL: URL? { didSet { _inMemoryIdentifier = nil } } /// A string used to identify a particular in-memory Realm. Mutually exclusive with `fileURL`. public var inMemoryIdentifier: String? { get { return _inMemoryIdentifier } set { fileURL = nil _inMemoryIdentifier = newValue } } private var _inMemoryIdentifier: String? /// A 64-byte key to use to encrypt the data, or `nil` if encryption is not enabled. public var encryptionKey: Data? /** Whether to open the Realm in read-only mode. This is required to be able to open Realm files which are not writeable or are in a directory which is not writeable. This should only be used on files which will not be modified by anyone while they are open, and not just to get a read-only view of a file which may be written to by another thread or process. Opening in read-only mode requires disabling Realm's reader/writer coordination, so committing a write transaction from another process will result in crashes. */ public var readOnly: Bool = false /// The current schema version. public var schemaVersion: UInt64 = 0 /// The block which migrates the Realm to the current version. @preconcurrency public var migrationBlock: MigrationBlock? /** Whether to recreate the Realm file with the provided schema if a migration is required. This is the case when the stored schema differs from the provided schema or the stored schema version differs from the version on this configuration. Setting this property to `true` deletes the file if a migration would otherwise be required or executed. - note: Setting this property to `true` doesn't disable file format migrations. */ public var deleteRealmIfMigrationNeeded: Bool = false /** A block called when opening a Realm for the first time during the life of a process to determine if it should be compacted before being returned to the user. It is passed the total file size (data + free space) and the total bytes used by data in the file. Return `true ` to indicate that an attempt to compact the file should be made. The compaction will be skipped if another process is accessing it. */ @preconcurrency public var shouldCompactOnLaunch: (@Sendable (Int, Int) -> Bool)? /// The classes managed by the Realm. public var objectTypes: [ObjectBase.Type]? { get { return self.customSchema.map { $0.objectSchema.compactMap { $0.objectClass as? ObjectBase.Type } } } set { self.customSchema = newValue.map { RLMSchema(objectClasses: $0) } } } /** The maximum number of live versions in the Realm file before an exception will be thrown when attempting to start a write transaction. Realm provides MVCC snapshot isolation, meaning that writes on one thread do not overwrite data being read on another thread, and instead write a new copy of that data. When a Realm refreshes it updates to the latest version of the data and releases the old versions, allowing them to be overwritten by subsequent write transactions. Under normal circumstances this is not a problem, but if the number of active versions grow too large, it will have a negative effect on the filesize on disk. This can happen when performing writes on many different threads at once, when holding on to frozen objects for an extended time, or when performing long operations on background threads which do not allow the Realm to refresh. Setting this property to a non-zero value makes it so that exceeding the set number of versions will instead throw an exception. This can be used with a low value during development to help identify places that may be problematic, or in production use to cause the app to crash rather than produce a Realm file which is too large to be opened. */ public var maximumNumberOfActiveVersions: UInt? /** When opening the Realm for the first time, instead of creating an empty file, the Realm file will be copied from the provided seed file path and used instead. This can be used to open a Realm file with pre-populated data. If a realm file already exists at the configurations's destination path, the seed file will not be copied and the already existing realm will be opened instead. This option is mutually exclusive with `inMemoryIdentifier`. Setting a `seedFilePath` will nil out the `inMemoryIdentifier`. */ public var seedFilePath: URL? /// A custom schema to use for the Realm. private var customSchema: RLMSchema? /// If `true`, disables automatic format upgrades when accessing the Realm. internal var disableFormatUpgrade: Bool = false // MARK: Private Methods internal var rlmConfiguration: RLMRealmConfiguration { let configuration = RLMRealmConfiguration() if let fileURL = fileURL { configuration.fileURL = fileURL } else if let inMemoryIdentifier = inMemoryIdentifier { configuration.inMemoryIdentifier = inMemoryIdentifier } else { fatalError("A Realm Configuration must specify a path or an in-memory identifier.") } configuration.seedFilePath = self.seedFilePath configuration.encryptionKey = self.encryptionKey configuration.readOnly = self.readOnly configuration.schemaVersion = self.schemaVersion configuration.migrationBlock = self.migrationBlock configuration.migrationObjectClass = MigrationObject.self configuration.deleteRealmIfMigrationNeeded = self.deleteRealmIfMigrationNeeded configuration.shouldCompactOnLaunch = self.shouldCompactOnLaunch.map(ObjectiveCSupport.convert(object:)) configuration.setCustomSchemaWithoutCopying(self.customSchema) configuration.disableFormatUpgrade = self.disableFormatUpgrade configuration.maximumNumberOfActiveVersions = self.maximumNumberOfActiveVersions ?? 0 return configuration } internal static func fromRLMRealmConfiguration(_ rlmConfiguration: RLMRealmConfiguration) -> Configuration { var configuration = Configuration() configuration.fileURL = rlmConfiguration.fileURL configuration._inMemoryIdentifier = rlmConfiguration.inMemoryIdentifier configuration.encryptionKey = rlmConfiguration.encryptionKey configuration.readOnly = rlmConfiguration.readOnly configuration.schemaVersion = rlmConfiguration.schemaVersion configuration.migrationBlock = rlmConfiguration.migrationBlock configuration.deleteRealmIfMigrationNeeded = rlmConfiguration.deleteRealmIfMigrationNeeded configuration.shouldCompactOnLaunch = rlmConfiguration.shouldCompactOnLaunch.map(ObjectiveCSupport.convert) configuration.customSchema = rlmConfiguration.customSchema configuration.disableFormatUpgrade = rlmConfiguration.disableFormatUpgrade configuration.maximumNumberOfActiveVersions = rlmConfiguration.maximumNumberOfActiveVersions configuration.seedFilePath = rlmConfiguration.seedFilePath return configuration } } } // MARK: CustomStringConvertible extension Realm.Configuration: CustomStringConvertible { /// A human-readable description of the configuration value. public var description: String { return gsub(pattern: "\\ARLMRealmConfiguration", template: "Realm.Configuration", string: rlmConfiguration.description) ?? "" } } // MARK: Equatable extension Realm.Configuration: Equatable { public static func == (lhs: Realm.Configuration, rhs: Realm.Configuration) -> Bool { lhs.encryptionKey == rhs.encryptionKey && lhs.fileURL == rhs.fileURL && lhs.inMemoryIdentifier == rhs.inMemoryIdentifier && lhs.readOnly == rhs.readOnly && lhs.schemaVersion == rhs.schemaVersion } } ================================================ FILE: RealmSwift/RealmKeyedCollection.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm /** A homogenous key-value collection of `Object`s which can be retrieved, filtered, sorted, and operated upon. */ public protocol RealmKeyedCollection: Sequence, ThreadConfined, CustomStringConvertible { /// The type of key associated with this collection associatedtype Key: _MapKey /// The type of value associated with this collection. associatedtype Value: RealmCollectionValue // MARK: Properties /// The Realm which manages the map, or `nil` if the map is unmanaged. var realm: Realm? { get } /// Indicates if the map can no longer be accessed. var isInvalidated: Bool { get } /// Returns the number of key-value pairs in this map. var count: Int { get } /// A human-readable description of the objects contained in the Map. var description: String { get } // MARK: Mutation /** Updates the value stored in the dictionary for the given key, or adds a new key-value pair if the key does not exist. - Note:If the value being added to the dictionary is an unmanaged object and the dictionary is managed then that unmanaged object will be added to the Realm. - warning: This method may only be called during a write transaction. - parameter value: a value's key path predicate. - parameter forKey: The direction to sort in. */ func updateValue(_ value: Value, forKey key: Key) /** Removes the given key and its associated object, only if the key exists in the dictionary. If the key does not exist, the dictionary will not be modified. - warning: This method may only be called during a write transaction. */ func removeObject(for key: Key) /** Removes all objects from the dictionary. The objects are not removed from the Realm that manages them. - warning: This method may only be called during a write transaction. */ func removeAll() /** Returns the value for a given key, or sets a value for a key should the subscript be used for an assign. - Note:If the value being added to the dictionary is an unmanaged object and the dictionary is managed then that unmanaged object will be added to the Realm. - Note:If the value being assigned for a key is `nil` then that key will be removed from the dictionary. - warning: This method may only be called during a write transaction. - parameter key: The key. */ subscript(key: Key) -> Value? { get set } // MARK: KVC /** Returns a type of `Value` for a specified key if it exists in the map. Note that when using key-value coding, the key must be a string. - parameter key: The key to the property whose values are desired. */ func value(forKey key: String) -> AnyObject? /** Returns a type of `Value` for a specified key if it exists in the map. - parameter keyPath: The key to the property whose values are desired. */ func value(forKeyPath keyPath: String) -> AnyObject? /** Adds a given key-value pair to the dictionary or updates a given key should it already exist. - warning: This method can only be called during a write transaction. - parameter value: The object value. - parameter key: The name of the property whose value should be set on each object. */ func setValue(_ value: Any?, forKey key: String) // MARK: Filtering /** Returns a `Results` containing all matching values in the dictionary with the given predicate. - Note: This will return the values in the dictionary, and not the key-value pairs. - parameter predicate: The predicate with which to filter the values. */ func filter(_ predicate: NSPredicate) -> Results /** Returns a Boolean value indicating whether the Map contains the key-value pair satisfies the given predicate - parameter where: a closure that test if any key-pair of the given map represents the match. */ func contains(where predicate: @escaping (_ key: Key, _ value: Value) -> Bool) -> Bool // MARK: Sorting /** Returns a `Results` containing the objects in the dictionary, but sorted. Objects are sorted based on their values. For example, to sort a dictionary of `Date`s from neweset to oldest based, you might call `dates.sorted(ascending: true)`. - parameter ascending: The direction to sort in. */ func sorted(ascending: Bool) -> Results /** Returns a `Results` containing the objects in the dictionary, but sorted. Objects are sorted based on the values of the given key path. For example, to sort a dictionary of `Student`s from youngest to oldest based on their `age` property, you might call `students.sorted(byKeyPath: "age", ascending: true)`. - warning: Dictionaries may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - parameter keyPath: The key path to sort by. - parameter ascending: The direction to sort in. */ func sorted(byKeyPath keyPath: String, ascending: Bool) -> Results /** Returns a `Results` containing the objects in the dictionary, but sorted. - warning: Dictionaries may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - see: `sorted(byKeyPath:ascending:)` */ func sorted(by sortDescriptors: S) -> Results where S.Iterator.Element == SortDescriptor /// Returns all of the keys in this dictionary. var keys: [Key] { get } /// Returns all of the values in the dictionary. var values: [Value] { get } // MARK: Aggregate Operations /** Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the dictionary is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ func min(ofProperty property: String) -> T? where T.PersistedType: MinMaxType /** Returns the maximum (highest) value of the given property among all the objects in the dictionary, or `nil` if the dictionary is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter property: The name of a property whose minimum value is desired. */ func max(ofProperty property: String) -> T? where T.PersistedType: MinMaxType /** Returns the sum of the given property for objects in the dictionary, or `nil` if the dictionary is empty. - warning: Only names of properties of a type conforming to the `AddableType` protocol can be used. - parameter property: The name of a property conforming to `AddableType` to calculate sum on. */ func sum(ofProperty property: String) -> T where T.PersistedType: AddableType /** Returns the average value of a given property over all the objects in the dictionary, or `nil` if the dictionary is empty. - warning: Only a property whose type conforms to the `AddableType` protocol can be specified. - parameter property: The name of a property whose values should be summed. */ func average(ofProperty property: String) -> T? where T.PersistedType: AddableType // MARK: Notifications /** Registers a block to be called each time the dictionary changes. The block will be asynchronously called with the initial dictionary, and then called again after each write transaction which changes either any of the keys or values in the dictionary. The `change` parameter that is passed to the block reports, in the form of keys within the dictionary, which of the key-value pairs were added, removed, or modified during each write transaction. At the time when the block is called, the dictionary will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let myStringMap = myObject.stringMap print("myStringMap.count: \(myStringMap?.count)") // => 0 let token = myStringMap.observe { changes in switch changes { case .initial(let myStringMap): // Will print "myStringMap.count: 1" print("myStringMap.count: \(myStringMap.count)") print("Dog Name: \(myStringMap["nameOfDog"])") // => "Rex" break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { myStringMap["nameOfDog"] = "Rex" } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = myObject.mapOfDogs let token = dogs.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let dogs): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - note: The keyPaths parameter refers to object properties of the collection type and *does not* refer to particular key/value pairs within the collection. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (RealmMapChange) -> Void) -> NotificationToken // MARK: Frozen Objects /// Returns if this collection is frozen var isFrozen: Bool { get } /** Returns a frozen (immutable) snapshot of this collection. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live collections, frozen collections can be accessed from any thread. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - warning: Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ func freeze() -> Self /** Returns a live (mutable) version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ func thaw() -> Self? } public extension RealmKeyedCollection { func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (RealmMapChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths, on: queue, block) } } /** Protocol for RealmKeyedCollections where the Value is of an Object type that enables aggregatable operations. */ public extension RealmKeyedCollection where Value: OptionalProtocol, Value.Wrapped: ObjectBase { /** Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter keyPath: The keyPath of a property whose minimum value is desired. */ func min(of keyPath: KeyPath) -> T? where T.PersistedType: MinMaxType { min(ofProperty: _name(for: keyPath)) } /** Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified. - parameter keyPath: The keyPath of a property whose minimum value is desired. */ func max(of keyPath: KeyPath) -> T? where T.PersistedType: MinMaxType { max(ofProperty: _name(for: keyPath)) } /** Returns the sum of the given property for objects in the collection, or `nil` if the collection is empty. - warning: Only names of properties of a type conforming to the `AddableType` protocol can be used. - parameter keyPath: The keyPath of a property conforming to `AddableType` to calculate sum on. */ func sum(of keyPath: KeyPath) -> T where T.PersistedType: AddableType { sum(ofProperty: _name(for: keyPath)) } /** Returns the average value of a given property over all the objects in the collection, or `nil` if the collection is empty. - warning: Only a property whose type conforms to the `AddableType` protocol can be specified. - parameter keyPath: The keyPath of a property whose values should be summed. */ func average(of keyPath: KeyPath) -> T? where T.PersistedType: AddableType { average(ofProperty: _name(for: keyPath)) } } // MARK: Sortable /** Protocol for RealmKeyedCollections where the Value is of an Object type that enables sortable operations. */ public extension RealmKeyedCollection where Value: OptionalProtocol, Value.Wrapped: ObjectBase, Value.Wrapped: RealmCollectionValue { /** Returns a `Results` containing the objects in the collection, but sorted. Objects are sorted based on the values of the given key path. For example, to sort a collection of `Student`s from youngest to oldest based on their `age` property, you might call `students.sorted(byKeyPath: "age", ascending: true)`. - warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision floating point, integer, and string types. - parameter keyPath: The key path to sort by. - parameter ascending: The direction to sort in. */ func sorted(by keyPath: KeyPath, ascending: Bool) -> Results where T.PersistedType: SortableType { sorted(byKeyPath: _name(for: keyPath), ascending: ascending) } } public extension RealmKeyedCollection where Value.PersistedType: MinMaxType { /** Returns the minimum (lowest) value of the collection, or `nil` if the collection is empty. */ func min() -> Value? { return min(ofProperty: "self") } /** Returns the maximum (highest) value of the collection, or `nil` if the collection is empty. */ func max() -> Value? { return max(ofProperty: "self") } } public extension RealmKeyedCollection where Value.PersistedType: AddableType { /** Returns the sum of the values in the collection, or `nil` if the collection is empty. */ func sum() -> Value { return sum(ofProperty: "self") } /** Returns the average of all of the values in the collection. */ func average() -> T? where T.PersistedType: AddableType { return average(ofProperty: "self") } } public extension RealmKeyedCollection where Value.PersistedType: SortableType { /** Returns a `Results` containing the objects in the collection, but sorted. Objects are sorted based on their values. For example, to sort a collection of `Date`s from neweset to oldest based, you might call `dates.sorted(ascending: true)`. - parameter ascending: The direction to sort in. */ func sorted(ascending: Bool = true) -> Results { return sorted(byKeyPath: "self", ascending: ascending) } } ================================================ FILE: RealmSwift/RealmProperty.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm /** A `RealmProperty` instance represents an polymorphic value for supported types. To change the underlying value stored by a `RealmProperty` instance, mutate the instance's `value` property. - Note: An `RealmProperty` should not be declared as `@objc dynamic` on a Realm Object. Use `let` instead. */ public final class RealmProperty: RLMSwiftValueStorage { /** Used for getting / setting the underlying value. - Usage: ``` class MyObject: Object { let myAnyValue = RealmProperty() } // Setting myObject.myAnyValue.value = .string("hello") // Getting if case let .string(s) = myObject.myAnyValue.value { print(s) // Prints 'Hello' } ``` */ public var value: Value { get { staticBridgeCast(fromObjectiveC: RLMGetSwiftValueStorage(self) ?? NSNull()) } set { RLMSetSwiftValueStorage(self, staticBridgeCast(fromSwift: newValue)) } } /// :nodoc: @objc public override var description: String { String(describing: value) } } extension RealmProperty: Equatable where Value: Equatable { public static func == (lhs: RealmProperty, rhs: RealmProperty) -> Bool { return lhs.value == rhs.value } } extension RealmProperty: Codable where Value: Codable { public convenience init(from decoder: Decoder) throws { self.init() self.value = try decoder.decodeOptional(Value.self) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value) } } /// A protocol describing types that can parameterize a `RealmPropertyType`. public protocol RealmPropertyType: _ObjcBridgeable, _RealmSchemaDiscoverable { } extension AnyRealmValue: RealmPropertyType { } extension Optional: RealmPropertyType where Wrapped: RealmOptionalType & _RealmSchemaDiscoverable { } ================================================ FILE: RealmSwift/Results.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm.Private // MARK: MinMaxType /** Types of properties which can be used with the minimum and maximum value APIs. - see: `min(ofProperty:)`, `max(ofProperty:)` */ @_marker public protocol MinMaxType {} extension NSNumber: MinMaxType {} extension Double: MinMaxType {} extension Float: MinMaxType {} extension Int: MinMaxType {} extension Int8: MinMaxType {} extension Int16: MinMaxType {} extension Int32: MinMaxType {} extension Int64: MinMaxType {} extension Date: MinMaxType {} extension NSDate: MinMaxType {} extension Decimal128: MinMaxType {} extension AnyRealmValue: MinMaxType {} extension Optional: MinMaxType where Wrapped: MinMaxType {} // MARK: AddableType /** Types of properties which can be used with the sum and average value APIs. - see: `sum(ofProperty:)`, `average(ofProperty:)` */ @_marker public protocol AddableType {} extension NSNumber: AddableType {} extension Double: AddableType {} extension Float: AddableType {} extension Int: AddableType {} extension Int8: AddableType {} extension Int16: AddableType {} extension Int32: AddableType {} extension Int64: AddableType {} extension Decimal128: AddableType {} extension AnyRealmValue: AddableType {} extension Optional: AddableType where Wrapped: AddableType {} /** Types of properties which can be directly sorted or distincted. - see: `sum(ascending:)`, `distinct()` */ @_marker public protocol SortableType {} extension AnyRealmValue: SortableType {} extension Data: SortableType {} extension Date: SortableType {} extension Decimal128: SortableType {} extension Double: SortableType {} extension Float: SortableType {} extension Int16: SortableType {} extension Int32: SortableType {} extension Int64: SortableType {} extension Int8: SortableType {} extension Int: SortableType {} extension String: SortableType {} extension Optional: SortableType where Wrapped: SortableType {} /** Types which have properties that can be sorted or distincted on. */ @_marker public protocol KeypathSortable {} extension ObjectBase: KeypathSortable {} extension Projection: KeypathSortable {} /** `Results` is an auto-updating container type in Realm returned from object queries. `Results` can be queried with the same predicates as `List`, and you can chain queries to further filter query results. `Results` always reflect the current state of the Realm on the current thread, including during write transactions on the current thread. The one exception to this is when using `for...in` enumeration, which will always enumerate over the objects which matched the query when the enumeration is begun, even if some of them are deleted or modified to be excluded by the filter during the enumeration. `Results` are lazily evaluated the first time they are accessed; they only run queries when the result of the query is requested. This means that chaining several temporary `Results` to sort and filter your data does not perform any unnecessary work processing the intermediate state. Once the results have been evaluated or a notification block has been added, the results are eagerly kept up-to-date, with the work done to keep them up-to-date done on a background thread whenever possible. Results instances cannot be directly instantiated. */ @frozen public struct Results: Equatable, RealmCollectionImpl { internal let collection: RLMCollection /// A human-readable description of the objects represented by the results. public var description: String { return RLMDescriptionWithMaxDepth("Results", collection, RLMDescriptionMaxDepth) } // MARK: Initializers internal init(collection: RLMCollection) { self.collection = collection } internal init(_ collection: RLMCollection) { self.collection = collection } // MARK: Object Retrieval /** Returns the object at the given `index`. - parameter index: The index. */ public subscript(position: Int) -> Element { throwForNegativeIndex(position) return staticBridgeCast(fromObjectiveC: collection.object(at: UInt(position))) } // MARK: Equatable public static func == (lhs: Results, rhs: Results) -> Bool { lhs.collection.isEqual(rhs.collection) } /// :nodoc: public func makeIterator() -> RLMIterator { return RLMIterator(collection: collection) } } extension Results: Encodable where Element: Encodable {} ================================================ FILE: RealmSwift/Schema.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm /** `Schema` instances represent collections of model object schemas managed by a Realm. When using Realm, `Schema` instances allow performing migrations and introspecting the database's schema. Schemas map to collections of tables in the core database. */ @frozen public struct Schema: CustomStringConvertible { // MARK: Properties internal let rlmSchema: RLMSchema /** An array of `ObjectSchema`s for all object types in the Realm. This property is intended to be used during migrations for dynamic introspection. */ public var objectSchema: [ObjectSchema] { return rlmSchema.objectSchema.map(ObjectSchema.init) } /// A human-readable description of the object schemas contained within. public var description: String { return rlmSchema.description } // MARK: Initializers internal init(_ rlmSchema: RLMSchema) { self.rlmSchema = rlmSchema } // MARK: ObjectSchema Retrieval /// Looks up and returns an `ObjectSchema` for the given class name in the Realm, if it exists. public subscript(className: String) -> ObjectSchema? { if let rlmObjectSchema = rlmSchema.schema(forClassName: className) { return ObjectSchema(rlmObjectSchema) } return nil } } // MARK: Equatable extension Schema: Equatable { /// Returns whether the two schemas are equal. public static func == (lhs: Schema, rhs: Schema) -> Bool { return lhs.rlmSchema.isEqual(to: rhs.rlmSchema) } } ================================================ FILE: RealmSwift/SectionedResults.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 Foundation import Realm /** `RealmSectionedResult` defines properties and methods which are common between `SectionedResults` and `ResultSection`. */ public protocol RealmSectionedResult: RandomAccessCollection, Equatable, ThreadConfined { // MARK: Properties /// The Realm which manages the collection, or `nil` if the collection is invalidated. var realm: Realm? { get } /** Indicates if the collection can no longer be accessed. The collection can no longer be accessed if `invalidate()` is called on the `Realm` that manages the collection. */ var isInvalidated: Bool { get } /// The number of objects in the collection. var count: Int { get } /// Returns true if this collection is frozen var isFrozen: Bool { get } /** Returns a frozen (immutable) snapshot of this collection. The frozen copy is an immutable collection which contains the same data as this collection currently contains, but will not update when writes are made to the containing Realm. Unlike live collections, frozen collections can be accessed from any thread. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - warning: Holding onto a frozen collection for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ func freeze() -> Self /** Returns a live (mutable) version of this frozen collection. This method resolves a reference to a live copy of the same frozen collection. If called on a live collection, will return itself. */ func thaw() -> Self? /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the sectioned results collection, or which objects are in the sectioned results collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `SectionedResultsChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial sectioned results collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) print("sectionedResults.count: \(sectionedResults?.count)") // => 0 let token = sectionedResults.observe { changes in switch changes { case .initial(let sectionedResults): // Will print "sectionedResults.count: 1" print("sectionedResults.count: \(sectionedResults.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) let token = sectionedResults.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let sectionedResults): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - Any modification to the section key path property which results in the object changing position in the section, or changing section entirely will trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. See description above for more detail on linked properties. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken } #if compiler(<6) public extension RealmSectionedResult { func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths, on: queue, block) } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe( keyPaths: [String]? = nil, on actor: A, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } } #else public extension RealmSectionedResult { func observe(keyPaths: [String]? = nil, on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths, on: queue, block) } /// :nodoc: @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe( keyPaths: [String]? = nil, on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in actor.invokeIsolated(block, change) } } } } #endif #if compiler(<6) public extension RealmSectionedResult where Element: RealmSectionedResult, Element.Element: ObjectBase { /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the sectioned results collection, or which objects are in the sectioned results collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `SectionedResultsChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial sectioned results collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) print("sectionedResults.count: \(sectionedResults?.count)") // => 0 let token = sectionedResults.observe { changes in switch changes { case .initial(let sectionedResults): // Will print "sectionedResults.count: 1" print("sectionedResults.count: \(sectionedResults.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) let token = sectionedResults.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let sectionedResults): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - Any modification to the section key path property which results in the object changing position in the section, or changing section entirely will trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } } public extension RealmSectionedResult where Element: ObjectBase { /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the sectioned results collection, or which objects are in the sectioned results collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `SectionedResultsChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial sectioned results collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) print("sectionedResults.count: \(sectionedResults?.count)") // => 0 let token = sectionedResults.observe { changes in switch changes { case .initial(let sectionedResults): // Will print "sectionedResults.count: 1" print("sectionedResults.count: \(sectionedResults.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) let token = sectionedResults.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let sectionedResults): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - Any modification to the section key path property which results in the object changing position in the section, or changing section entirely will trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor func observe( keyPaths: [PartialKeyPath], on actor: A, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } } #else public extension RealmSectionedResult where Element: RealmSectionedResult, Element.Element: ObjectBase { /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the sectioned results collection, or which objects are in the sectioned results collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `SectionedResultsChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial sectioned results collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) print("sectionedResults.count: \(sectionedResults?.count)") // => 0 let token = sectionedResults.observe { changes in switch changes { case .initial(let sectionedResults): // Will print "sectionedResults.count: 1" print("sectionedResults.count: \(sectionedResults.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) let token = sectionedResults.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let sectionedResults): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - Any modification to the section key path property which results in the object changing position in the section, or changing section entirely will trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } /// :nodoc: @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } } public extension RealmSectionedResult where Element: ObjectBase { /** Registers a block to be called each time the sectioned results collection changes. The block will be asynchronously called with the initial sectioned results collection, and then called again after each write transaction which changes either any of the objects in the sectioned results collection, or which objects are in the sectioned results collection. The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of the objects were added, removed, or modified during each write transaction. See the `SectionedResultsChange` documentation for more information on the change information supplied and an example of how to use it to update a `UITableView`. At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never perform blocking work. If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial sectioned results collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. ```swift let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) print("sectionedResults.count: \(sectionedResults?.count)") // => 0 let token = sectionedResults.observe { changes in switch changes { case .initial(let sectionedResults): // Will print "sectionedResults.count: 1" print("sectionedResults.count: \(sectionedResults.count)") break case .update: // Will not be hit in this example break case .error: break } } try! realm.write { let dog = Dog() dog.name = "Rex" person.dogs.append(dog) } // end of run loop execution context ``` If no key paths are given, the block will be executed on any insertion, modification, or deletion for all object properties and the properties of any nested, linked objects. If a key path or key paths are provided, then the block will be called for changes which occur only on the provided key paths. For example, if: ```swift class Dog: Object { @Persisted var name: String @Persisted var age: Int @Persisted var toys: List } // ... let dogs = realm.objects(Dog.self) let sectionedResults = dogs.sectioned(by: \.age, ascending: true) let token = sectionedResults.observe(keyPaths: ["name"]) { changes in switch changes { case .initial(let sectionedResults): // ... case .update: // This case is hit: // - after the token is initialized // - when the name property of an object in the // collection is modified // - when an element is inserted or removed // from the collection. // This block is not triggered: // - when a value other than name is modified on // one of the elements. case .error: // ... } } // end of run loop execution context ``` - If the observed key path were `["toys.brand"]`, then any insertion or deletion to the `toys` list on any of the collection's elements would trigger the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this collection will trigger the block. Changes to a value other than `brand` on any `Toy` that is linked to a `Dog` in this collection would not trigger the block. Any insertion or removal to the `Dog` type collection being observed would also trigger a notification. - If the above example observed the `["toys"]` key path, then any insertion, deletion, or modification to the `toys` list for any element in the collection would trigger the block. Changes to any value on any `Toy` that is linked to a `Dog` in this collection would *not* trigger the block. Any insertion or removal to the `Dog` type collection being observed would still trigger a notification. - Any modification to the section key path property which results in the object changing position in the section, or changing section entirely will trigger a notification. - note: Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `invalidate()` on the token. - warning: This method cannot be called during a write transaction, or when the containing Realm is read-only. - parameter keyPaths: Only properties contained in the key paths array will trigger the block when they are modified. If `nil`, notifications will be delivered for any property change on the object. - parameter queue: The serial dispatch queue to receive notification on. If `nil`, notifications are delivered to the current thread. - parameter block: The block to be called whenever a change occurs. - returns: A token which must be held for as long as you want updates to be delivered. */ func observe(keyPaths: [PartialKeyPath], on queue: DispatchQueue? = nil, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { observe(keyPaths: keyPaths.map(_name(for:)), on: queue, block) } /// :nodoc: @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func observe( keyPaths: [PartialKeyPath], on actor: A, _isolation: isolated (any Actor)? = #isolation, _ block: @Sendable @escaping (isolated A, SectionedResultsChange) -> Void ) async -> NotificationToken { await observe(keyPaths: keyPaths.map(_name(for:)), on: actor, block) } } #endif // Shared implementation of SectionedResults and ResultsSection private protocol SectionedResultImpl: RealmSectionedResult { associatedtype Collection: RLMSectionedResult var collection: Collection { get set } init(rlmSectionedResult: Collection) } /// :nodoc: extension SectionedResultImpl { public var startIndex: Int { 0 } public var endIndex: Int { Int(collection.count) } public var realm: Realm? { collection.realm.map(Realm.init) } public var isInvalidated: Bool { collection.isInvalidated } public var isFrozen: Bool { collection.isFrozen } public func freeze() -> Self { Self(rlmSectionedResult: collection.freeze()) } public func thaw() -> Self? { Self(rlmSectionedResult: collection.thaw()) } public func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (SectionedResultsChange) -> Void) -> NotificationToken { let wrapped = { (collection: RLMSectionedResult, change: RLMSectionedResultsChange) in block(SectionedResultsChange.fromObjc(value: Self(rlmSectionedResult: collection as! Self.Collection), change: change)) } return collection.addNotificationBlock(wrapped, keyPaths: keyPaths, queue: queue) } } /// `SectionedResults` is a type safe collection which holds individual `ResultsSection`s as its elements. /// The container is lazily evaluated, meaning that if the underlying collection has changed a full recalculation of the section keys will take place. /// A `SectionedResults` instance can be observed and it also conforms to `ThreadConfined`. public struct SectionedResults: SectionedResultImpl { internal var collection: RLMSectionedResults internal init(rlmSectionedResult: RLMSectionedResults) { self.collection = rlmSectionedResult } public typealias Element = ResultsSection /// An array of all keys in the sectioned results collection. public var allKeys: [Key] { collection.allKeys.map { Key._rlmFromObjc($0)! } } /** Returns the section at the given `index`. - parameter index: The index. */ public subscript(_ index: Int) -> Element { return Element(rlmSectionedResult: collection[UInt(index)]) } /** Returns the object at the given `IndexPath`. - parameter indexPath: The IndexPath. */ public subscript(_ indexPath: IndexPath) -> SectionElement { return self[indexPath.section][indexPath.item] } /// :nodoc: public func makeIterator() -> SectionedResultsIterator { return SectionedResultsIterator(collection: collection) } /// :nodoc: public static func == (lhs: Self, rhs: Self) -> Bool { return lhs.collection == rhs.collection } } /// `ResultsSection` is a collection which allows access to objects that belong to a given section key. /// The collection is lazily evaluated, meaning that if the underlying collection has changed a full recalculation of the section keys will take place. /// A `ResultsSection` instance can be observed and it also conforms to `ThreadConfined`. public struct ResultsSection: SectionedResultImpl { public typealias Element = T internal var collection: RLMSection internal init(rlmSectionedResult: RLMSection) { self.collection = rlmSectionedResult } /// The key which represents this section. public var key: Key { return Key._rlmFromObjc(collection.key)! } /// :nodoc: public var id: Key { key } /** Returns the object at the given `index`. - parameter index: The index. */ public subscript(_ index: Int) -> T { return T._rlmFromObjc(collection[UInt(index)])! } /// :nodoc: public func makeIterator() -> SectionIterator { return SectionIterator(collection: collection) } /// :nodoc: public static func == (lhs: ResultsSection, rhs: ResultsSection) -> Bool { return lhs.collection == rhs.collection } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension ResultsSection: Identifiable { } /** A `SectionedResultsChange` value encapsulates information about changes to sectioned results that are reported by Realm notifications. The first time a notification is delivered it will be `.initial`, and all subsequent notifications will be `.change()` with information about what has changed since the last time the callback was invoked. } */ @frozen public enum SectionedResultsChange { /** `.initial` indicates that the initial run of the query has completed (if applicable), and the collection can now be used without performing any blocking work. */ case initial(Collection) /** `.update` indicates that a write transaction has been committed which either changed which objects are in the collection, and/or modified one or more of the objects in the collection. All three of the change arrays are always sorted in ascending order. - parameter deletions: The indexPaths in the previous version of the collection which were removed from this one. - parameter insertions: The indexPaths in the new collection which were added in this version. - parameter modifications: The indexPaths of the objects which were modified in the previous version of this collection. - parameter sectionsToInsert: The indexSet of the sections which were newly inserted into the sectioned results collection. - parameter sectionsToDelete: The indexSet of the sections which were recently deleted from the previous sectioned results collection. */ case update(Collection, deletions: [IndexPath], insertions: [IndexPath], modifications: [IndexPath], sectionsToInsert: IndexSet, sectionsToDelete: IndexSet) /// :nodoc: static func fromObjc(value: Collection, change: RLMSectionedResultsChange?) -> SectionedResultsChange { if let change = change { return .update(value, deletions: change.deletions as [IndexPath], insertions: change.insertions as [IndexPath], modifications: change.modifications as [IndexPath], sectionsToInsert: change.sectionsToInsert, sectionsToDelete: change.sectionsToRemove) } return .initial(value) } } /// :nodoc: @available(*, deprecated, renamed: "SectionedResultsChange") public typealias RealmSectionedResultsChange = SectionedResultsChange /** An iterator for a `SectionedResults` instance. */ @frozen public struct SectionedResultsIterator: IteratorProtocol { private var generatorBase: NSFastEnumerationIterator init(collection: RLMSectionedResults) { generatorBase = NSFastEnumerationIterator(collection) } /// Advance to the next element and return it, or `nil` if no next element exists. public mutating func next() -> ResultsSection? { guard let next = generatorBase.next() else { return nil } return ResultsSection(rlmSectionedResult: next as! RLMSection) } } /// :nodoc: @available(*, deprecated, renamed: "SectionedResultsIterator") public typealias RLMSectionedResultsIterator = SectionedResultsIterator /** An iterator for a `Section` instance. */ @frozen public struct SectionIterator: IteratorProtocol { private var generatorBase: NSFastEnumerationIterator init(collection: RLMSection) { generatorBase = NSFastEnumerationIterator(collection) } /// Advance to the next element and return it, or `nil` if no next element exists. public mutating func next() -> Element? { guard let next = generatorBase.next() else { return nil } return next as? Element } } /// :nodoc: @available(*, deprecated, renamed: "SectionIterator") public typealias RLMSectionIterator = SectionIterator ================================================ FILE: RealmSwift/SortDescriptor.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Foundation import Realm /** A `SortDescriptor` stores a key path and a sort order for use with `sorted(sortDescriptors:)`. It is similar to `NSSortDescriptor`, but supports only the subset of functionality which can be efficiently run by Realm's query engine. */ @frozen public struct SortDescriptor { // MARK: Properties /// The key path which the sort descriptor orders results by. public let keyPath: String /// Whether this descriptor sorts in ascending or descending order. public let ascending: Bool /// Converts the receiver to an `RLMSortDescriptor`. internal var rlmSortDescriptorValue: RLMSortDescriptor { return RLMSortDescriptor(keyPath: keyPath, ascending: ascending) } // MARK: Initializers /** Creates a sort descriptor with the given key path and sort order values. - parameter keyPath: The key path which the sort descriptor orders results by. - parameter ascending: Whether the descriptor sorts in ascending or descending order. */ public init(keyPath: String, ascending: Bool = true) { self.keyPath = keyPath self.ascending = ascending } /** Creates a sort descriptor with the given key path and sort order values. - parameter keyPath: The key path which the sort descriptor orders results by. - parameter ascending: Whether the descriptor sorts in ascending or descending order. */ public init(keyPath: PartialKeyPath, ascending: Bool = true) { self.keyPath = _name(for: keyPath) self.ascending = ascending } // MARK: Functions /// Returns a copy of the sort descriptor with the sort order reversed. public func reversed() -> SortDescriptor { return SortDescriptor(keyPath: keyPath, ascending: !ascending) } } // MARK: CustomStringConvertible extension SortDescriptor: CustomStringConvertible { /// A human-readable description of the sort descriptor. public var description: String { let direction = ascending ? "ascending" : "descending" return "SortDescriptor(keyPath: \(keyPath), direction: \(direction))" } } // MARK: Equatable extension SortDescriptor: Equatable { /// Returns whether the two sort descriptors are equal. public static func == (lhs: SortDescriptor, rhs: SortDescriptor) -> Bool { return lhs.keyPath == rhs.keyPath && lhs.ascending == rhs.ascending } } // MARK: StringLiteralConvertible extension SortDescriptor: ExpressibleByStringLiteral { public typealias UnicodeScalarLiteralType = StringLiteralType public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType /** Creates a `SortDescriptor` out of a string literal. - parameter stringLiteral: Property name literal. */ public init(stringLiteral value: StringLiteralType) { self.init(keyPath: value) } } ================================================ FILE: RealmSwift/SwiftUI.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import SwiftUI import Combine import Realm import Realm.Private private func write(_ value: Value, _ block: (Value) -> Void) where Value: ThreadConfined { let thawed = value.realm == nil ? value : value.thaw() ?? value if let realm = thawed.realm, !realm.isInWriteTransaction { try! realm.write { block(thawed) } } else { block(thawed) } } private func thawObjectIfFrozen(_ value: Value) -> Value where Value: ObjectBase & ThreadConfined { return value.realm == nil ? value : value.thaw() ?? value } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @MainActor private func createBinding( _ value: T, forKeyPath keyPath: ReferenceWritableKeyPath) -> Binding { guard let value = value.isFrozen ? value.thaw() : value else { throwRealmException("Could not bind value") } // store last known value outside of the binding so that we can reference it if the parent // is invalidated var lastValue = value[keyPath: keyPath] return Binding(get: { guard !value.isInvalidated else { return lastValue } lastValue = value[keyPath: keyPath] return lastValue }, set: { newValue in guard !value.isInvalidated else { return } write(value) { value in value[keyPath: keyPath] = newValue } }) } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @MainActor private func createCollectionBinding( _ value: T, forKeyPath keyPath: ReferenceWritableKeyPath) -> Binding { guard let value = value.isFrozen ? value.thaw() : value else { throwRealmException("Could not bind value") } var lastValue = value[keyPath: keyPath] return Binding(get: { guard !value.isInvalidated else { return lastValue } lastValue = value[keyPath: keyPath] if lastValue.realm != nil { lastValue = lastValue.freeze() } return lastValue }, set: { newValue in guard !value.isInvalidated else { return } write(value) { value in value[keyPath: keyPath] = newValue } }) } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @MainActor private func createEquatableBinding( _ value: T, forKeyPath keyPath: ReferenceWritableKeyPath) -> Binding { guard let value = value.isFrozen ? value.thaw() : value else { throwRealmException("Could not bind value") } var lastValue = value[keyPath: keyPath] return Binding(get: { guard !value.isInvalidated else { return lastValue } lastValue = value[keyPath: keyPath] return lastValue }, set: { newValue in guard !value.isInvalidated else { return } guard value[keyPath: keyPath] != newValue else { return } write(value) { value in value[keyPath: keyPath] = newValue } }) } // MARK: SwiftUIKVO @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @objc(RLMSwiftUIKVO) internal final class SwiftUIKVO: NSObject { /// Objects must have observers removed before being added to a realm. /// They are stored here so that if they are appended through the Bound Property /// system, they can be de-observed before hand. private static let observedObjects = AllocatedUnfairLock([NSObject: Subscription]()) static func store(_ obj: NSObject, _ subscription: Subscription) { SwiftUIKVO.observedObjects.withLock { $0[obj] = subscription } } static func cancel(_ obj: NSObject) { SwiftUIKVO.observedObjects.withLock { if let subscription: Subscription = $0.removeValue(forKey: obj) { subscription.removeObservers() } } } struct Subscription: Combine.Subscription { let observer: NSObject let value: NSObject let keyPaths: [String] var combineIdentifier: CombineIdentifier { CombineIdentifier(value) } func request(_ demand: Subscribers.Demand) { } func cancel() { SwiftUIKVO.cancel(value) } fileprivate func removeObservers() { keyPaths.forEach { value.removeObserver(observer, forKeyPath: $0) } } fileprivate func addObservers() { keyPaths.forEach { value.addObserver(observer, forKeyPath: $0, options: .init(), context: nil) } } } private let receive: () -> Void override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { receive() } init(subscriber: S) where S: Subscriber, S.Input == Void { receive = { _ = subscriber.receive() } super.init() } } // MARK: - ObservableStorage @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class ObservableStoragePublisher: Publisher where ObjectType: ThreadConfined & RealmSubscribable { public typealias Output = Void public typealias Failure = Never var subscribers = [AnySubscriber]() private var value: ObjectType private let keyPaths: [String]? private let unwrappedValue: ObjectBase? init(_ value: ObjectType, _ keyPaths: [String]? = nil) { self.value = value self.keyPaths = keyPaths self.unwrappedValue = nil } init(_ value: ObjectType, _ keyPaths: [String]? = nil) where ObjectType: ObjectBase { self.value = value self.keyPaths = keyPaths self.unwrappedValue = value } init(_ value: ObjectType, _ keyPaths: [String]? = nil) where ObjectType: ProjectionObservable { self.value = value self.keyPaths = keyPaths self.unwrappedValue = value.rootObject } // Refresh the publisher with a managed object. func update(value: ObjectType) { self.value = value } func send() { subscribers.forEach { _ = $0.receive() } } public func receive(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { subscribers.append(AnySubscriber(subscriber)) if value.realm != nil && !value.isInvalidated, let value = value.thaw() { // This path is for cases where the object is already managed. If an // unmanaged object becomes managed it will continue to use KVO. let token = value._observe(keyPaths, subscriber) subscriber.receive(subscription: ObservationSubscription(token: token)) } else if let value = unwrappedValue, !value.isInvalidated { // else if the value is unmanaged let schema = ObjectSchema(RLMObjectBaseObjectSchema(value)!) let kvo = SwiftUIKVO(subscriber: subscriber) var keyPaths = [String]() for property in schema.properties { keyPaths.append(property.name) value.addObserver(kvo, forKeyPath: property.name, options: .init(), context: nil) } let subscription = SwiftUIKVO.Subscription(observer: kvo, value: value, keyPaths: keyPaths) subscriber.receive(subscription: subscription) SwiftUIKVO.store(value, subscription) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private class ObservableStorage: ObservableObject where ObservedType: RealmSubscribable & ThreadConfined & Equatable { @Published var value: ObservedType { willSet { if newValue != value { objectWillChange.send() objectWillChange.update(value: newValue) objectWillChange.subscribers.forEach { $0.receive(subscription: ObservationSubscription(token: newValue._observe(keyPaths, $0))) } } } } let objectWillChange: ObservableStoragePublisher let keyPaths: [String]? init(_ value: ObservedType, _ keyPaths: [String]? = nil) { self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value self.objectWillChange = ObservableStoragePublisher(value, keyPaths) self.keyPaths = keyPaths } init(_ value: ObservedType, _ keyPaths: [String]? = nil) where ObservedType: ObjectBase { self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value self.objectWillChange = ObservableStoragePublisher(value, keyPaths) self.keyPaths = keyPaths } init(_ value: ObservedType, _ keyPaths: [String]? = nil) where ObservedType: ProjectionObservable { self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value self.objectWillChange = ObservableStoragePublisher(value, keyPaths) self.keyPaths = keyPaths } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private class ObservableResultsStorage: ObservableStorage where T: RealmSubscribable & ThreadConfined & Equatable { private var setupHasRun = false func didSet() { if setupHasRun { updateValue() } } func updateValue() { // Implemented in subclasses fatalError() } func setupValue() { guard !setupHasRun else { return } updateValue() setupHasRun = true } var sortDescriptor: SortDescriptor? { didSet { didSet() } } var filter: NSPredicate? { didSet { didSet() } } var configuration: Realm.Configuration? { didSet { didSet() } } var searchFilter: NSPredicate? { didSet { didSet() } } private var searchString: String = "" fileprivate func searchText(_ text: String, on keyPath: KeyPath) { guard text != searchString else { return } if text.isEmpty { searchFilter = nil } else { searchFilter = Query()[dynamicMember: keyPath].contains(text).predicate } searchString = text } } // MARK: - StateRealmObject /// A property wrapper type that instantiates an observable object. /// /// Create a state realm object in a ``SwiftUI/View``, ``SwiftUI/App``, or /// ``SwiftUI/Scene`` by applying the `@StateRealmObject` attribute to a property /// declaration and providing an initial value that conforms to the /// /// protocol: /// /// @StateRealmObject var model = DataModel() /// /// SwiftUI creates a new instance of the object only once for each instance of /// the structure that declares the object. When published properties of the /// observable realm object change, SwiftUI updates the parts of any view that depend /// on those properties. If unmanaged, the property will be read from the object itself, /// otherwise, it will be read from the underlying Realm. Changes to the value will update /// the view asynchronously: /// /// Text(model.title) // Updates the view any time `title` changes. /// /// You can pass the state object into a property that has the /// ``SwiftUI/ObservedRealmObject`` attribute. /// /// Get a ``SwiftUI/Binding`` to one of the state object's properties using the /// `$` operator. Use a binding when you want to create a two-way connection to /// one of the object's properties. For example, you can let a /// ``SwiftUI/Toggle`` control a Boolean value called `isEnabled` stored in the /// model: /// /// Toggle("Enabled", isOn: $model.isEnabled) /// /// This will write the modified `isEnabled` property to the `model` object's Realm. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @MainActor @propertyWrapper public struct StateRealmObject: DynamicProperty { @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @StateObject private var storage: ObservableStorage private let defaultValue: T /// :nodoc: public var wrappedValue: T { get { let value = storage.value if value.realm == nil { // if unmanaged return the unmanaged value return value } else if value.isInvalidated { // if invalidated, return the default value return defaultValue } // else return the frozen value. the frozen value // will be consumed by SwiftUI, which requires // the ability to cache and diff objects and collections // during some timeframe. The ObjectType is frozen so that // SwiftUI can cache state. other access points will thaw // the ObjectType return value.freeze() } nonmutating set { storage.value = newValue } } /// :nodoc: public var projectedValue: Binding { Binding(get: { let value = self.storage.value if value.isInvalidated { return self.defaultValue } return value }, set: { newValue in self.storage.value = newValue }) } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The List reference to wrap and observe. */ @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public init(wrappedValue: T) where T == List { self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = T() } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The MutableSet reference to wrap and observe. */ @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public init(wrappedValue: T) where T == MutableSet { self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = T() } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The Map reference to wrap and observe. */ @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public init(wrappedValue: T) where T == Map { self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = T() } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The ObjectBase reference to wrap and observe. */ @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public init(wrappedValue: T) where T: ObjectBase & Identifiable { self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = T() } /** Initialize a RealmState struct for a given Projection type. - parameter wrappedValue The Projection reference to wrap and observe. */ @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public init(wrappedValue: T) where T: ProjectionObservable { self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = T(projecting: T.Root()) } /// :nodoc: public var _publisher: some Publisher { self.storage.objectWillChange } } // MARK: ObservedResults /** A type which can be used with @ObservedResults propperty wrapper. Children class of Realm Object or Projection. It's made to specialize the init methods of ObservedResults. */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol _ObservedResultsValue: RealmCollectionValue { } /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Object: _ObservedResultsValue { } /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Projection: _ObservedResultsValue { } /// A property wrapper type that represents the results of a query on a realm. /// /// The results use the realm configuration provided by /// the environment value `EnvironmentValues/realmConfiguration`. /// /// Unlike non-SwiftUI results collections, the ObservedResults is mutable. Writes to an ObservedResults collection implicitly /// perform a write transaction. If you add an object to the ObservedResults that the associated query would filter out, the object /// is added to the realm but not included in the ObservedResults. /// /// Given `@ObservedResults var v` in SwiftUI, `$v` refers to a `BoundCollection`. /// @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ObservedResults: DynamicProperty, BoundCollection where ResultType: _ObservedResultsValue & RealmFetchable & KeypathSortable & Identifiable { public typealias Element = ResultType private class Storage: ObservableResultsStorage> { override func updateValue() { let realm = try! Realm(configuration: configuration ?? Realm.Configuration.defaultConfiguration) var value = realm.objects(ResultType.self) if let sortDescriptor = sortDescriptor { value = value.sorted(byKeyPath: sortDescriptor.keyPath, ascending: sortDescriptor.ascending) } let filters = [searchFilter, filter].compactMap { $0 } if !filters.isEmpty { let compoundFilter = NSCompoundPredicate(andPredicateWithSubpredicates: filters) value = value.filter(compoundFilter) } self.value = value } } @Environment(\.realmConfiguration) var configuration @ObservedObject private var storage: Storage fileprivate func searchText(_ text: String, on keyPath: KeyPath) { storage.searchText(text, on: keyPath) } /// Stores an NSPredicate used for filtering the Results. This is mutually exclusive /// to the `where` parameter. @State public var filter: NSPredicate? { willSet { storage.filter = newValue } } /// Stores a type safe query used for filtering the Results. This is mutually exclusive /// to the `filter` parameter. @State public var `where`: ((Query) -> Query)? { willSet { storage.filter = newValue?(Query()).predicate } } /// :nodoc: @State public var sortDescriptor: SortDescriptor? { willSet { storage.sortDescriptor = newValue } } /// :nodoc: public var wrappedValue: Results { storage.setupValue() return storage.configuration != nil ? storage.value.freeze() : storage.value } /// :nodoc: public var projectedValue: Self { return self } /** Initialize a `ObservedResults` struct for a given `Projection` type. - parameter type: Observed type - parameter configuration: The `Realm.Configuration` used when creating the Realm, user's sync configuration for the given partition value will be set as the `syncConfiguration`, if empty the configuration is set to the `defaultConfiguration` - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by */ public init(_ type: ResultType.Type, configuration: Realm.Configuration? = nil, filter: NSPredicate? = nil, keyPaths: [String]? = nil, sortDescriptor: SortDescriptor? = nil) where ResultType: Projection, ObjectType: ThreadConfined { let results = Results(RLMResults.emptyDetached()) self.storage = Storage(results, keyPaths) self.storage.configuration = configuration self.filter = filter self.sortDescriptor = sortDescriptor } /** Initialize a `ObservedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter configuration: The `Realm.Configuration` used when creating the Realm, user's sync configuration for the given partition value will be set as the `syncConfiguration`, if empty the configuration is set to the `defaultConfiguration` - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by */ public init(_ type: ResultType.Type, configuration: Realm.Configuration? = nil, filter: NSPredicate? = nil, keyPaths: [String]? = nil, sortDescriptor: SortDescriptor? = nil) where ResultType: Object { self.storage = Storage(Results(RLMResults.emptyDetached()), keyPaths) self.storage.configuration = configuration self.filter = filter self.sortDescriptor = sortDescriptor } /** Initialize a `ObservedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter configuration: The `Realm.Configuration` used when creating the Realm, user's sync configuration for the given partition value will be set as the `syncConfiguration`, if empty the configuration is set to the `defaultConfiguration` - parameter where: Observations will be made only for passing objects. If no type safe query is given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by */ public init(_ type: ResultType.Type, configuration: Realm.Configuration? = nil, where: ((Query) -> Query)? = nil, keyPaths: [String]? = nil, sortDescriptor: SortDescriptor? = nil) where ResultType: Object { self.storage = Storage(Results(RLMResults.emptyDetached()), keyPaths) self.storage.configuration = configuration self.where = `where` self.sortDescriptor = sortDescriptor } /// :nodoc: public init(_ type: ResultType.Type, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil, sortDescriptor: SortDescriptor? = nil) where ResultType: Object { self.storage = Storage(Results(RLMResults.emptyDetached()), keyPaths) self.storage.configuration = configuration self.sortDescriptor = sortDescriptor } nonisolated public func update() { MainActor.assumeIsolated { // When the view updates, it will inject the @Environment // into the propertyWrapper if storage.configuration == nil { storage.configuration = configuration } } } } /// A property wrapper type that represents a sectioned results collection. /// /// The sectioned results use the realm configuration provided by /// the environment value `EnvironmentValues/realmConfiguration` /// if `configuration` is not set in the initializer. /// /// /// Given `@ObservedSectionedResults var v` in SwiftUI, `$v` refers to a `BoundCollection`. /// @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ObservedSectionedResults: DynamicProperty, BoundCollection where ResultType: _ObservedResultsValue & RealmFetchable & KeypathSortable & Identifiable { public typealias Element = ResultType private class Storage: ObservableResultsStorage> { var sectionedResults: SectionedResults! var token: AnyCancellable? override func updateValue() { let realm = try! Realm(configuration: configuration ?? Realm.Configuration.defaultConfiguration) var results = realm.objects(ResultType.self) let filters = [searchFilter, filter].compactMap { $0 } if !filters.isEmpty { let compoundFilter = NSCompoundPredicate(andPredicateWithSubpredicates: filters) results = results.filter(compoundFilter) } if let keyPathString = keyPathString, sortDescriptors.isEmpty { sortDescriptors.append(.init(keyPath: keyPathString, ascending: true)) } value = results /* Observing the sectioned results directly doesn't allow the SwiftUI diff to work correctly as the previous state of the sectioned results will have the new values. An example of when this is an issue is when an item is deleted in a List containing sectioned results, the diff needs a stable state of the previous transaction but due to the observation callback calling calculate_sections the collection will be brought up to date. The solution around this is to store a frozen copy of the sectioned results and observe the parent `Results` instead. Each time the results observation callback is invoked and the SwiftUI View is redrawn the sectioned results will be updated. */ sectionedResults = value.sectioned(sortDescriptors: sortDescriptors, sectionBlock).freeze() token = self.objectWillChange.sink { [weak self] _ in guard let self = self else { return } self.sectionedResults = self.value.sectioned(sortDescriptors: self.sortDescriptors, self.sectionBlock).freeze() } } var sortDescriptors: [SortDescriptor] = [] { didSet { didSet() } } var sectionBlock: ((ResultType) -> Key) var keyPathString: String? init(_ value: Results, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor], keyPathString: String? = nil, keyPaths: [String]? = nil) { self.sectionBlock = sectionBlock self.sortDescriptors = sortDescriptors if let keyPathString = keyPathString { self.keyPathString = keyPathString self.sortDescriptors.append(.init(keyPath: keyPathString, ascending: true)) } if self.sortDescriptors.isEmpty { throwRealmException("sortDescriptors must not be empty when sectioning ObservedSectionedResults with `sectionBlock`") } super.init(value, keyPaths) } } @Environment(\.realmConfiguration) var configuration @ObservedObject private var storage: Storage /// :nodoc: fileprivate func searchText(_ text: String, on keyPath: KeyPath) { storage.searchText(text, on: keyPath) } /// Stores an NSPredicate used for filtering the SectionedResults. This is mutually exclusive /// to the `where` parameter. @State public var filter: NSPredicate? { willSet { storage.filter = newValue } } /// Stores a type safe query used for filtering the SectionedResults. This is mutually exclusive /// to the `filter` parameter. @State public var `where`: ((Query) -> Query)? { willSet { storage.filter = newValue?(Query()).predicate } } /// :nodoc: @State public var sortDescriptors: [SortDescriptor] = [] { willSet { storage.sortDescriptors = newValue } } /// :nodoc: public var wrappedValue: SectionedResults { storage.setupValue() return storage.sectionedResults } /// :nodoc: public var projectedValue: Self { return self } /// Removes items from an `@ObservedSectionedResults` collection /// with a given `IndexSet` and `ResultsSection`. /// - Parameters: /// - offsets: Index offsets in the section. /// - section: The section containing the items to remove. public func remove(atOffsets offsets: IndexSet, section: ResultsSection) where ResultType: ObjectBase & ThreadConfined { write(wrappedValue) { collection in collection.realm?.delete(offsets.compactMap { section[$0].thaw() ?? nil }) } } private init(type: ResultType.Type, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor] = [], filter: NSPredicate? = nil, where: ((Query) -> Query)? = nil, keyPaths: [String]? = nil, keyPathString: String? = nil, configuration: Realm.Configuration? = nil) where ResultType: AnyObject { let results = Results(RLMResults.emptyDetached()) self.storage = Storage(results, sectionBlock: sectionBlock, sortDescriptors: sortDescriptors, keyPathString: keyPathString, keyPaths: keyPaths) self.storage.configuration = configuration if let filter = filter { self.filter = filter } else if let `where` = `where` { self.where = `where` } self.sortDescriptors = sortDescriptors } /** Initialize a `ObservedSectionedResults` struct for a given `Projection` type. - parameter type: Observed type - parameter sectionKeyPath: The keyPath that will produce the key for each section. For every unique value retrieved from the keyPath a section key will be generated. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionKeyPath: KeyPath, sortDescriptors: [SortDescriptor] = [], filter: NSPredicate? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Projection, ObjectType: ThreadConfined { self.init(type: type, sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] }, sortDescriptors: sortDescriptors, filter: filter, keyPaths: keyPaths, keyPathString: _name(for: sectionKeyPath), configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Projection` type. - parameter type: Observed type - parameter sectionBlock: A callback which returns the section key for each object in the collection. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor] = [], filter: NSPredicate? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Projection, ObjectType: ThreadConfined { self.init(type: type, sectionBlock: sectionBlock, sortDescriptors: sortDescriptors, filter: filter, keyPaths: keyPaths, configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionKeyPath: The keyPath that will produce the key for each section. For every unique value retrieved from the keyPath a section key will be generated. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionKeyPath: KeyPath, sortDescriptors: [SortDescriptor] = [], filter: NSPredicate? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] }, sortDescriptors: sortDescriptors, filter: filter, keyPaths: keyPaths, keyPathString: _name(for: sectionKeyPath), configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionBlock: A callback which returns the section key for each object in the collection. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter filter: Observations will be made only for passing objects. If no filter given - all objects will be observed - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor] = [], filter: NSPredicate? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: sectionBlock, sortDescriptors: sortDescriptors, filter: filter, keyPaths: keyPaths, configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionBlock: A callback which returns the section key for each object in the collection. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter where: Observations will be made only for passing objects. If no type safe query is given - all objects will be observed. - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor] = [], where: ((Query) -> Query)? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: sectionBlock, sortDescriptors: sortDescriptors, where: `where`, keyPaths: keyPaths, configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionKeyPath: The keyPath that will produce the key for each section. For every unique value retrieved from the keyPath a section key will be generated. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter where: Observations will be made only for passing objects. If no type safe query is given - all objects will be observed. - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionKeyPath: KeyPath, sortDescriptors: [SortDescriptor] = [], where: ((Query) -> Query)? = nil, keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] }, sortDescriptors: sortDescriptors, where: `where`, keyPaths: keyPaths, keyPathString: _name(for: sectionKeyPath), configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionKeyPath: The keyPath that will produce the key for each section. For every unique value retrieved from the keyPath a section key will be generated. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionKeyPath: KeyPath, sortDescriptors: [SortDescriptor] = [], keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] }, sortDescriptors: sortDescriptors, keyPaths: keyPaths, keyPathString: _name(for: sectionKeyPath), configuration: configuration) } /** Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type. - parameter type: Observed type - parameter sectionBlock: A callback which returns the section key for each object in the collection. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by. - parameter keyPaths: Only properties contained in the key paths array will be observed. If `nil`, notifications will be delivered for any property change on the object. String key paths which do not correspond to a valid a property will throw an exception. - parameter configuration: The `Realm.Configuration` used when creating the Realm. If empty the configuration is set to the `defaultConfiguration` - note: The primary sort descriptor must be responsible for determining the section key. */ public init(_ type: ResultType.Type, sectionBlock: @escaping ((ResultType) -> Key), sortDescriptors: [SortDescriptor], keyPaths: [String]? = nil, configuration: Realm.Configuration? = nil) where ResultType: Object { self.init(type: type, sectionBlock: sectionBlock, sortDescriptors: sortDescriptors, keyPaths: keyPaths, configuration: configuration) } nonisolated public func update() { MainActor.assumeIsolated { // When the view updates, it will inject the @Environment // into the propertyWrapper if storage.configuration == nil { storage.configuration = configuration } } } } // MARK: ObservedRealmObject /// A property wrapper type that subscribes to an observable Realm `Object` or `List` and /// invalidates a view whenever the observable object changes. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @MainActor @propertyWrapper public struct ObservedRealmObject: DynamicProperty where ObjectType: RealmSubscribable & ThreadConfined & ObservableObject & Equatable { /// A wrapper of the underlying observable object that can create bindings to /// its properties using dynamic member lookup. @MainActor @dynamicMemberLookup @frozen public struct Wrapper { /// :nodoc: public var wrappedValue: ObjectType /// Returns a binding to the resulting value of a given key path. /// /// - Parameter keyPath : A key path to a specific resulting value. /// - Returns: A new binding. public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Binding { createBinding(wrappedValue, forKeyPath: keyPath) } /// Returns a binding to the resulting equatable value of a given key path. /// /// This binding's set() will only perform a write if the new value is different from the existing value. /// /// - Parameter keyPath : A key path to a specific resulting value. /// - Returns: A new binding. public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Binding { createEquatableBinding(wrappedValue, forKeyPath: keyPath) } /// Returns a binding to the resulting collection value of a given key path. /// /// - Parameter keyPath : A key path to a specific resulting value. /// - Returns: A new binding. public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Binding { createCollectionBinding(wrappedValue, forKeyPath: keyPath) } } /// The object to observe. @ObservedObject private var storage: ObservableStorage /// A default value to avoid invalidated access. private let defaultValue: ObjectType /// :nodoc: public var wrappedValue: ObjectType { get { if storage.value.realm == nil { // if unmanaged return the unmanaged value return storage.value } else if storage.value.isInvalidated { // if invalidated, return the default value return defaultValue } // else return the frozen value. the frozen value // will be consumed by SwiftUI, which requires // the ability to cache and diff objects and collections // during some timeframe. The ObjectType is frozen so that // SwiftUI can cache state. other access points will thaw // the ObjectType return storage.value.freeze() } set { storage.value = newValue } } /// :nodoc: public var projectedValue: Wrapper { return Wrapper(wrappedValue: storage.value.isInvalidated ? defaultValue : storage.value) } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The RealmSubscribable value to wrap and observe. */ public init(wrappedValue: ObjectType) where ObjectType: ObjectBase & Identifiable { _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = ObjectType() } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The RealmSubscribable value to wrap and observe. */ public init(wrappedValue: ObjectType) where ObjectType == List { _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = List() } /** Initialize a RealmState struct for a given thread confined type. - parameter wrappedValue The RealmSubscribable value to wrap and observe. */ public init(wrappedValue: ObjectType) where ObjectType: ProjectionObservable { _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue)) defaultValue = ObjectType(projecting: ObjectType.Root()) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Binding where Value: ObjectBase & ThreadConfined { /// :nodoc: @MainActor public subscript(dynamicMember member: ReferenceWritableKeyPath) -> Binding where V: _Persistable { createBinding(wrappedValue, forKeyPath: member) } /// :nodoc: @MainActor public subscript(dynamicMember member: ReferenceWritableKeyPath) -> Binding where V: _Persistable & RLMSwiftCollectionBase & ThreadConfined { createCollectionBinding(wrappedValue, forKeyPath: member) } /// :nodoc: @MainActor public subscript(dynamicMember member: ReferenceWritableKeyPath) -> Binding where V: _Persistable & Equatable { createEquatableBinding(wrappedValue, forKeyPath: member) } } // MARK: - BoundCollection /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @preconcurrency @MainActor public protocol BoundCollection { /// :nodoc: associatedtype Value /// :nodoc: associatedtype Element: RealmCollectionValue /// :nodoc: var wrappedValue: Value { get } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension BoundCollection { private func write(_ block: (Value) -> Void) where Value: ThreadConfined { RealmSwift.write(wrappedValue, block) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value: RealmCollection { /// :nodoc: typealias Element = Value.Element /// :nodoc: typealias Index = Value.Index /// :nodoc: typealias Indices = Value.Indices } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == List { /// :nodoc: func remove(at index: Index) { write { list in list.remove(at: index) } } /// :nodoc: func remove(atOffsets offsets: IndexSet) { write { list in list.remove(atOffsets: offsets) } } /// :nodoc: func move(fromOffsets offsets: IndexSet, toOffset destination: Int) { write { list in list.move(fromOffsets: offsets, toOffset: destination) } } /// :nodoc: func append(_ value: Value.Element) { write { list in list.append(value) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == List, Element: ObjectBase & ThreadConfined { /// :nodoc: func append(_ value: Value.Element) { write { list in if value.realm == nil && list.realm != nil { SwiftUIKVO.cancel(value) } list.append(thawObjectIfFrozen(value)) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == Results, Element: ObjectBase & ThreadConfined { /// :nodoc: func remove(_ object: Value.Element) { guard let thawed = object.thaw() else { return } write { results in if results.index(of: thawed) != nil { results.realm?.delete(thawed) } } } /// :nodoc: func remove(atOffsets offsets: IndexSet) { write { results in results.realm?.delete(Array(offsets.map { results[$0] })) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == MutableSet { /// :nodoc: func remove(_ element: Value.Element) { write { mutableSet in mutableSet.remove(element) } } /// :nodoc: func insert(_ value: Value.Element) { write { mutableSet in mutableSet.insert(value) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == MutableSet, Element: ObjectBase & ThreadConfined { /// :nodoc: func remove(_ object: Value.Element) { write { mutableSet in mutableSet.remove(thawObjectIfFrozen(object)) } } /// :nodoc: func insert(_ value: Value.Element) { write { mutableSet in if value.realm == nil && mutableSet.realm != nil { SwiftUIKVO.cancel(value) } mutableSet.insert(thawObjectIfFrozen(value)) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == Results, Element: Object { /// :nodoc: func append(_ value: Value.Element) { write { results in if value.realm == nil && results.realm != nil { SwiftUIKVO.cancel(value) } results.realm?.add(thawObjectIfFrozen(value)) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundCollection where Value == Results, Element: ProjectionObservable & ThreadConfined, Element.Root: Object { /// :nodoc: func append(_ value: Value.Element) { write { results in if value.realm == nil && results.realm != nil { SwiftUIKVO.cancel(value.rootObject) } results.realm?.add(thawObjectIfFrozen(value.rootObject)) } } } @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Binding: BoundCollection where Value: RealmCollection { /// :nodoc: public typealias Element = Value.Element /// :nodoc: public typealias Index = Value.Index /// :nodoc: public typealias Indices = Value.Indices } // MARK: - BoundMap /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol BoundMap { /// :nodoc: associatedtype Value: RealmKeyedCollection /// :nodoc: var wrappedValue: Value { get } } /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundMap { // The compiler will not allow us to assign values by subscript as the binding is a get-only // property. To get around this we need an explicit `set` method. /// :nodoc: subscript( key: Value.Key) -> Value.Value? { self.wrappedValue[key] } /// :nodoc: func set(object: Value.Value?, for key: Value.Key) { write(self.wrappedValue) { map in var m = map m[key] = object } } } /// :nodoc: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension BoundMap where Value.Value: ObjectBase & ThreadConfined { /// :nodoc: func set(object: Value.Value?, for key: Value.Key) { // If the value is `nil` remove it from the map. guard let value = object else { write(self.wrappedValue) { map in map.removeObject(for: key) } return } // if the value is unmanaged but the map is managed, we are adding this value to the realm if value.realm == nil && self.wrappedValue.realm != nil { SwiftUIKVO.cancel(value) } write(self.wrappedValue) { map in var m = map m[key] = thawObjectIfFrozen(value) } } } @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Binding: BoundMap where Value: RealmKeyedCollection { } @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Binding where Value: Object { /// :nodoc: public func delete() { write(wrappedValue) { object in object.realm?.delete(thawObjectIfFrozen(self.wrappedValue)) } } } @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Binding where Value: ProjectionObservable, Value.Root: ThreadConfined { /// :nodoc: public func delete() { write(wrappedValue.rootObject) { object in object.realm?.delete(thawObjectIfFrozen(object)) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ThreadConfined where Self: ProjectionObservable { /** Create a `Binding` for a given property, allowing for automatically transacted reads and writes behind the scenes. This is a convenience method for SwiftUI views (e.g., TextField, DatePicker) that require a `Binding` to be passed in. SwiftUI will automatically read/write from the binding. - parameter keyPath The key path to the member property. - returns A `Binding` to the member property. */ @MainActor public func bind(_ keyPath: ReferenceWritableKeyPath) -> Binding { createEquatableBinding(self, forKeyPath: keyPath) } /// :nodoc: @MainActor public func bind(_ keyPath: ReferenceWritableKeyPath) -> Binding { createCollectionBinding(self, forKeyPath: keyPath) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ObservedRealmObject.Wrapper where ObjectType: ObjectBase { /// :nodoc: public func delete() { write(wrappedValue) { object in object.realm?.delete(self.wrappedValue) } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ThreadConfined where Self: ObjectBase { /** Create a `Binding` for a given property, allowing for automatically transacted reads and writes behind the scenes. This is a convenience method for SwiftUI views (e.g., TextField, DatePicker) that require a `Binding` to be passed in. SwiftUI will automatically read/write from the binding. - parameter keyPath The key path to the member property. - returns A `Binding` to the member property. */ @MainActor public func bind(_ keyPath: ReferenceWritableKeyPath) -> Binding { createEquatableBinding(self, forKeyPath: keyPath) } /// :nodoc: @MainActor public func bind(_ keyPath: ReferenceWritableKeyPath) -> Binding { createCollectionBinding(self, forKeyPath: keyPath) } } private struct RealmEnvironmentKey: EnvironmentKey { static let defaultValue = Realm.Configuration.defaultConfiguration } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension EnvironmentValues { /// The current `Realm.Configuration` that the view should use. public var realmConfiguration: Realm.Configuration { get { return self[RealmEnvironmentKey.self] } set { self[RealmEnvironmentKey.self] = newValue } } /// The current `Realm` that the view should use. public var realm: Realm { get { return try! Realm(configuration: self[RealmEnvironmentKey.self]) } set { self[RealmEnvironmentKey.self] = newValue.configuration } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension SwiftUIKVO { @objc(removeObserversFromObject:) static func removeObservers(object: NSObject) -> Bool { Self.observedObjects.withLock { if let subscription = $0[object] { subscription.removeObservers() return true } return false } } @objc(addObserversToObject:) static func addObservers(object: NSObject) { Self.observedObjects.withLock { $0[object]?.addObservers() } } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) extension View { /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection, only key paths with `String` type are allowed. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A `Text` representing the prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: Text? = nil) -> some View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: The key for the localized prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey) -> some View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A string representing the prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: S) -> some View where S: StringProtocol { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A `Text` representing the prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: Text? = nil, @ViewBuilder suggestions: () -> S) -> some View where S: View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: The key for the localized prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey, @ViewBuilder suggestions: () -> S) -> some View where S: View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminder in /// ReminderRowView(reminder: reminder) /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A string representing the prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: S, @ViewBuilder suggestions: () -> V) -> some View where V: View, S: StringProtocol { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } private func filterCollection(_ collection: ObservedResults, for text: String, on keyPath: KeyPath) { MainActor.assumeIsolated { collection.searchText(text, on: keyPath) } } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedSectionedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection, only key paths with `String` type are allowed. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A `Text` representing the prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: Text? = nil) -> some View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: The key for the localized prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey) -> some View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A string representing the prompt of the search field which provides users with guidance on what to search for. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: S) -> some View where S: StringProtocol { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A `Text` representing the prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: Text? = nil, @ViewBuilder suggestions: () -> S) -> some View where S: View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: The key for the localized prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey, @ViewBuilder suggestions: () -> S) -> some View where S: View { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } /// Marks this view as searchable, which configures the display of a search field. /// You can provide a collection and a key path to be filtered using the search /// field string provided by the searchable component, this will result in the collection /// querying for all items containing the search field string for the given key path. /// /// @State var searchString: String /// @ObservedResults(Reminder.self) var reminders /// /// List { /// ForEach(reminders) { reminderSection in /// Section(reminderSection.key) { /// ForEach(reminderSection) { object in /// ReminderRowView(reminder: object) /// } /// } /// } /// } /// .searchable(text: $searchFilter, /// collection: $reminders, /// keyPath: \.name) { /// ForEach(reminders) { remindersFiltered in /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) /// } /// } /// /** - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)`` for more information on searchable view modifier. - parameter text: The text to display and edit in the search field. - parameter collection: The collection to be filtered. - parameter keyPath: The key path to the property which will be used to filter the collection. - parameter placement: The preferred placement of the search field within the containing view hierarchy. - parameter prompt: A string representing the prompt of the search field which provides users with guidance on what to search for. - parameter suggestions: A view builder that produces content that populates a list of suggestions. */ public func searchable(text: Binding, collection: ObservedSectionedResults, keyPath: KeyPath, placement: SearchFieldPlacement = .automatic, prompt: S, @ViewBuilder suggestions: () -> V) -> some View where V: View, S: StringProtocol { filterCollection(collection, for: text.wrappedValue, on: keyPath) return searchable(text: text, placement: placement, prompt: prompt, suggestions: suggestions) } private func filterCollection(_ collection: ObservedSectionedResults, for text: String, on keyPath: KeyPath) { MainActor.assumeIsolated { collection.searchText(text, on: keyPath) } } } ================================================ FILE: RealmSwift/Tests/AnyRealmValueTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import Realm import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif class AnyRealmTypeObject: Object { let anyValue = RealmProperty() } protocol AnyValueFactory: ValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { get } static func anyValues() -> [AnyRealmValue] } extension AnyValueFactory { static func anyValues() -> [AnyRealmValue] { return values().map(anyInitializer) } } extension Int: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.int } } extension Bool: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.bool } static func anyValues() -> [AnyRealmValue] { return [.bool(false), .bool(true), .none] } } extension Float: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.float } } extension Double: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.double } } extension String: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.string } } extension Data: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.data } } extension Date: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.date } } extension ObjectId: AnyValueFactory { static var anyInitializer: (ObjectId) -> AnyRealmValue { AnyRealmValue.objectId } } extension Decimal128: AnyValueFactory { static var anyInitializer: (Decimal128) -> AnyRealmValue { AnyRealmValue.decimal128 } } extension UUID: AnyValueFactory { static var anyInitializer: (Self) -> AnyRealmValue { AnyRealmValue.uuid } } extension SwiftStringObject: AnyValueFactory { static func values() -> [SwiftStringObject] { return [SwiftStringObject(value: ["a"]), SwiftStringObject(value: ["b"]), SwiftStringObject(value: ["c"])] } static func doubleValue(_ value: SwiftStringObject) -> Double { fatalError() } static var anyInitializer: (SwiftStringObject) -> AnyRealmValue { AnyRealmValue.object } } func doubleValue(_ value: AnyRealmValue) -> Double { if case let .double(d) = value { return d } else if case let .decimal128(d) = value { return d.doubleValue } else { fatalError("Unexpected mixed value: \(value)") } } class AnyRealmValueTests: TestCase { func testAnyRealmValue() { let values = T.anyValues() let o = AnyRealmTypeObject() o.anyValue.value = values[0] XCTAssertEqual(o.anyValue.value, values[0]) o.anyValue.value = values[1] XCTAssertEqual(o.anyValue.value, values[1]) let realm = realmWithTestPath() try! realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value, values[1]) try! realm.write { o.anyValue.value = values[2] } XCTAssertEqual(o.anyValue.value, values[2]) } } class AnyRealmValuePrimitiveTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Any Realm Value Tests") AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueTests.defaultTestSuite.tests.forEach(suite.addTest) return suite } } class AnyRealmValueObjectTests: TestCase { func testObject() { let o = AnyRealmTypeObject() let so = SwiftStringObject() so.stringCol = "hello" o.anyValue.value = .object(so) XCTAssertEqual(o.anyValue.value.object(SwiftStringObject.self)!.stringCol, "hello") o.anyValue.value.object(SwiftStringObject.self)!.stringCol = "there" XCTAssertEqual(o.anyValue.value.object(SwiftStringObject.self)!.stringCol, "there") let realm = realmWithTestPath() try! realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.object(SwiftStringObject.self)!.stringCol, "there") try! realm.write { o.anyValue.value.object(SwiftStringObject.self)!.stringCol = "bye!" } XCTAssertEqual(o.anyValue.value.object(SwiftStringObject.self)!.stringCol, "bye!") } func testAssortment() { // The purpose of this test is to reuse a mixed container // and ensure no issues exist in doing that. let o = AnyRealmTypeObject() let so = SwiftStringObject() so.stringCol = "hello" let data = Data(repeating: 1, count: 64) let date = Date() let objectId = ObjectId.generate() let decimal = Decimal128(floatLiteral: 12345.6789) let tests: ((Realm?) -> Void) = { (realm: Realm?) in self.testVariation(object: o, value: .int(123), keyPath: \.intValue, expected: 123, realm: realm) self.testVariation(object: o, value: .float(123.456), keyPath: \.floatValue, expected: 123.456, realm: realm) self.testVariation(object: o, value: .string("hello there"), keyPath: \.stringValue, expected: "hello there", realm: realm) self.testVariation(object: o, value: .data(data), keyPath: \.dataValue, expected: data, realm: realm) self.testVariation(object: o, value: .date(date), keyPath: \.dateValue, expected: date, realm: realm) self.testVariation(object: o, value: .objectId(objectId), keyPath: \.objectIdValue, expected: objectId, realm: realm) self.testVariation(object: o, value: .decimal128(decimal), keyPath: \.decimal128Value, expected: decimal, realm: realm) } // unmanaged tests(nil) o.anyValue.value = .none let realm = realmWithTestPath() try! realm.write { realm.add(o) } // managed tests(realm) try! realm.write { o.anyValue.value = .object(so) } XCTAssertEqual(o.anyValue.value.object(SwiftStringObject.self)!.stringCol, "hello") } func testDynamicObjectAccessor() { // The first block knows about SwiftStringObject and will add it as a value to // SwiftObject.anyCol autoreleasepool { let realm = realmWithTestPath(configuration: .init(objectTypes: [SwiftObject.self, SwiftOwnerObject.self, SwiftBoolObject.self, SwiftDogObject.self, SwiftStringObject.self])) try! realm.write { let dog = SwiftStringObject(value: ["stringCol": "some string..."]) let parent = SwiftObject(value: ["anyCol": dog]) realm.add(parent) } XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) } // The second block omits SwiftStringObject from objectTypes so test that the // object can be retrieved dynamically. let realm = realmWithTestPath(configuration: .init(objectTypes: [SwiftObject.self, SwiftOwnerObject.self, SwiftBoolObject.self, SwiftDogObject.self])) // Ensure that SwiftStringObject does not exist in the schema XCTAssertFalse(realm.schema.objectSchema.map { $0.className }.contains("SwiftStringObject")) guard let object = realm.objects(SwiftObject.self).first else { return XCTFail("SwiftObject does not exist") } guard let dynamicObject = object.anyCol.value.dynamicObject else { return XCTFail("dynamicObject does not exist") } XCTAssertEqual(dynamicObject.stringCol as! String, "some string...") } private func testVariation(object: AnyRealmTypeObject, value: AnyRealmValue, keyPath: KeyPath, expected: T, realm: Realm?) { realm?.beginWrite() object.anyValue.value = value try! realm?.commitWrite() if let date = expected as? Date { XCTAssertEqual(object.anyValue.value.dateValue!.timeIntervalSince1970, date.timeIntervalSince1970, accuracy: 1.0) } else { XCTAssertEqual(object.anyValue.value[keyPath: keyPath], expected) } } } // MARK: - List tests class AnyRealmValueListTestsBase: TestCase { var realm: Realm? var obj: ModernAllTypesObject! var array: List! var values: [AnyRealmValue]! override func setUp() { obj = O.get() realm = obj.realm array = obj.arrayAny values = V.anyValues() } override func tearDown() { realm?.cancelWrite() array = nil obj = nil } } class AnyRealmValueListTests: AnyRealmValueListTestsBase { private func assertEqual(_ obj: AnyRealmValue, _ anotherObj: AnyRealmValue) { if case let .object(a) = obj, case let .object(b) = anotherObj { XCTAssertEqual((a as! SwiftStringObject).stringCol, (b as! SwiftStringObject).stringCol) } else { XCTAssertEqual(obj, anotherObj) } } func testInvalidated() { XCTAssertFalse(array.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(array.isInvalidated) } } func testIndexOf() { XCTAssertNil(array.index(of: values[0])) array.append(values[0]) XCTAssertEqual(0, array.index(of: values[0])) array.append(values[1]) XCTAssertEqual(0, array.index(of: values[0])) XCTAssertEqual(1, array.index(of: values[1])) } func disabled_testIndexMatching() { XCTAssertNil(array.index(matching: "self = %@", values[0])) array.append(values[0]) XCTAssertEqual(0, array.index(matching: "self = %@", values[0])) array.append(values[1]) XCTAssertEqual(0, array.index(matching: "self = %@", values[0])) XCTAssertEqual(1, array.index(matching: "self = %@", values[1])) } func testSubscript() { array.append(objectsIn: values) for i in 0..: AnyRealmValueListTestsBase { func testMin() { XCTAssertNil(array.min()) array.append(objectsIn: values.reversed()) XCTAssertEqual(array.min(), values.first) } func testMax() { XCTAssertNil(array.max()) array.append(objectsIn: values.reversed()) XCTAssertEqual(array.max(), values.last) } } class AddableAnyRealmValueListTests: AnyRealmValueListTestsBase where V: NumericValueFactory { func testSum() { if array.realm != nil { XCTAssertEqual(array.sum().intValue, nil) } else { XCTAssertEqual(array.sum().intValue, 0) } array.append(objectsIn: values) XCTAssertEqual(doubleValue(array.sum()), V.sum(), accuracy: 0.1) } func testAverage() { XCTAssertNil(array.average() as V.AverageType?) array.append(objectsIn: values) let v: AnyRealmValue = array.average()! XCTAssertEqual(doubleValue(v), V.average(), accuracy: 0.1) } } func addAnyRealmValueTests(_ suite: XCTestSuite, _ type: OF.Type) { AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueListTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedAnyRealmValueListTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged AnyRealmValue Lists") addAnyRealmValueTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedAnyRealmValueListTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed AnyRealmValue Lists") addAnyRealmValueTests(suite, ManagedObjectFactory.self) return suite } } // MARK: - Set tests class AnyRealmValueSetTestsBase: TestCase { var realm: Realm? var obj: ModernAllTypesObject! var obj2: ModernAllTypesObject! var mutableSet: MutableSet! var otherMutableSet: MutableSet! var values: [AnyRealmValue]! override func setUp() { obj = O.get() obj2 = O.get() realm = obj.realm mutableSet = obj.setAny otherMutableSet = obj2.setAny values = V.anyValues() } override func tearDown() { realm?.cancelWrite() mutableSet = nil obj = nil realm = nil } } class AnyRealmValueMutableSetTests: AnyRealmValueSetTestsBase { private func assertEqual(_ obj: AnyRealmValue, _ anotherObj: AnyRealmValue) { if case let .object(a) = obj, case let .object(b) = anotherObj { XCTAssertEqual((a as! SwiftStringObject).stringCol, (b as! SwiftStringObject).stringCol) } else { XCTAssertEqual(obj, anotherObj) } } func testInvalidated() { XCTAssertFalse(mutableSet.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(mutableSet.isInvalidated) } } func testValueForKey() { XCTAssertEqual(mutableSet.value(forKey: "self").count, 0) mutableSet.insert(values[0]) let kvc = (mutableSet.value(forKey: "self") as [AnyObject]).first! switch values[0] { case let .object(o): if let obj = kvc as? SwiftStringObject { XCTAssertEqual(obj.stringCol, (o as! SwiftStringObject).stringCol) } else { XCTFail("not an object") } case let .bool(b): XCTAssertEqual(kvc as! Bool, b) case let .data(d): XCTAssertEqual(kvc as! Data, d) case let .date(d): XCTAssertEqual(kvc as! Date, d) case let .decimal128(d): XCTAssertEqual(kvc as! Decimal128, d) case let .double(d): XCTAssertEqual(kvc as! Double, d) case let .float(f): XCTAssertEqual(kvc as! Float, f) case let .int(i): XCTAssertEqual(kvc as! Int, i) case .none: XCTAssertNil(kvc) case let .objectId(o): XCTAssertEqual(kvc as! ObjectId, o) case let .string(s): XCTAssertEqual(kvc as! String, s) case let .uuid(u): XCTAssertEqual(kvc as! UUID, u) case let .dictionary(d): XCTAssertEqual(kvc as! Map, d) case let .list(l): XCTAssertEqual(kvc as! List, l) } assertThrows(mutableSet.value(forKey: "not self"), named: "NSUnknownKeyException") } func testInsert() { XCTAssertEqual(Int(0), mutableSet.count) mutableSet.insert(values[0]) XCTAssertEqual(Int(1), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) mutableSet.insert(values[1]) XCTAssertEqual(Int(2), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) // Insert duplicate mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) } func testRemove() { mutableSet.removeAll() XCTAssertEqual(mutableSet.count, 0) mutableSet.insert(objectsIn: values) mutableSet.remove(values[0]) XCTAssertFalse(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) } func testRemoveAll() { mutableSet.removeAll() mutableSet.insert(objectsIn: values) mutableSet.removeAll() XCTAssertEqual(mutableSet.count, 0) } func testIsSubset() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] XCTAssertTrue(otherMutableSet.isSubset(of: mutableSet)) otherMutableSet.remove(values[0]) XCTAssertFalse(mutableSet.isSubset(of: otherMutableSet)) } func testContains() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) XCTAssertEqual(values.count, mutableSet.count) values.forEach { XCTAssertTrue(mutableSet.contains($0)) } } func testIntersects() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] XCTAssertTrue(otherMutableSet.intersects(mutableSet)) otherMutableSet.remove(values[0]) XCTAssertFalse(mutableSet.intersects(otherMutableSet)) } func testFormIntersection() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] mutableSet.formIntersection(otherMutableSet) XCTAssertEqual(Int(1), mutableSet.count) assertEqual(mutableSet[0], values[0]) } func testFormUnion() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(values[0]) mutableSet.insert(values[1]) otherMutableSet.insert(values[0]) otherMutableSet.insert(values[2]) mutableSet.formUnion(otherMutableSet) XCTAssertEqual(Int(3), mutableSet.count) if values[0].object(SwiftStringObject.self) != nil { XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[0].object(SwiftStringObject.self)?.stringCol)) XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[1].object(SwiftStringObject.self)?.stringCol)) XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[2].object(SwiftStringObject.self)?.stringCol)) } else { XCTAssertTrue(values.map { $0 }.contains(mutableSet[0])) XCTAssertTrue(values.map { $0 }.contains(mutableSet[1])) XCTAssertTrue(values.map { $0 }.contains(mutableSet[2])) } } func testSubtract() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(values[0]) mutableSet.insert(values[1]) otherMutableSet.insert(values[0]) otherMutableSet.insert(values[2]) mutableSet.subtract(otherMutableSet) XCTAssertEqual(Int(1), mutableSet.count) XCTAssertFalse(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) } func testSubscript() { mutableSet.insert(objectsIn: values) if values[0].object(SwiftStringObject.self) != nil { XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[0].object(SwiftStringObject.self)?.stringCol)) XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[1].object(SwiftStringObject.self)?.stringCol)) XCTAssertTrue(values.map { $0.object(SwiftStringObject.self)?.stringCol }.contains(mutableSet[2].object(SwiftStringObject.self)?.stringCol)) } else { XCTAssertTrue(values.map { $0 }.contains(mutableSet[0])) XCTAssertTrue(values.map { $0 }.contains(mutableSet[1])) XCTAssertTrue(values.map { $0 }.contains(mutableSet[2])) } } } class MinMaxAnyRealmValueMutableSetTests: AnyRealmValueSetTestsBase { func testMin() { XCTAssertNil(mutableSet.min()) mutableSet.insert(objectsIn: values) XCTAssertEqual(mutableSet.min(), values.first) } func testMax() { XCTAssertNil(mutableSet.max()) mutableSet.insert(objectsIn: values) XCTAssertEqual(mutableSet.max(), values.last) } } class AddableAnyRealmValueMutableSetTests: AnyRealmValueSetTestsBase where V: NumericValueFactory { func testSum() { if mutableSet.realm != nil { XCTAssertEqual(mutableSet.sum().intValue, nil) } else { XCTAssertEqual(mutableSet.sum().intValue, 0) } mutableSet.insert(objectsIn: values) XCTAssertEqual(doubleValue(mutableSet.sum()), V.sum(), accuracy: 0.1) } func testAverage() { XCTAssertNil(mutableSet.average() as V.AverageType?) mutableSet.insert(objectsIn: values) let v: AnyRealmValue = mutableSet.average()! XCTAssertEqual(doubleValue(v), V.average(), accuracy: 0.1) } } func addAnyRealmValueMutableSetTests(_ suite: XCTestSuite, _ type: OF.Type) { AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedAnyRealmValueMutableSetTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged Primitive Sets") addAnyRealmValueMutableSetTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedAnyRealmValueMutableSetTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed Primitive Sets") addAnyRealmValueMutableSetTests(suite, ManagedObjectFactory.self) return suite } } // MARK: - Map tests class AnyRealmValueMapTestsBase: TestCase { var realm: Realm? var obj: ModernAllTypesObject! var map: Map! var values: [(key: String, value: AnyRealmValue)]! override func setUp() { obj = O.get() realm = obj.realm map = obj.mapAny values = V.anyValues().enumerated().map { (key: "key\($0)", value: $1) } } override func tearDown() { realm?.cancelWrite() map = nil obj = nil realm = nil } } class AnyRealmValueMapTests: AnyRealmValueMapTestsBase { func testInvalidated() { XCTAssertFalse(map.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(map.isInvalidated) } } // KVC requires the key to be a string. func testValueForKey() { let key = values[0].key XCTAssertNil(map.value(forKey: key)) map[values[0].key] = values[0].value let kvc: AnyObject? = map.value(forKey: key) switch values[0].value { case let .object(o): if let obj = kvc as? SwiftStringObject { XCTAssertEqual(obj.stringCol, (o as! SwiftStringObject).stringCol) } else { XCTFail("not an object") } case let .bool(b): XCTAssertEqual(kvc as! Bool, b) case let .data(d): XCTAssertEqual(kvc as! Data, d) case let .date(d): XCTAssertEqual(kvc as! Date, d) case let .decimal128(d): XCTAssertEqual(kvc as! Decimal128, d) case let .double(d): XCTAssertEqual(kvc as! Double, d) case let .float(f): XCTAssertEqual(kvc as! Float, f) case let .int(i): XCTAssertEqual(kvc as! Int, i) case .none: XCTAssertNil(kvc) case let .objectId(o): XCTAssertEqual(kvc as! ObjectId, o) case let .string(s): XCTAssertEqual(kvc as! String, s) case let .uuid(u): XCTAssertEqual(kvc as! UUID, u) case let .dictionary(d): XCTAssertEqual(kvc as! Map, d) case let .list(l): XCTAssertEqual(kvc as! List, l) } } func assertValue(_ value: AnyRealmValue, key: String) { if case let .object(o) = map[key] { XCTAssertTrue(map.contains(where: { key, value in return key == key && (value.object(SwiftStringObject.self)!.stringCol == o["stringCol"] as! String) })) } else { XCTAssertTrue(map.contains(where: { k, v in return k == key && v == value })) } } func testInsert() { XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value XCTAssertEqual(1, map.count) XCTAssertEqual(1, map.keys.count) XCTAssertEqual(1, map.values.count) XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) assertValue(values[0].value, key: values[0].key) map[values[1].key] = values[1].value XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[1].key]).isSubset(of: map.keys)) assertValue(values[1].value, key: values[1].key) map[values[2].key] = values[2].value XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) assertValue(values[2].value, key: values[2].key) } func testRemove() { XCTAssertEqual(0, map.count) map.merge(values) { $1 } XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) let key = values[0].key map.setValue(nil, forKey: key) XCTAssertNil(map.value(forKey: key)) map.removeAll() XCTAssertEqual(0, map.count) map.merge(values) { $1 } map[values[1].key] = nil XCTAssertNil(map[values[1].key]) map.removeObject(for: values[2].key) // make sure the key was deleted XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) } func testSubscript() { // setter XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value map[values[1].key] = values[1].value map[values[2].key] = values[2].value XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) map[values[0].key] = values[0].value map[values[1].key] = nil map[values[2].key] = values[2].value XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[2].key]).isSubset(of: map.keys)) map[values[0].key] = AnyRealmValue.none XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[2].key]).isSubset(of: map.keys)) // getter map.removeAll() XCTAssertNil(map[values[0].key]) map[values[0].key] = values[0].value if case let .object(o) = map[values[0].key] { XCTAssertEqual(values[0].value.object(SwiftStringObject.self)!.stringCol, (o as! SwiftStringObject).stringCol) } else { XCTAssertEqual(values[0].value, map[values[0].key]) } } } class MinMaxAnyRealmValueMapTests: AnyRealmValueMapTestsBase { func testMin() { XCTAssertNil(map.min()) map.merge(values) { $1 } XCTAssertEqual(map.min(), values.first?.value) } func testMax() { XCTAssertNil(map.max()) map.merge(values) { $1 } XCTAssertEqual(map.max(), values.last?.value) } } class AddableAnyRealmValueMapTests: AnyRealmValueMapTestsBase where V: NumericValueFactory { func testSum() { XCTAssertEqual(map.sum().intValue, 0) map.merge(values) { $1 } XCTAssertEqual(doubleValue(map.sum()), V.sum(), accuracy: 0.1) } func testAverage() { XCTAssertNil(map.average() as V.AverageType?) map.merge(values) { $1 } let v: AnyRealmValue = map.average()! XCTAssertEqual(doubleValue(v), V.average(), accuracy: 0.1) } } func addAnyRealmValueMapTests(_ suite: XCTestSuite, _ type: OF.Type) { AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddableAnyRealmValueMapTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedAnyRealmValueMapTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged AnyRealmValue Maps") addAnyRealmValueMapTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedAnyRealmValueMapTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed AnyRealmValue Maps") addAnyRealmValueMapTests(suite, ManagedObjectFactory.self) return suite } } ================================================ FILE: RealmSwift/Tests/CodableTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2019 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 import RealmSwift @available(*, deprecated) // Silence deprecation warnings for RealmOptional final class CodableObject: Object, Codable { @objc dynamic var string: String = "" @objc dynamic var data: Data = Data() @objc dynamic var date: Date = Date() @objc dynamic var int: Int = 0 @objc dynamic var int8: Int8 = 0 @objc dynamic var int16: Int16 = 0 @objc dynamic var int32: Int32 = 0 @objc dynamic var int64: Int64 = 0 @objc dynamic var float: Float = 0 @objc dynamic var double: Double = 0 @objc dynamic var bool: Bool = false @objc dynamic var decimal: Decimal128 = 0 @objc dynamic var objectId = ObjectId() @objc dynamic var uuid = UUID() @objc dynamic var stringOpt: String? @objc dynamic var dataOpt: Data? @objc dynamic var dateOpt: Date? @objc dynamic var decimalOpt: Decimal128? @objc dynamic var objectIdOpt: ObjectId? var intOpt = RealmOptional() var int8Opt = RealmOptional() var int16Opt = RealmOptional() var int32Opt = RealmOptional() var int64Opt = RealmOptional() var floatOpt = RealmOptional() var doubleOpt = RealmOptional() var boolOpt = RealmOptional() var otherInt = RealmProperty() var otherInt8 = RealmProperty() var otherInt16 = RealmProperty() var otherInt32 = RealmProperty() var otherInt64 = RealmProperty() var otherFloat = RealmProperty() var otherDouble = RealmProperty() var otherBool = RealmProperty() var otherEnum = RealmProperty() @objc dynamic var uuidOpt: UUID? var boolList = List() var intList = List() var int8List = List() var int16List = List() var int32List = List() var int64List = List() var floatList = List() var doubleList = List() var stringList = List() var dataList = List() var dateList = List() var decimalList = List() var objectIdList = List() var uuidList = List() var boolOptList = List() var intOptList = List() var int8OptList = List() var int16OptList = List() var int32OptList = List() var int64OptList = List() var floatOptList = List() var doubleOptList = List() var stringOptList = List() var dataOptList = List() var dateOptList = List() var decimalOptList = List() var objectIdOptList = List() var uuidOptList = List() var boolSet = MutableSet() var intSet = MutableSet() var int8Set = MutableSet() var int16Set = MutableSet() var int32Set = MutableSet() var int64Set = MutableSet() var floatSet = MutableSet() var doubleSet = MutableSet() var stringSet = MutableSet() var dataSet = MutableSet() var dateSet = MutableSet() var decimalSet = MutableSet() var objectIdSet = MutableSet() var uuidSet = MutableSet() var boolOptSet = MutableSet() var intOptSet = MutableSet() var int8OptSet = MutableSet() var int16OptSet = MutableSet() var int32OptSet = MutableSet() var int64OptSet = MutableSet() var floatOptSet = MutableSet() var doubleOptSet = MutableSet() var stringOptSet = MutableSet() var dataOptSet = MutableSet() var dateOptSet = MutableSet() var decimalOptSet = MutableSet() var objectIdOptSet = MutableSet() var uuidOptSet = MutableSet() var boolMap = Map() var intMap = Map() var int8Map = Map() var int16Map = Map() var int32Map = Map() var int64Map = Map() var floatMap = Map() var doubleMap = Map() var stringMap = Map() var dataMap = Map() var dateMap = Map() var decimalMap = Map() var objectIdMap = Map() var uuidMap = Map() var boolOptMap = Map() var intOptMap = Map() var int8OptMap = Map() var int16OptMap = Map() var int32OptMap = Map() var int64OptMap = Map() var floatOptMap = Map() var doubleOptMap = Map() var stringOptMap = Map() var dataOptMap = Map() var dateOptMap = Map() var decimalOptMap = Map() var objectIdOptMap = Map() var uuidOptMap = Map() } final class ModernCodableObject: Object, Codable { @Persisted var string: String @Persisted var data: Data @Persisted var date: Date @Persisted var int: Int @Persisted var int8: Int8 @Persisted var int16: Int16 @Persisted var int32: Int32 @Persisted var int64: Int64 @Persisted var float: Float @Persisted var double: Double @Persisted var bool: Bool @Persisted var decimal: Decimal128 @Persisted var objectId: ObjectId @Persisted var uuid: UUID @Persisted var stringOpt: String? @Persisted var dataOpt: Data? @Persisted var dateOpt: Date? @Persisted var decimalOpt: Decimal128? @Persisted var objectIdOpt: ObjectId? @Persisted var intOpt: Int? @Persisted var int8Opt: Int8? @Persisted var int16Opt: Int16? @Persisted var int32Opt: Int32? @Persisted var int64Opt: Int64? @Persisted var floatOpt: Float? @Persisted var doubleOpt: Double? @Persisted var boolOpt: Bool? @Persisted var uuidOpt: UUID? @Persisted var objectOpt: CodableTopLevelObject? @Persisted var embeddedObjectOpt: CodableEmbeddedObject? @Persisted var boolList: List @Persisted var intList: List @Persisted var int8List: List @Persisted var int16List: List @Persisted var int32List: List @Persisted var int64List: List @Persisted var floatList: List @Persisted var doubleList: List @Persisted var stringList: List @Persisted var dataList: List @Persisted var dateList: List @Persisted var decimalList: List @Persisted var objectIdList: List @Persisted var uuidList: List @Persisted var objectList: List @Persisted var embeddedObjectList: List @Persisted var boolOptList: List @Persisted var intOptList: List @Persisted var int8OptList: List @Persisted var int16OptList: List @Persisted var int32OptList: List @Persisted var int64OptList: List @Persisted var floatOptList: List @Persisted var doubleOptList: List @Persisted var stringOptList: List @Persisted var dataOptList: List @Persisted var dateOptList: List @Persisted var decimalOptList: List @Persisted var objectIdOptList: List @Persisted var uuidOptList: List @Persisted var boolSet: MutableSet @Persisted var intSet: MutableSet @Persisted var int8Set: MutableSet @Persisted var int16Set: MutableSet @Persisted var int32Set: MutableSet @Persisted var int64Set: MutableSet @Persisted var floatSet: MutableSet @Persisted var doubleSet: MutableSet @Persisted var stringSet: MutableSet @Persisted var dataSet: MutableSet @Persisted var dateSet: MutableSet @Persisted var decimalSet: MutableSet @Persisted var objectIdSet: MutableSet @Persisted var uuidSet: MutableSet @Persisted var objectSet: MutableSet @Persisted var boolOptSet: MutableSet @Persisted var intOptSet: MutableSet @Persisted var int8OptSet: MutableSet @Persisted var int16OptSet: MutableSet @Persisted var int32OptSet: MutableSet @Persisted var int64OptSet: MutableSet @Persisted var floatOptSet: MutableSet @Persisted var doubleOptSet: MutableSet @Persisted var stringOptSet: MutableSet @Persisted var dataOptSet: MutableSet @Persisted var dateOptSet: MutableSet @Persisted var decimalOptSet: MutableSet @Persisted var objectIdOptSet: MutableSet @Persisted var uuidOptSet: MutableSet @Persisted var boolMap: Map @Persisted var intMap: Map @Persisted var int8Map: Map @Persisted var int16Map: Map @Persisted var int32Map: Map @Persisted var int64Map: Map @Persisted var floatMap: Map @Persisted var doubleMap: Map @Persisted var stringMap: Map @Persisted var dataMap: Map @Persisted var dateMap: Map @Persisted var decimalMap: Map @Persisted var objectIdMap: Map @Persisted var uuidMap: Map @Persisted var boolOptMap: Map @Persisted var intOptMap: Map @Persisted var int8OptMap: Map @Persisted var int16OptMap: Map @Persisted var int32OptMap: Map @Persisted var int64OptMap: Map @Persisted var floatOptMap: Map @Persisted var doubleOptMap: Map @Persisted var stringOptMap: Map @Persisted var dataOptMap: Map @Persisted var dateOptMap: Map @Persisted var decimalOptMap: Map @Persisted var objectIdOptMap: Map @Persisted var uuidOptMap: Map @Persisted var objectOptMap: Map @Persisted var embeddedObjectOptMap: Map } final class CodableTopLevelObject: Object, Codable { @Persisted var value: Int } final class CodableEmbeddedObject: EmbeddedObject, Codable { @Persisted var value: Int } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class CodableTests: TestCase { let decoder = JSONDecoder() let encoder = JSONEncoder() func decode(_ type: T.Type, _ str: String) -> RealmOptional { return decode(RealmOptional.self, str) } func decode(_ type: T.Type, _ str: String) -> T { return try! decoder.decode([T].self, from: str.data(using: .utf8)!).first! } func encode(_ value: T?) -> String { let opt = RealmOptional() opt.value = value return try! String(data: encoder.encode([opt]), encoding: .utf8)! } func encode(_ value: T?) -> String { return try! String(data: encoder.encode([value]), encoding: .utf8)! } func legacyObjectString(_ nullRealmProperty: Bool = false) -> String { """ { "bool": true, "string": "abc", "int": 123, "int8": 123, "int16": 123, "int32": 123, "int64": 123, "float": 2.5, "double": 2.5, "date": 2.5, "data": "\(Data("def".utf8).base64EncodedString())", "decimal": "1.5e2", "objectId": "1234567890abcdef12345678", "uuid": "00000000-0000-0000-0000-000000000000", "boolOpt": true, "stringOpt": "abc", "intOpt": 123, "int8Opt": 123, "int16Opt": 123, "int32Opt": 123, "int64Opt": 123, "floatOpt": 2.5, "doubleOpt": 2.5, "dateOpt": 2.5, "dataOpt": "\(Data("def".utf8).base64EncodedString())", "decimalOpt": "1.5e2", "objectIdOpt": "1234567890abcdef12345678", "uuidOpt": "00000000-0000-0000-0000-000000000000", "otherBool": \(nullRealmProperty ? "null" : "true"), "otherInt": \(nullRealmProperty ? "null" : "123"), "otherInt8": \(nullRealmProperty ? "null" : "123"), "otherInt16": \(nullRealmProperty ? "null" : "123"), "otherInt32": \(nullRealmProperty ? "null" : "123"), "otherInt64": \(nullRealmProperty ? "null" : "123"), "otherFloat": \(nullRealmProperty ? "null" : "2.5"), "otherDouble": \(nullRealmProperty ? "null" : "2.5"), "otherEnum": \(nullRealmProperty ? "null" : "1"), "otherAny": \(nullRealmProperty ? "null" : "1"), "boolList": [true], "stringList": ["abc"], "intList": [123], "int8List": [123], "int16List": [123], "int32List": [123], "int64List": [123], "floatList": [2.5], "doubleList": [2.5], "dateList": [2.5], "dataList": ["\(Data("def".utf8).base64EncodedString())"], "decimalList": ["1.5e2"], "objectIdList": ["1234567890abcdef12345678"], "uuidList": ["00000000-0000-0000-0000-000000000000"], "boolOptList": [true], "stringOptList": ["abc"], "intOptList": [123], "int8OptList": [123], "int16OptList": [123], "int32OptList": [123], "int64OptList": [123], "floatOptList": [2.5], "doubleOptList": [2.5], "dateOptList": [2.5], "dataOptList": ["\(Data("def".utf8).base64EncodedString())"], "decimalOptList": ["1.5e2"], "objectIdOptList": ["1234567890abcdef12345678"], "uuidOptList": ["00000000-0000-0000-0000-000000000000"], "boolSet": [true], "stringSet": ["abc"], "intSet": [123], "int8Set": [123], "int16Set": [123], "int32Set": [123], "int64Set": [123], "floatSet": [2.5], "doubleSet": [2.5], "dateSet": [2.5], "dataSet": ["\(Data("def".utf8).base64EncodedString())"], "decimalSet": ["1.5e2"], "objectIdSet": ["1234567890abcdef12345678"], "uuidSet": ["00000000-0000-0000-0000-000000000000"], "boolOptSet": [true], "stringOptSet": ["abc"], "intOptSet": [123], "int8OptSet": [123], "int16OptSet": [123], "int32OptSet": [123], "int64OptSet": [123], "floatOptSet": [2.5], "doubleOptSet": [2.5], "dateOptSet": [2.5], "dataOptSet": ["\(Data("def".utf8).base64EncodedString())"], "decimalOptSet": ["1.5e2"], "objectIdOptSet": ["1234567890abcdef12345678"], "uuidOptSet": ["00000000-0000-0000-0000-000000000000"], "boolMap": {"foo": true}, "stringMap": {"foo": "abc"}, "intMap": {"foo": 123}, "int8Map": {"foo": 123}, "int16Map": {"foo": 123}, "int32Map": {"foo": 123}, "int64Map": {"foo": 123}, "floatMap": {"foo": 2.5}, "doubleMap": {"foo": 2.5}, "dateMap": {"foo": 2.5}, "dataMap": {"foo": "\(Data("def".utf8).base64EncodedString())"}, "decimalMap": {"foo": "1.5e2"}, "objectIdMap": {"foo": "1234567890abcdef12345678"}, "uuidMap": {"foo": "00000000-0000-0000-0000-000000000000"}, "boolOptMap": {"foo": true}, "stringOptMap": {"foo": "abc"}, "intOptMap": {"foo": 123}, "int8OptMap": {"foo": 123}, "int16OptMap": {"foo": 123}, "int32OptMap": {"foo": 123}, "int64OptMap": {"foo": 123}, "floatOptMap": {"foo": 2.5}, "doubleOptMap": {"foo": 2.5}, "dateOptMap": {"foo": 2.5}, "dataOptMap": {"foo": "\(Data("def".utf8).base64EncodedString())"}, "decimalOptMap": {"foo": "1.5e2"}, "objectIdOptMap": {"foo": "1234567890abcdef12345678"}, "uuidOptMap": {"foo": "00000000-0000-0000-0000-000000000000"} } """ } func testBool() { XCTAssertEqual(true, decode(Bool.self, "[true]").value) XCTAssertNil(decode(Bool.self, "[null]").value) XCTAssertEqual(encode(true), "[true]") XCTAssertEqual(encode(nil as Bool?), "[null]") } func testInt() { XCTAssertEqual(1, decode(Int.self, "[1]").value) XCTAssertNil(decode(Int.self, "[null]").value) XCTAssertEqual(encode(10), "[10]") XCTAssertEqual(encode(nil as Int?), "[null]") } func testFloat() { XCTAssertEqual(2.2, decode(Float.self, "[2.2]").value) XCTAssertNil(decode(Float.self, "[null]").value) XCTAssertEqual(encode(2.25), "[2.25]") XCTAssertEqual(encode(nil as Float?), "[null]") } func testDouble() { XCTAssertEqual(2.2, decode(Double.self, "[2.2]").value) XCTAssertNil(decode(Double.self, "[null]").value) XCTAssertEqual(encode(2.25), "[2.25]") XCTAssertEqual(encode(nil as Double?), "[null]") } func testDecimal() { XCTAssertEqual("2.2", decode(Decimal128.self, "[2.2]")) XCTAssertEqual("1234567890e123", decode(Decimal128.self, "[\"1234567890e123\"]")) XCTAssertEqual(nil, decode(Decimal128?.self, "[null]")) XCTAssertEqual("[\"1.23456789E132\"]", encode("1234567890e123" as Decimal128)) } func testNullableRealmProperty() { let decoder = JSONDecoder() let obj = try! decoder.decode(CodableObject.self, from: Data(legacyObjectString(true).utf8)) XCTAssertEqual(obj.otherBool.value, nil) XCTAssertEqual(obj.otherInt.value, nil) XCTAssertEqual(obj.otherInt8.value, nil) XCTAssertEqual(obj.otherInt16.value, nil) XCTAssertEqual(obj.otherInt32.value, nil) XCTAssertEqual(obj.otherInt64.value, nil) XCTAssertEqual(obj.otherFloat.value, nil) XCTAssertEqual(obj.otherDouble.value, nil) XCTAssertEqual(obj.otherDouble.value, .none) } func testObject() throws { let decoder = JSONDecoder() let obj = try decoder.decode(CodableObject.self, from: Data(legacyObjectString().utf8)) XCTAssertEqual(obj.bool, true) XCTAssertEqual(obj.int, 123) XCTAssertEqual(obj.int8, 123) XCTAssertEqual(obj.int16, 123) XCTAssertEqual(obj.int32, 123) XCTAssertEqual(obj.int64, 123) XCTAssertEqual(obj.float, 2.5) XCTAssertEqual(obj.double, 2.5) XCTAssertEqual(obj.string, "abc") XCTAssertEqual(obj.date, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.data, Data("def".utf8)) XCTAssertEqual(obj.decimal, "1.5e2") XCTAssertEqual(obj.objectId, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOpt.value, true) XCTAssertEqual(obj.intOpt.value, 123) XCTAssertEqual(obj.int8Opt.value, 123) XCTAssertEqual(obj.int16Opt.value, 123) XCTAssertEqual(obj.int32Opt.value, 123) XCTAssertEqual(obj.int64Opt.value, 123) XCTAssertEqual(obj.floatOpt.value, 2.5) XCTAssertEqual(obj.doubleOpt.value, 2.5) XCTAssertEqual(obj.stringOpt, "abc") XCTAssertEqual(obj.dateOpt, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOpt, Data("def".utf8)) XCTAssertEqual(obj.decimalOpt, "1.5e2") XCTAssertEqual(obj.objectIdOpt, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.otherBool.value, true) XCTAssertEqual(obj.otherInt.value, 123) XCTAssertEqual(obj.otherInt8.value, 123) XCTAssertEqual(obj.otherInt16.value, 123) XCTAssertEqual(obj.otherInt32.value, 123) XCTAssertEqual(obj.otherInt64.value, 123) XCTAssertEqual(obj.otherFloat.value, 2.5) XCTAssertEqual(obj.otherDouble.value, 2.5) XCTAssertEqual(obj.otherEnum.value, .value1) XCTAssertEqual(obj.boolList.first, true) XCTAssertEqual(obj.intList.first, 123) XCTAssertEqual(obj.int8List.first, 123) XCTAssertEqual(obj.int16List.first, 123) XCTAssertEqual(obj.int32List.first, 123) XCTAssertEqual(obj.int64List.first, 123) XCTAssertEqual(obj.floatList.first, 2.5) XCTAssertEqual(obj.doubleList.first, 2.5) XCTAssertEqual(obj.stringList.first, "abc") XCTAssertEqual(obj.dateList.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataList.first, Data("def".utf8)) XCTAssertEqual(obj.decimalList.first, "1.5e2") XCTAssertEqual(obj.objectIdList.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptList.first, true) XCTAssertEqual(obj.intOptList.first, 123) XCTAssertEqual(obj.int8OptList.first, 123) XCTAssertEqual(obj.int16OptList.first, 123) XCTAssertEqual(obj.int32OptList.first, 123) XCTAssertEqual(obj.int64OptList.first, 123) XCTAssertEqual(obj.floatOptList.first, 2.5) XCTAssertEqual(obj.doubleOptList.first, 2.5) XCTAssertEqual(obj.stringOptList.first, "abc") XCTAssertEqual(obj.dateOptList.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptList.first, Data("def".utf8)) XCTAssertEqual(obj.decimalOptList.first, "1.5e2") XCTAssertEqual(obj.objectIdOptList.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolSet.first, true) XCTAssertEqual(obj.intSet.first, 123) XCTAssertEqual(obj.int8Set.first, 123) XCTAssertEqual(obj.int16Set.first, 123) XCTAssertEqual(obj.int32Set.first, 123) XCTAssertEqual(obj.int64Set.first, 123) XCTAssertEqual(obj.floatSet.first, 2.5) XCTAssertEqual(obj.doubleSet.first, 2.5) XCTAssertEqual(obj.stringSet.first, "abc") XCTAssertEqual(obj.dateSet.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataSet.first, Data("def".utf8)) XCTAssertEqual(obj.decimalSet.first, "1.5e2") XCTAssertEqual(obj.objectIdSet.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptSet.first, true) XCTAssertEqual(obj.intOptSet.first, 123) XCTAssertEqual(obj.int8OptSet.first, 123) XCTAssertEqual(obj.int16OptSet.first, 123) XCTAssertEqual(obj.int32OptSet.first, 123) XCTAssertEqual(obj.int64OptSet.first, 123) XCTAssertEqual(obj.floatOptSet.first, 2.5) XCTAssertEqual(obj.doubleOptSet.first, 2.5) XCTAssertEqual(obj.stringOptSet.first, "abc") XCTAssertEqual(obj.dateOptSet.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptSet.first, Data("def".utf8)) XCTAssertEqual(obj.decimalOptSet.first, "1.5e2") XCTAssertEqual(obj.objectIdOptSet.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolMap["foo"], true) XCTAssertEqual(obj.intMap["foo"], 123) XCTAssertEqual(obj.int8Map["foo"], 123) XCTAssertEqual(obj.int16Map["foo"], 123) XCTAssertEqual(obj.int32Map["foo"], 123) XCTAssertEqual(obj.int64Map["foo"], 123) XCTAssertEqual(obj.floatMap["foo"], 2.5) XCTAssertEqual(obj.doubleMap["foo"], 2.5) XCTAssertEqual(obj.stringMap["foo"], "abc") XCTAssertEqual(obj.dateMap["foo"], Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataMap["foo"], Data("def".utf8)) XCTAssertEqual(obj.decimalMap["foo"], "1.5e2") XCTAssertEqual(obj.objectIdMap["foo"], ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptMap["foo"], true) XCTAssertEqual(obj.intOptMap["foo"], 123) XCTAssertEqual(obj.int8OptMap["foo"], 123) XCTAssertEqual(obj.int16OptMap["foo"], 123) XCTAssertEqual(obj.int32OptMap["foo"], 123) XCTAssertEqual(obj.int64OptMap["foo"], 123) XCTAssertEqual(obj.floatOptMap["foo"], 2.5) XCTAssertEqual(obj.doubleOptMap["foo"], 2.5) XCTAssertEqual(obj.stringOptMap["foo"], "abc") XCTAssertEqual(obj.dateOptMap["foo"], Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptMap["foo"], Data("def".utf8)) XCTAssertEqual(obj.decimalOptMap["foo"], "1.5e2") XCTAssertEqual(obj.objectIdOptMap["foo"], ObjectId("1234567890abcdef12345678")) let expected = "{\"doubleOptMap\":{\"foo\":2.5},\"floatSet\":[2.5],\"int8\":123,\"otherInt32\":123,\"int16Map\":{\"foo\":123},\"stringOpt\":\"abc\",\"uuidOptSet\":[\"00000000-0000-0000-0000-000000000000\"],\"int8OptMap\":{\"foo\":123},\"dataOptSet\":[\"ZGVm\"],\"stringOptSet\":[\"abc\"],\"doubleMap\":{\"foo\":2.5},\"int16OptMap\":{\"foo\":123},\"decimalOpt\":\"1.5E2\",\"decimalOptSet\":[\"1.5E2\"],\"uuidList\":[\"00000000-0000-0000-0000-000000000000\"],\"otherFloat\":2.5,\"dateOptSet\":[2.5],\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"floatOpt\":2.5,\"int32OptSet\":[123],\"string\":\"abc\",\"dataOpt\":\"ZGVm\",\"int8Opt\":123,\"int16\":123,\"floatMap\":{\"foo\":2.5},\"decimalMap\":{\"foo\":\"1.5E2\"},\"dateOpt\":2.5,\"int64List\":[123],\"otherBool\":true,\"floatOptList\":[2.5],\"boolOptList\":[true],\"intOptSet\":[123],\"int32\":123,\"floatList\":[2.5],\"date\":2.5,\"dataSet\":[\"ZGVm\"],\"uuidOptList\":[\"00000000-0000-0000-0000-000000000000\"],\"int8Set\":[123],\"intOptList\":[123],\"int32Set\":[123],\"int32OptMap\":{\"foo\":123},\"dateSet\":[2.5],\"int32List\":[123],\"objectId\":\"1234567890abcdef12345678\",\"stringOptMap\":{\"foo\":\"abc\"},\"doubleOpt\":2.5,\"objectIdOptMap\":{\"foo\":\"1234567890abcdef12345678\"},\"boolOptSet\":[true],\"otherInt16\":123,\"intOpt\":123,\"intMap\":{\"foo\":123},\"objectIdOptSet\":[\"1234567890abcdef12345678\"],\"stringOptList\":[\"abc\"],\"int8OptList\":[123],\"int32Opt\":123,\"double\":2.5,\"stringSet\":[\"abc\"],\"otherDouble\":2.5,\"decimal\":\"1.5E2\",\"int32Map\":{\"foo\":123},\"int8OptSet\":[123],\"boolMap\":{\"foo\":true},\"int64OptList\":[123],\"dateOptList\":[2.5],\"intOptMap\":{\"foo\":123},\"bool\":true,\"int32OptList\":[123],\"intSet\":[123],\"dataOptList\":[\"ZGVm\"],\"float\":2.5,\"floatOptSet\":[2.5],\"decimalOptMap\":{\"foo\":\"1.5E2\"},\"uuidMap\":{\"foo\":\"00000000-0000-0000-0000-000000000000\"},\"int\":123,\"decimalSet\":[\"1.5E2\"],\"int16List\":[123],\"dataList\":[\"ZGVm\"],\"uuidOptMap\":{\"foo\":\"00000000-0000-0000-0000-000000000000\"},\"dataOptMap\":{\"foo\":\"ZGVm\"},\"otherEnum\":1,\"int8List\":[123],\"objectIdSet\":[\"1234567890abcdef12345678\"],\"objectIdOptList\":[\"1234567890abcdef12345678\"],\"otherInt64\":123,\"doubleOptList\":[2.5],\"floatOptMap\":{\"foo\":2.5},\"intList\":[123],\"int64Set\":[123],\"dateOptMap\":{\"foo\":2.5},\"int16OptList\":[123],\"boolList\":[true],\"doubleOptSet\":[2.5],\"doubleSet\":[2.5],\"stringMap\":{\"foo\":\"abc\"},\"int64OptSet\":[123],\"decimalOptList\":[\"1.5E2\"],\"otherInt\":123,\"dateList\":[2.5],\"objectIdList\":[\"1234567890abcdef12345678\"],\"stringList\":[\"abc\"],\"boolOpt\":true,\"objectIdMap\":{\"foo\":\"1234567890abcdef12345678\"},\"doubleList\":[2.5],\"dataMap\":{\"foo\":\"ZGVm\"},\"int16Set\":[123],\"int64\":123,\"int8Map\":{\"foo\":123},\"int64Opt\":123,\"boolSet\":[true],\"int64Map\":{\"foo\":123},\"dateMap\":{\"foo\":2.5},\"uuidOpt\":\"00000000-0000-0000-0000-000000000000\",\"int64OptMap\":{\"foo\":123},\"boolOptMap\":{\"foo\":true},\"otherInt8\":123,\"objectIdOpt\":\"1234567890abcdef12345678\",\"data\":\"ZGVm\",\"int16OptSet\":[123],\"decimalList\":[\"1.5E2\"],\"int16Opt\":123,\"uuidSet\":[\"00000000-0000-0000-0000-000000000000\"]}" let expectedData = expected.data(using: .utf8)! let expectedDictionary = try JSONSerialization.jsonObject(with: expectedData, options: []) as? [String: Any] let encodedDictionary = try JSONSerialization.jsonObject(with: encoder.encode(obj), options: []) as? [String: Any] XCTAssertEqual(expectedDictionary! as NSDictionary, encodedDictionary! as NSDictionary) } func testLegacyObjectOptionalNotRequired() { let str = """ { "bool": true, "string": "abc", "int": 123, "int8": 123, "int16": 123, "int32": 123, "int64": 123, "float": 2.5, "double": 2.5, "date": 2.5, "data": "\(Data("def".utf8).base64EncodedString())", "decimal": "1.5e2", "objectId": "1234567890abcdef12345678", "uuid": "00000000-0000-0000-0000-000000000000", "intOpt": null, "int8Opt": null, "int16Opt": null, "int32Opt": null, "int64Opt": null, "floatOpt": null, "doubleOpt": null, "boolOpt": null, "otherBool": true, "otherInt": 123, "otherInt8": 123, "otherInt16": 123, "otherInt32": 123, "otherInt64": 123, "otherFloat": 2.5, "otherDouble": 2.5, "otherEnum": 1, "otherAny": 1, "boolList": [true], "stringList": ["abc"], "intList": [123], "int8List": [123], "int16List": [123], "int32List": [123], "int64List": [123], "floatList": [2.5], "doubleList": [2.5], "dateList": [2.5], "dataList": ["\(Data("def".utf8).base64EncodedString())"], "decimalList": ["1.5e2"], "objectIdList": ["1234567890abcdef12345678"], "uuidList": ["00000000-0000-0000-0000-000000000000"], "boolOptList": [true], "stringOptList": ["abc"], "intOptList": [123], "int8OptList": [123], "int16OptList": [123], "int32OptList": [123], "int64OptList": [123], "floatOptList": [2.5], "doubleOptList": [2.5], "dateOptList": [2.5], "dataOptList": ["\(Data("def".utf8).base64EncodedString())"], "decimalOptList": ["1.5e2"], "objectIdOptList": ["1234567890abcdef12345678"], "uuidOptList": ["00000000-0000-0000-0000-000000000000"], "boolSet": [true], "stringSet": ["abc"], "intSet": [123], "int8Set": [123], "int16Set": [123], "int32Set": [123], "int64Set": [123], "floatSet": [2.5], "doubleSet": [2.5], "dateSet": [2.5], "dataSet": ["\(Data("def".utf8).base64EncodedString())"], "decimalSet": ["1.5e2"], "objectIdSet": ["1234567890abcdef12345678"], "uuidSet": ["00000000-0000-0000-0000-000000000000"], "boolOptSet": [true], "stringOptSet": ["abc"], "intOptSet": [123], "int8OptSet": [123], "int16OptSet": [123], "int32OptSet": [123], "int64OptSet": [123], "floatOptSet": [2.5], "doubleOptSet": [2.5], "dateOptSet": [2.5], "dataOptSet": ["\(Data("def".utf8).base64EncodedString())"], "decimalOptSet": ["1.5e2"], "objectIdOptSet": ["1234567890abcdef12345678"], "uuidOptSet": ["00000000-0000-0000-0000-000000000000"], "boolMap": {"foo": true}, "stringMap": {"foo": "abc"}, "intMap": {"foo": 123}, "int8Map": {"foo": 123}, "int16Map": {"foo": 123}, "int32Map": {"foo": 123}, "int64Map": {"foo": 123}, "floatMap": {"foo": 2.5}, "doubleMap": {"foo": 2.5}, "dateMap": {"foo": 2.5}, "dataMap": {"foo": "\(Data("def".utf8).base64EncodedString())"}, "decimalMap": {"foo": "1.5e2"}, "objectIdMap": {"foo": "1234567890abcdef12345678"}, "uuidMap": {"foo": "00000000-0000-0000-0000-000000000000"}, "boolOptMap": {"foo": true}, "stringOptMap": {"foo": "abc"}, "intOptMap": {"foo": 123}, "int8OptMap": {"foo": 123}, "int16OptMap": {"foo": 123}, "int32OptMap": {"foo": 123}, "int64OptMap": {"foo": 123}, "floatOptMap": {"foo": 2.5}, "doubleOptMap": {"foo": 2.5}, "dateOptMap": {"foo": 2.5}, "dataOptMap": {"foo": "\(Data("def".utf8).base64EncodedString())"}, "decimalOptMap": {"foo": "1.5e2"}, "objectIdOptMap": {"foo": "1234567890abcdef12345678"}, "uuidOptMap": {"foo": "00000000-0000-0000-0000-000000000000"} } """ let decoder = JSONDecoder() let obj = try! decoder.decode(CodableObject.self, from: Data(str.utf8)) XCTAssertNil(obj.stringOpt) XCTAssertNil(obj.dateOpt) XCTAssertNil(obj.dataOpt) XCTAssertNil(obj.decimalOpt) XCTAssertNil(obj.objectIdOpt) } func testModernObject() throws { // Note: "ZGVm" is Data("def".utf8).base64EncodedString() // This string needs to exactly match what JSONEncoder produces so that // we can validate round-tripping let str = if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, *) { """ { "bool" : true, "boolList" : [ true ], "boolMap" : { "foo" : true }, "boolOpt" : true, "boolOptList" : [ true ], "boolOptMap" : { "foo" : true }, "boolOptSet" : [ true ], "boolSet" : [ true ], "data" : "ZGVm", "dataList" : [ "ZGVm" ], "dataMap" : { "foo" : "ZGVm" }, "dataOpt" : "ZGVm", "dataOptList" : [ "ZGVm" ], "dataOptMap" : { "foo" : "ZGVm" }, "dataOptSet" : [ "ZGVm" ], "dataSet" : [ "ZGVm" ], "date" : 2.5, "dateList" : [ 2.5 ], "dateMap" : { "foo" : 2.5 }, "dateOpt" : 2.5, "dateOptList" : [ 2.5 ], "dateOptMap" : { "foo" : 2.5 }, "dateOptSet" : [ 2.5 ], "dateSet" : [ 2.5 ], "decimal" : "1.5E2", "decimalList" : [ "1.5E2" ], "decimalMap" : { "foo" : "1.5E2" }, "decimalOpt" : "1.5E2", "decimalOptList" : [ "1.5E2" ], "decimalOptMap" : { "foo" : "1.5E2" }, "decimalOptSet" : [ "1.5E2" ], "decimalSet" : [ "1.5E2" ], "double" : 2.5, "doubleList" : [ 2.5 ], "doubleMap" : { "foo" : 2.5 }, "doubleOpt" : 2.5, "doubleOptList" : [ 2.5 ], "doubleOptMap" : { "foo" : 2.5 }, "doubleOptSet" : [ 2.5 ], "doubleSet" : [ 2.5 ], "embeddedObjectList" : [ { "value" : 8 } ], "embeddedObjectOpt" : { "value" : 6 }, "embeddedObjectOptMap" : { "b" : { "value" : 10 } }, "float" : 2.5, "floatList" : [ 2.5 ], "floatMap" : { "foo" : 2.5 }, "floatOpt" : 2.5, "floatOptList" : [ 2.5 ], "floatOptMap" : { "foo" : 2.5 }, "floatOptSet" : [ 2.5 ], "floatSet" : [ 2.5 ], "int" : 123, "int16" : 123, "int16List" : [ 123 ], "int16Map" : { "foo" : 123 }, "int16Opt" : 123, "int16OptList" : [ 123 ], "int16OptMap" : { "foo" : 123 }, "int16OptSet" : [ 123 ], "int16Set" : [ 123 ], "int32" : 123, "int32List" : [ 123 ], "int32Map" : { "foo" : 123 }, "int32Opt" : 123, "int32OptList" : [ 123 ], "int32OptMap" : { "foo" : 123 }, "int32OptSet" : [ 123 ], "int32Set" : [ 123 ], "int64" : 123, "int64List" : [ 123 ], "int64Map" : { "foo" : 123 }, "int64Opt" : 123, "int64OptList" : [ 123 ], "int64OptMap" : { "foo" : 123 }, "int64OptSet" : [ 123 ], "int64Set" : [ 123 ], "int8" : 123, "int8List" : [ 123 ], "int8Map" : { "foo" : 123 }, "int8Opt" : 123, "int8OptList" : [ 123 ], "int8OptMap" : { "foo" : 123 }, "int8OptSet" : [ 123 ], "int8Set" : [ 123 ], "intList" : [ 123 ], "intMap" : { "foo" : 123 }, "intOpt" : 123, "intOptList" : [ 123 ], "intOptMap" : { "foo" : 123 }, "intOptSet" : [ 123 ], "intSet" : [ 123 ], "objectId" : "1234567890abcdef12345678", "objectIdList" : [ "1234567890abcdef12345678" ], "objectIdMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOpt" : "1234567890abcdef12345678", "objectIdOptList" : [ "1234567890abcdef12345678" ], "objectIdOptMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOptSet" : [ "1234567890abcdef12345678" ], "objectIdSet" : [ "1234567890abcdef12345678" ], "objectList" : [ { "value" : 7 } ], "objectOpt" : { "value" : 5 }, "objectOptMap" : { "a" : { "value" : 9 } }, "objectSet" : [ { "value" : 9 } ], "string" : "abc", "stringList" : [ "abc" ], "stringMap" : { "foo" : "abc" }, "stringOpt" : "abc", "stringOptList" : [ "abc" ], "stringOptMap" : { "foo" : "abc" }, "stringOptSet" : [ "abc" ], "stringSet" : [ "abc" ], "uuid" : "00000000-0000-0000-0000-000000000000", "uuidList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOpt" : "00000000-0000-0000-0000-000000000000", "uuidOptList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidOptMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOptSet" : [ "00000000-0000-0000-0000-000000000000" ], "uuidSet" : [ "00000000-0000-0000-0000-000000000000" ] } """ } else { """ { "bool" : true, "boolList" : [ true ], "boolMap" : { "foo" : true }, "boolOpt" : true, "boolOptList" : [ true ], "boolOptMap" : { "foo" : true }, "boolOptSet" : [ true ], "boolSet" : [ true ], "data" : "ZGVm", "dataList" : [ "ZGVm" ], "dataMap" : { "foo" : "ZGVm" }, "dataOpt" : "ZGVm", "dataOptList" : [ "ZGVm" ], "dataOptMap" : { "foo" : "ZGVm" }, "dataOptSet" : [ "ZGVm" ], "dataSet" : [ "ZGVm" ], "date" : 2.5, "dateList" : [ 2.5 ], "dateMap" : { "foo" : 2.5 }, "dateOpt" : 2.5, "dateOptList" : [ 2.5 ], "dateOptMap" : { "foo" : 2.5 }, "dateOptSet" : [ 2.5 ], "dateSet" : [ 2.5 ], "decimal" : "1.5E2", "decimalList" : [ "1.5E2" ], "decimalMap" : { "foo" : "1.5E2" }, "decimalOpt" : "1.5E2", "decimalOptList" : [ "1.5E2" ], "decimalOptMap" : { "foo" : "1.5E2" }, "decimalOptSet" : [ "1.5E2" ], "decimalSet" : [ "1.5E2" ], "double" : 2.5, "doubleList" : [ 2.5 ], "doubleMap" : { "foo" : 2.5 }, "doubleOpt" : 2.5, "doubleOptList" : [ 2.5 ], "doubleOptMap" : { "foo" : 2.5 }, "doubleOptSet" : [ 2.5 ], "doubleSet" : [ 2.5 ], "embeddedObjectList" : [ { "value" : 8 } ], "embeddedObjectOpt" : { "value" : 6 }, "embeddedObjectOptMap" : { "b" : { "value" : 10 } }, "float" : 2.5, "floatList" : [ 2.5 ], "floatMap" : { "foo" : 2.5 }, "floatOpt" : 2.5, "floatOptList" : [ 2.5 ], "floatOptMap" : { "foo" : 2.5 }, "floatOptSet" : [ 2.5 ], "floatSet" : [ 2.5 ], "int" : 123, "int8" : 123, "int8List" : [ 123 ], "int8Map" : { "foo" : 123 }, "int8Opt" : 123, "int8OptList" : [ 123 ], "int8OptMap" : { "foo" : 123 }, "int8OptSet" : [ 123 ], "int8Set" : [ 123 ], "int16" : 123, "int16List" : [ 123 ], "int16Map" : { "foo" : 123 }, "int16Opt" : 123, "int16OptList" : [ 123 ], "int16OptMap" : { "foo" : 123 }, "int16OptSet" : [ 123 ], "int16Set" : [ 123 ], "int32" : 123, "int32List" : [ 123 ], "int32Map" : { "foo" : 123 }, "int32Opt" : 123, "int32OptList" : [ 123 ], "int32OptMap" : { "foo" : 123 }, "int32OptSet" : [ 123 ], "int32Set" : [ 123 ], "int64" : 123, "int64List" : [ 123 ], "int64Map" : { "foo" : 123 }, "int64Opt" : 123, "int64OptList" : [ 123 ], "int64OptMap" : { "foo" : 123 }, "int64OptSet" : [ 123 ], "int64Set" : [ 123 ], "intList" : [ 123 ], "intMap" : { "foo" : 123 }, "intOpt" : 123, "intOptList" : [ 123 ], "intOptMap" : { "foo" : 123 }, "intOptSet" : [ 123 ], "intSet" : [ 123 ], "objectId" : "1234567890abcdef12345678", "objectIdList" : [ "1234567890abcdef12345678" ], "objectIdMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOpt" : "1234567890abcdef12345678", "objectIdOptList" : [ "1234567890abcdef12345678" ], "objectIdOptMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOptSet" : [ "1234567890abcdef12345678" ], "objectIdSet" : [ "1234567890abcdef12345678" ], "objectList" : [ { "value" : 7 } ], "objectOpt" : { "value" : 5 }, "objectOptMap" : { "a" : { "value" : 9 } }, "objectSet" : [ { "value" : 9 } ], "string" : "abc", "stringList" : [ "abc" ], "stringMap" : { "foo" : "abc" }, "stringOpt" : "abc", "stringOptList" : [ "abc" ], "stringOptMap" : { "foo" : "abc" }, "stringOptSet" : [ "abc" ], "stringSet" : [ "abc" ], "uuid" : "00000000-0000-0000-0000-000000000000", "uuidList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOpt" : "00000000-0000-0000-0000-000000000000", "uuidOptList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidOptMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOptSet" : [ "00000000-0000-0000-0000-000000000000" ], "uuidSet" : [ "00000000-0000-0000-0000-000000000000" ] } """ } let decoder = JSONDecoder() let obj = try decoder.decode(ModernCodableObject.self, from: Data(str.utf8)) XCTAssertEqual(obj.bool, true) XCTAssertEqual(obj.int, 123) XCTAssertEqual(obj.int8, 123) XCTAssertEqual(obj.int16, 123) XCTAssertEqual(obj.int32, 123) XCTAssertEqual(obj.int64, 123) XCTAssertEqual(obj.float, 2.5) XCTAssertEqual(obj.double, 2.5) XCTAssertEqual(obj.string, "abc") XCTAssertEqual(obj.date, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.data, Data("def".utf8)) XCTAssertEqual(obj.decimal, "1.5e2") XCTAssertEqual(obj.objectId, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOpt, true) XCTAssertEqual(obj.intOpt, 123) XCTAssertEqual(obj.int8Opt, 123) XCTAssertEqual(obj.int16Opt, 123) XCTAssertEqual(obj.int32Opt, 123) XCTAssertEqual(obj.int64Opt, 123) XCTAssertEqual(obj.floatOpt, 2.5) XCTAssertEqual(obj.doubleOpt, 2.5) XCTAssertEqual(obj.stringOpt, "abc") XCTAssertEqual(obj.dateOpt, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOpt, Data("def".utf8)) XCTAssertEqual(obj.decimalOpt, "1.5e2") XCTAssertEqual(obj.objectIdOpt, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolList.first, true) XCTAssertEqual(obj.intList.first, 123) XCTAssertEqual(obj.int8List.first, 123) XCTAssertEqual(obj.int16List.first, 123) XCTAssertEqual(obj.int32List.first, 123) XCTAssertEqual(obj.int64List.first, 123) XCTAssertEqual(obj.floatList.first, 2.5) XCTAssertEqual(obj.doubleList.first, 2.5) XCTAssertEqual(obj.stringList.first, "abc") XCTAssertEqual(obj.dateList.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataList.first, Data("def".utf8)) XCTAssertEqual(obj.decimalList.first, "1.5e2") XCTAssertEqual(obj.objectIdList.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptList.first, true) XCTAssertEqual(obj.intOptList.first, 123) XCTAssertEqual(obj.int8OptList.first, 123) XCTAssertEqual(obj.int16OptList.first, 123) XCTAssertEqual(obj.int32OptList.first, 123) XCTAssertEqual(obj.int64OptList.first, 123) XCTAssertEqual(obj.floatOptList.first, 2.5) XCTAssertEqual(obj.doubleOptList.first, 2.5) XCTAssertEqual(obj.stringOptList.first, "abc") XCTAssertEqual(obj.dateOptList.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptList.first, Data("def".utf8)) XCTAssertEqual(obj.decimalOptList.first, "1.5e2") XCTAssertEqual(obj.objectIdOptList.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolSet.first, true) XCTAssertEqual(obj.intSet.first, 123) XCTAssertEqual(obj.int8Set.first, 123) XCTAssertEqual(obj.int16Set.first, 123) XCTAssertEqual(obj.int32Set.first, 123) XCTAssertEqual(obj.int64Set.first, 123) XCTAssertEqual(obj.floatSet.first, 2.5) XCTAssertEqual(obj.doubleSet.first, 2.5) XCTAssertEqual(obj.stringSet.first, "abc") XCTAssertEqual(obj.dateSet.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataSet.first, Data("def".utf8)) XCTAssertEqual(obj.decimalSet.first, "1.5e2") XCTAssertEqual(obj.objectIdSet.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptSet.first, true) XCTAssertEqual(obj.intOptSet.first, 123) XCTAssertEqual(obj.int8OptSet.first, 123) XCTAssertEqual(obj.int16OptSet.first, 123) XCTAssertEqual(obj.int32OptSet.first, 123) XCTAssertEqual(obj.int64OptSet.first, 123) XCTAssertEqual(obj.floatOptSet.first, 2.5) XCTAssertEqual(obj.doubleOptSet.first, 2.5) XCTAssertEqual(obj.stringOptSet.first, "abc") XCTAssertEqual(obj.dateOptSet.first, Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptSet.first, Data("def".utf8)) XCTAssertEqual(obj.decimalOptSet.first, "1.5e2") XCTAssertEqual(obj.objectIdOptSet.first, ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolMap["foo"], true) XCTAssertEqual(obj.intMap["foo"], 123) XCTAssertEqual(obj.int8Map["foo"], 123) XCTAssertEqual(obj.int16Map["foo"], 123) XCTAssertEqual(obj.int32Map["foo"], 123) XCTAssertEqual(obj.int64Map["foo"], 123) XCTAssertEqual(obj.floatMap["foo"], 2.5) XCTAssertEqual(obj.doubleMap["foo"], 2.5) XCTAssertEqual(obj.stringMap["foo"], "abc") XCTAssertEqual(obj.dateMap["foo"], Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataMap["foo"], Data("def".utf8)) XCTAssertEqual(obj.decimalMap["foo"], "1.5e2") XCTAssertEqual(obj.objectIdMap["foo"], ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.boolOptMap["foo"], true) XCTAssertEqual(obj.intOptMap["foo"], 123) XCTAssertEqual(obj.int8OptMap["foo"], 123) XCTAssertEqual(obj.int16OptMap["foo"], 123) XCTAssertEqual(obj.int32OptMap["foo"], 123) XCTAssertEqual(obj.int64OptMap["foo"], 123) XCTAssertEqual(obj.floatOptMap["foo"], 2.5) XCTAssertEqual(obj.doubleOptMap["foo"], 2.5) XCTAssertEqual(obj.stringOptMap["foo"], "abc") XCTAssertEqual(obj.dateOptMap["foo"], Date(timeIntervalSinceReferenceDate: 2.5)) XCTAssertEqual(obj.dataOptMap["foo"], Data("def".utf8)) XCTAssertEqual(obj.decimalOptMap["foo"], "1.5e2") XCTAssertEqual(obj.objectIdOptMap["foo"], ObjectId("1234567890abcdef12345678")) XCTAssertEqual(obj.objectOpt?.value, 5) XCTAssertEqual(obj.embeddedObjectOpt?.value, 6) XCTAssertEqual(obj.objectList.first?.value, 7) XCTAssertEqual(obj.embeddedObjectList.first?.value, 8) XCTAssertEqual(obj.objectSet.first?.value, 9) XCTAssertEqual(obj.objectOptMap["a"]??.value, 9) XCTAssertEqual(obj.embeddedObjectOptMap["b"]??.value, 10) // Verify that it encodes to exactly the original string (which requires // that the original string be formatted how JSONEncoder formats things) encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let actual = try XCTUnwrap(String(data: encoder.encode(obj), encoding: .utf8)) XCTAssertEqual(str, actual) let realm = try! Realm() try! realm.write { realm.add(obj) } XCTAssertThrowsError(try encoder.encode(obj)) } func testModernObjectNil() throws { // Note: "ZGVm" is Data("def".utf8).base64EncodedString() // This string needs to exactly match what JSONEncoder produces so that // we can validate round-tripping let str = if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, *) { """ { "bool" : true, "boolList" : [ true ], "boolMap" : { "foo" : true }, "boolOpt" : null, "boolOptList" : [ null ], "boolOptMap" : { "foo" : null }, "boolOptSet" : [ null ], "boolSet" : [ true ], "data" : "ZGVm", "dataList" : [ "ZGVm" ], "dataMap" : { "foo" : "ZGVm" }, "dataOpt" : null, "dataOptList" : [ null ], "dataOptMap" : { "foo" : null }, "dataOptSet" : [ null ], "dataSet" : [ "ZGVm" ], "date" : 2.5, "dateList" : [ 2.5 ], "dateMap" : { "foo" : 2.5 }, "dateOpt" : null, "dateOptList" : [ null ], "dateOptMap" : { "foo" : null }, "dateOptSet" : [ null ], "dateSet" : [ 2.5 ], "decimal" : "1.5E2", "decimalList" : [ "1.5E2" ], "decimalMap" : { "foo" : "1.5E2" }, "decimalOpt" : null, "decimalOptList" : [ null ], "decimalOptMap" : { "foo" : null }, "decimalOptSet" : [ null ], "decimalSet" : [ "1.5E2" ], "double" : 2.5, "doubleList" : [ 2.5 ], "doubleMap" : { "foo" : 2.5 }, "doubleOpt" : null, "doubleOptList" : [ null ], "doubleOptMap" : { "foo" : null }, "doubleOptSet" : [ null ], "doubleSet" : [ 2.5 ], "embeddedObjectList" : [ ], "embeddedObjectOpt" : null, "embeddedObjectOptMap" : { "foo" : null }, "float" : 2.5, "floatList" : [ 2.5 ], "floatMap" : { "foo" : 2.5 }, "floatOpt" : null, "floatOptList" : [ null ], "floatOptMap" : { "foo" : null }, "floatOptSet" : [ null ], "floatSet" : [ 2.5 ], "int" : 123, "int16" : 123, "int16List" : [ 123 ], "int16Map" : { "foo" : 123 }, "int16Opt" : null, "int16OptList" : [ null ], "int16OptMap" : { "foo" : null }, "int16OptSet" : [ null ], "int16Set" : [ 123 ], "int32" : 123, "int32List" : [ 123 ], "int32Map" : { "foo" : 123 }, "int32Opt" : null, "int32OptList" : [ null ], "int32OptMap" : { "foo" : null }, "int32OptSet" : [ null ], "int32Set" : [ 123 ], "int64" : 123, "int64List" : [ 123 ], "int64Map" : { "foo" : 123 }, "int64Opt" : null, "int64OptList" : [ null ], "int64OptMap" : { "foo" : null }, "int64OptSet" : [ null ], "int64Set" : [ 123 ], "int8" : 123, "int8List" : [ 123 ], "int8Map" : { "foo" : 123 }, "int8Opt" : null, "int8OptList" : [ null ], "int8OptMap" : { "foo" : null }, "int8OptSet" : [ null ], "int8Set" : [ 123 ], "intList" : [ 123 ], "intMap" : { "foo" : 123 }, "intOpt" : null, "intOptList" : [ null ], "intOptMap" : { "foo" : null }, "intOptSet" : [ null ], "intSet" : [ 123 ], "objectId" : "1234567890abcdef12345678", "objectIdList" : [ "1234567890abcdef12345678" ], "objectIdMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOpt" : null, "objectIdOptList" : [ null ], "objectIdOptMap" : { "foo" : null }, "objectIdOptSet" : [ null ], "objectIdSet" : [ "1234567890abcdef12345678" ], "objectList" : [ ], "objectOpt" : null, "objectOptMap" : { "foo" : null }, "objectSet" : [ ], "string" : "abc", "stringList" : [ "abc" ], "stringMap" : { "foo" : "abc" }, "stringOpt" : null, "stringOptList" : [ null ], "stringOptMap" : { "foo" : null }, "stringOptSet" : [ null ], "stringSet" : [ "abc" ], "uuid" : "00000000-0000-0000-0000-000000000000", "uuidList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOpt" : null, "uuidOptList" : [ null ], "uuidOptMap" : { "foo" : null }, "uuidOptSet" : [ null ], "uuidSet" : [ "00000000-0000-0000-0000-000000000000" ] } """ } else { """ { "bool" : true, "boolList" : [ true ], "boolMap" : { "foo" : true }, "boolOpt" : null, "boolOptList" : [ null ], "boolOptMap" : { "foo" : null }, "boolOptSet" : [ null ], "boolSet" : [ true ], "data" : "ZGVm", "dataList" : [ "ZGVm" ], "dataMap" : { "foo" : "ZGVm" }, "dataOpt" : null, "dataOptList" : [ null ], "dataOptMap" : { "foo" : null }, "dataOptSet" : [ null ], "dataSet" : [ "ZGVm" ], "date" : 2.5, "dateList" : [ 2.5 ], "dateMap" : { "foo" : 2.5 }, "dateOpt" : null, "dateOptList" : [ null ], "dateOptMap" : { "foo" : null }, "dateOptSet" : [ null ], "dateSet" : [ 2.5 ], "decimal" : "1.5E2", "decimalList" : [ "1.5E2" ], "decimalMap" : { "foo" : "1.5E2" }, "decimalOpt" : null, "decimalOptList" : [ null ], "decimalOptMap" : { "foo" : null }, "decimalOptSet" : [ null ], "decimalSet" : [ "1.5E2" ], "double" : 2.5, "doubleList" : [ 2.5 ], "doubleMap" : { "foo" : 2.5 }, "doubleOpt" : null, "doubleOptList" : [ null ], "doubleOptMap" : { "foo" : null }, "doubleOptSet" : [ null ], "doubleSet" : [ 2.5 ], "embeddedObjectList" : [ ], "embeddedObjectOpt" : null, "embeddedObjectOptMap" : { "foo" : null }, "float" : 2.5, "floatList" : [ 2.5 ], "floatMap" : { "foo" : 2.5 }, "floatOpt" : null, "floatOptList" : [ null ], "floatOptMap" : { "foo" : null }, "floatOptSet" : [ null ], "floatSet" : [ 2.5 ], "int" : 123, "int8" : 123, "int8List" : [ 123 ], "int8Map" : { "foo" : 123 }, "int8Opt" : null, "int8OptList" : [ null ], "int8OptMap" : { "foo" : null }, "int8OptSet" : [ null ], "int8Set" : [ 123 ], "int16" : 123, "int16List" : [ 123 ], "int16Map" : { "foo" : 123 }, "int16Opt" : null, "int16OptList" : [ null ], "int16OptMap" : { "foo" : null }, "int16OptSet" : [ null ], "int16Set" : [ 123 ], "int32" : 123, "int32List" : [ 123 ], "int32Map" : { "foo" : 123 }, "int32Opt" : null, "int32OptList" : [ null ], "int32OptMap" : { "foo" : null }, "int32OptSet" : [ null ], "int32Set" : [ 123 ], "int64" : 123, "int64List" : [ 123 ], "int64Map" : { "foo" : 123 }, "int64Opt" : null, "int64OptList" : [ null ], "int64OptMap" : { "foo" : null }, "int64OptSet" : [ null ], "int64Set" : [ 123 ], "intList" : [ 123 ], "intMap" : { "foo" : 123 }, "intOpt" : null, "intOptList" : [ null ], "intOptMap" : { "foo" : null }, "intOptSet" : [ null ], "intSet" : [ 123 ], "objectId" : "1234567890abcdef12345678", "objectIdList" : [ "1234567890abcdef12345678" ], "objectIdMap" : { "foo" : "1234567890abcdef12345678" }, "objectIdOpt" : null, "objectIdOptList" : [ null ], "objectIdOptMap" : { "foo" : null }, "objectIdOptSet" : [ null ], "objectIdSet" : [ "1234567890abcdef12345678" ], "objectList" : [ ], "objectOpt" : null, "objectOptMap" : { "foo" : null }, "objectSet" : [ ], "string" : "abc", "stringList" : [ "abc" ], "stringMap" : { "foo" : "abc" }, "stringOpt" : null, "stringOptList" : [ null ], "stringOptMap" : { "foo" : null }, "stringOptSet" : [ null ], "stringSet" : [ "abc" ], "uuid" : "00000000-0000-0000-0000-000000000000", "uuidList" : [ "00000000-0000-0000-0000-000000000000" ], "uuidMap" : { "foo" : "00000000-0000-0000-0000-000000000000" }, "uuidOpt" : null, "uuidOptList" : [ null ], "uuidOptMap" : { "foo" : null }, "uuidOptSet" : [ null ], "uuidSet" : [ "00000000-0000-0000-0000-000000000000" ] } """ } let decoder = JSONDecoder() let obj = try decoder.decode(ModernCodableObject.self, from: Data(str.utf8)) XCTAssertNil(obj.boolOpt) XCTAssertNil(obj.intOpt) XCTAssertNil(obj.int8Opt) XCTAssertNil(obj.int16Opt) XCTAssertNil(obj.int32Opt) XCTAssertNil(obj.int64Opt) XCTAssertNil(obj.floatOpt) XCTAssertNil(obj.doubleOpt) XCTAssertNil(obj.stringOpt) XCTAssertNil(obj.dateOpt) XCTAssertNil(obj.dataOpt) XCTAssertNil(obj.decimalOpt) XCTAssertNil(obj.objectIdOpt) XCTAssertNil(obj.boolOptList.first!) XCTAssertNil(obj.intOptList.first!) XCTAssertNil(obj.int8OptList.first!) XCTAssertNil(obj.int16OptList.first!) XCTAssertNil(obj.int32OptList.first!) XCTAssertNil(obj.int64OptList.first!) XCTAssertNil(obj.floatOptList.first!) XCTAssertNil(obj.doubleOptList.first!) XCTAssertNil(obj.stringOptList.first!) XCTAssertNil(obj.dateOptList.first!) XCTAssertNil(obj.dataOptList.first!) XCTAssertNil(obj.decimalOptList.first!) XCTAssertNil(obj.objectIdOptList.first!) XCTAssertNil(obj.boolOptSet.first!) XCTAssertNil(obj.intOptSet.first!) XCTAssertNil(obj.int8OptSet.first!) XCTAssertNil(obj.int16OptSet.first!) XCTAssertNil(obj.int32OptSet.first!) XCTAssertNil(obj.int64OptSet.first!) XCTAssertNil(obj.floatOptSet.first!) XCTAssertNil(obj.doubleOptSet.first!) XCTAssertNil(obj.stringOptSet.first!) XCTAssertNil(obj.dateOptSet.first!) XCTAssertNil(obj.dataOptSet.first!) XCTAssertNil(obj.decimalOptSet.first!) XCTAssertNil(obj.objectIdOptSet.first!) XCTAssertNil(obj.boolOptMap["foo"]!) XCTAssertNil(obj.intOptMap["foo"]!) XCTAssertNil(obj.int8OptMap["foo"]!) XCTAssertNil(obj.int16OptMap["foo"]!) XCTAssertNil(obj.int32OptMap["foo"]!) XCTAssertNil(obj.int64OptMap["foo"]!) XCTAssertNil(obj.floatOptMap["foo"]!) XCTAssertNil(obj.doubleOptMap["foo"]!) XCTAssertNil(obj.stringOptMap["foo"]!) XCTAssertNil(obj.dateOptMap["foo"]!) XCTAssertNil(obj.dataOptMap["foo"]!) XCTAssertNil(obj.decimalOptMap["foo"]!) XCTAssertNil(obj.objectIdOptMap["foo"]!) XCTAssertNil(obj.objectOptMap["foo"]!) XCTAssertNil(obj.embeddedObjectOptMap["foo"]!) // Verify that it encodes to exactly the original string (which requires // that the original string be formatted how JSONEncoder formats things) encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let actual = try String(data: encoder.encode(obj), encoding: .utf8) XCTAssertEqual(str, actual) } func testModernObjectOptionalNotRequired() throws { let str = """ { "bool": true, "string": "abc", "int": 123, "int8": 123, "int16": 123, "int32": 123, "int64": 123, "float": 2.5, "double": 2.5, "date": 2.5, "data": "\(Data("def".utf8).base64EncodedString())", "decimal": "1.5e2", "objectId": "1234567890abcdef12345678", "uuid": "00000000-0000-0000-0000-000000000000", "otherBool": true, "otherInt": 123, "otherInt8": 123, "otherInt16": 123, "otherInt32": 123, "otherInt64": 123, "otherFloat": 2.5, "otherDouble": 2.5, "otherEnum": 1, "boolList": [true], "stringList": ["abc"], "intList": [123], "int8List": [123], "int16List": [123], "int32List": [123], "int64List": [123], "floatList": [2.5], "doubleList": [2.5], "dateList": [2.5], "dataList": ["\(Data("def".utf8).base64EncodedString())"], "decimalList": ["1.5e2"], "objectIdList": ["1234567890abcdef12345678"], "uuidList": ["00000000-0000-0000-0000-000000000000"], "objectList": [], "embeddedObjectList": [], "boolOptList": [null], "stringOptList": [null], "intOptList": [null], "int8OptList": [null], "int16OptList": [null], "int32OptList": [null], "int64OptList": [null], "floatOptList": [null], "doubleOptList": [null], "dateOptList": [null], "dataOptList": [null], "decimalOptList": [null], "objectIdOptList": [null], "uuidOptList": [null], "boolSet": [true], "stringSet": ["abc"], "intSet": [123], "int8Set": [123], "int16Set": [123], "int32Set": [123], "int64Set": [123], "floatSet": [2.5], "doubleSet": [2.5], "dateSet": [2.5], "dataSet": ["\(Data("def".utf8).base64EncodedString())"], "decimalSet": ["1.5e2"], "objectIdSet": ["1234567890abcdef12345678"], "uuidSet": ["00000000-0000-0000-0000-000000000000"], "objectSet": [], "boolOptSet": [null], "stringOptSet": [null], "intOptSet": [null], "int8OptSet": [null], "int16OptSet": [null], "int32OptSet": [null], "int64OptSet": [null], "floatOptSet": [null], "doubleOptSet": [null], "dateOptSet": [null], "dataOptSet": [null], "decimalOptSet": [null], "objectIdOptSet": [null], "uuidOptSet": [null], "boolMap": {"foo": true}, "stringMap": {"foo": "abc"}, "intMap": {"foo": 123}, "int8Map": {"foo": 123}, "int16Map": {"foo": 123}, "int32Map": {"foo": 123}, "int64Map": {"foo": 123}, "floatMap": {"foo": 2.5}, "doubleMap": {"foo": 2.5}, "dateMap": {"foo": 2.5}, "dataMap": {"foo": "\(Data("def".utf8).base64EncodedString())"}, "decimalMap": {"foo": "1.5e2"}, "objectIdMap": {"foo": "1234567890abcdef12345678"}, "uuidMap": {"foo": "00000000-0000-0000-0000-000000000000"}, "boolOptMap": {"foo": null}, "stringOptMap": {"foo": null}, "intOptMap": {"foo": null}, "int8OptMap": {"foo": null}, "int16OptMap": {"foo": null}, "int32OptMap": {"foo": null}, "int64OptMap": {"foo": null}, "floatOptMap": {"foo": null}, "doubleOptMap": {"foo": null}, "dateOptMap": {"foo": null}, "dataOptMap": {"foo": null}, "decimalOptMap": {"foo": null}, "objectIdOptMap": {"foo": null}, "uuidOptMap": {"foo": null}, "objectOptMap": {"foo": null}, "embeddedObjectOptMap": {"foo": null} } """ let decoder = JSONDecoder() let obj = try decoder.decode(ModernCodableObject.self, from: Data(str.utf8)) XCTAssertNil(obj.boolOpt) XCTAssertNil(obj.intOpt) XCTAssertNil(obj.int8Opt) XCTAssertNil(obj.int16Opt) XCTAssertNil(obj.int32Opt) XCTAssertNil(obj.int64Opt) XCTAssertNil(obj.floatOpt) XCTAssertNil(obj.doubleOpt) XCTAssertNil(obj.stringOpt) XCTAssertNil(obj.dateOpt) XCTAssertNil(obj.dataOpt) XCTAssertNil(obj.decimalOpt) XCTAssertNil(obj.objectIdOpt) } func testCustomDateEncoding() throws { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .custom { date, encoder in try "custom: \(date.timeIntervalSince1970)".encode(to: encoder) } let obj = ModernCodableObject() obj.date = Date(timeIntervalSince1970: 1) obj.dateOpt = Date(timeIntervalSince1970: 2) obj.dateList.append(Date(timeIntervalSince1970: 3)) obj.dateOptList.append(Date(timeIntervalSince1970: 4)) obj.dateSet.insert(Date(timeIntervalSince1970: 5)) obj.dateOptSet.insert(Date(timeIntervalSince1970: 6)) obj.dateMap["a"] = Date(timeIntervalSince1970: 7) obj.dateOptMap["b"] = Date(timeIntervalSince1970: 8) let encoded = try encoder.encode(obj) let dict = try JSONSerialization.jsonObject(with: encoded, options: []) as! [String: Any] XCTAssertEqual(dict["date"] as! String, "custom: 1.0") XCTAssertEqual(dict["dateOpt"] as! String, "custom: 2.0") XCTAssertEqual(dict["dateList"] as! [String], ["custom: 3.0"]) XCTAssertEqual(dict["dateOptList"] as! [String], ["custom: 4.0"]) XCTAssertEqual(dict["dateSet"] as! [String], ["custom: 5.0"]) XCTAssertEqual(dict["dateOptSet"] as! [String], ["custom: 6.0"]) XCTAssertEqual(dict["dateMap"] as! [String: String], ["a": "custom: 7.0"]) XCTAssertEqual(dict["dateOptMap"] as! [String: String], ["b": "custom: 8.0"]) } func testCustomDataEncoding() throws { let encoder = JSONEncoder() encoder.dataEncodingStrategy = .custom { data, encoder in try "length: \(data.count)".encode(to: encoder) } let obj = ModernCodableObject() obj.data = Data(repeating: 0, count: 1) obj.dataOpt = Data(repeating: 0, count: 2) obj.dataList.append(Data(repeating: 0, count: 3)) obj.dataOptList.append(Data(repeating: 0, count: 4)) obj.dataSet.insert(Data(repeating: 0, count: 5)) obj.dataOptSet.insert(Data(repeating: 0, count: 6)) obj.dataMap["a"] = Data(repeating: 0, count: 7) obj.dataOptMap["b"] = Data(repeating: 0, count: 8) let encoded = try encoder.encode(obj) let dict = try JSONSerialization.jsonObject(with: encoded, options: []) as! [String: Any] XCTAssertEqual(dict["data"] as! String, "length: 1") XCTAssertEqual(dict["dataOpt"] as! String, "length: 2") XCTAssertEqual(dict["dataList"] as! [String], ["length: 3"]) XCTAssertEqual(dict["dataOptList"] as! [String], ["length: 4"]) XCTAssertEqual(dict["dataSet"] as! [String], ["length: 5"]) XCTAssertEqual(dict["dataOptSet"] as! [String], ["length: 6"]) XCTAssertEqual(dict["dataMap"] as! [String: String], ["a": "length: 7"]) XCTAssertEqual(dict["dataOptMap"] as! [String: String], ["b": "length: 8"]) } func testKeyEncodingStrategy() throws { encoder.keyEncodingStrategy = .convertToSnakeCase encoder.outputFormatting = [.sortedKeys] let obj = ModernCodableObject() obj.objectId = ObjectId("1234567890abcdef12345678") obj.uuid = UUID(uuidString: "00000000-0000-0000-0000-000000000000")! obj.date = Date(timeIntervalSince1970: 0) let actual = try XCTUnwrap(String(data: encoder.encode(obj), encoding: .utf8)) // Before the 2024 OS versions, int8 was sorted before int16 let expected = if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, *) { #"{"bool":false,"bool_list":[],"bool_map":{},"bool_opt":null,"bool_opt_list":[],"bool_opt_map":{},"bool_opt_set":[],"bool_set":[],"data":"","data_list":[],"data_map":{},"data_opt":null,"data_opt_list":[],"data_opt_map":{},"data_opt_set":[],"data_set":[],"date":-978307200,"date_list":[],"date_map":{},"date_opt":null,"date_opt_list":[],"date_opt_map":{},"date_opt_set":[],"date_set":[],"decimal":"0","decimal_list":[],"decimal_map":{},"decimal_opt":null,"decimal_opt_list":[],"decimal_opt_map":{},"decimal_opt_set":[],"decimal_set":[],"double":0,"double_list":[],"double_map":{},"double_opt":null,"double_opt_list":[],"double_opt_map":{},"double_opt_set":[],"double_set":[],"embedded_object_list":[],"embedded_object_opt":null,"embedded_object_opt_map":{},"float":0,"float_list":[],"float_map":{},"float_opt":null,"float_opt_list":[],"float_opt_map":{},"float_opt_set":[],"float_set":[],"int":0,"int16":0,"int16_list":[],"int16_map":{},"int16_opt":null,"int16_opt_list":[],"int16_opt_map":{},"int16_opt_set":[],"int16_set":[],"int32":0,"int32_list":[],"int32_map":{},"int32_opt":null,"int32_opt_list":[],"int32_opt_map":{},"int32_opt_set":[],"int32_set":[],"int64":0,"int64_list":[],"int64_map":{},"int64_opt":null,"int64_opt_list":[],"int64_opt_map":{},"int64_opt_set":[],"int64_set":[],"int8":0,"int8_list":[],"int8_map":{},"int8_opt":null,"int8_opt_list":[],"int8_opt_map":{},"int8_opt_set":[],"int8_set":[],"int_list":[],"int_map":{},"int_opt":null,"int_opt_list":[],"int_opt_map":{},"int_opt_set":[],"int_set":[],"object_id":"1234567890abcdef12345678","object_id_list":[],"object_id_map":{},"object_id_opt":null,"object_id_opt_list":[],"object_id_opt_map":{},"object_id_opt_set":[],"object_id_set":[],"object_list":[],"object_opt":null,"object_opt_map":{},"object_set":[],"string":"","string_list":[],"string_map":{},"string_opt":null,"string_opt_list":[],"string_opt_map":{},"string_opt_set":[],"string_set":[],"uuid":"00000000-0000-0000-0000-000000000000","uuid_list":[],"uuid_map":{},"uuid_opt":null,"uuid_opt_list":[],"uuid_opt_map":{},"uuid_opt_set":[],"uuid_set":[]}"# } else { #"{"bool":false,"bool_list":[],"bool_map":{},"bool_opt":null,"bool_opt_list":[],"bool_opt_map":{},"bool_opt_set":[],"bool_set":[],"data":"","data_list":[],"data_map":{},"data_opt":null,"data_opt_list":[],"data_opt_map":{},"data_opt_set":[],"data_set":[],"date":-978307200,"date_list":[],"date_map":{},"date_opt":null,"date_opt_list":[],"date_opt_map":{},"date_opt_set":[],"date_set":[],"decimal":"0","decimal_list":[],"decimal_map":{},"decimal_opt":null,"decimal_opt_list":[],"decimal_opt_map":{},"decimal_opt_set":[],"decimal_set":[],"double":0,"double_list":[],"double_map":{},"double_opt":null,"double_opt_list":[],"double_opt_map":{},"double_opt_set":[],"double_set":[],"embedded_object_list":[],"embedded_object_opt":null,"embedded_object_opt_map":{},"float":0,"float_list":[],"float_map":{},"float_opt":null,"float_opt_list":[],"float_opt_map":{},"float_opt_set":[],"float_set":[],"int":0,"int_list":[],"int_map":{},"int_opt":null,"int_opt_list":[],"int_opt_map":{},"int_opt_set":[],"int_set":[],"int8":0,"int8_list":[],"int8_map":{},"int8_opt":null,"int8_opt_list":[],"int8_opt_map":{},"int8_opt_set":[],"int8_set":[],"int16":0,"int16_list":[],"int16_map":{},"int16_opt":null,"int16_opt_list":[],"int16_opt_map":{},"int16_opt_set":[],"int16_set":[],"int32":0,"int32_list":[],"int32_map":{},"int32_opt":null,"int32_opt_list":[],"int32_opt_map":{},"int32_opt_set":[],"int32_set":[],"int64":0,"int64_list":[],"int64_map":{},"int64_opt":null,"int64_opt_list":[],"int64_opt_map":{},"int64_opt_set":[],"int64_set":[],"object_id":"1234567890abcdef12345678","object_id_list":[],"object_id_map":{},"object_id_opt":null,"object_id_opt_list":[],"object_id_opt_map":{},"object_id_opt_set":[],"object_id_set":[],"object_list":[],"object_opt":null,"object_opt_map":{},"object_set":[],"string":"","string_list":[],"string_map":{},"string_opt":null,"string_opt_list":[],"string_opt_map":{},"string_opt_set":[],"string_set":[],"uuid":"00000000-0000-0000-0000-000000000000","uuid_list":[],"uuid_map":{},"uuid_opt":null,"uuid_opt_list":[],"uuid_opt_map":{},"uuid_opt_set":[],"uuid_set":[]}"# } XCTAssertEqual(expected, actual) } } ================================================ FILE: RealmSwift/Tests/CombineTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import Combine import Realm.Private import RealmSwift class CombineIdentifiableObject: Object, ObjectKeyIdentifiable { @objc dynamic var value = 0 @objc dynamic var child: CombineIdentifiableEmbeddedObject? } class CombineIdentifiableEmbeddedObject: EmbeddedObject, ObjectKeyIdentifiable { @objc dynamic var value = 0 } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension Publisher { public func signal(_ semaphore: DispatchSemaphore) -> Publishers.HandleEvents { self.handleEvents(receiveOutput: { _ in semaphore.signal() }) } } // XCTest doesn't care about the @available on the class and will try to run // the tests even on older versions. Putting this check inside `defaultTestSuite` // results in a warning about it being redundant due to the enclosing check, so // it needs to be out of line. func hasCombine() -> Bool { if #available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) { return true } return false } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) class ObjectIdentifiableTests: TestCase { override class var defaultTestSuite: XCTestSuite { if hasCombine() { return super.defaultTestSuite } return XCTestSuite(name: "\(type(of: self))") } func testUnmanaged() { let obj1 = CombineIdentifiableObject(value: [1]) let obj2 = CombineIdentifiableObject(value: [1]) let obj3 = CombineIdentifiableObject(value: [2]) XCTAssertEqual(obj1.id, obj1.id) XCTAssertNotEqual(obj1.id, obj2.id) XCTAssertNotEqual(obj2.id, obj3.id) XCTAssertNotEqual(obj1.id, obj3.id) } func testManagedTopLevel() { let realm = try! Realm() let (obj1, obj2) = try! realm.write { return ( realm.create(CombineIdentifiableObject.self, value: [1]), realm.create(CombineIdentifiableObject.self, value: [2]) ) } XCTAssertEqual(obj1.id, obj1.id) XCTAssertNotEqual(obj1.id, obj2.id) XCTAssertEqual(obj1.id, realm.objects(CombineIdentifiableObject.self).first!.id) XCTAssertEqual(obj2.id, realm.objects(CombineIdentifiableObject.self).last!.id) } func testManagedEmbedded() { let realm = try! Realm() let (obj1, obj2) = try! realm.write { return ( realm.create(CombineIdentifiableObject.self, value: [1, [1]] as [Any]), realm.create(CombineIdentifiableObject.self, value: [2, [2]] as [Any]) ) } XCTAssertEqual(obj1.child!.id, obj1.child!.id) XCTAssertNotEqual(obj1.child!.id, obj2.child!.id) } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class CombinePublisherTestCase: TestCase { var realm: Realm! var cancellable: AnyCancellable? var notificationToken: NotificationToken? let subscribeOnQueue = DispatchQueue(label: "subscribe on", qos: .userInteractive, autoreleaseFrequency: .workItem) let receiveOnQueue = DispatchQueue(label: "receive on", qos: .userInteractive, autoreleaseFrequency: .workItem) override class var defaultTestSuite: XCTestSuite { if hasCombine() { return super.defaultTestSuite } return XCTestSuite(name: "\(type(of: self))") } override func setUp() { super.setUp() realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "CombinePublisherTestCase")) XCTAssertTrue(realm.isEmpty) } override func tearDown() { if let cancellable = cancellable { cancellable.cancel() } if let notificationToken = notificationToken { notificationToken.invalidate() } realm.invalidate() realm = nil subscribeOnQueue.sync { } receiveOnQueue.sync { } super.tearDown() } func watchForNotifierAdded() -> XCTestExpectation { // .subscribe(on:) is asynchronous, so we need to wait for the notifier // to be ready before we do the thing which should produce notifications let ex = expectation(description: "added notifier") subscribeOnQueue.sync { let r = try! Realm(configuration: realm.configuration, queue: subscribeOnQueue) RLMAddBeforeNotifyBlock(ObjectiveCSupport.convert(object: r)) { _ = r // retain the Realm until the block is released ex.fulfill() } } return ex } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class CombineRealmTests: CombinePublisherTestCase { func testWillChangeLocalWrite() { var called = false cancellable = realm .objectWillChange .sink { called = true } try! realm.write { realm.create(SwiftIntObject.self) } XCTAssertTrue(called) } func testWillChangeLocalWriteWithToken() { var called = false cancellable = realm .objectWillChange .saveToken(on: self, for: \.notificationToken) .sink { called = true } try! realm.write { realm.create(SwiftIntObject.self) } XCTAssertNotNil(notificationToken) XCTAssertTrue(called) } func testWillChangeLocalWriteWithoutNotifying() { var called = false cancellable = realm .objectWillChange .saveToken(on: self, for: \.notificationToken) .sink { called = true } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write(withoutNotifying: [notificationToken!]) { realm.create(SwiftIntObject.self) } XCTAssertFalse(called) } } func testWillChangeRemoteWrite() { let exp = XCTestExpectation() cancellable = realm.objectWillChange.sink { exp.fulfill() } let configuration = self.realm.configuration subscribeOnQueue.async { let backgroundRealm = try! Realm(configuration: configuration) try! backgroundRealm.write { backgroundRealm.create(SwiftIntObject.self) } } wait(for: [exp], timeout: 1) } } // MARK: - Object @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class CombineObjectPublisherTests: CombinePublisherTestCase { var obj: SwiftIntObject! override func setUp() { super.setUp() obj = try! realm.write { realm.create(SwiftIntObject.self) } } func testWillChange() { let exp = XCTestExpectation() cancellable = obj.objectWillChange.sink { exp.fulfill() } try! realm.write { obj.intCol = 1 } wait(for: [exp], timeout: 1) } func testWillChangeWithToken() { let exp = XCTestExpectation() cancellable = obj .objectWillChange .saveToken(on: self, at: \.notificationToken) .sink { exp.fulfill() } XCTAssertNotNil(notificationToken) try! realm.write { obj.intCol = 1 } } func testChange() { let exp = XCTestExpectation() cancellable = valuePublisher(obj).assertNoFailure().sink { o in XCTAssertEqual(self.obj, o) exp.fulfill() } try! realm.write { obj.intCol = 1 } wait(for: [exp], timeout: 1) } func testChangeSet() { let exp = XCTestExpectation() cancellable = changesetPublisher(obj).assertNoFailure().sink { change in if case .change(let o, let properties) = change { XCTAssertEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, 1) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() } try! realm.write { obj.intCol = 1 } wait(for: [exp], timeout: 1) } func testDelete() { let exp = XCTestExpectation() cancellable = valuePublisher(obj).sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { _ in }) try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testSubscribeOn() { let ex = watchForNotifierAdded() let sema = DispatchSemaphore(value: 0) var i = 1 cancellable = valuePublisher(obj) .subscribe(on: subscribeOnQueue) .map { obj -> SwiftIntObject in sema.signal() XCTAssertEqual(obj.intCol, i) i += 1 return obj } .collect() .assertNoFailure() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) sema.signal() } wait(for: [ex], timeout: 2.0) for _ in 0..<10 { try! realm.write { obj.intCol += 1 } // wait between each write so that the notifications can't get coalesced // also would deadlock if the subscription was on the main thread sema.wait() } try! realm.write { realm.delete(obj) } sema.wait() } func testReceiveOn() { var exp = XCTestExpectation() cancellable = valuePublisher(obj) .receive(on: receiveOnQueue) .map { obj -> Int in exp.fulfill() return obj.intCol } .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for i in 1..<10 { XCTAssertTrue(arr.contains(i)) } exp.fulfill() } for _ in 0..<10 { try! realm.write { obj.intCol += 1 } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 10) } func testChangeSetSubscribeOn() { let ex = watchForNotifierAdded() let sema = DispatchSemaphore(value: 0) var prev: SwiftIntObject? cancellable = changesetPublisher(obj) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let o, let properties) = change { XCTAssertNotEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.intCol) } XCTAssertEqual(properties[0].newValue as? Int, o.intCol) prev = o.freeze() XCTAssertEqual(prev!.intCol, o.intCol) if o.intCol == 100 { sema.signal() } } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } sema.wait() try! realm.write { realm.delete(obj) } sema.wait() XCTAssertNotNil(prev) XCTAssertEqual(prev!.intCol, 100) } func testChangeSetSubscribeOnKeyPath() { let obj = try! realm.write { realm.create(SwiftObject.self, value: ["intCol": 0, "boolCol": false]) } let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() var prev: SwiftObject? cancellable = changesetPublisher(obj, keyPaths: ["intCol"]) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let o, let properties) = change { XCTAssertNotEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.intCol) } XCTAssertEqual(properties[0].newValue as? Int, o.intCol) prev = o.freeze() XCTAssertEqual(prev!.intCol, o.intCol) if o.intCol >= 100 { sema.signal() } } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } sema.wait() // The following two lines check if a write outside of // the intended keyPath does *not* publish a // change. // If a changeset is published for boolCol, the test would fail // above when checking for property name "intCol". try! realm.write { obj.boolCol = true } try! realm.write { obj.intCol += 1 } sema.wait() try! realm.write { realm.delete(obj) } sema.wait() XCTAssertNotNil(prev) XCTAssertEqual(prev!.intCol, 101) } func testChangeSetReceiveOn() { var exp = XCTestExpectation(description: "change") cancellable = changesetPublisher(obj) .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { change in if case .change(let o, let properties) = change { XCTAssertNotEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") // oldValue is always nil because we subscribed on the thread doing the writing XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, o.intCol) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() }) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { obj.intCol += 1 } wait(for: [exp], timeout: 1) } exp = XCTestExpectation(description: "completion") try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testChangeSetSubscribeOnAndReceiveOn() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() var prev: SwiftIntObject? cancellable = changesetPublisher(obj) .subscribe(on: subscribeOnQueue) .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let o, let properties) = change { XCTAssertNotEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.intCol) } XCTAssertEqual(properties[0].newValue as? Int, o.intCol) prev = o.freeze() XCTAssertEqual(prev!.intCol, o.intCol) if o.intCol == 100 { sema.signal() } o.realm?.invalidate() } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } sema.wait() try! realm.write { realm.delete(obj) } sema.wait() XCTAssertNotNil(prev) XCTAssertEqual(prev!.intCol, 100) } func testChangeSetMakeThreadSafe() { var exp = XCTestExpectation(description: "change") cancellable = changesetPublisher(obj) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { change in if case .change(let o, let properties) = change { XCTAssertNotEqual(self.obj, o) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, o.intCol) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() }) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { obj.intCol += 1 } wait(for: [exp], timeout: 1) } exp = XCTestExpectation(description: "completion") try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testFrozen() { let exp = XCTestExpectation() cancellable = valuePublisher(obj) .freeze() .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for i in 0..<10 { XCTAssertEqual(arr[i].intCol, i + 1) } exp.fulfill() } for _ in 0..<10 { try! realm.write { obj.intCol += 1 } } try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testFrozenChangeSetSubscribeOn() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() cancellable = changesetPublisher(obj) .subscribe(on: subscribeOnQueue) .freeze() .collect() .assertNoFailure() .sink { @Sendable arr in var prev: SwiftIntObject? for change in arr { guard case .change(let obj, let properties) = change else { XCTFail("Expected .change but got \(change)") sema.signal() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") XCTAssertEqual(properties[0].newValue as? Int, obj.intCol) if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.intCol) } prev = obj } sema.signal() } wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } try! realm.write { realm.delete(obj) } sema.wait() } func testFrozenChangeSetReceiveOn() { let exp = XCTestExpectation(description: "sink complete") cancellable = changesetPublisher(obj) .freeze() .receive(on: receiveOnQueue) .collect() .assertNoFailure() .sink { @Sendable arr in for change in arr { guard case .change(let obj, let properties) = change else { XCTFail("Expected .change but got \(change)") exp.fulfill() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") XCTAssertEqual(properties[0].newValue as? Int, obj.intCol) // subscribing on the thread making writes means that oldValue // is always nil XCTAssertNil(properties[0].oldValue) } exp.fulfill() } for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testFrozenChangeSetSubscribeOnAndReceiveOn() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() cancellable = changesetPublisher(obj) .subscribe(on: subscribeOnQueue) .freeze() .receive(on: receiveOnQueue) .collect() .assertNoFailure() .sink { @Sendable arr in var prev: SwiftIntObject? for change in arr { guard case .change(let obj, let properties) = change else { XCTFail("Expected .change but got \(change)") sema.signal() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "intCol") XCTAssertEqual(properties[0].newValue as? Int, obj.intCol) if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.intCol) } prev = obj } sema.signal() } wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { obj.intCol += 1 } } try! realm.write { realm.delete(obj) } sema.wait() } func testReceiveOnAfterMap() { var exp = XCTestExpectation() cancellable = valuePublisher(obj) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .map { obj -> Int in exp.fulfill() return obj.intCol } .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for i in 1..<10 { XCTAssertTrue(arr.contains(i)) } exp.fulfill() } for _ in 0..<10 { try! realm.write { obj.intCol += 1 } wait(for: [exp], timeout: 1) exp = XCTestExpectation() } try! realm.write { realm.delete(obj) } wait(for: [exp], timeout: 1) } func testUnmanagedMakeThreadSafe() { let objects = [SwiftIntObject(value: [1]), SwiftIntObject(value: [2]), SwiftIntObject(value: [3])] let exp = XCTestExpectation() cancellable = objects.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.intCol } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3]) exp.fulfill() } wait(for: [exp], timeout: 1) } func testManagedMakeThreadSafe() { let objects = try! realm.write { return [ realm.create(SwiftIntObject.self, value: [1]), realm.create(SwiftIntObject.self, value: [2]), realm.create(SwiftIntObject.self, value: [3]) ] } let exp = XCTestExpectation() cancellable = objects.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.intCol } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3]) exp.fulfill() } wait(for: [exp], timeout: 1) } func testFrozenMakeThreadSafe() { var exp = XCTestExpectation() cancellable = valuePublisher(obj) .freeze() .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink { obj in XCTAssertTrue(obj.isFrozen) exp.fulfill() } for _ in 0..<10 { try! realm.write { obj.intCol += 1 } wait(for: [exp], timeout: 1) exp = XCTestExpectation() } } func testMixedMakeThreadSafe() { let realm2 = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "test2")) var objects = try! realm.write { try! realm2.write { return [ realm.create(SwiftIntObject.self, value: [1]), realm2.create(SwiftIntObject.self, value: [2]), SwiftIntObject(value: [3]), realm.create(SwiftIntObject.self, value: [4]), realm2.create(SwiftIntObject.self, value: [5]) ] } } objects[3] = objects[3].freeze() objects[4] = objects[4].freeze() let exp = XCTestExpectation() cancellable = objects.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.intCol } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3, 4, 5]) exp.fulfill() } wait(for: [exp], timeout: 1) } } private protocol CombineTestCollection { static func getCollection(_ realm: Realm) -> Self func appendObject() func modifyObject() // Keypath which is modified by `modifyObject` var includedKeyPath: [String] { get } // Keypath which is not modified by `modifyObject` var excludedKeyPath: [String] { get } } // MARK: - List, MutableSet private func checkChangeset(_ change: RealmCollectionChange, calls: Int, frozen: Bool = false) { switch change { case .initial(let collection): XCTAssertEqual(collection.isFrozen, frozen) XCTAssertEqual(calls, 0) XCTAssertEqual(collection.count, 0) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): XCTAssertEqual(collection.isFrozen, frozen) XCTAssertEqual(collection.count, calls) XCTAssertEqual(insertions, [calls - 1]) XCTAssertEqual(deletions, []) XCTAssertEqual(modifications, []) case .error(let error): XCTFail("Unexpected error \(error)") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) private class CombineCollectionPublisherTests: CombinePublisherTestCase where Collection: CombineTestCollection, Collection: RealmSubscribable { var collection: Collection! class func testSuite(_ name: String) -> XCTestSuite { if hasCombine() { // By default this test suite's name will be the generic type's // mangled name, which is an unreadable mess. It appears that the // way to override it is with a subclass with an explicit name, which // can't be done in pure Swift. let cls: AnyClass = objc_allocateClassPair(CombineCollectionPublisherTests.self, "CombinePublisherTests<\(name)>", 0)! objc_registerClassPair(cls) return cls.defaultTestSuite } return XCTestSuite(name: "CombinePublisherTests<\(name)>") } override func setUp() { super.setUp() collection = Collection.getCollection(realm) } func testWillChange() { let exp = XCTestExpectation() cancellable = collection.objectWillChange.sink { exp.fulfill() } try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 1) } func testBasic() { var exp = XCTestExpectation() var calls = 0 cancellable = collection.collectionPublisher .assertNoFailure() .sink { c in XCTAssertEqual(c.count, calls) calls += 1 exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } } func testBasicWithNotificationToken() { var exp = XCTestExpectation() var calls = 0 cancellable = collection.collectionPublisher .saveToken(on: self, at: \.notificationToken) .assertNoFailure() .sink { c in XCTAssertEqual(c.count, calls) calls += 1 exp.fulfill() } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } } func testBasicWithoutNotifying() { var calls = 0 cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .assertNoFailure() .sink { _ in calls += 1 } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write(withoutNotifying: [notificationToken!]) { collection.appendObject() } XCTAssertEqual(calls, 1) // 1 for the initial notification } } func testChangeSet() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetWithToken() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetWithoutNotifying() { var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .sink { _ in calls += 1 } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write(withoutNotifying: [notificationToken!]) { collection.appendObject() } XCTAssertEqual(calls, 1) // 1 for the initial observation } } func testSubscribeOn() { let sema = DispatchSemaphore(value: 0) var calls = 0 cancellable = collection .collectionPublisher .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 sema.signal() } sema.wait() for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testSubscribeOnKeyPath() { var ex = expectation(description: "initial notification") cancellable = collection.collectionPublisher(keyPaths: collection.includedKeyPath) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testSubscribeOnKeyPathNoChange() { var ex = expectation(description: "initial notification") cancellable = collection.collectionPublisher(keyPaths: collection.excludedKeyPath) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "no change notification") ex.isInverted = true try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testSubscribeOnWithToken() { let sema = DispatchSemaphore(value: 0) var calls = 0 cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 sema.signal() } sema.wait() XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testReceiveOn() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection.collectionPublisher .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testReceiveOnWithToken() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetSubscribeOn() { var calls = 0 let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 sema.signal() } sema.wait() for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testChangeSetSubscribeOnKeyPath() { var ex = expectation(description: "initial notification") cancellable = collection.changesetPublisher(keyPaths: collection.includedKeyPath) .subscribe(on: subscribeOnQueue) .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testChangeSetSubscribeOnKeyPathNoChange() { var ex = expectation(description: "initial notification") cancellable = collection.changesetPublisher(keyPaths: collection.excludedKeyPath) .subscribe(on: subscribeOnQueue) .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "no change notification") ex.isInverted = true try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testChangeSetSubscribeOnWithToken() { var calls = 0 let sema = DispatchSemaphore(value: 0) cancellable = collection .changesetPublisher .subscribe(on: subscribeOnQueue) .saveToken(on: self, at: \.notificationToken) .sink { change in checkChangeset(change, calls: calls) calls += 1 sema.signal() } sema.wait() XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testChangeSetReceiveOn() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetReceiveOnWithToken() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafe() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection.collectionPublisher .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafeChangeset() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafeWithChangesetToken() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testFrozen() { let exp = XCTestExpectation() cancellable = collection.collectionPublisher .freeze() .prefix(10) .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for (i, collection) in arr.enumerated() { XCTAssertTrue(collection.isFrozen) XCTAssertEqual(collection.count, i) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } func testFrozenChangeSetSubscribeOn() { let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .freeze() .assertNoFailure() .signal(sema) .prefix(10) .collect() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } sema.signal() } for _ in 0..<10 { sema.wait() try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenChangeSetReceiveOn() { let exp = XCTestExpectation() cancellable = collection.changesetPublisher .freeze() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } func testFrozenChangeSetSubscribeOnAndReceiveOn() { let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .freeze() .receive(on: receiveOnQueue) .signal(sema) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } sema.signal() } for _ in 0..<10 { sema.wait() try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenMakeThreadSafe() { let sema = DispatchSemaphore(value: 0) cancellable = collection.collectionPublisher .freeze() .threadSafeReference() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, collection) in arr.enumerated() { XCTAssertTrue(collection.isFrozen) XCTAssertEqual(collection.count, i) } sema.signal() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenMakeThreadSafeChangeset() { let exp = XCTestExpectation() cancellable = collection.changesetPublisher .freeze() .threadSafeReference() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } } extension Results: CombineTestCollection where Element == ModernAllTypesObject { static func getCollection(_ realm: Realm) -> Results { return realm.objects(Element.self) } func appendObject() { realm?.create(Element.self) } func modifyObject() { self.first!.intCol += 1 } var includedKeyPath: [String] { return ["intCol"] } var excludedKeyPath: [String] { return ["stringCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class ResultsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineCollectionPublisherTests>.testSuite("Results") } } extension List: CombineTestCollection where Element == ModernAllTypesObject { static func getCollection(_ realm: Realm) -> List { return try! realm.write { realm.create(ModernAllTypesObject.self).arrayCol } } func appendObject() { append(realm!.create(Element.self)) } func modifyObject() { self.first!.intCol += 1 } var includedKeyPath: [String] { return ["intCol"] } var excludedKeyPath: [String] { return ["stringCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class ManagedListPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineCollectionPublisherTests>.testSuite("List") } } extension MutableSet: CombineTestCollection where Element == ModernAllTypesObject { static func getCollection(_ realm: Realm) -> MutableSet { return try! realm.write { realm.create(ModernAllTypesObject.self).setCol } } func appendObject() { insert(realm!.create(Element.self)) } func modifyObject() { self.first!.intCol += 1 } var includedKeyPath: [String] { return ["intCol"] } var excludedKeyPath: [String] { return ["stringCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class ManagedMutableSetPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineCollectionPublisherTests>.testSuite("MutableSet") } } extension LinkingObjects: CombineTestCollection where Element == ModernAllTypesObject { static func getCollection(_ realm: Realm) -> LinkingObjects { return try! realm.write { realm.create(ModernAllTypesObject.self).linkingObjects } } func appendObject() { let link = realm!.objects(ModernAllTypesObject.self).first! let parent = ModernAllTypesObject() parent.objectCol = link realm!.add(parent) } func modifyObject() { self.first!.stringCol += "concat" } var includedKeyPath: [String] { return ["stringCol"] } var excludedKeyPath: [String] { return ["intCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class LinkingObjectsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineCollectionPublisherTests>.testSuite("LinkingObjects") } } extension AnyRealmCollection: CombineTestCollection where Element == ModernAllTypesObject { static func getCollection(_ realm: Realm) -> AnyRealmCollection { return AnyRealmCollection(realm.objects(Element.self)) } func appendObject() { realm?.create(Element.self) } func modifyObject() { self.first!.intCol += 1 } var includedKeyPath: [String] { return ["intCol"] } var excludedKeyPath: [String] { return ["stringCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class AnyRealmCollectionPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineCollectionPublisherTests>.testSuite("AnyRealmCollection") } } // MARK: - Map private func checkChangeset(_ change: RealmMapChange, calls: Int, frozen: Bool = false) { switch change { case .initial(let collection): XCTAssertEqual(collection.isFrozen, frozen) XCTAssertEqual(calls, 0) XCTAssertEqual(collection.count, 0) case .update(let collection, deletions: let deletions, insertions: let insertions, modifications: let modifications): XCTAssertEqual(collection.isFrozen, frozen) XCTAssertEqual(collection.count, calls) // one insertion at a time XCTAssertEqual(insertions.count, 1) XCTAssertEqual(modifications, []) XCTAssertEqual(deletions, []) case .error(let error): XCTFail("Unexpected error \(error)") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) private class CombineMapPublisherTests: CombinePublisherTestCase where Collection: CombineTestCollection, Collection: RealmSubscribable { var collection: Collection! class func testSuite(_ name: String) -> XCTestSuite { if hasCombine() { // By default this test suite's name will be the generic type's // mangled name, which is an unreadable mess. It appears that the // way to override it is with a subclass with an explicit name, which // can't be done in pure Swift. let cls: AnyClass = objc_allocateClassPair(CombineMapPublisherTests.self, "CombinePublisherTests<\(name)>", 0)! objc_registerClassPair(cls) return cls.defaultTestSuite } return XCTestSuite(name: "CombinePublisherTests<\(name)>") } override func setUp() { super.setUp() collection = Collection.getCollection(realm) } func testWillChange() { let exp = XCTestExpectation() cancellable = collection.objectWillChange.sink { exp.fulfill() } try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 1) } func testBasic() { var exp = XCTestExpectation() var calls = 0 cancellable = collection.collectionPublisher .assertNoFailure() .sink { c in XCTAssertEqual(c.count, calls) calls += 1 exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } } func testBasicWithNotificationToken() { var exp = XCTestExpectation() var calls = 0 cancellable = collection.collectionPublisher .saveToken(on: self, at: \.notificationToken) .assertNoFailure() .sink { c in XCTAssertEqual(c.count, calls) calls += 1 exp.fulfill() } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } } func testBasicWithoutNotifying() { var calls = 0 cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .assertNoFailure() .sink { _ in calls += 1 } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write(withoutNotifying: [notificationToken!]) { collection.appendObject() } XCTAssertEqual(calls, 1) // 1 for the initial notification } } func testChangeSet() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetWithToken() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetWithoutNotifying() { var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .sink { _ in calls += 1 } XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write(withoutNotifying: [notificationToken!]) { collection.appendObject() } XCTAssertEqual(calls, 1) // 1 for the initial observation } } func testSubscribeOn() { let sema = DispatchSemaphore(value: 0) var calls = 0 cancellable = collection .collectionPublisher .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 sema.signal() } sema.wait() for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testSubscribeOnKeyPath() { var ex = expectation(description: "initial notification") cancellable = collection.collectionPublisher(keyPaths: collection.includedKeyPath) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testSubscribeOnKeyPathNoChange() { var ex = expectation(description: "initial notification") cancellable = collection.collectionPublisher(keyPaths: collection.excludedKeyPath) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "no change notification") ex.isInverted = true try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testSubscribeOnWithToken() { let sema = DispatchSemaphore(value: 0) var calls = 0 cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 sema.signal() } sema.wait() XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testReceiveOn() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection.collectionPublisher .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testReceiveOnWithToken() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection .collectionPublisher .saveToken(on: self, at: \.notificationToken) .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetSubscribeOn() { var calls = 0 let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 sema.signal() } sema.wait() for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testChangeSetSubscribeOnKeyPath() { var ex = expectation(description: "initial notification") cancellable = collection.changesetPublisher(keyPaths: collection.includedKeyPath) .subscribe(on: subscribeOnQueue) .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testChangeSetSubscribeOnKeyPathNoChange() { var ex = expectation(description: "initial notification") cancellable = collection.changesetPublisher(keyPaths: collection.excludedKeyPath) .subscribe(on: subscribeOnQueue) .sink { _ in ex.fulfill() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "change notification") try! realm.write { collection.appendObject() } wait(for: [ex], timeout: 1.0) ex = expectation(description: "no change notification") ex.isInverted = true try! realm.write { collection.modifyObject() } wait(for: [ex], timeout: 1.0) } func testChangeSetSubscribeOnWithToken() { var calls = 0 let sema = DispatchSemaphore(value: 0) cancellable = collection .changesetPublisher .subscribe(on: subscribeOnQueue) .saveToken(on: self, at: \.notificationToken) .sink { change in checkChangeset(change, calls: calls) calls += 1 sema.signal() } sema.wait() XCTAssertNotNil(notificationToken) for _ in 0..<10 { try! realm.write { collection.appendObject() } sema.wait() } } func testChangeSetReceiveOn() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testChangeSetReceiveOnWithToken() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafe() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection.collectionPublisher .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink { r in XCTAssertEqual(r.count, calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafeChangeset() { var exp = XCTestExpectation(description: "initial") var calls = 0 cancellable = collection.changesetPublisher .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testMakeThreadSafeWithChangesetToken() { var calls = 0 var exp = XCTestExpectation(description: "initial") cancellable = collection .changesetPublisher .saveToken(on: self, at: \.notificationToken) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .sink { change in checkChangeset(change, calls: calls) calls += 1 exp.fulfill() } wait(for: [exp], timeout: 10) XCTAssertNotNil(notificationToken) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) } } func testFrozen() { let exp = XCTestExpectation() cancellable = collection.collectionPublisher .freeze() .prefix(10) .collect() .assertNoFailure() .sink { arr in for (i, collection) in arr.enumerated() { XCTAssertTrue(collection.isFrozen) XCTAssertEqual(collection.count, i) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } func testFrozenChangeSetSubscribeOn() { let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .freeze() .assertNoFailure() .signal(sema) .prefix(10) .collect() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } sema.signal() } for _ in 0..<10 { sema.wait() try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenChangeSetReceiveOn() { let exp = XCTestExpectation() cancellable = collection.changesetPublisher .freeze() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } func testFrozenChangeSetSubscribeOnAndReceiveOn() { let sema = DispatchSemaphore(value: 0) cancellable = collection.changesetPublisher .subscribe(on: subscribeOnQueue) .freeze() .receive(on: receiveOnQueue) .signal(sema) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } sema.signal() } for _ in 0..<10 { sema.wait() try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenMakeThreadSafe() { let sema = DispatchSemaphore(value: 0) cancellable = collection.collectionPublisher .freeze() .threadSafeReference() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, collection) in arr.enumerated() { XCTAssertTrue(collection.isFrozen) XCTAssertEqual(collection.count, i) } sema.signal() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } sema.wait() } func testFrozenMakeThreadSafeChangeset() { let exp = XCTestExpectation() cancellable = collection.changesetPublisher .freeze() .threadSafeReference() .receive(on: receiveOnQueue) .prefix(10) .collect() .assertNoFailure() .sink { @Sendable arr in for (i, change) in arr.enumerated() { checkChangeset(change, calls: i, frozen: true) } exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } } wait(for: [exp], timeout: 10) } } extension Map: CombineTestCollection where Key == String, Value == SwiftObject? { static func getCollection(_ realm: Realm) -> Map { return try! realm.write { realm.create(SwiftMapPropertyObject.self).swiftObjectMap } } func appendObject() { let key = UUID().uuidString self[key] = realm!.create(SwiftObject.self) } func modifyObject() { self.values.first!!.intCol += 1 } var includedKeyPath: [String] { return ["intCol"] } var excludedKeyPath: [String] { return ["stringCol"] } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class ManagedMapPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineMapPublisherTests>.testSuite("Map") } } // MARK: - Sectioned Results protocol RealmSectionedObject: ObjectBase { associatedtype Key: _Persistable, Hashable var key: Key { get } } extension ModernAllTypesObject: RealmSectionedObject { var key: Int8 { int8Col } // This property will never change. } private func checkChangeset( _ change: SectionedResultsChange, insertions: [IndexPath] = [], deletions: [IndexPath] = [], frozen: Bool = false) { switch change { case .initial(let collection): XCTAssertEqual(collection.isFrozen, frozen) case .update(let collection, deletions: let del, insertions: let ins, modifications: let modifications, _, _): XCTAssertEqual(collection.isFrozen, frozen) XCTAssertEqual(ins, insertions) XCTAssertEqual(del, deletions) XCTAssertEqual(modifications, []) } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) private class CombineSectionedResultsPublisherTests: CombinePublisherTestCase where Collection: CombineTestCollection, Collection: RealmSubscribable, Collection.Element: RealmSectionedObject { var collection: Collection! class func testSuite(_ name: String) -> XCTestSuite { if hasCombine() { // By default this test suite's name will be the generic type's // mangled name, which is an unreadable mess. It appears that the // way to override it is with a subclass with an explicit name, which // can't be done in pure Swift. let cls: AnyClass = objc_allocateClassPair(CombineSectionedResultsPublisherTests.self, "CombineSectionedResultsPublisherTests<\(name)>", 0)! objc_registerClassPair(cls) return cls.defaultTestSuite } return XCTestSuite(name: "CombineSectionedResultsPublisherTests<\(name)>") } override func setUp() { super.setUp() collection = Collection.getCollection(realm) } func testWillChange() { let exp = XCTestExpectation() let sectionedResults = collection.sectioned(by: \.key) cancellable = sectionedResults.objectWillChange.sink { exp.fulfill() } try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 1) // Test section let sectionExp = XCTestExpectation() cancellable = sectionedResults[0].objectWillChange.sink { sectionExp.fulfill() } try! realm.write { realm.deleteAll() } wait(for: [sectionExp], timeout: 1) } func testBasic() { var exp = XCTestExpectation() var calls = 0 let sectionedResults = collection.sectioned(by: \.key) cancellable = sectionedResults.collectionPublisher .assertNoFailure() .sink { c in if c.count != 0 { XCTAssertEqual(c[0].count, calls) } calls += 1 exp.fulfill() } for _ in 0..<10 { try! realm.write { collection.appendObject() } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } // Test section var sectionExp = XCTestExpectation() var sectionCalls = 10 cancellable = sectionedResults[0].collectionPublisher .assertNoFailure() .sink { c in XCTAssertEqual(c.count, sectionCalls) sectionCalls -= 1 sectionExp.fulfill() } for _ in 0..>.testSuite("Results") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) // swiftlint:disable:next type_name class ManagedListSectionedResultsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineSectionedResultsPublisherTests>.testSuite("List") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) // swiftlint:disable:next type_name class ManagedMutableSetSectionedResultsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineSectionedResultsPublisherTests>.testSuite("MutableSet") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) // swiftlint:disable:next type_name class LinkingObjectsSectionedResultsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineSectionedResultsPublisherTests>.testSuite("LinkingObjects") } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) // swiftlint:disable:next type_name class AnyRealmCollectionSectionedResultsPublisherTests: TestCase { override class var defaultTestSuite: XCTestSuite { return CombineSectionedResultsPublisherTests>.testSuite("AnyRealmCollection") } } // MARK: - Projection @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension SimpleObject: ObjectKeyIdentifiable { } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension SimpleProjection: ObjectKeyIdentifiable { } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public final class AltSimpleProjection: Projection, ObjectKeyIdentifiable { @Projected(\SimpleObject.int) var int } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class CombineProjectionPublisherTests: CombinePublisherTestCase { var object: SimpleObject! var projection: SimpleProjection! override func setUp() { super.setUp() try! realm.write { object = realm.create(SimpleObject.self) } projection = realm.objects(SimpleProjection.self).first! } func testWillChange() { let exp = XCTestExpectation() cancellable = projection.objectWillChange.sink { exp.fulfill() } try! realm.write { object.int = 1 } wait(for: [exp], timeout: 1) } func testWillChangeWithToken() { let exp = XCTestExpectation() cancellable = projection .objectWillChange .saveToken(on: self, at: \.notificationToken) .sink { exp.fulfill() } XCTAssertNotNil(notificationToken) try! realm.write { object.int = 1 } } func testChange() { let exp = XCTestExpectation() cancellable = valuePublisher(projection) .assertNoFailure() .sink { o in XCTAssertEqual(self.projection, o) exp.fulfill() } try! realm.write { object.int = 1 } wait(for: [exp], timeout: 1) } func testChangeSet() { let exp = XCTestExpectation() cancellable = changesetPublisher(projection) .assertNoFailure() .sink { change in if case .change(let p, let properties) = change { XCTAssertEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, 1) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() } try! realm.write { object.int = 1 } wait(for: [exp], timeout: 1) } func testDelete() { let exp = XCTestExpectation() cancellable = valuePublisher(projection) .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { _ in }) try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testSubscribeOn() { let ex = watchForNotifierAdded() let sema = DispatchSemaphore(value: 0) var i = 1 cancellable = valuePublisher(projection) .subscribe(on: subscribeOnQueue) .map { projection -> SimpleProjection in sema.signal() XCTAssertEqual(projection.int, i) i += 1 return projection } .collect() .assertNoFailure() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) sema.signal() } wait(for: [ex], timeout: 2.0) for _ in 0..<10 { try! realm.write { object.int += 1 } // wait between each write so that the notifications can't get coalesced // also would deadlock if the subscription was on the main thread sema.wait() } try! realm.write { realm.delete(object) } sema.wait() } func testReceiveOn() { var exp = XCTestExpectation() cancellable = valuePublisher(projection) .receive(on: receiveOnQueue) .map { projection -> Int in exp.fulfill() return projection.int } .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for i in 1..<10 { XCTAssertTrue(arr.contains(i)) } exp.fulfill() } for _ in 0..<10 { try! realm.write { object.int += 1 } wait(for: [exp], timeout: 10) exp = XCTestExpectation() } try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 10) } func testChangeSetSubscribeOn() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() var prevProj: SimpleProjection? cancellable = changesetPublisher(projection) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let p, let properties) = change { XCTAssertNotEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") if let prevProj = prevProj { XCTAssertEqual(properties[0].oldValue as? Int, prevProj.int) } XCTAssertEqual(properties[0].newValue as? Int, p.int) prevProj = p.freeze() XCTAssertEqual(prevProj!.int, p.int) if p.int == 100 { sema.signal() } } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { object.int += 1 } } sema.wait() try! realm.write { realm.delete(object) } sema.wait() XCTAssertNotNil(prevProj) XCTAssertEqual(prevProj!.int, 100) } func testChangeSetSubscribeOnKeyPath() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() var prevProj: SimpleProjection? cancellable = changesetPublisher(projection, keyPaths: ["int"]) .subscribe(on: subscribeOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let p, let properties) = change { XCTAssertNotEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") if let prevProj = prevProj { XCTAssertEqual(properties[0].oldValue as? Int, prevProj.int) } XCTAssertEqual(properties[0].newValue as? Int, p.int) prevProj = p.freeze() XCTAssertEqual(prevProj!.int, p.int) if p.int >= 100 { sema.signal() } } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { object.int += 1 } } sema.wait() // The following two lines check if a write outside of // the intended keyPath does *not* publish a // change. // If a changeset is published for boolCol, the test would fail // above when checking for property name "intCol". try! realm.write { object.bool = true } try! realm.write { object.int += 1 } sema.wait() try! realm.write { realm.delete(object) } sema.wait() XCTAssertNotNil(prevProj) XCTAssertEqual(prevProj!.int, 101) } func testChangeSetReceiveOn() { var exp = XCTestExpectation(description: "change") cancellable = changesetPublisher(projection) .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { change in if case .change(let p, let properties) = change { XCTAssertNotEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") // oldValue is always nil because we subscribed on the thread doing the writing XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, p.int) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() }) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { object.int += 1 } wait(for: [exp], timeout: 1) } exp = XCTestExpectation(description: "completion") try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testChangeSetSubscribeOnAndReceiveOn() { let sema = DispatchSemaphore(value: 0) let ex = watchForNotifierAdded() var prev: SimpleProjection? cancellable = changesetPublisher(projection) .subscribe(on: subscribeOnQueue) .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in sema.signal() }, receiveValue: { change in if case .change(let p, let properties) = change { XCTAssertNotEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.int) } XCTAssertEqual(properties[0].newValue as? Int, p.int) prev = p.freeze() XCTAssertEqual(prev!.int, p.int) if p.int == 100 { sema.signal() } p.realm?.invalidate() } else { XCTFail("Expected .change but got \(change)") } }) wait(for: [ex], timeout: 2.0) for _ in 0..<100 { try! realm.write { object.int += 1 } } sema.wait() try! realm.write { realm.delete(object) } sema.wait() XCTAssertNotNil(prev) XCTAssertEqual(prev!.int, 100) } func testChangeSetMakeThreadSafe() { var exp = XCTestExpectation(description: "change") cancellable = changesetPublisher(projection) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { change in if case .change(let p, let properties) = change { XCTAssertNotEqual(self.projection, p) XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") XCTAssertNil(properties[0].oldValue) XCTAssertEqual(properties[0].newValue as? Int, p.int) } else { XCTFail("Expected .change but got \(change)") } exp.fulfill() }) for _ in 0..<10 { exp = XCTestExpectation(description: "change") try! realm.write { object.int += 1 } wait(for: [exp], timeout: 1) } exp = XCTestExpectation(description: "completion") try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testFrozen() { let exp = XCTestExpectation() cancellable = valuePublisher(projection) .freeze() .collect() .assertNoFailure() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) for i in 0..<10 { XCTAssertEqual(arr[i].int, i + 1) } exp.fulfill() } for _ in 0..<10 { try! realm.write { object.int += 1 } } try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testFrozenPublisherSubscribeOn() { let setupEx = watchForNotifierAdded() let completeEx = expectation(description: "pipeline complete") var gotValueEx: XCTestExpectation! cancellable = valuePublisher(projection) .subscribe(on: subscribeOnQueue) .freeze() .map { (v: SimpleProjection) -> SimpleProjection in gotValueEx.fulfill() return v } .collect() .assertNoFailure() .sink { @Sendable arr in XCTAssertEqual(arr.count, 10) for i in 0..<10 { XCTAssertEqual(arr[i].int, i + 1) } completeEx.fulfill() } wait(for: [setupEx], timeout: 2.0) for _ in 0..<10 { gotValueEx = expectation(description: "got value") try! realm.write { object.int += 1 } wait(for: [gotValueEx], timeout: 2.0) } try! realm.write { realm.delete(object) } wait(for: [completeEx], timeout: 1) } func testFrozenChangeSetSubscribeOn() { let setupEx = watchForNotifierAdded() let sema = DispatchSemaphore(value: 0) cancellable = changesetPublisher(projection) .subscribe(on: subscribeOnQueue) .freeze() .collect() .assertNoFailure() .sink { @Sendable arr in var prev: SimpleProjection? for change in arr { guard case .change(let p, let properties) = change else { XCTFail("Expected .change but got \(change)") sema.signal() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") XCTAssertEqual(properties[0].newValue as? Int, p.int) if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.int) } prev = p } sema.signal() } wait(for: [setupEx], timeout: 2.0) for _ in 0..<100 { try! realm.write { object.int += 1 } } try! realm.write { realm.delete(object) } sema.wait() } func testFrozenChangeSetReceiveOn() { let exp = XCTestExpectation(description: "sink complete") cancellable = changesetPublisher(projection) .freeze() .receive(on: receiveOnQueue) .collect() .assertNoFailure() .sink { @Sendable arr in for change in arr { guard case .change(let p, let properties) = change else { XCTFail("Expected .change but got \(change)") exp.fulfill() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") XCTAssertEqual(properties[0].newValue as? Int, p.int) // subscribing on the thread making writes means that oldValue // is always nil XCTAssertNil(properties[0].oldValue) } exp.fulfill() } for _ in 0..<100 { try! realm.write { object.int += 1 } } try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testFrozenChangeSetSubscribeOnAndReceiveOn() { let setupEx = watchForNotifierAdded() let sema = DispatchSemaphore(value: 0) cancellable = changesetPublisher(projection) .subscribe(on: subscribeOnQueue) .freeze() .receive(on: receiveOnQueue) .collect() .assertNoFailure() .sink { @Sendable arr in var prev: SimpleProjection? for change in arr { guard case .change(let p, let properties) = change else { XCTFail("Expected .change but got \(change)") sema.signal() return } XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "int") XCTAssertEqual(properties[0].newValue as? Int, p.int) if let prev = prev { XCTAssertEqual(properties[0].oldValue as? Int, prev.int) } prev = p } sema.signal() } wait(for: [setupEx], timeout: 2.0) for _ in 0..<100 { try! realm.write { object.int += 1 } } try! realm.write { realm.delete(object) } sema.wait() } func testReceiveOnAfterMap() { var exp = XCTestExpectation() cancellable = valuePublisher(projection) .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .map { projection -> Int in exp.fulfill() return projection.int } .collect() .assertNoFailure() .sink { arr in XCTAssertEqual(arr.count, 10) for i in 1..<10 { XCTAssertTrue(arr.contains(i)) } exp.fulfill() } for _ in 0..<10 { try! realm.write { object.int += 1 } wait(for: [exp], timeout: 1) exp = XCTestExpectation() } try! realm.write { realm.delete(object) } wait(for: [exp], timeout: 1) } func testUnmanagedMakeThreadSafe() { let projections = try! realm.write { return [ SimpleProjection(projecting: realm.create(SimpleObject.self, value: [1])), SimpleProjection(projecting: realm.create(SimpleObject.self, value: [2])), SimpleProjection(projecting: realm.create(SimpleObject.self, value: [3])) ] } let exp = XCTestExpectation() cancellable = projections.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.int } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3]) exp.fulfill() } wait(for: [exp], timeout: 1) } func testManagedMakeThreadSafe() { let projections = try! realm.write { return [ SimpleProjection(projecting: realm.create(SimpleObject.self, value: [1])), SimpleProjection(projecting: realm.create(SimpleObject.self, value: [2])), SimpleProjection(projecting: realm.create(SimpleObject.self, value: [3])) ] } let exp = XCTestExpectation() cancellable = projections.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.int } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3]) exp.fulfill() } wait(for: [exp], timeout: 1) } func testFrozenMakeThreadSafe() { var exp = XCTestExpectation() cancellable = valuePublisher(projection) .freeze() .map { $0 } .threadSafeReference() .receive(on: receiveOnQueue) .assertNoFailure() .sink { projection in XCTAssertTrue(projection.isFrozen) exp.fulfill() } for _ in 0..<10 { try! realm.write { object.int += 1 } wait(for: [exp], timeout: 1) exp = XCTestExpectation() } } func testMixedMakeThreadSafe() { let realm2 = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "test2")) var projections = try! realm.write { try! realm2.write { return [ SimpleProjection(projecting: realm.create(SimpleObject.self, value: [1])), SimpleProjection(projecting: realm2.create(SimpleObject.self, value: [2])), SimpleProjection(projecting: realm.create(SimpleObject.self, value: [3])), SimpleProjection(projecting: realm2.create(SimpleObject.self, value: [4])) ] } } projections[2] = projections[2].freeze() projections[3] = projections[3].freeze() let exp = XCTestExpectation() cancellable = projections.publisher .threadSafeReference() .receive(on: receiveOnQueue) .map { $0.int } .collect() .sink { (arr: [Int]) in XCTAssertEqual(arr, [1, 2, 3, 4]) exp.fulfill() } wait(for: [exp], timeout: 1) } func testIdentifiable() { let realm = realmWithTestPath() try! realm.write { realm.create(SimpleObject.self, value: [1]) realm.create(SimpleObject.self, value: [2]) } let objects = realm.objects(SimpleObject.self) let projections = realm.objects(SimpleProjection.self) XCTAssertEqual(objects[0].id, objects[0].id) XCTAssertEqual(objects[1].id, objects[1].id) XCTAssertNotEqual(objects[0].id, objects[1].id) XCTAssertEqual(projections[0].id, projections[0].id) XCTAssertEqual(projections[1].id, projections[1].id) XCTAssertNotEqual(projections[0].id, projections[1].id) XCTAssertEqual(objects[0].id, projections[0].id) XCTAssertEqual(objects[1].id, projections[1].id) let altProjection = AltSimpleProjection(projecting: objects[0]) XCTAssertEqual(altProjection.id, projections[0].id) let storedId = altProjection.id try! realm.write { altProjection.int += 1 } XCTAssertEqual(storedId, altProjection.id) } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class CombineAsyncRealmTests: CombinePublisherTestCase { @MainActor func testWillChangeLocalWrite() { let asyncWriteExpectation = expectation(description: "Should complete async write") cancellable = realm.objectWillChange.sink { asyncWriteExpectation.fulfill() } realm.writeAsync { self.realm.create(SwiftIntObject.self) } waitForExpectations(timeout: 1, handler: nil) } func testWillChangeRemoteWrite() { let exp = XCTestExpectation() cancellable = realm.objectWillChange.sink { exp.fulfill() } queue.async { let realm = try! Realm(configuration: self.realm.configuration, queue: self.queue) realm.writeAsync { realm.create(SwiftIntObject.self) } } wait(for: [exp], timeout: 3) } } ================================================ FILE: RealmSwift/Tests/CompactionTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 import RealmSwift private func fileSize(path: String) -> Int { let attributes = try! FileManager.default.attributesOfItem(atPath: path) return attributes[.size] as! Int } class CompactionTests: TestCase { func testSuccessfulCompactOnLaunch() { let expectedUsedBytesBeforeMin = 50000 let count = 1000 autoreleasepool { // Make compactable Realm let realm = realmWithTestPath() let uuid = UUID().uuidString try! realm.write { realm.create(SwiftStringObject.self, value: ["A"]) for _ in 0.. expectedUsedBytesBeforeMin)) return true }) // Confirm expected sizes before and after opening the Realm XCTAssertEqual(fileSize(path: config.fileURL!.path), expectedTotalBytesBefore) let realm = try! Realm(configuration: config) XCTAssertLessThan(fileSize(path: config.fileURL!.path), expectedTotalBytesBefore) // Validate that the file still contains what it should XCTAssertEqual(realm.objects(SwiftStringObject.self).count, count + 2) XCTAssertEqual("A", realm.objects(SwiftStringObject.self).first?.stringCol) XCTAssertEqual("B", realm.objects(SwiftStringObject.self).last?.stringCol) } } ================================================ FILE: RealmSwift/Tests/CustomColumnNameTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 import RealmSwift // MARK: - Custom Column Object Factory protocol CustomColumnObjectFactory { associatedtype Root: Object static func create(primaryKey: ObjectId, nestedObject: Root?) -> Root static func createValues(primaryKey: ObjectId) -> [String: Any] } // MARK: - Models let propertiesModernCustomMapping: [String: String] = ["pk": "custom_pk", "intCol": "custom_intCol", "anyCol": "custom_anyCol", "intEnumCol": "custom_intEnumCol", "objectCol": "custom_objectCol", "arrayCol": "custom_arrayCol", "setCol": "custom_setCol", "mapCol": "custom_mapCol", "embeddedObject": "custom_embeddedObject", "arrayIntCol": "custom_arrayIntCol", "setIntCol": "custom_setIntCol", "mapIntCol": "custom_mapIntCol"] class ModernCustomObject: Object { @Persisted(primaryKey: true) var pk: ObjectId @Persisted var intCol: Int @Persisted var anyCol: AnyRealmValue @Persisted var intEnumCol: ModernIntEnum @Persisted var objectCol: ModernCustomObject? @Persisted var arrayCol = List() @Persisted var setCol = MutableSet() @Persisted var mapCol = Map() @Persisted var embeddedObject: EmbeddedModernCustomObject? @Persisted var arrayIntCol = List() @Persisted var setIntCol = MutableSet() @Persisted var mapIntCol = Map() override class func propertiesMapping() -> [String: String] { propertiesModernCustomMapping } convenience init(pk: ObjectId) { self.init() self.pk = pk } } let propertiesModernEmbeddedCustomMapping: [String: String] = ["intCol": "custom_intCol"] class EmbeddedModernCustomObject: EmbeddedObject { @Persisted var intCol: Int override class func propertiesMapping() -> [String: String] { propertiesModernEmbeddedCustomMapping } } extension ModernCustomObject: CustomColumnObjectFactory { typealias Root = ModernCustomObject static func create(primaryKey: ObjectId, nestedObject: ModernCustomObject?) -> ModernCustomObject { let object = ModernCustomObject() object.pk = primaryKey let linkedObject = ModernCustomObject() linkedObject.pk = ObjectId.generate() linkedObject.embeddedObject = EmbeddedModernCustomObject() object.embeddedObject = EmbeddedModernCustomObject() if let nestedObject = nestedObject { object.anyCol = .object(nestedObject) object.objectCol = nestedObject object.arrayCol.append(nestedObject) object.setCol.insert(nestedObject) object.mapCol["key"] = nestedObject } return object } static func createValues(primaryKey: ObjectId) -> [String: Any] { return [ "pk": primaryKey, "intCol": 123, "anyCol": AnyRealmValue.int(345), "intEnumCol": ModernIntEnum.value2, "objectCol": ModernCustomObject(), "arrayCol": [ModernCustomObject()], "setCol": [ModernCustomObject()], "mapCol": ["key": ModernCustomObject()], "embeddedObject": EmbeddedModernCustomObject(), "arrayIntCol": [123], "setIntCol": [345], "mapIntCol": ["key": 123] ] } } let propertiesCustomMapping: [String: String] = { ["pk": "custom_pk", "intCol": "custom_intCol", "objectCol": "custom_objectCol", "arrayCol": "custom_arrayCol", "setCol": "custom_setCol", "mapCol": "custom_mapCol", "anyCol": "custom_anyCol", "intEnumCol": "custom_intEnumCol", "embeddedObject": "custom_embeddedObject", "arrayIntCol": "custom_arrayIntCol", "setIntCol": "custom_setIntCol", "mapIntCol": "custom_mapIntCol"] }() class OldCustomObject: Object { @objc dynamic var pk = ObjectId.generate() @objc dynamic var intCol = 123 var anyCol = RealmProperty() @objc dynamic var intEnumCol = IntEnum.value1 @objc dynamic var objectCol: OldCustomObject? let arrayCol = List() let setCol = MutableSet() let mapCol = Map() @objc dynamic var embeddedObject: EmbeddedCustomObject? let arrayIntCol = List() let setIntCol = MutableSet() let mapIntCol = Map() override class func primaryKey() -> String? { return "pk" } override class func propertiesMapping() -> [String: String] { propertiesCustomMapping } } extension OldCustomObject: CustomColumnObjectFactory { typealias Root = OldCustomObject static func create(primaryKey: ObjectId, nestedObject: OldCustomObject?) -> OldCustomObject { let object = OldCustomObject() object.pk = primaryKey object.embeddedObject = EmbeddedCustomObject() let linkedObject = OldCustomObject() linkedObject.pk = ObjectId.generate() linkedObject.embeddedObject = EmbeddedCustomObject() if let nestedObject = nestedObject { object.objectCol = nestedObject object.arrayCol.append(nestedObject) object.setCol.insert(nestedObject) object.mapCol["key"] = nestedObject } return object } static func createValues(primaryKey: ObjectId) -> [String: Any] { return [ "pk": primaryKey, "intCol": 123, "anyCol": AnyRealmValue.int(345), "intEnumCol": IntEnum.value2, "objectCol": OldCustomObject(), "arrayCol": [OldCustomObject()], "setCol": [OldCustomObject()], "mapCol": ["key": OldCustomObject()], "embeddedObject": EmbeddedCustomObject(), "arrayIntCol": [123], "setIntCol": [345], "mapIntCol": ["key": 123] ] } } let propertiesEmbeddedCustomMapping: [String: String] = { ["intCol": "custom_intCol"] }() class EmbeddedCustomObject: EmbeddedObject { @objc dynamic var intCol = 123 override class func propertiesMapping() -> [String: String] { propertiesEmbeddedCustomMapping } } // MARK: - Schema Discovery class CustomColumnNamesSchemaTest: TestCase { func testCustomColumnNameSchema() { let modernCustomObjectSchema = ModernCustomObject().objectSchema for property in modernCustomObjectSchema.properties { XCTAssertEqual(propertiesModernCustomMapping[property.name], property.columnName) } let modernEmbeddedCustomObjectSchema = EmbeddedModernCustomObject().objectSchema for property in modernEmbeddedCustomObjectSchema.properties { XCTAssertEqual(propertiesModernEmbeddedCustomMapping[property.name], property.columnName) } let oldCustomObjectSchema = OldCustomObject().objectSchema for property in oldCustomObjectSchema.properties { XCTAssertEqual(propertiesCustomMapping[property.name], property.columnName) } let oldEmbeddedCustomObjectSchema = EmbeddedCustomObject().objectSchema for property in oldEmbeddedCustomObjectSchema.properties { XCTAssertEqual(propertiesEmbeddedCustomMapping[property.name], property.columnName) } } func testDescriptionWithCustomColumnName() { let modernCustomObjectSchema = ModernCustomObject().objectSchema let modernObjectExpected = """ ModernCustomObject { pk { type = object id; columnName = custom_pk; indexed = YES; isPrimary = YES; array = NO; set = NO; dictionary = NO; optional = NO; } intCol { type = int; columnName = custom_intCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } anyCol { type = mixed; columnName = custom_anyCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } intEnumCol { type = int; columnName = custom_intEnumCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } objectCol { type = object; objectClassName = ModernCustomObject; linkOriginPropertyName = (null); columnName = custom_objectCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = YES; } arrayCol { type = object; objectClassName = ModernCustomObject; linkOriginPropertyName = (null); columnName = custom_arrayCol; indexed = NO; isPrimary = NO; array = YES; set = NO; dictionary = NO; optional = NO; } setCol { type = object; objectClassName = ModernCustomObject; linkOriginPropertyName = (null); columnName = custom_setCol; indexed = NO; isPrimary = NO; array = NO; set = YES; dictionary = NO; optional = NO; } mapCol { type = object; objectClassName = ModernCustomObject; linkOriginPropertyName = (null); columnName = custom_mapCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = YES; optional = YES; } embeddedObject { type = object; objectClassName = EmbeddedModernCustomObject; linkOriginPropertyName = (null); columnName = custom_embeddedObject; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = YES; } arrayIntCol { type = int; columnName = custom_arrayIntCol; indexed = NO; isPrimary = NO; array = YES; set = NO; dictionary = NO; optional = NO; } setIntCol { type = int; columnName = custom_setIntCol; indexed = NO; isPrimary = NO; array = NO; set = YES; dictionary = NO; optional = NO; } mapIntCol { type = int; columnName = custom_mapIntCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = YES; optional = NO; } } """ XCTAssertEqual(modernCustomObjectSchema.description, modernObjectExpected.replacingOccurrences(of: " ", with: "\t")) } } class CustomColumnModernDynamicObjectTest: TestCase { var realm: Realm! override func setUp() { super.setUp() realm = inMemoryRealm("CustomColumnTests") let object = ModernCustomObject.create(primaryKey: ObjectId("6058f12682b2fbb1f334ef1d"), nestedObject: nil) object.anyCol = .object(ModernCustomObject()) try! realm.write { realm.add(object) } } override func tearDown() { realm = nil super.tearDown() } func testCustomColumnDynamicObjectSubscript() throws { let object = realm.object(ofType: ModernCustomObject.self, forPrimaryKey: ObjectId("6058f12682b2fbb1f334ef1d"))! guard let dynamicObject: DynamicObject = object.anyCol.dynamicObject else { return XCTFail("DynamicObject does not exist") } // Set Value / Get Value try realm.write { dynamicObject[_name(for: \ModernCustomObject.intCol)] = 56789 } XCTAssertEqual(dynamicObject[_name(for: \ModernCustomObject.intCol)] as! Int, 56789) } func testCustomColumnDynamicObjectSetValue() throws { let dynamicObjects = realm.dynamicObjects("ModernCustomObject") XCTAssertEqual(dynamicObjects.count, 2) let dynamicObject = dynamicObjects.first! XCTAssertNotNil(dynamicObject) try realm.write { dynamicObject.setValue(45678, forUndefinedKey: _name(for: \ModernCustomObject.intCol)) } XCTAssertEqual(dynamicObject.value(forUndefinedKey: _name(for: \ModernCustomObject.intCol)) as! Int, 45678) } func testCustomColumnDynamicObjectMemberLookUp() throws { let dynamicObjects = realm.dynamicObjects("ModernCustomObject") XCTAssertEqual(dynamicObjects.count, 2) let dynamicObject = dynamicObjects.first! XCTAssertNotNil(dynamicObject) try realm.write { dynamicObject.intCol = 98765 } XCTAssertEqual( dynamicObject.intCol as! Int, 98765) } func testCustomColumnDynamicSchema() throws { let dynamicObjects = realm.dynamicObjects("ModernCustomObject") XCTAssertEqual(dynamicObjects.count, 2) let dynamicObject = dynamicObjects.first! XCTAssertNotNil(dynamicObject) let schema = dynamicObject.objectSchema for property in schema.properties { XCTAssertEqual(propertiesModernCustomMapping[property.name], property.columnName) } } } class CustomColumnTestsBase: TestCase where O.Root == F.Root { var realm: Realm! public var notificationTokens: [NotificationToken] = [] var object: O.Root! var nestedObject: O.Root! var primaryKey: ObjectId! override func setUp() { realm = inMemoryRealm("CustomColumnTests") try! realm.write { primaryKey = ObjectId("61184062c1d8f096a3695045") nestedObject = O.create(primaryKey: ObjectId.generate(), nestedObject: nil) object = O.create(primaryKey: primaryKey, nestedObject: nestedObject) for (keyPath, value) in F.keyPaths { object.setValue(value, forKeyPath: _name(for: keyPath)) } realm.add(object) } } override func tearDown() { object = nil realm = nil for token in notificationTokens { token.invalidate() } notificationTokens = [] } func setValue(_ value: F.ValueType, for keyPath: KeyPath) throws { try realm.write { let keyPathString = _name(for: keyPath) if keyPathString.components(separatedBy: ".").count > 1 { nestedObject[keyPathString.components(separatedBy: ".").last!] = value } else { object[keyPathString] = value } } } override func invokeTest() { autoreleasepool { super.invokeTest() } } } class CustomColumnResultsTestBase: CustomColumnTestsBase where O.Root == F.Root, F.ValueType: Equatable { var results: Results! override func setUp() { super.setUp() results = realm.objects(O.Root.self) } override func tearDown() { results = nil super.tearDown() } } class CustomColumnResultsTest: CustomColumnResultsTestBase where O.Root == F.Root, F.ValueType: Equatable { // MARK: - Create Object func testCustomColumnResultsCreate() throws { let primaryKey = ObjectId.generate() let objectValues = O.createValues(primaryKey: primaryKey) try realm.write { realm.create(O.Root.self, value: objectValues) } XCTAssertNotNil(realm.object(ofType: O.Root.self, forPrimaryKey: primaryKey)) } // MARK: - Results Queries func testCustomColumnResultsByPrimaryKey() throws { XCTAssertEqual(realm.object(ofType: O.Root.self, forPrimaryKey: primaryKey), object) } // MARK: - Results Queries func testCustomColumnResultsQueries() throws { // Assert Queries for (keyPath, query) in F.query { assertQuery(query, keyPath: keyPath, expectedCount: 1) } } func testCustomColumnResultsIndexMatching() throws { // Assert Query Index Matching for (_, query) in F.query { assertIndexMatching(query, expectedIndex: 0) } } // MARK: - Results Distinct & Sort func testCustomColumnResultsDistinct() throws { // Distinct by KeyPath for (keyPath, count) in F.distincts { assertDistinct(for: keyPath, count: count) } } func testCustomColumnResultsSort() throws { let object2 = O.create(primaryKey: ObjectId.generate(), nestedObject: O.create(primaryKey: ObjectId.generate(), nestedObject: nil)) try realm.write { realm.add(object2) } // Sort by KeyPath for (keyPath, value) in F.sort { try realm.write { object2.setValue(value, forKeyPath: _name(for: keyPath)) } assertSort(for: keyPath, value: value) } } // MARK: - Get/Set ValueForKey func testCustomColumnResultsSetGetValueForKey() throws { for (keyPath, value) in F.values { try realm.write { results.setValue(value, forKey: _name(for: keyPath)) let valuesForKey = results.value(forKey: _name(for: keyPath)) as! [F.ValueType] XCTAssertNotNil(valuesForKey) } } } func testCustomColumnResultsGetValueForKeyPath() throws { for (keyPath, _) in F.keyPaths { let valuesForKeyPath = results.value(forKeyPath: _name(for: keyPath)) as! [F.ValueType?] XCTAssertNotNil(valuesForKeyPath) } } // MARK: - Observation func testCustomColumnResultsPropertyObservation() throws { for (keyPath, value) in F.values { let ex = XCTestExpectation(description: "Notification to be called") let notificationToken = results.observe(keyPaths: [keyPath]) { changes in switch changes { case .update(_, _, _, let modifications): XCTAssertGreaterThan(modifications.count, 0) ex.fulfill() case .initial: break default: XCTFail("No other changes are done to the object") } } notificationTokens.append(notificationToken) try setValue(value, for: keyPath) wait(for: [ex], timeout: 1.0) } } // MARK: - Private private func assertQuery(_ query: ((Query) -> Query), keyPath: PartialKeyPath, expectedCount: Int) { // TSQ let tsqResults: Results = results.where(query) XCTAssertEqual(tsqResults.count, expectedCount) let (queryStr, constructedValues) = query(Query._constructForTesting())._constructPredicate() // NSPredicate let predicate = NSPredicate(format: queryStr, argumentArray: constructedValues) let predicateResults: Results = results.filter(predicate) XCTAssertEqual(predicateResults.count, expectedCount) } private func assertIndexMatching(_ query: ((Query) -> Query), expectedIndex: Int?) { let indexOf = results.index(matching: query) XCTAssertEqual(indexOf, expectedIndex) } private func assertDistinct(for keyPath: PartialKeyPath, count expectedCount: Int) { let distincts = results.distinct(by: [_name(for: keyPath)]) XCTAssertEqual(distincts.count, expectedCount) } private func assertSort(for keyPath: PartialKeyPath, value expectedValue: F.ValueType) { let sortDescriptor = SortDescriptor(keyPath: _name(for: keyPath), ascending: true) let results: Results = results.sorted(by: [sortDescriptor]) let sortValue = results.first![keyPath: keyPath] as! F.ValueType XCTAssertEqual(sortValue, expectedValue) } } class CustomColumnResultsAggregatesTest: CustomColumnResultsTestBase where O.Root == F.Root, F.ValueType: Equatable { // MARK: - Aggregates func testCustomColumnResultsAggregateAvg() throws { // Sort by KeyPath for (keyPath, value) in F.average { assertAverage(for: keyPath, value: value) } } func testCustomColumnResultsAggregateSum() throws { // Sum by KeyPath for (keyPath, value) in F.sum { assertSum(for: keyPath, value: value) } } func testCustomColumnResultsAggregateMax() throws { // Max by KeyPath for (keyPath, value) in F.max { assertMax(for: keyPath, value: value) } } func testCustomColumnResultsAggregateMin() throws { // Min by KeyPath for (keyPath, value) in F.min { assertMin(for: keyPath, value: value) } } // MARK: - Private private func assertAverage(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(results.average(of: keyPath), value) let average: F.ValueType? = results.average(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertSum(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(results.sum(of: keyPath), value) let average: F.ValueType? = results.sum(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertMax(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(results.max(of: keyPath), value) let average: F.ValueType? = results.max(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertMin(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(results.min(of: keyPath), value) let average: F.ValueType? = results.min(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } } class CustomColumnResultsSectionedTest: CustomColumnResultsTestBase where O.Root == F.Root { // MARK: - Sectioned func testCustomColumnSectionedResults() throws { for (keyPath, sectionCount) in F.sectioned { let sectioned = results.sectioned(by: keyPath, ascending: true) XCTAssertEqual(sectioned.allKeys.count, sectionCount) for section in sectioned { XCTAssertEqual(section.count, 1) } } } } class CustomColumnObjectTest: CustomColumnTestsBase where O: ObjectBase, O.Root == F.Root, F.ValueType: Equatable { // MARK: - Subscript func testCustomColumnObjectKVC() throws { for (keyPath, value) in F.propertyValues { try setValue(value, for: keyPath) } } // MARK: - Dynamic Listing func testCustomColumnObjectDynamicListing() throws { for (keyPath, count) in F.dynamicListProperty { XCTAssertEqual(object.dynamicList(_name(for: keyPath)).count, count) } } func testCustomColumnObjectDynamicMutableSet() throws { for (keyPath, count) in F.dynamicMutableSetProperty { XCTAssertEqual(object.dynamicMutableSet(_name(for: keyPath)).count, count) } } // MARK: - Observation func testCustomColumnObjectPropertyObservation() throws { for (keyPath, value) in F.propertyValues { let ex = XCTestExpectation(description: "Notification to be called") let notificationToken = object.observe(keyPaths: [keyPath]) { changes in switch changes { case .change(_, let propertyChanges): XCTAssertGreaterThan(propertyChanges.count, 0) ex.fulfill() default: XCTFail("No other changes are done to the object") } } notificationTokens.append(notificationToken) try setValue(value, for: keyPath) wait(for: [ex], timeout: 1.0) } } // MARK: - Private private func assertObjectGetKVCProperty(for keyPath: KeyPath) { let value: F.ValueType = object[_name(for: keyPath)] as! F.ValueType XCTAssertNotNil(value) } } class CustomColumnKeyedObjectTest: CustomColumnTestsBase where O: ObjectBase, O.Root == F.Root, F.ValueType: RealmKeyedCollection { func testCustomColumnObjectDynamicMap() throws { func testCustomColumnObjectDynamicMap() throws { for (keyPath, count) in F.dynamicMutableSetProperty { let map: Map = object.dynamicMap(_name(for: keyPath)) XCTAssertEqual(map.count, count) } } } } class CustomColumnListTest: CustomColumnTestsBase where O.Root == F.Root, F.ValueType: RealmCollectionValue { var list: List! override func setUp() { super.setUp() var listObjects: [O.Root] = [] for _ in 0...2 { let newObject = O.create(primaryKey: ObjectId.generate(), nestedObject: O.create(primaryKey: ObjectId.generate(), nestedObject: nil)) for (keyPath, value) in F.keyPaths { newObject[_name(for: keyPath)] = value } listObjects.append(newObject) } try! realm.write { object[_name(for: F.listKeyPath)] = listObjects } list = object[_name(for: F.listKeyPath)] as? List } override func tearDown() { list = nil super.tearDown() } // MARK: - ValueForKey func testCustomColumnListGetValueForKey() throws { for (keyPath, value) in F.keyPaths { let valuesArray: [AnyObject] = list.value(forKey: _name(for: keyPath)) let expectedArray = valuesArray as! [F.ValueType] XCTAssertEqual(expectedArray.count, 3) XCTAssertEqual(expectedArray.first!, value) let valuesArrayKeyPath: [AnyObject] = list.value(forKeyPath: _name(for: keyPath)) let expectedArrayKeyPath = valuesArrayKeyPath as! [F.ValueType] XCTAssertEqual(expectedArrayKeyPath.count, 3) XCTAssertEqual(expectedArrayKeyPath.first!, value) } } } class CustomColumnSetTest: CustomColumnTestsBase where O.Root == F.Root, F.ValueType: RealmCollectionValue { var set: MutableSet! override func setUp() { super.setUp() let setObjects: MutableSet = MutableSet() for _ in 0...2 { let newObject = O.create(primaryKey: ObjectId.generate(), nestedObject: O.create(primaryKey: ObjectId.generate(), nestedObject: nil)) for (keyPath, value) in F.keyPaths { newObject[_name(for: keyPath)] = value } setObjects.insert(newObject) } try! realm.write { object[_name(for: F.setKeyPath)] = setObjects } set = object[_name(for: F.setKeyPath)] as? MutableSet } override func tearDown() { set = nil super.tearDown() } // MARK: - ValueForKey func testCustomColumnListGetValueForKey() throws { for (keyPath, value) in F.keyPaths { let valuesSet: [AnyObject] = set.value(forKey: _name(for: keyPath)) let expectedSet = valuesSet as! [F.ValueType] XCTAssertEqual(expectedSet.count, 1) XCTAssertEqual(expectedSet.first!, value) } } } class CustomColumnMapTestBase: CustomColumnTestsBase where O.Root == F.Root { var map: Map! override func setUp() { super.setUp() let mapObjects: Map = Map() for (key, (keyPath, value)) in F.keyValues { let newObject = O.create(primaryKey: ObjectId.generate(), nestedObject: O.create(primaryKey: ObjectId.generate(), nestedObject: nil)) newObject[_name(for: keyPath)] = value mapObjects[key] = newObject } try! realm.write { object[_name(for: F.mapKeyPath)] = mapObjects } map = object[_name(for: F.mapKeyPath)] as? Map } override func tearDown() { map = nil super.tearDown() } } class CustomColumnMapTest: CustomColumnMapTestBase where O.Root == F.Root, F.ValueType: Equatable { // MARK: - ValueForKey func testCustomColumnMapGetValueForKey() throws { for (keyPath, value) in F.keyPaths { let valuesMap: AnyObject? = map.value(forKey: "key0") let expectedValue = valuesMap as! O.Root XCTAssertEqual(expectedValue[_name(for: keyPath)] as! F.ValueType, value) } } func testCustomColumnMapSetValueForKey() throws { for (keyPath, value) in F.values { try realm.write { let newObject = O.create(primaryKey: ObjectId.generate(), nestedObject: O.create(primaryKey: ObjectId.generate(), nestedObject: nil)) newObject[_name(for: keyPath)] = value map.setValue(newObject, forKey: "key0") } let valuesMapKeyPath: AnyObject? = map.value(forKeyPath: "key0") let expectedKeyPathValue = valuesMapKeyPath as! O.Root XCTAssertEqual(expectedKeyPathValue[_name(for: keyPath)] as! F.ValueType, value) } } // MARK: - Observation func testCustomColumnMapPropertyObservation() throws { for (keyPath, value) in F.values { let ex = XCTestExpectation(description: "Notification to be called") let notificationToken = map.observe(keyPaths: [_name(for: keyPath)]) { changes in switch changes { case .update(_, _, _, let mapChanges): XCTAssertEqual(mapChanges.count, 1) ex.fulfill() case .initial: break default: XCTFail("No other changes are done to the object") } } notificationTokens.append(notificationToken) try realm.write { let map = object[_name(for: F.mapKeyPath)] as! Map let mapObject = map["key0"] mapObject!![_name(for: keyPath)] = value } wait(for: [ex], timeout: 1.0) } } func testCustomColumnMapSortedObservation() throws { for (keyPath, value) in F.sort { let mapSorted: Results = map.sorted(byKeyPath: _name(for: keyPath), ascending: true) XCTAssertEqual(mapSorted.first!![_name(for: keyPath)] as! F.ValueType, value) } } } class CustomColumnAggregatesMapTest: CustomColumnMapTestBase where O.Root == F.Root, F.ValueType: RealmCollectionValue { // MARK: - Aggregates func testCustomColumnResultsAggregateAvg() throws { // Sort by KeyPath for (keyPath, value) in F.average { assertAverage(for: keyPath, value: value) } } func testCustomColumnResultsAggregateSum() throws { // Sum by KeyPath for (keyPath, value) in F.sum { assertSum(for: keyPath, value: value) } } func testCustomColumnResultsAggregateMax() throws { // Max by KeyPath for (keyPath, value) in F.max { assertMax(for: keyPath, value: value) } } func testCustomColumnResultsAggregateMin() throws { // Min by KeyPath for (keyPath, value) in F.min { assertMin(for: keyPath, value: value) } } // MARK: - Private private func assertAverage(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(map.average(of: keyPath), value) let average: F.ValueType? = map.average(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertSum(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(map.sum(of: keyPath), value) let average: F.ValueType? = map.sum(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertMax(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(map.max(of: keyPath), value) let average: F.ValueType? = map.max(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } private func assertMin(for keyPath: KeyPath, value: F.ValueType) { XCTAssertEqual(map.min(of: keyPath), value) let average: F.ValueType? = map.min(ofProperty: _name(for: keyPath)) XCTAssertEqual(average, value) } } class CustomObjectTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "CustomColumnNameTests") // MARK: - Results // ModernCustomObject CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnResultsTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - Results Aggregates // ModernCustomObject CustomColumnResultsAggregatesTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnResultsAggregatesTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - Results Sectioned // ModernCustomObject CustomColumnResultsSectionedTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnResultsSectionedTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - Object // ModernCustomObject CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnObjectTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - Object Keyed Property // ModernCustomObject CustomColumnKeyedObjectTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnKeyedObjectTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - List // ModernCustomObject CustomColumnListTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnListTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - MutableSet // ModernCustomObject CustomColumnSetTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnSetTest.defaultTestSuite.tests.forEach(suite.addTest) // MARK: - Map // ModernCustomObject CustomColumnMapTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnAggregatesMapTest.defaultTestSuite.tests.forEach(suite.addTest) // OldCustomObject CustomColumnMapTest.defaultTestSuite.tests.forEach(suite.addTest) CustomColumnAggregatesMapTest.defaultTestSuite.tests.forEach(suite.addTest) return suite } } // MARK: - Custom Column Tests Factory protocol CustomColumnTypeFactoryBase { associatedtype Root associatedtype ValueType static var keyPaths: [KeyPath: ValueType] { get } static var listKeyPath: PartialKeyPath { get } static var setKeyPath: PartialKeyPath { get } static var mapKeyPath: PartialKeyPath { get } } protocol CustomColumnResultsTypeFactory: CustomColumnTypeFactoryBase { static var query: [KeyPath: (Query) -> Query] { get } static var distincts: [KeyPath: Int] { get } static var sort: [KeyPath: ValueType] { get } static var values: [KeyPath: ValueType] { get } } protocol CustomColumnAggregatesTypeFactory: CustomColumnTypeFactoryBase where ValueType: _HasPersistedType, ValueType.PersistedType: AddableType & MinMaxType { // Nested keyPaths are not available in Aggregates static var average: [KeyPath: ValueType] { get } static var sum: [KeyPath: ValueType] { get } static var max: [KeyPath: ValueType] { get } static var min: [KeyPath: ValueType] { get } } protocol CustomColumnResultsSectionedTypeFactory: CustomColumnTypeFactoryBase where ValueType: _Persistable & Hashable { static var sectioned: [KeyPath: Int] { get } } protocol ObjectCustomColumnObjectTypeFactory: CustomColumnTypeFactoryBase { // Nested keyPaths are not available in Object Subscripts/Observation static var propertyValues: [KeyPath: ValueType] { get } static var dynamicListProperty: [KeyPath: Int] { get } static var dynamicMutableSetProperty: [KeyPath: Int] { get } } protocol CustomColumnMapTypeBaseFactory: CustomColumnTypeFactoryBase { static var keyValues: [String: (KeyPath, ValueType)] { get } } protocol CustomColumnObjectKeyedTypeFactory: CustomColumnTypeFactoryBase where ValueType: RealmKeyedCollection { static var dynamicMapValue: [KeyPath: Int] { get } } protocol CustomColumnMapTypeFactory: CustomColumnMapTypeBaseFactory { static var values: [KeyPath: ValueType] { get } static var sort: [KeyPath: ValueType] { get } } protocol CustomColumnMapAggregatesTypeFactory: CustomColumnMapTypeBaseFactory, CustomColumnAggregatesTypeFactory {} // MARK: - ModernCustom Object Factory struct ModernResultsIntType: CustomColumnResultsTypeFactory { typealias Root = ModernCustomObject typealias ValueType = Int static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\ModernCustomObject.intCol: 123, \ModernCustomObject.objectCol!.intCol: 345, \ModernCustomObject.embeddedObject!.intCol: 567, \ModernCustomObject.objectCol!.embeddedObject!.intCol: 789] } static var query: [KeyPath: (Query) -> Query] { [\ModernCustomObject.intCol: { $0.intCol == 123 }, \ModernCustomObject.objectCol!.intCol: { $0.objectCol.intCol == 345 }, \ModernCustomObject.embeddedObject!.intCol: { $0.embeddedObject.intCol == 567 }, \ModernCustomObject.objectCol!.embeddedObject!.intCol: { $0.objectCol.embeddedObject.intCol == 789 }] } static var distincts: [KeyPath: Int] { [\ModernCustomObject.intCol: 2, \ModernCustomObject.objectCol!.intCol: 1, \ModernCustomObject.embeddedObject!.intCol: 2, \ModernCustomObject.objectCol!.embeddedObject!.intCol: 1] } static var sort: [KeyPath: Int] { [\ModernCustomObject.intCol: 0, \ModernCustomObject.objectCol!.intCol: 0, \ModernCustomObject.embeddedObject!.intCol: 0, \ModernCustomObject.objectCol!.embeddedObject!.intCol: 0] } static var propertyValues: [KeyPath: Int] { [\ModernCustomObject.intCol: 111, \ModernCustomObject.objectCol!.intCol: 999] } } extension ModernResultsIntType: CustomColumnAggregatesTypeFactory { // Nested keyPaths are not available in Aggregates static var average: [KeyPath: Int] { [\ModernCustomObject.intCol: 234] } static var sum: [KeyPath: Int] { [\ModernCustomObject.intCol: 468] } static var max: [KeyPath: Int] { [\ModernCustomObject.intCol: 345] } static var min: [KeyPath: Int] { [\ModernCustomObject.intCol: 123] } } extension ModernResultsIntType: CustomColumnResultsSectionedTypeFactory { static var sectioned: [KeyPath: Int] { [\ModernCustomObject.intCol: 2] } } extension ModernResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath: Int] { [\ModernCustomObject.intCol: 256] } static var dynamicListProperty: [KeyPath: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath: Int] { [:] } // Not Applicable } struct ModernListResultsIntType: CustomColumnResultsTypeFactory { typealias Root = ModernCustomObject typealias ValueType = List static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: List] { let list = List() list.append(objectsIn: [123, 345, 567, 789, 901]) return [\ModernCustomObject.arrayIntCol: list] } static var query: [KeyPath>: (Query) -> Query] { [\ModernCustomObject.arrayIntCol: { $0.arrayIntCol.contains(123) }] } static var distincts: [KeyPath>: Int] { [:] } // Not Applicable static var sort: [KeyPath>: ValueType] { [:] } // Not Applicable static var propertyValues: [KeyPath>: List] { let list = List() list.append(objectsIn: [111, 222, 333, 444]) return [\ModernCustomObject.arrayIntCol: list] } } extension ModernListResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: List] { let list = List() list.append(objectsIn: [987, 765, 543, 321]) return [\ModernCustomObject.arrayIntCol: list] } static var dynamicListProperty: [KeyPath>: Int] { [\ModernCustomObject.arrayIntCol: 5] } static var dynamicMutableSetProperty: [KeyPath>: Int] { [:] } // Not Applicable } struct ModernMutableSetResultsIntType: CustomColumnResultsTypeFactory { typealias Root = ModernCustomObject typealias ValueType = MutableSet static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: MutableSet] { let set = MutableSet() set.insert(objectsIn: [123, 345, 567, 789, 901]) return [\ModernCustomObject.setIntCol: set] } static var query: [KeyPath>: (Query) -> Query] { [\ModernCustomObject.setIntCol: { $0.setIntCol.contains(123) }] } static var distincts: [KeyPath: Int] { [:] } // Not Applicable static var sort: [KeyPath: ValueType] { [:] } // Not Applicable static var propertyValues: [KeyPath>: MutableSet] { let set = MutableSet() set.insert(objectsIn: [111, 222, 333, 444]) return [\ModernCustomObject.setIntCol: set] } } extension ModernMutableSetResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: MutableSet] { let set = MutableSet() set.insert(objectsIn: [987, 765, 543, 321]) return [\ModernCustomObject.setIntCol: set] } static var dynamicListProperty: [KeyPath>: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath>: Int] { [\ModernCustomObject.setIntCol: 5] } } struct ModernMapResultsIntType: CustomColumnResultsTypeFactory { typealias Root = ModernCustomObject typealias ValueType = Map static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: Map] { let map = Map() map["key"] = 123 map["key1"] = 345 map["key3"] = 567 map["key3"] = 879 return [\ModernCustomObject.mapIntCol: map] } static var query: [KeyPath: (Query) -> Query] { [\ModernCustomObject.mapIntCol: { $0.mapIntCol.keys.contains("key") }] } static var distincts: [KeyPath>: Int] { [:] } // Not Applicable static var sort: [KeyPath>: Map] { [:] } // Not Applicable static var propertyValues: [KeyPath>: Map] { let map = Map() map["key"] = 111 map["key1"] = 222 map["key3"] = 333 map["key3"] = 444 return [\ModernCustomObject.mapIntCol: map] } } extension ModernMapResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: Map] { let map = Map() map["key"] = 123 map["key1"] = 345 map["key3"] = 567 map["key3"] = 879 return [\ModernCustomObject.mapIntCol: map] } static var dynamicListProperty: [KeyPath>: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath>: Int] { [:] } // Not Applicable } extension ModernMapResultsIntType: CustomColumnObjectKeyedTypeFactory { static var dynamicMapValue: [KeyPath>: Int] { [\ModernCustomObject.mapIntCol: 5] } } struct ModernListIntType: CustomColumnTypeFactoryBase { typealias Root = ModernCustomObject typealias ValueType = Int static var listKeyPath: PartialKeyPath { \.arrayCol } static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\ModernCustomObject.intCol: 102] } } struct ModernSetIntType: CustomColumnTypeFactoryBase { typealias Root = ModernCustomObject typealias ValueType = Int static var setKeyPath: PartialKeyPath { \.setCol } static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\ModernCustomObject.intCol: 938] } } struct ModernMapIntType: CustomColumnMapTypeFactory { typealias Root = ModernCustomObject typealias ValueType = Int static var mapKeyPath: PartialKeyPath { \.mapCol } static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyValues: [String: (KeyPath, Int)] { ["key0": (\ModernCustomObject.intCol, 938), "key1": (\ModernCustomObject.intCol, 588), "key2": (\ModernCustomObject.intCol, 610)] } static var keyPaths: [KeyPath: Int] { [\ModernCustomObject.intCol: 938] } static var values: [KeyPath: Int] { [\ModernCustomObject.intCol: 1234] } static var sort: [KeyPath: Int] { [\ModernCustomObject.intCol: 588] } } extension ModernMapIntType: CustomColumnMapAggregatesTypeFactory { static var average: [KeyPath: Int] { [\ModernCustomObject.intCol: 712] } static var sum: [KeyPath: Int] { [\ModernCustomObject.intCol: 2136] } static var max: [KeyPath: Int] { [\ModernCustomObject.intCol: 938] } static var min: [KeyPath: Int] { [\ModernCustomObject.intCol: 588] } } // MARK: - Old Object Factory struct OldResultsIntType: CustomColumnResultsTypeFactory { typealias Root = OldCustomObject typealias ValueType = Int static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\OldCustomObject.intCol: 12, \OldCustomObject.objectCol!.intCol: 24, \OldCustomObject.embeddedObject!.intCol: 36, \OldCustomObject.objectCol!.embeddedObject!.intCol: 48] } static var query: [KeyPath: (Query) -> Query] { [\OldCustomObject.intCol: { $0.intCol == 12 }, \OldCustomObject.objectCol!.intCol: { $0.objectCol.intCol == 24 }, \OldCustomObject.embeddedObject!.intCol: { $0.embeddedObject.intCol == 36 }, \OldCustomObject.objectCol!.embeddedObject!.intCol: { $0.objectCol.embeddedObject.intCol == 48 }] } static var distincts: [KeyPath: Int] { [\OldCustomObject.intCol: 2, \OldCustomObject.objectCol!.intCol: 1, \OldCustomObject.embeddedObject!.intCol: 2, \OldCustomObject.objectCol!.embeddedObject!.intCol: 1] } static var sort: [KeyPath: Int] { [\OldCustomObject.intCol: 0, \OldCustomObject.objectCol!.intCol: 0, \OldCustomObject.embeddedObject!.intCol: 0, \OldCustomObject.objectCol!.embeddedObject!.intCol: 0] } static var propertyValues: [KeyPath: Int] { [\OldCustomObject.intCol: 111] } } extension OldResultsIntType: CustomColumnAggregatesTypeFactory { // Nested keyPaths are not available in Aggregates static var average: [KeyPath: Int] { [\OldCustomObject.intCol: 18] } static var sum: [KeyPath: Int] { [\OldCustomObject.intCol: 36] } static var max: [KeyPath: Int] { [\OldCustomObject.intCol: 24] } static var min: [KeyPath: Int] { [\OldCustomObject.intCol: 12] } } extension OldResultsIntType: CustomColumnResultsSectionedTypeFactory { static var sectioned: [KeyPath: Int] { [\OldCustomObject.intCol: 2] } } extension OldResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath: Int] { [\OldCustomObject.intCol: 96] } static var dynamicListProperty: [KeyPath: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath: Int] { [:] } // Not Applicable } struct OldListResultsIntType: CustomColumnResultsTypeFactory { typealias Root = OldCustomObject typealias ValueType = List static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: List] { let list = List() list.append(objectsIn: [56, 12, 34, 67]) return [\OldCustomObject.arrayIntCol: list] } static var query: [KeyPath>: (Query) -> Query] { [\OldCustomObject.arrayIntCol: { $0.arrayIntCol.contains(34) }] } static var distincts: [KeyPath>: Int] { [:] } // Not Applicable static var sort: [KeyPath>: ValueType] { [:] } // Not Applicable static var propertyValues: [KeyPath>: List] { let list = List() list.append(objectsIn: [99, 88, 22, 11]) return [\OldCustomObject.arrayIntCol: list] } static var valueKeyPath: [KeyPath>: List] { let list = List() list.append(objectsIn: [56, 12, 34, 67]) return [\OldCustomObject.arrayIntCol: list] } } extension OldListResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: List] { let list = List() list.append(objectsIn: [43, 87, 23, 18]) return [\OldCustomObject.arrayIntCol: list] } static var dynamicListProperty: [KeyPath>: Int] { [\OldCustomObject.arrayIntCol: 4] } static var dynamicMutableSetProperty: [KeyPath>: Int] { [:] } // Not Applicable } struct OldMutableSetResultsIntType: CustomColumnResultsTypeFactory { typealias Root = OldCustomObject typealias ValueType = MutableSet static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: MutableSet] { let set = MutableSet() set.insert(objectsIn: [56, 93, 67, 22]) return [\OldCustomObject.setIntCol: set] } static var query: [KeyPath>: (Query) -> Query] { [\OldCustomObject.setIntCol: { $0.setIntCol.contains(22) }] } static var distincts: [KeyPath: Int] { [:] } // Not Applicable static var sort: [KeyPath: ValueType] { [:] } // Not Applicable static var propertyValues: [KeyPath>: MutableSet] { let set = MutableSet() set.insert(objectsIn: [67, 45, 27, 84]) return [\OldCustomObject.setIntCol: set] } } extension OldMutableSetResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: RealmSwift.MutableSet] { let set = MutableSet() set.insert(objectsIn: [23, 45, 36, 28]) return [\OldCustomObject.setIntCol: set] } static var dynamicListProperty: [KeyPath>: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath>: Int] { [\OldCustomObject.setIntCol: 4] } } struct OldMapResultsIntType: CustomColumnResultsTypeFactory { typealias Root = OldCustomObject typealias ValueType = Map static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath>: Map] { let map = Map() map["key"] = 123 map["key1"] = 345 map["key3"] = 567 map["key3"] = 879 return [\OldCustomObject.mapIntCol: map] } static var query: [KeyPath: (Query) -> Query] { [\OldCustomObject.mapIntCol: { $0.mapIntCol.keys.contains("key") }] } static var distincts: [KeyPath>: Int] { [:] } // Not Applicable static var sort: [KeyPath>: Map] { [:] } // Not Applicable static var propertyValues: [KeyPath>: Map] { let map = Map() map["key"] = 111 map["key1"] = 222 map["key3"] = 333 map["key3"] = 444 return [\OldCustomObject.mapIntCol: map] } } extension OldMapResultsIntType: ObjectCustomColumnObjectTypeFactory { static var values: [KeyPath>: Map] { let map = Map() map["key"] = 123 map["key1"] = 345 map["key3"] = 567 map["key3"] = 879 return [\OldCustomObject.mapIntCol: map] } static var dynamicListProperty: [KeyPath>: Int] { [:] } // Not Applicable static var dynamicMutableSetProperty: [KeyPath>: Int] { [:] } // Not Applicable } extension OldMapResultsIntType: CustomColumnObjectKeyedTypeFactory { static var dynamicMapValue: [KeyPath>: Int] { [\OldCustomObject.mapIntCol: 4] } } struct OldListIntType: CustomColumnTypeFactoryBase { typealias Root = OldCustomObject typealias ValueType = Int static var listKeyPath: PartialKeyPath { \.arrayCol } static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\OldCustomObject.intCol: 102] } } struct OldSetIntType: CustomColumnTypeFactoryBase { typealias Root = OldCustomObject typealias ValueType = Int static var setKeyPath: PartialKeyPath { \.setCol } static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var mapKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyPaths: [KeyPath: Int] { [\OldCustomObject.intCol: 938] } } struct OldMapIntType: CustomColumnMapTypeFactory { typealias Root = OldCustomObject typealias ValueType = Int static var mapKeyPath: PartialKeyPath { \.mapCol } static var listKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var setKeyPath: PartialKeyPath { fatalError() } // Not Applicable static var keyValues: [String: (KeyPath, Int)] { ["key0": (\OldCustomObject.intCol, 938), "key1": (\OldCustomObject.intCol, 588), "key2": (\OldCustomObject.intCol, 610)] } static var keyPaths: [KeyPath: Int] { [\OldCustomObject.intCol: 938] } static var values: [KeyPath: Int] { [\OldCustomObject.intCol: 1234] } static var sort: [KeyPath: Int] { [\OldCustomObject.intCol: 588] } } extension OldMapIntType: CustomColumnMapAggregatesTypeFactory { static var average: [KeyPath: Int] { [\OldCustomObject.intCol: 712] } static var sum: [KeyPath: Int] { [\OldCustomObject.intCol: 2136] } static var max: [KeyPath: Int] { [\OldCustomObject.intCol: 938] } static var min: [KeyPath: Int] { [\OldCustomObject.intCol: 588] } } ================================================ FILE: RealmSwift/Tests/CustomObjectCreationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift import Realm.Private private func mapValues(_ values: [T]) -> [String: T] { var map = [String: T]() for (i, v) in values.enumerated() { map["\(i)"] = v } return map } private func objectValues() -> [ModernEmbeddedObject] { return [.init(value: [1]), .init(value: [2]), .init(value: [3])] } private func objectWrapperValues() -> [EmbeddedObjectWrapper] { return objectValues().map(EmbeddedObjectWrapper.init) } class CustomObjectCreationTests: TestCase { var rawValues: [String: Any]! var wrappedValues: [String: Any]! var nilOptionalValues: [String: Any]! override func setUp() { rawValues = [ "bool": Bool.values().last!, "int": Int.values().last!, "int8": Int8.values().last!, "int16": Int16.values().last!, "int32": Int32.values().last!, "int64": Int64.values().last!, "float": Float.values().last!, "double": Double.values().last!, "string": String.values().last!, "binary": Data.values().last!, "date": Date.values().last!, "decimal": Decimal128.values().last!, "objectId": ObjectId.values().last!, "uuid": UUID.values().last!, "object": objectValues().last!, "optBool": Bool.values().last!, "optInt": Int.values().last!, "optInt8": Int8.values().last!, "optInt16": Int16.values().last!, "optInt32": Int32.values().last!, "optInt64": Int64.values().last!, "optFloat": Float.values().last!, "optDouble": Double.values().last!, "optString": String.values().last!, "optBinary": Data.values().last!, "optDate": Date.values().last!, "optDecimal": Decimal128.values().last!, "optObjectId": ObjectId.values().last!, "optUuid": UUID.values().last!, "optObject": objectValues().last!, "listBool": Bool.values(), "listInt": Int.values(), "listInt8": Int8.values(), "listInt16": Int16.values(), "listInt32": Int32.values(), "listInt64": Int64.values(), "listFloat": Float.values(), "listDouble": Double.values(), "listString": String.values(), "listBinary": Data.values(), "listDate": Date.values(), "listDecimal": Decimal128.values(), "listUuid": UUID.values(), "listObjectId": ObjectId.values(), "listObject": objectValues(), "listOptBool": Bool?.values(), "listOptInt": Int?.values(), "listOptInt8": Int8?.values(), "listOptInt16": Int16?.values(), "listOptInt32": Int32?.values(), "listOptInt64": Int64?.values(), "listOptFloat": Float?.values(), "listOptDouble": Double?.values(), "listOptString": String?.values(), "listOptBinary": Data?.values(), "listOptDate": Date?.values(), "listOptDecimal": Decimal128?.values(), "listOptUuid": UUID?.values(), "listOptObjectId": ObjectId?.values(), "setBool": Bool.values(), "setInt": Int.values(), "setInt8": Int8.values(), "setInt16": Int16.values(), "setInt32": Int32.values(), "setInt64": Int64.values(), "setFloat": Float.values(), "setDouble": Double.values(), "setString": String.values(), "setBinary": Data.values(), "setDate": Date.values(), "setDecimal": Decimal128.values(), "setUuid": UUID.values(), "setObjectId": ObjectId.values(), "setOptBool": Bool?.values(), "setOptInt": Int?.values(), "setOptInt8": Int8?.values(), "setOptInt16": Int16?.values(), "setOptInt32": Int32?.values(), "setOptInt64": Int64?.values(), "setOptFloat": Float?.values(), "setOptDouble": Double?.values(), "setOptString": String?.values(), "setOptBinary": Data?.values(), "setOptDate": Date?.values(), "setOptDecimal": Decimal128?.values(), "setOptUuid": UUID?.values(), "setOptObjectId": ObjectId?.values(), "mapBool": mapValues(Bool.values()), "mapInt": mapValues(Int.values()), "mapInt8": mapValues(Int8.values()), "mapInt16": mapValues(Int16.values()), "mapInt32": mapValues(Int32.values()), "mapInt64": mapValues(Int64.values()), "mapFloat": mapValues(Float.values()), "mapDouble": mapValues(Double.values()), "mapString": mapValues(String.values()), "mapBinary": mapValues(Data.values()), "mapDate": mapValues(Date.values()), "mapDecimal": mapValues(Decimal128.values()), "mapUuid": mapValues(UUID.values()), "mapObjectId": mapValues(ObjectId.values()), "mapObject": mapValues(objectValues()), "mapOptBool": mapValues(Bool?.values()), "mapOptInt": mapValues(Int?.values()), "mapOptInt8": mapValues(Int8?.values()), "mapOptInt16": mapValues(Int16?.values()), "mapOptInt32": mapValues(Int32?.values()), "mapOptInt64": mapValues(Int64?.values()), "mapOptFloat": mapValues(Float?.values()), "mapOptDouble": mapValues(Double?.values()), "mapOptString": mapValues(String?.values()), "mapOptBinary": mapValues(Data?.values()), "mapOptDate": mapValues(Date?.values()), "mapOptDecimal": mapValues(Decimal128?.values()), "mapOptUuid": mapValues(UUID?.values()), "mapOptObjectId": mapValues(ObjectId?.values()), "mapOptObject": mapValues(objectValues()) ] wrappedValues = [ "bool": BoolWrapper.values().last!, "int": IntWrapper.values().last!, "int8": Int8Wrapper.values().last!, "int16": Int16Wrapper.values().last!, "int32": Int32Wrapper.values().last!, "int64": Int64Wrapper.values().last!, "float": FloatWrapper.values().last!, "double": DoubleWrapper.values().last!, "string": StringWrapper.values().last!, "binary": DataWrapper.values().last!, "date": DateWrapper.values().last!, "decimal": Decimal128Wrapper.values().last!, "objectId": ObjectIdWrapper.values().last!, "uuid": UUIDWrapper.values().last!, "object": objectWrapperValues().last!, "optBool": BoolWrapper.values().last!, "optInt": IntWrapper.values().last!, "optInt8": Int8Wrapper.values().last!, "optInt16": Int16Wrapper.values().last!, "optInt32": Int32Wrapper.values().last!, "optInt64": Int64Wrapper.values().last!, "optFloat": FloatWrapper.values().last!, "optDouble": DoubleWrapper.values().last!, "optString": StringWrapper.values().last!, "optBinary": DataWrapper.values().last!, "optDate": DateWrapper.values().last!, "optDecimal": Decimal128Wrapper.values().last!, "optObjectId": ObjectIdWrapper.values().last!, "optUuid": UUIDWrapper.values().last!, "optObject": objectWrapperValues().last!, "listBool": BoolWrapper.values(), "listInt": IntWrapper.values(), "listInt8": Int8Wrapper.values(), "listInt16": Int16Wrapper.values(), "listInt32": Int32Wrapper.values(), "listInt64": Int64Wrapper.values(), "listFloat": FloatWrapper.values(), "listDouble": DoubleWrapper.values(), "listString": StringWrapper.values(), "listBinary": DataWrapper.values(), "listDate": DateWrapper.values(), "listDecimal": Decimal128Wrapper.values(), "listUuid": UUIDWrapper.values(), "listObjectId": ObjectIdWrapper.values(), "listObject": objectWrapperValues(), "listOptBool": BoolWrapper?.values(), "listOptInt": IntWrapper?.values(), "listOptInt8": Int8Wrapper?.values(), "listOptInt16": Int16Wrapper?.values(), "listOptInt32": Int32Wrapper?.values(), "listOptInt64": Int64Wrapper?.values(), "listOptFloat": FloatWrapper?.values(), "listOptDouble": DoubleWrapper?.values(), "listOptString": StringWrapper?.values(), "listOptBinary": DataWrapper?.values(), "listOptDate": DateWrapper?.values(), "listOptDecimal": Decimal128Wrapper?.values(), "listOptUuid": UUIDWrapper?.values(), "listOptObjectId": ObjectIdWrapper?.values(), "setBool": BoolWrapper.values(), "setInt": IntWrapper.values(), "setInt8": Int8Wrapper.values(), "setInt16": Int16Wrapper.values(), "setInt32": Int32Wrapper.values(), "setInt64": Int64Wrapper.values(), "setFloat": FloatWrapper.values(), "setDouble": DoubleWrapper.values(), "setString": StringWrapper.values(), "setBinary": DataWrapper.values(), "setDate": DateWrapper.values(), "setDecimal": Decimal128Wrapper.values(), "setUuid": UUIDWrapper.values(), "setObjectId": ObjectIdWrapper.values(), "setOptBool": BoolWrapper?.values(), "setOptInt": IntWrapper?.values(), "setOptInt8": Int8Wrapper?.values(), "setOptInt16": Int16Wrapper?.values(), "setOptInt32": Int32Wrapper?.values(), "setOptInt64": Int64Wrapper?.values(), "setOptFloat": FloatWrapper?.values(), "setOptDouble": DoubleWrapper?.values(), "setOptString": StringWrapper?.values(), "setOptBinary": DataWrapper?.values(), "setOptDate": DateWrapper?.values(), "setOptDecimal": Decimal128Wrapper?.values(), "setOptUuid": UUIDWrapper?.values(), "setOptObjectId": ObjectIdWrapper?.values(), "mapBool": mapValues(BoolWrapper.values()), "mapInt": mapValues(IntWrapper.values()), "mapInt8": mapValues(Int8Wrapper.values()), "mapInt16": mapValues(Int16Wrapper.values()), "mapInt32": mapValues(Int32Wrapper.values()), "mapInt64": mapValues(Int64Wrapper.values()), "mapFloat": mapValues(FloatWrapper.values()), "mapDouble": mapValues(DoubleWrapper.values()), "mapString": mapValues(StringWrapper.values()), "mapBinary": mapValues(DataWrapper.values()), "mapDate": mapValues(DateWrapper.values()), "mapDecimal": mapValues(Decimal128Wrapper.values()), "mapUuid": mapValues(UUIDWrapper.values()), "mapObjectId": mapValues(ObjectIdWrapper.values()), "mapObject": mapValues(objectWrapperValues()), "mapOptBool": mapValues(BoolWrapper?.values()), "mapOptInt": mapValues(IntWrapper?.values()), "mapOptInt8": mapValues(Int8Wrapper?.values()), "mapOptInt16": mapValues(Int16Wrapper?.values()), "mapOptInt32": mapValues(Int32Wrapper?.values()), "mapOptInt64": mapValues(Int64Wrapper?.values()), "mapOptFloat": mapValues(FloatWrapper?.values()), "mapOptDouble": mapValues(DoubleWrapper?.values()), "mapOptString": mapValues(StringWrapper?.values()), "mapOptBinary": mapValues(DataWrapper?.values()), "mapOptDate": mapValues(DateWrapper?.values()), "mapOptDecimal": mapValues(Decimal128Wrapper?.values()), "mapOptUuid": mapValues(UUIDWrapper?.values()), "mapOptObjectId": mapValues(ObjectIdWrapper?.values()), "mapOptObject": mapValues(objectWrapperValues()) ] nilOptionalValues = [ "bool": BoolWrapper.values().last!, "int": IntWrapper.values().last!, "int8": Int8Wrapper.values().last!, "int16": Int16Wrapper.values().last!, "int32": Int32Wrapper.values().last!, "int64": Int64Wrapper.values().last!, "float": FloatWrapper.values().last!, "double": DoubleWrapper.values().last!, "string": StringWrapper.values().last!, "binary": DataWrapper.values().last!, "date": DateWrapper.values().last!, "decimal": Decimal128Wrapper.values().last!, "objectId": ObjectIdWrapper.values().last!, "uuid": UUIDWrapper.values().last!, "object": NSNull(), "optBool": NSNull(), "optInt": NSNull(), "optInt8": NSNull(), "optInt16": NSNull(), "optInt32": NSNull(), "optInt64": NSNull(), "optFloat": NSNull(), "optDouble": NSNull(), "optString": NSNull(), "optBinary": NSNull(), "optDate": NSNull(), "optDecimal": NSNull(), "optObjectId": NSNull(), "optUuid": NSNull(), "optObject": NSNull(), "listBool": BoolWrapper.values(), "listInt": IntWrapper.values(), "listInt8": Int8Wrapper.values(), "listInt16": Int16Wrapper.values(), "listInt32": Int32Wrapper.values(), "listInt64": Int64Wrapper.values(), "listFloat": FloatWrapper.values(), "listDouble": DoubleWrapper.values(), "listString": StringWrapper.values(), "listBinary": DataWrapper.values(), "listDate": DateWrapper.values(), "listDecimal": Decimal128Wrapper.values(), "listUuid": UUIDWrapper.values(), "listObjectId": ObjectIdWrapper.values(), "listObject": objectWrapperValues(), "listOptBool": [NSNull()], "listOptInt": [NSNull()], "listOptInt8": [NSNull()], "listOptInt16": [NSNull()], "listOptInt32": [NSNull()], "listOptInt64": [NSNull()], "listOptFloat": [NSNull()], "listOptDouble": [NSNull()], "listOptString": [NSNull()], "listOptBinary": [NSNull()], "listOptDate": [NSNull()], "listOptDecimal": [NSNull()], "listOptUuid": [NSNull()], "listOptObjectId": [NSNull()], "setBool": BoolWrapper.values(), "setInt": IntWrapper.values(), "setInt8": Int8Wrapper.values(), "setInt16": Int16Wrapper.values(), "setInt32": Int32Wrapper.values(), "setInt64": Int64Wrapper.values(), "setFloat": FloatWrapper.values(), "setDouble": DoubleWrapper.values(), "setString": StringWrapper.values(), "setBinary": DataWrapper.values(), "setDate": DateWrapper.values(), "setDecimal": Decimal128Wrapper.values(), "setUuid": UUIDWrapper.values(), "setObjectId": ObjectIdWrapper.values(), "setOptBool": [NSNull()], "setOptInt": [NSNull()], "setOptInt8": [NSNull()], "setOptInt16": [NSNull()], "setOptInt32": [NSNull()], "setOptInt64": [NSNull()], "setOptFloat": [NSNull()], "setOptDouble": [NSNull()], "setOptString": [NSNull()], "setOptBinary": [NSNull()], "setOptDate": [NSNull()], "setOptDecimal": [NSNull()], "setOptUuid": [NSNull()], "setOptObjectId": [NSNull()], "mapBool": mapValues(BoolWrapper.values()), "mapInt": mapValues(IntWrapper.values()), "mapInt8": mapValues(Int8Wrapper.values()), "mapInt16": mapValues(Int16Wrapper.values()), "mapInt32": mapValues(Int32Wrapper.values()), "mapInt64": mapValues(Int64Wrapper.values()), "mapFloat": mapValues(FloatWrapper.values()), "mapDouble": mapValues(DoubleWrapper.values()), "mapString": mapValues(StringWrapper.values()), "mapBinary": mapValues(DataWrapper.values()), "mapDate": mapValues(DateWrapper.values()), "mapDecimal": mapValues(Decimal128Wrapper.values()), "mapUuid": mapValues(UUIDWrapper.values()), "mapObjectId": mapValues(ObjectIdWrapper.values()), "mapObject": ["0": NSNull()], "mapOptBool": ["0": NSNull()], "mapOptInt": ["0": NSNull()], "mapOptInt8": ["0": NSNull()], "mapOptInt16": ["0": NSNull()], "mapOptInt32": ["0": NSNull()], "mapOptInt64": ["0": NSNull()], "mapOptFloat": ["0": NSNull()], "mapOptDouble": ["0": NSNull()], "mapOptString": ["0": NSNull()], "mapOptBinary": ["0": NSNull()], "mapOptDate": ["0": NSNull()], "mapOptDecimal": ["0": NSNull()], "mapOptUuid": ["0": NSNull()], "mapOptObjectId": ["0": NSNull()], "mapOptObject": ["0": NSNull()], ] super.setUp() } override func tearDown() { rawValues = nil wrappedValues = nil super.tearDown() } @nonobjc func verifyDefault(_ obj: AllCustomPersistableTypes) { XCTAssertEqual(obj.bool, BoolWrapper(value: .init())) XCTAssertEqual(obj.int, IntWrapper(value: .init())) XCTAssertEqual(obj.int8, Int8Wrapper(value: .init())) XCTAssertEqual(obj.int16, Int16Wrapper(value: .init())) XCTAssertEqual(obj.int32, Int32Wrapper(value: .init())) XCTAssertEqual(obj.int64, Int64Wrapper(value: .init())) XCTAssertEqual(obj.float, FloatWrapper(value: .init())) XCTAssertEqual(obj.double, DoubleWrapper(value: .init())) XCTAssertEqual(obj.string, StringWrapper(value: .init())) XCTAssertEqual(obj.binary, DataWrapper(value: .init())) XCTAssertEqual(obj.decimal, Decimal128Wrapper(value: .init())) XCTAssertEqual(obj.object, EmbeddedObjectWrapper(value: .init())) // Date and UUID default init generate new values each time XCTAssertEqual(obj.date.value.timeIntervalSince1970, DateWrapper(value: .init()).value.timeIntervalSince1970, accuracy: 1.0) XCTAssertNotEqual(obj.uuid, UUIDWrapper(value: .init())) XCTAssertNotEqual(obj.objectId, ObjectIdWrapper(value: .init())) XCTAssertEqual(obj.optBool, nil) XCTAssertEqual(obj.optInt, nil) XCTAssertEqual(obj.optInt8, nil) XCTAssertEqual(obj.optInt16, nil) XCTAssertEqual(obj.optInt32, nil) XCTAssertEqual(obj.optInt64, nil) XCTAssertEqual(obj.optFloat, nil) XCTAssertEqual(obj.optDouble, nil) XCTAssertEqual(obj.optString, nil) XCTAssertEqual(obj.optBinary, nil) XCTAssertEqual(obj.optDate, nil) XCTAssertEqual(obj.optDecimal, nil) XCTAssertEqual(obj.optObjectId, nil) XCTAssertEqual(obj.optUuid, nil) XCTAssertEqual(obj.optObject, nil) } @nonobjc func verifyObject(_ obj: AllCustomPersistableTypes) { XCTAssertEqual(obj.bool, BoolWrapper.values().last!) XCTAssertEqual(obj.int, IntWrapper.values().last!) XCTAssertEqual(obj.int8, Int8Wrapper.values().last!) XCTAssertEqual(obj.int16, Int16Wrapper.values().last!) XCTAssertEqual(obj.int32, Int32Wrapper.values().last!) XCTAssertEqual(obj.int64, Int64Wrapper.values().last!) XCTAssertEqual(obj.float, FloatWrapper.values().last!) XCTAssertEqual(obj.double, DoubleWrapper.values().last!) XCTAssertEqual(obj.string, StringWrapper.values().last!) XCTAssertEqual(obj.binary, DataWrapper.values().last!) XCTAssertEqual(obj.date, DateWrapper.values().last!) XCTAssertEqual(obj.decimal, Decimal128Wrapper.values().last!) XCTAssertEqual(obj.objectId, ObjectIdWrapper.values().last!) XCTAssertEqual(obj.uuid, UUIDWrapper.values().last!) XCTAssertEqual(obj.object, objectWrapperValues().last!) XCTAssertEqual(obj.optBool, BoolWrapper.values().last!) XCTAssertEqual(obj.optInt, IntWrapper.values().last!) XCTAssertEqual(obj.optInt8, Int8Wrapper.values().last!) XCTAssertEqual(obj.optInt16, Int16Wrapper.values().last!) XCTAssertEqual(obj.optInt32, Int32Wrapper.values().last!) XCTAssertEqual(obj.optInt64, Int64Wrapper.values().last!) XCTAssertEqual(obj.optFloat, FloatWrapper.values().last!) XCTAssertEqual(obj.optDouble, DoubleWrapper.values().last!) XCTAssertEqual(obj.optString, StringWrapper.values().last!) XCTAssertEqual(obj.optBinary, DataWrapper.values().last!) XCTAssertEqual(obj.optDate, DateWrapper.values().last!) XCTAssertEqual(obj.optDecimal, Decimal128Wrapper.values().last!) XCTAssertEqual(obj.optObjectId, ObjectIdWrapper.values().last!) XCTAssertEqual(obj.optUuid, UUIDWrapper.values().last!) XCTAssertEqual(obj.optObject, objectWrapperValues().last!) } @nonobjc func verifyDefault(_ obj: CustomPersistableCollections) { XCTAssertEqual(obj.listBool.count, 0) XCTAssertEqual(obj.listInt.count, 0) XCTAssertEqual(obj.listInt8.count, 0) XCTAssertEqual(obj.listInt16.count, 0) XCTAssertEqual(obj.listInt32.count, 0) XCTAssertEqual(obj.listInt64.count, 0) XCTAssertEqual(obj.listFloat.count, 0) XCTAssertEqual(obj.listDouble.count, 0) XCTAssertEqual(obj.listString.count, 0) XCTAssertEqual(obj.listBinary.count, 0) XCTAssertEqual(obj.listDate.count, 0) XCTAssertEqual(obj.listDecimal.count, 0) XCTAssertEqual(obj.listUuid.count, 0) XCTAssertEqual(obj.listObjectId.count, 0) XCTAssertEqual(obj.listObject.count, 0) XCTAssertEqual(obj.listOptBool.count, 0) XCTAssertEqual(obj.listOptInt.count, 0) XCTAssertEqual(obj.listOptInt8.count, 0) XCTAssertEqual(obj.listOptInt16.count, 0) XCTAssertEqual(obj.listOptInt32.count, 0) XCTAssertEqual(obj.listOptInt64.count, 0) XCTAssertEqual(obj.listOptFloat.count, 0) XCTAssertEqual(obj.listOptDouble.count, 0) XCTAssertEqual(obj.listOptString.count, 0) XCTAssertEqual(obj.listOptBinary.count, 0) XCTAssertEqual(obj.listOptDate.count, 0) XCTAssertEqual(obj.listOptDecimal.count, 0) XCTAssertEqual(obj.listOptUuid.count, 0) XCTAssertEqual(obj.listOptObjectId.count, 0) XCTAssertEqual(obj.setBool.count, 0) XCTAssertEqual(obj.setInt.count, 0) XCTAssertEqual(obj.setInt8.count, 0) XCTAssertEqual(obj.setInt16.count, 0) XCTAssertEqual(obj.setInt32.count, 0) XCTAssertEqual(obj.setInt64.count, 0) XCTAssertEqual(obj.setFloat.count, 0) XCTAssertEqual(obj.setDouble.count, 0) XCTAssertEqual(obj.setString.count, 0) XCTAssertEqual(obj.setBinary.count, 0) XCTAssertEqual(obj.setDate.count, 0) XCTAssertEqual(obj.setDecimal.count, 0) XCTAssertEqual(obj.setUuid.count, 0) XCTAssertEqual(obj.setObjectId.count, 0) XCTAssertEqual(obj.setOptBool.count, 0) XCTAssertEqual(obj.setOptInt.count, 0) XCTAssertEqual(obj.setOptInt8.count, 0) XCTAssertEqual(obj.setOptInt16.count, 0) XCTAssertEqual(obj.setOptInt32.count, 0) XCTAssertEqual(obj.setOptInt64.count, 0) XCTAssertEqual(obj.setOptFloat.count, 0) XCTAssertEqual(obj.setOptDouble.count, 0) XCTAssertEqual(obj.setOptString.count, 0) XCTAssertEqual(obj.setOptBinary.count, 0) XCTAssertEqual(obj.setOptDate.count, 0) XCTAssertEqual(obj.setOptDecimal.count, 0) XCTAssertEqual(obj.setOptUuid.count, 0) XCTAssertEqual(obj.setOptObjectId.count, 0) XCTAssertEqual(obj.mapBool.count, 0) XCTAssertEqual(obj.mapInt.count, 0) XCTAssertEqual(obj.mapInt8.count, 0) XCTAssertEqual(obj.mapInt16.count, 0) XCTAssertEqual(obj.mapInt32.count, 0) XCTAssertEqual(obj.mapInt64.count, 0) XCTAssertEqual(obj.mapFloat.count, 0) XCTAssertEqual(obj.mapDouble.count, 0) XCTAssertEqual(obj.mapString.count, 0) XCTAssertEqual(obj.mapBinary.count, 0) XCTAssertEqual(obj.mapDate.count, 0) XCTAssertEqual(obj.mapDecimal.count, 0) XCTAssertEqual(obj.mapUuid.count, 0) XCTAssertEqual(obj.mapObjectId.count, 0) XCTAssertEqual(obj.mapObject.count, 0) XCTAssertEqual(obj.mapOptBool.count, 0) XCTAssertEqual(obj.mapOptInt.count, 0) XCTAssertEqual(obj.mapOptInt8.count, 0) XCTAssertEqual(obj.mapOptInt16.count, 0) XCTAssertEqual(obj.mapOptInt32.count, 0) XCTAssertEqual(obj.mapOptInt64.count, 0) XCTAssertEqual(obj.mapOptFloat.count, 0) XCTAssertEqual(obj.mapOptDouble.count, 0) XCTAssertEqual(obj.mapOptString.count, 0) XCTAssertEqual(obj.mapOptBinary.count, 0) XCTAssertEqual(obj.mapOptDate.count, 0) XCTAssertEqual(obj.mapOptDecimal.count, 0) XCTAssertEqual(obj.mapOptUuid.count, 0) XCTAssertEqual(obj.mapOptObjectId.count, 0) } @nonobjc func verifyNil(_ obj: AllCustomPersistableTypes) { XCTAssertEqual(obj.object, EmbeddedObjectWrapper(value: 0)) XCTAssertNil(obj.optBool) XCTAssertNil(obj.optInt) XCTAssertNil(obj.optInt8) XCTAssertNil(obj.optInt16) XCTAssertNil(obj.optInt32) XCTAssertNil(obj.optInt64) XCTAssertNil(obj.optFloat) XCTAssertNil(obj.optDouble) XCTAssertNil(obj.optString) XCTAssertNil(obj.optBinary) XCTAssertNil(obj.optDate) XCTAssertNil(obj.optDecimal) XCTAssertNil(obj.optObjectId) XCTAssertNil(obj.optUuid) XCTAssertNil(obj.optObject) } @nonobjc func verifyNil(_ obj: CustomPersistableCollections) { assertListEqual(obj.listOptBool, [nil]) assertListEqual(obj.listOptInt, [nil]) assertListEqual(obj.listOptInt8, [nil]) assertListEqual(obj.listOptInt16, [nil]) assertListEqual(obj.listOptInt32, [nil]) assertListEqual(obj.listOptInt64, [nil]) assertListEqual(obj.listOptFloat, [nil]) assertListEqual(obj.listOptDouble, [nil]) assertListEqual(obj.listOptString, [nil]) assertListEqual(obj.listOptBinary, [nil]) assertListEqual(obj.listOptDate, [nil]) assertListEqual(obj.listOptDecimal, [nil]) assertListEqual(obj.listOptUuid, [nil]) assertListEqual(obj.listOptObjectId, [nil]) assertSetEqual(obj.setOptBool, [nil]) assertSetEqual(obj.setOptInt, [nil]) assertSetEqual(obj.setOptInt8, [nil]) assertSetEqual(obj.setOptInt16, [nil]) assertSetEqual(obj.setOptInt32, [nil]) assertSetEqual(obj.setOptInt64, [nil]) assertSetEqual(obj.setOptFloat, [nil]) assertSetEqual(obj.setOptDouble, [nil]) assertSetEqual(obj.setOptString, [nil]) assertSetEqual(obj.setOptBinary, [nil]) assertSetEqual(obj.setOptDate, [nil]) assertSetEqual(obj.setOptDecimal, [nil]) assertSetEqual(obj.setOptUuid, [nil]) assertSetEqual(obj.setOptObjectId, [nil]) assertMapEqual(obj.mapObject, [EmbeddedObjectWrapper(value: 0)]) assertMapEqual(obj.mapOptBool, [nil]) assertMapEqual(obj.mapOptInt, [nil]) assertMapEqual(obj.mapOptInt8, [nil]) assertMapEqual(obj.mapOptInt16, [nil]) assertMapEqual(obj.mapOptInt32, [nil]) assertMapEqual(obj.mapOptInt64, [nil]) assertMapEqual(obj.mapOptFloat, [nil]) assertMapEqual(obj.mapOptDouble, [nil]) assertMapEqual(obj.mapOptString, [nil]) assertMapEqual(obj.mapOptBinary, [nil]) assertMapEqual(obj.mapOptDate, [nil]) assertMapEqual(obj.mapOptDecimal, [nil]) assertMapEqual(obj.mapOptUuid, [nil]) assertMapEqual(obj.mapOptObjectId, [nil]) assertMapEqual(obj.mapOptObject, [nil]) } @nonobjc func assertListEqual(_ list: List, _ expected: [T]) { XCTAssertEqual(Array(list), Array(expected)) } @nonobjc func assertSetEqual(_ set: MutableSet, _ expected: [T]) { XCTAssertEqual(set.count, Set(expected).count) XCTAssertEqual(Set(set), Set(expected)) } @nonobjc func assertMapEqual(_ map: Map, _ expected: [T]) { XCTAssertEqual(map.count, expected.count) for (i, value) in expected.enumerated() { XCTAssertEqual(map["\(i)"], value) } } @nonobjc func verifyObject(_ obj: CustomPersistableCollections) { assertListEqual(obj.listBool, BoolWrapper.values()) assertListEqual(obj.listInt, IntWrapper.values()) assertListEqual(obj.listInt8, Int8Wrapper.values()) assertListEqual(obj.listInt16, Int16Wrapper.values()) assertListEqual(obj.listInt32, Int32Wrapper.values()) assertListEqual(obj.listInt64, Int64Wrapper.values()) assertListEqual(obj.listFloat, FloatWrapper.values()) assertListEqual(obj.listDouble, DoubleWrapper.values()) assertListEqual(obj.listString, StringWrapper.values()) assertListEqual(obj.listBinary, DataWrapper.values()) assertListEqual(obj.listDate, DateWrapper.values()) assertListEqual(obj.listDecimal, Decimal128Wrapper.values()) assertListEqual(obj.listUuid, UUIDWrapper.values()) assertListEqual(obj.listObjectId, ObjectIdWrapper.values()) assertListEqual(obj.listObject, objectWrapperValues()) assertListEqual(obj.listOptBool, BoolWrapper?.values()) assertListEqual(obj.listOptInt, IntWrapper?.values()) assertListEqual(obj.listOptInt8, Int8Wrapper?.values()) assertListEqual(obj.listOptInt16, Int16Wrapper?.values()) assertListEqual(obj.listOptInt32, Int32Wrapper?.values()) assertListEqual(obj.listOptInt64, Int64Wrapper?.values()) assertListEqual(obj.listOptFloat, FloatWrapper?.values()) assertListEqual(obj.listOptDouble, DoubleWrapper?.values()) assertListEqual(obj.listOptString, StringWrapper?.values()) assertListEqual(obj.listOptBinary, DataWrapper?.values()) assertListEqual(obj.listOptDate, DateWrapper?.values()) assertListEqual(obj.listOptDecimal, Decimal128Wrapper?.values()) assertListEqual(obj.listOptUuid, UUIDWrapper?.values()) assertListEqual(obj.listOptObjectId, ObjectIdWrapper?.values()) assertSetEqual(obj.setBool, BoolWrapper.values()) assertSetEqual(obj.setInt, IntWrapper.values()) assertSetEqual(obj.setInt8, Int8Wrapper.values()) assertSetEqual(obj.setInt16, Int16Wrapper.values()) assertSetEqual(obj.setInt32, Int32Wrapper.values()) assertSetEqual(obj.setInt64, Int64Wrapper.values()) assertSetEqual(obj.setFloat, FloatWrapper.values()) assertSetEqual(obj.setDouble, DoubleWrapper.values()) assertSetEqual(obj.setString, StringWrapper.values()) assertSetEqual(obj.setBinary, DataWrapper.values()) assertSetEqual(obj.setDate, DateWrapper.values()) assertSetEqual(obj.setDecimal, Decimal128Wrapper.values()) assertSetEqual(obj.setUuid, UUIDWrapper.values()) assertSetEqual(obj.setObjectId, ObjectIdWrapper.values()) assertSetEqual(obj.setOptBool, BoolWrapper?.values()) assertSetEqual(obj.setOptInt, IntWrapper?.values()) assertSetEqual(obj.setOptInt8, Int8Wrapper?.values()) assertSetEqual(obj.setOptInt16, Int16Wrapper?.values()) assertSetEqual(obj.setOptInt32, Int32Wrapper?.values()) assertSetEqual(obj.setOptInt64, Int64Wrapper?.values()) assertSetEqual(obj.setOptFloat, FloatWrapper?.values()) assertSetEqual(obj.setOptDouble, DoubleWrapper?.values()) assertSetEqual(obj.setOptString, StringWrapper?.values()) assertSetEqual(obj.setOptBinary, DataWrapper?.values()) assertSetEqual(obj.setOptDate, DateWrapper?.values()) assertSetEqual(obj.setOptDecimal, Decimal128Wrapper?.values()) assertSetEqual(obj.setOptUuid, UUIDWrapper?.values()) assertSetEqual(obj.setOptObjectId, ObjectIdWrapper?.values()) assertMapEqual(obj.mapBool, BoolWrapper.values()) assertMapEqual(obj.mapInt, IntWrapper.values()) assertMapEqual(obj.mapInt8, Int8Wrapper.values()) assertMapEqual(obj.mapInt16, Int16Wrapper.values()) assertMapEqual(obj.mapInt32, Int32Wrapper.values()) assertMapEqual(obj.mapInt64, Int64Wrapper.values()) assertMapEqual(obj.mapFloat, FloatWrapper.values()) assertMapEqual(obj.mapDouble, DoubleWrapper.values()) assertMapEqual(obj.mapString, StringWrapper.values()) assertMapEqual(obj.mapBinary, DataWrapper.values()) assertMapEqual(obj.mapDate, DateWrapper.values()) assertMapEqual(obj.mapDecimal, Decimal128Wrapper.values()) assertMapEqual(obj.mapUuid, UUIDWrapper.values()) assertMapEqual(obj.mapObjectId, ObjectIdWrapper.values()) assertMapEqual(obj.mapObject, objectWrapperValues()) assertMapEqual(obj.mapOptBool, BoolWrapper?.values()) assertMapEqual(obj.mapOptInt, IntWrapper?.values()) assertMapEqual(obj.mapOptInt8, Int8Wrapper?.values()) assertMapEqual(obj.mapOptInt16, Int16Wrapper?.values()) assertMapEqual(obj.mapOptInt32, Int32Wrapper?.values()) assertMapEqual(obj.mapOptInt64, Int64Wrapper?.values()) assertMapEqual(obj.mapOptFloat, FloatWrapper?.values()) assertMapEqual(obj.mapOptDouble, DoubleWrapper?.values()) assertMapEqual(obj.mapOptString, StringWrapper?.values()) assertMapEqual(obj.mapOptBinary, DataWrapper?.values()) assertMapEqual(obj.mapOptDate, DateWrapper?.values()) assertMapEqual(obj.mapOptDecimal, Decimal128Wrapper?.values()) assertMapEqual(obj.mapOptUuid, UUIDWrapper?.values()) assertMapEqual(obj.mapOptObjectId, ObjectIdWrapper?.values()) assertMapEqual(obj.mapOptObject, objectWrapperValues()) } // MARK: - Tests func testInitDefault() { verifyDefault(AllCustomPersistableTypes()) verifyDefault(CustomPersistableCollections()) } private func arrayValues(_ type: T.Type, _ values: [String: Any]) -> [Any] { T.sharedSchema()!.properties.map { values[$0.name] as Any } } func testInitWithArray() { verifyObject(AllCustomPersistableTypes(value: arrayValues(AllCustomPersistableTypes.self, wrappedValues!))) verifyObject(AllCustomPersistableTypes(value: arrayValues(AllCustomPersistableTypes.self, rawValues!))) verifyNil(AllCustomPersistableTypes(value: arrayValues(AllCustomPersistableTypes.self, nilOptionalValues!))) verifyObject(CustomPersistableCollections(value: arrayValues(CustomPersistableCollections.self, wrappedValues!))) verifyObject(CustomPersistableCollections(value: arrayValues(CustomPersistableCollections.self, rawValues!))) verifyNil(CustomPersistableCollections(value: arrayValues(CustomPersistableCollections.self, nilOptionalValues!))) } func testInitWithDictionary() { verifyObject(AllCustomPersistableTypes(value: rawValues!)) verifyObject(AllCustomPersistableTypes(value: wrappedValues!)) verifyNil(AllCustomPersistableTypes(value: nilOptionalValues!)) verifyObject(CustomPersistableCollections(value: rawValues!)) verifyObject(CustomPersistableCollections(value: wrappedValues!)) verifyNil(CustomPersistableCollections(value: nilOptionalValues!)) } func testInitWithObject() { verifyObject(AllCustomPersistableTypes(value: AllCustomPersistableTypes(value: wrappedValues!))) verifyNil(AllCustomPersistableTypes(value: AllCustomPersistableTypes(value: nilOptionalValues!))) verifyObject(CustomPersistableCollections(value: CustomPersistableCollections(value: wrappedValues!))) verifyNil(CustomPersistableCollections(value: CustomPersistableCollections(value: nilOptionalValues!))) } func testInitFailable() { _ = FailableCustomObject() assertThrows(FailableCustomObject(value: ["int": 1]), reason: "Could not convert value '1' to type 'IntFailableWrapper'.") let obj = FailableCustomObject(value: ["optInt": 1, "listInt": [1], "optListInt": [1], "setInt": [1], "optSetInt": [1], "mapInt": ["1": 1], "optMapInt": ["1": 1]] as [String: Any]) XCTAssertNil(obj.optInt) assertThrows(obj.listInt[0], reason: "Could not convert value '1' to type 'IntFailableWrapper'.") assertListEqual(obj.optListInt, [nil]) assertThrows(obj.setInt.first, reason: "Could not convert value '1' to type 'IntFailableWrapper'.") assertSetEqual(obj.optSetInt, [nil]) assertThrows(obj.mapInt["1"], reason: "Could not convert value '1' to type 'IntFailableWrapper'.") XCTAssertEqual(obj.optMapInt["1"], .some(nil)) } func testCreateDefault() { let realm = try! Realm() try! realm.write { verifyDefault(realm.create(AllCustomPersistableTypes.self)) verifyDefault(realm.create(CustomPersistableCollections.self)) } } func testCreateWithArray() { let realm = try! Realm() try! realm.write { verifyObject(realm.create(AllCustomPersistableTypes.self, value: arrayValues(AllCustomPersistableTypes.self, wrappedValues!))) verifyObject(realm.create(AllCustomPersistableTypes.self, value: arrayValues(AllCustomPersistableTypes.self, rawValues!))) verifyObject(realm.create(CustomPersistableCollections.self, value: arrayValues(CustomPersistableCollections.self, wrappedValues!))) verifyObject(realm.create(CustomPersistableCollections.self, value: arrayValues(CustomPersistableCollections.self, rawValues!))) } } func testCreateWithDictionary() { let realm = try! Realm() try! realm.write { verifyObject(realm.create(AllCustomPersistableTypes.self, value: wrappedValues!)) verifyObject(realm.create(AllCustomPersistableTypes.self, value: rawValues!)) verifyObject(realm.create(CustomPersistableCollections.self, value: wrappedValues!)) verifyObject(realm.create(CustomPersistableCollections.self, value: rawValues!)) } } func testCreateWithObject() { let realm = try! Realm() try! realm.write { let obj = AllCustomPersistableTypes(value: wrappedValues!) verifyObject(realm.create(AllCustomPersistableTypes.self, value: obj)) } } func testCreateFailable() { let realm = try! Realm() realm.beginWrite() let obj = realm.create(FailableCustomObject.self, value: ["int": 1, "optInt": 1, "listInt": [1], "optListInt": [1], "setInt": [1], "optSetInt": [1], "mapInt": ["1": 1], "optMapInt": ["1": 1]] as [String: Any]) assertThrows(obj.int, reason: "Failed to convert persisted value '1' to type 'IntFailableWrapper' in a non-optional context.") XCTAssertNil(obj.optInt) assertThrows(obj.listInt[0], reason: "Could not convert value '1' to type 'IntFailableWrapper'.") assertListEqual(obj.optListInt, [nil]) assertThrows(obj.setInt.first, reason: "Could not convert value '1' to type 'IntFailableWrapper'.") assertSetEqual(obj.optSetInt, [nil]) assertThrows(obj.mapInt["1"], reason: "Could not convert value '1' to type 'IntFailableWrapper'.") XCTAssertEqual(obj.optMapInt["1"], .some(nil)) realm.cancelWrite() } func testAddDefault() { let realm = try! Realm() let obj1 = AllCustomPersistableTypes() let obj2 = CustomPersistableCollections() try! realm.write { realm.add(obj1) realm.add(obj2) } verifyDefault(obj1) verifyDefault(obj2) } func testAdd() { let realm = try! Realm() let obj1 = AllCustomPersistableTypes(value: wrappedValues!) let obj2 = CustomPersistableCollections(value: wrappedValues!) try! realm.write { realm.add(obj1) realm.add(obj2) } verifyObject(obj1) verifyObject(obj2) } func testNullValueForNonOptionalPropertyBackedByOptional() { let realm = try! Realm() realm.beginWrite() // Non-optional object col can be null var values = wrappedValues! values["object"] = NSNull() XCTAssertEqual(AllCustomPersistableTypes(value: values).object.value, 0) XCTAssertEqual(realm.create(AllCustomPersistableTypes.self, value: values).object.value, 0) // Non-optional map col can contain null values = wrappedValues! values["mapObject"] = ["1": NSNull()] XCTAssertEqual(CustomPersistableCollections(value: values).mapObject["1"]!.value, 0) XCTAssertEqual(realm.create(CustomPersistableCollections.self, value: values).mapObject["1"]!.value, 0) // List can't, as the backing storage is actually non-optional values = wrappedValues! values["listObject"] = [NSNull()] assertThrows(CustomPersistableCollections(value: values)) assertThrows(realm.create(CustomPersistableCollections.self, value: values)) realm.cancelWrite() } func testInvalidDefaultInit() { let expectedError = "Failed to default construct a InvalidDefaultInit using the default value for persisted type Int. This conversion must either succeed, the property must be optional, or you must explicitly specify a default value for the property." let obj = InvalidDefaultInitObject() assertThrows(obj.value, reason: expectedError) let realm = try! Realm() realm.beginWrite() assertThrows(realm.create(InvalidDefaultInitObject.self), reason: expectedError) assertThrows(realm.add(obj), reason: expectedError) let obj2 = ValidDefaultInitObject() XCTAssertEqual(obj2.value.persistableValue, 0) _ = realm.create(ValidDefaultInitObject.self) realm.add(obj2) realm.cancelWrite() } } private struct InvalidDefaultInit: FailableCustomPersistable { typealias PersistedType = Int init?(persistedValue: Int) { if persistedValue == 0 { return nil } } var persistableValue: Int { 0 } } @objc(InvalidDefaultInitObject) private class InvalidDefaultInitObject: Object { @Persisted var value: InvalidDefaultInit } @objc(ValidDefaultInitObject) private class ValidDefaultInitObject: Object { @Persisted var value = InvalidDefaultInit(persistedValue: 1)! } ================================================ FILE: RealmSwift/Tests/CustomPersistableTestObjects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import RealmSwift protocol TrivialCustomPersistable: CustomPersistable { var value: PersistedType { get set } init(value: PersistedType) } extension TrivialCustomPersistable { init(persistedValue: PersistedType) { self.init(value: persistedValue) } var persistableValue: PersistedType { value } } struct BoolWrapper: TrivialCustomPersistable { typealias PersistedType = Bool var value: Bool } struct IntWrapper: TrivialCustomPersistable { typealias PersistedType = Int var value: Int } struct Int8Wrapper: TrivialCustomPersistable { typealias PersistedType = Int8 var value: Int8 } struct Int16Wrapper: TrivialCustomPersistable { typealias PersistedType = Int16 var value: Int16 } struct Int32Wrapper: TrivialCustomPersistable { typealias PersistedType = Int32 var value: Int32 } struct Int64Wrapper: TrivialCustomPersistable { typealias PersistedType = Int64 var value: Int64 } struct FloatWrapper: TrivialCustomPersistable { typealias PersistedType = Float var value: Float } struct DoubleWrapper: TrivialCustomPersistable { typealias PersistedType = Double var value: Double } struct StringWrapper: TrivialCustomPersistable { typealias PersistedType = String var value: String } struct DataWrapper: TrivialCustomPersistable { typealias PersistedType = Data var value: Data } struct DateWrapper: TrivialCustomPersistable { typealias PersistedType = Date var value: Date } struct Decimal128Wrapper: TrivialCustomPersistable { typealias PersistedType = Decimal128 var value: Decimal128 } struct ObjectIdWrapper: TrivialCustomPersistable { typealias PersistedType = ObjectId var value: ObjectId } struct UUIDWrapper: TrivialCustomPersistable { typealias PersistedType = UUID var value: UUID } struct IntFailableWrapper: FailableCustomPersistable { typealias PersistedType = Int init?(persistedValue: Int) { if persistedValue == 1 { return nil } self.persistableValue = persistedValue } let persistableValue: Int } // MARK: EmbeddedObject custom persistable wrappers struct EmbeddedObjectWrapper: CustomPersistable { typealias PersistedType = ModernEmbeddedObject init(persistedValue: PersistedType) { self.value = persistedValue.value } var persistableValue: ModernEmbeddedObject { return ModernEmbeddedObject(value: [value]) } init(value: Int = 1) { self.value = value } var value: Int } class TypeWithObjectLink: EmbeddedObject { @Persisted var value: ModernEmbeddedObject? } struct WrapperForTypeWithObjectLink: CustomPersistable { typealias PersistedType = TypeWithObjectLink var persistableValue: TypeWithObjectLink { return TypeWithObjectLink() } init(persistedValue: TypeWithObjectLink) {} init() {} } class LinkToWrapperForTypeWithObjectLink: Object { @Persisted var link: WrapperForTypeWithObjectLink? } class TypeWithCollection: EmbeddedObject { @Persisted var list: List } struct WrapperForTypeWithCollection: CustomPersistable { typealias PersistedType = TypeWithCollection var persistableValue: TypeWithCollection { return TypeWithCollection() } init(persistedValue: TypeWithCollection) {} init() {} } class LinkToWrapperForTypeWithCollection: Object { @Persisted var link: WrapperForTypeWithCollection? } struct AddressSwiftWrapper: CustomPersistable { typealias PersistedType = AddressSwift var city = "" var country = "" init(persistedValue: AddressSwift) { city = persistedValue.city country = persistedValue.country } var persistableValue: AddressSwift { AddressSwift(value: [city, country]) } } class LinkToAddressSwiftWrapper: Object { @Persisted var object: AddressSwiftWrapper @Persisted var optObject: AddressSwiftWrapper @Persisted var list: List @Persisted var map: Map @Persisted var optMap: Map } // MARK: Objects class AllCustomPersistableTypes: Object { @Persisted var bool: BoolWrapper @Persisted var int: IntWrapper @Persisted var int8: Int8Wrapper @Persisted var int16: Int16Wrapper @Persisted var int32: Int32Wrapper @Persisted var int64: Int64Wrapper @Persisted var float: FloatWrapper @Persisted var double: DoubleWrapper @Persisted var string: StringWrapper @Persisted var binary: DataWrapper @Persisted var date: DateWrapper @Persisted var decimal: Decimal128Wrapper @Persisted var objectId: ObjectIdWrapper @Persisted var uuid: UUIDWrapper @Persisted var object: EmbeddedObjectWrapper @Persisted var optBool: BoolWrapper? @Persisted var optInt: IntWrapper? @Persisted var optInt8: Int8Wrapper? @Persisted var optInt16: Int16Wrapper? @Persisted var optInt32: Int32Wrapper? @Persisted var optInt64: Int64Wrapper? @Persisted var optFloat: FloatWrapper? @Persisted var optDouble: DoubleWrapper? @Persisted var optString: StringWrapper? @Persisted var optBinary: DataWrapper? @Persisted var optDate: DateWrapper? @Persisted var optDecimal: Decimal128Wrapper? @Persisted var optObjectId: ObjectIdWrapper? @Persisted var optUuid: UUIDWrapper? @Persisted var optObject: EmbeddedObjectWrapper? } class CustomPersistableCollections: Object { @Persisted var listBool: List @Persisted var listInt: List @Persisted var listInt8: List @Persisted var listInt16: List @Persisted var listInt32: List @Persisted var listInt64: List @Persisted var listFloat: List @Persisted var listDouble: List @Persisted var listString: List @Persisted var listBinary: List @Persisted var listDate: List @Persisted var listDecimal: List @Persisted var listUuid: List @Persisted var listObjectId: List @Persisted var listObject: List @Persisted var listOptBool: List @Persisted var listOptInt: List @Persisted var listOptInt8: List @Persisted var listOptInt16: List @Persisted var listOptInt32: List @Persisted var listOptInt64: List @Persisted var listOptFloat: List @Persisted var listOptDouble: List @Persisted var listOptString: List @Persisted var listOptBinary: List @Persisted var listOptDate: List @Persisted var listOptDecimal: List @Persisted var listOptUuid: List @Persisted var listOptObjectId: List @Persisted var setBool: MutableSet @Persisted var setInt: MutableSet @Persisted var setInt8: MutableSet @Persisted var setInt16: MutableSet @Persisted var setInt32: MutableSet @Persisted var setInt64: MutableSet @Persisted var setFloat: MutableSet @Persisted var setDouble: MutableSet @Persisted var setString: MutableSet @Persisted var setBinary: MutableSet @Persisted var setDate: MutableSet @Persisted var setDecimal: MutableSet @Persisted var setUuid: MutableSet @Persisted var setObjectId: MutableSet @Persisted var setOptBool: MutableSet @Persisted var setOptInt: MutableSet @Persisted var setOptInt8: MutableSet @Persisted var setOptInt16: MutableSet @Persisted var setOptInt32: MutableSet @Persisted var setOptInt64: MutableSet @Persisted var setOptFloat: MutableSet @Persisted var setOptDouble: MutableSet @Persisted var setOptString: MutableSet @Persisted var setOptBinary: MutableSet @Persisted var setOptDate: MutableSet @Persisted var setOptDecimal: MutableSet @Persisted var setOptUuid: MutableSet @Persisted var setOptObjectId: MutableSet @Persisted var mapBool: Map @Persisted var mapInt: Map @Persisted var mapInt8: Map @Persisted var mapInt16: Map @Persisted var mapInt32: Map @Persisted var mapInt64: Map @Persisted var mapFloat: Map @Persisted var mapDouble: Map @Persisted var mapString: Map @Persisted var mapBinary: Map @Persisted var mapDate: Map @Persisted var mapDecimal: Map @Persisted var mapUuid: Map @Persisted var mapObjectId: Map @Persisted var mapObject: Map @Persisted var mapOptBool: Map @Persisted var mapOptInt: Map @Persisted var mapOptInt8: Map @Persisted var mapOptInt16: Map @Persisted var mapOptInt32: Map @Persisted var mapOptInt64: Map @Persisted var mapOptFloat: Map @Persisted var mapOptDouble: Map @Persisted var mapOptString: Map @Persisted var mapOptBinary: Map @Persisted var mapOptDate: Map @Persisted var mapOptDecimal: Map @Persisted var mapOptUuid: Map @Persisted var mapOptObjectId: Map @Persisted var mapOptObject: Map } class LinkToAllCustomPersistableTypes: Object { @Persisted var object: AllCustomPersistableTypes? @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } class LinkToCustomPersistableCollections: Object { @Persisted var object: CustomPersistableCollections? @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } class CustomAllIndexableTypesObject: Object { @Persisted(indexed: true) var boolCol: BoolWrapper @Persisted(indexed: true) var intCol: IntWrapper @Persisted(indexed: true) var int8Col: Int8Wrapper @Persisted(indexed: true) var int16Col: Int16Wrapper @Persisted(indexed: true) var int32Col: Int32Wrapper @Persisted(indexed: true) var int64Col: Int64Wrapper @Persisted(indexed: true) var stringCol: StringWrapper @Persisted(indexed: true) var dateCol: DateWrapper @Persisted(indexed: true) var uuidCol: UUIDWrapper @Persisted(indexed: true) var objectIdCol: ObjectIdWrapper @Persisted(indexed: true) var optIntCol: IntWrapper? @Persisted(indexed: true) var optInt8Col: Int8Wrapper? @Persisted(indexed: true) var optInt16Col: Int16Wrapper? @Persisted(indexed: true) var optInt32Col: Int32Wrapper? @Persisted(indexed: true) var optInt64Col: Int64Wrapper? @Persisted(indexed: true) var optBoolCol: BoolWrapper? @Persisted(indexed: true) var optStringCol: StringWrapper? @Persisted(indexed: true) var optDateCol: DateWrapper? @Persisted(indexed: true) var optUuidCol: UUIDWrapper? @Persisted(indexed: true) var optObjectIdCol: ObjectIdWrapper? } class CustomAllIndexableButNotIndexedObject: Object { @Persisted(indexed: false) var boolCol: BoolWrapper @Persisted(indexed: false) var intCol: IntWrapper @Persisted(indexed: false) var int8Col: Int8Wrapper @Persisted(indexed: false) var int16Col: Int16Wrapper @Persisted(indexed: false) var int32Col: Int32Wrapper @Persisted(indexed: false) var int64Col: Int64Wrapper @Persisted(indexed: false) var stringCol: StringWrapper @Persisted(indexed: false) var dateCol: DateWrapper @Persisted(indexed: false) var uuidCol: UUIDWrapper @Persisted(indexed: false) var objectIdCol: ObjectIdWrapper @Persisted(indexed: false) var optIntCol: IntWrapper? @Persisted(indexed: false) var optInt8Col: Int8Wrapper? @Persisted(indexed: false) var optInt16Col: Int16Wrapper? @Persisted(indexed: false) var optInt32Col: Int32Wrapper? @Persisted(indexed: false) var optInt64Col: Int64Wrapper? @Persisted(indexed: false) var optBoolCol: BoolWrapper? @Persisted(indexed: false) var optStringCol: StringWrapper? @Persisted(indexed: false) var optDateCol: DateWrapper? @Persisted(indexed: false) var optUuidCol: UUIDWrapper? @Persisted(indexed: false) var optObjectIdCol: ObjectIdWrapper? } class FailableCustomObject: Object { @Persisted var int: IntFailableWrapper @Persisted var optInt: IntFailableWrapper? @Persisted var listInt: List @Persisted var optListInt: List @Persisted var setInt: MutableSet @Persisted var optSetInt: MutableSet @Persisted var mapInt: Map @Persisted var optMapInt: Map } ================================================ FILE: RealmSwift/Tests/Decimal128Tests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import RealmSwift class Decimal128Tests: TestCase { // MARK: - Initialization func testDecimal128Initialization() { let d1: Decimal128 = 3.14159 let d2: Decimal128 = .init(number: 3.14159) let d3: Decimal128 = 123 let d4: Decimal128 = "9.876543" let d5 = Decimal128.init(exactly: 0b00000101) XCTAssertEqual(d1, 3.14159) XCTAssertEqual(d2, 3.14159) XCTAssertEqual(d3, 123) XCTAssertEqual(d4, "9.876543") XCTAssertEqual(d5, 5) } // MARK: Arithmetic func testDecimal128Addition() { let d1: Decimal128 = 3.144444 let d2: Decimal128 = 3.144444 let d3: Decimal128 = "1.234567" let d4: Decimal128 = "9.876543" let d5 = Decimal128.init(exactly: 0b00000010) let d6 = Decimal128.init(exactly: 0b00000001) let addition1 = d1+d2 let addition2 = d3+d4 let addition3 = d5!+d6! XCTAssertEqual(addition1, 6.288888) XCTAssertEqual(addition2.description, "11.11111") XCTAssertEqual(d1, 3.144444) XCTAssertEqual(d2, 3.144444) XCTAssertEqual(d3.description, "1.234567") XCTAssertEqual(d4.description, "9.876543") XCTAssertEqual(addition3, 3.0) XCTAssertEqual(d5, 2.0) XCTAssertEqual(d6, 1.0) } func testDecimal128Subtraction() { let d1: Decimal128 = 2.5 let d2: Decimal128 = 3.5 let d3: Decimal128 = "2.5" let d4: Decimal128 = "3.5" let d5 = Decimal128.init(exactly: 0b00000010) let d6 = Decimal128.init(exactly: 0b00000001) let subtraction1 = d1-d2 let subtraction2 = d3-d4 let subtraction3 = d5!-d6! XCTAssertEqual(subtraction1, -1.0) XCTAssertEqual(subtraction2.description, "-1") XCTAssertEqual(d1, 2.5) XCTAssertEqual(d2, 3.5) XCTAssertEqual(d3.description, "2.5") XCTAssertEqual(d4.description, "3.5") XCTAssertEqual(subtraction3, 1.0) XCTAssertEqual(d5, 2.0) XCTAssertEqual(d6, 1.0) } func testDecimal128Division() { let d1: Decimal128 = 7 let d2: Decimal128 = 3.5 let d3: Decimal128 = "0.21" let d4: Decimal128 = "0.7" let d5 = Decimal128.init(exactly: 0b00000010) let d6 = Decimal128.init(exactly: 0b00000001) let division1 = d1/d2 let division2 = d3/d4 let division3 = d5!/d6! XCTAssertEqual(division1, 2) XCTAssertEqual(division2, 0.3) XCTAssertEqual(division3, 2) } func testDecimal128Multiplication() { let d1: Decimal128 = 7 let d2: Decimal128 = 3.5 let d3: Decimal128 = "0.21" let d4: Decimal128 = "0.7" let d5 = Decimal128.init(exactly: 0b00000010) let multiplication1 = d1*d2 let multiplication2 = d3*d4 let multiplication3 = d5!*d5! XCTAssertEqual(multiplication1, 24.5) XCTAssertEqual(multiplication2, 0.147) XCTAssertEqual(multiplication3, 4) } // MARK: Comparison func testDecimal128ComparisionEquals() { let d1: Decimal128 = 3.14159 let d2: Decimal128 = .init(number: 3.14159) let d3: Decimal128 = 123 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000101) let d6: Decimal128 = 5 XCTAssertTrue(d1 == d2) XCTAssertTrue(d3 == d4) XCTAssertTrue(d5 == d6) } func testDecimal128ComparisionNotEquals() { let d1: Decimal128 = 3.14159 let d2: Decimal128 = .init(number: 3.14159) let d3: Decimal128 = 123 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000101) let d6: Decimal128 = 5 XCTAssertFalse(d1 != d2) XCTAssertFalse(d3 != d4) XCTAssertFalse(d5 != d6) } func testDecimal128ComparisionGreaterThan() { let d1: Decimal128 = 3.14160 let d2: Decimal128 = .init(number: 3.14159) let d3: Decimal128 = 124 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000001) let d6: Decimal128 = 5 XCTAssertTrue(d1 > d2) XCTAssertTrue(d3 > d4) XCTAssertFalse(d5! > d6) } func testDecimal128ComparisionGreaterThanEquals() { let d1: Decimal128 = 3.14159 let d2: Decimal128 = .init(number: 3.14159) let d3: Decimal128 = 124 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000001) let d6: Decimal128 = 5 XCTAssertTrue(d1 >= d2) XCTAssertTrue(d3 >= d4) XCTAssertFalse(d5! >= d6) } func testDecimal128ComparisionLessThan() { let d1: Decimal128 = 3.14159 let d2: Decimal128 = .init(number: 3.14160) let d3: Decimal128 = 122 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000010) let d6: Decimal128 = 1 XCTAssertTrue(d1 < d2) XCTAssertTrue(d3 < d4) XCTAssertFalse(d5! < d6) } func testDecimal128ComparisionLessThanEqual() { let d1: Decimal128 = 3.14160 let d2: Decimal128 = .init(number: 3.14160) let d3: Decimal128 = 123 let d4: Decimal128 = "123" let d5 = Decimal128.init(exactly: 0b00000010) let d6: Decimal128 = 1 XCTAssertTrue(d1 <= d2) XCTAssertTrue(d3 <= d4) XCTAssertFalse(d5! <= d6) } // MARK: Miscellaneous func testIsNaN() { let d1: Decimal128 = .init(value: NSNull.init()) XCTAssertTrue(d1.isNaN) XCTAssertTrue(d1.isSignaling) XCTAssertTrue(d1.isSignalingNaN) } func testMinMax() { let min: Decimal128 = .min let max: Decimal128 = .max XCTAssertGreaterThan(max, min) XCTAssertLessThan(min, max) } func testMagnitude() { let d1: Decimal128 = -123.321 let exp1: Decimal128 = 123.321 let d2: Decimal128 = 456.321 let exp2: Decimal128 = 456.321 XCTAssertEqual(d1.magnitude, exp1) XCTAssertEqual(d2.magnitude, exp2) } func testNegate() { let d1: Decimal128 = -123.321 let d2: Decimal128 = 456.321 let exp1: Decimal128 = 123.321 let exp2: Decimal128 = -456.321 d1.negate() d2.negate() XCTAssertEqual(d1, exp1) XCTAssertEqual(d2, exp2) } func testAdvance() { let d1: Decimal128 = -123.321 let result1 = d1.advanced(by: -123.321) let d2: Decimal128 = -150.0 let result2 = d2.advanced(by: 300.0) XCTAssertEqual(result1, -246.642) XCTAssertEqual(result2, 150.0) } func testDistance() { let d: Decimal128 = 10.0 let result1 = d.distance(to: 5.0) let result2 = d.distance(to: 15.0) XCTAssertEqual(result1, -5.0) XCTAssertEqual(result2, 5.0) } } ================================================ FILE: RealmSwift/Tests/GeospatialTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 import RealmSwift import Realm.Private // Template `EmbeddedObject` for storing GeoPoints in Realm. public class Location: EmbeddedObject { @Persisted private(set) var coordinates: List @Persisted public var type: String = "Point" // public for testing public var latitude: Double { return coordinates[1] } public var longitude: Double { return coordinates[0] } convenience init(_ latitude: Double, _ longitude: Double) { self.init() coordinates.append(objectsIn: [longitude, latitude]) } } public class PersonWithInvalidTypes: Object { @Persisted public var geoPointCoordinatesEmbedded: CoordinatesGeoPointEmbedded? @Persisted public var geoPointTypeEmbedded: TypeGeoPointEmbedded? @Persisted public var geoPoint: TopLevelGeoPoint? } public class CoordinatesGeoPointEmbedded: EmbeddedObject { @Persisted public var coordinates: List } public class TypeGeoPointEmbedded: EmbeddedObject { @Persisted public var type: String = "Point" // public for testing } public class TopLevelGeoPoint: Object { @Persisted public var coordinates: List @Persisted public var type: String = "Point" // public for testing } class PersonLocation: Object { @Persisted var name: String @Persisted var location: Location? convenience init(_ name: String, _ location: Location?) { self.init() self.name = name self.location = location } } class GeospatialTests: TestCase { func populatePersonLocationTable() throws { let realm = realmWithTestPath() try realm.write { realm.add(PersonLocation("Diana", Location(40.7128, -74.0060))) realm.add(PersonLocation("Maria", Location(55.6761, 12.5683))) realm.add(PersonLocation("Tomas", Location(55.6280, 12.0826))) realm.add(PersonLocation("Alba", Location(-76, -76))) realm.add(PersonLocation("Manuela", nil)) } } func testGeoPoints() throws { assertGeoPoint(90, 0) assertGeoPoint(-90, 0) assertGeoPoint(12.3456789, 0) assertGeoPoint(90.000000001, 0, isNull: true) assertGeoPoint(-90.000000001, 0, isNull: true) assertGeoPoint(9999999, 0, isNull: true) assertGeoPoint(-9999999, 0, isNull: true) assertGeoPoint(0, 180) assertGeoPoint(0, -180) assertGeoPoint(0, 12.3456789) assertGeoPoint(0, 180.000000001, isNull: true) assertGeoPoint(0, -180.000000001, isNull: true) assertGeoPoint(0, 9999999, isNull: true) assertGeoPoint(0, -9999999, isNull: true) assertGeoPoint(90, 0, 0) assertGeoPoint(90, 0, 500) assertGeoPoint(90, 0, -1, isNull: true) assertGeoPoint(90, 0, Double.nan, isNull: true) assertGeoPoint(90, 0, -500, isNull: true) assertGeoPoint(Double.nan, 0, isNull: true) assertGeoPoint(0, Double.nan, isNull: true) func assertGeoPoint(_ latitude: Double, _ longitude: Double, _ altitude: Double = 0, isNull: Bool = false) { if isNull { XCTAssertNil(GeoPoint(latitude: latitude, longitude: longitude, altitude: altitude)) } else { XCTAssertNotNil(GeoPoint(latitude: latitude, longitude: longitude, altitude: altitude)) } } } func testGeoBox() throws { XCTAssertNotNil(GeoBox(bottomLeft: GeoPoint(latitude: -90, longitude: -180)!, topRight: GeoPoint(latitude: 90, longitude: 180)!)) XCTAssertNotNil(GeoBox(bottomLeft: (-90, -180), topRight: (90, 180))) XCTAssertNil(GeoBox(bottomLeft: (-91, -181), topRight: (91, 181))) } func testGeoPolygon() throws { // GeoPolygon require outerRing with at least three vertices and 4 points assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 0, longitude: 0)!, isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, isNull: true) // GeoPolygon require outerRing first and last point to be equal to close the Polygon assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 0)!, GeoPoint(latitude: 0, longitude: 0)!) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 0)!, GeoPoint(latitude: 3, longitude: 3)!, isNull: true) // GeoPolygon require holes to have at least three vertices and 4 points, and first and last point to be equal for each hole assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!]) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!], isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 0, longitude: 0)!], isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!], isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 3, longitude: 3)!], isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!], [GeoPoint(latitude: 0, longitude: 0)!], isNull: true) assertGeoPolygon(outerRing: GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!, holes: [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 0, longitude: 0)!], [GeoPoint(latitude: 0, longitude: 0)!, GeoPoint(latitude: 1, longitude: 1)!, GeoPoint(latitude: 2, longitude: 2)!, GeoPoint(latitude: 3, longitude: 3)!], isNull: true) func assertGeoPolygon(outerRing: GeoPoint..., holes: [GeoPoint]..., isNull: Bool = false) { if isNull { XCTAssertNil(GeoPolygon(outerRing: outerRing.map { $0 }, holes: holes.map { $0 })) } else { XCTAssertNotNil(GeoPolygon(outerRing: outerRing.map { $0 }, holes: holes.map { $0 })) } } // Using Simplified initialisers XCTAssertNotNil(GeoPolygon(outerRing: [(40.0096192, -75.5175781), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175781)])) XCTAssertNil(GeoPolygon(outerRing: [(40.0096192, -75.5175781)])) XCTAssertNotNil(GeoPolygon(outerRing: [(0, 0), (1, 1), (2, 2), (0, 0)], holes: [[(0, 0), (1, 1), (2, 2), (0, 0)]])) XCTAssertNotNil(GeoPolygon(outerRing: [(0, 0), (1, 1), (2, 2), (0, 0)], holes: [(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2), (0, 0)])) XCTAssertNil(GeoPolygon(outerRing: [(0, 0), (1, 1), (2, 2), (0, 0)], holes: [[(0, 0)]])) XCTAssertNil(GeoPolygon(outerRing: [(0, 0), (1, 1), (2, 2), (0, 0)], holes: [[(0, 0), (1, 1), (2, 2), (3, 3)]])) XCTAssertNil(GeoPolygon(outerRing: [(0, 0), (1, 1), (2, 2), (0, 0)], holes: [(0, 0), (1, 1), (2, 2), (0, 0)], [(0, 0), (1, 1), (2, 2), (3, 3)])) } func testGeoDistance() throws { assertGeoDistance(Distance.radians(0)) assertGeoDistance(Distance.radians(20)) assertGeoDistance(Distance.radians(-20), isNull: true) assertGeoDistance(Distance.radians(.nan), isNull: true) assertGeoDistance(Distance.kilometers(0)) assertGeoDistance(Distance.kilometers(10)) assertGeoDistance(Distance.kilometers(-10), isNull: true) assertGeoDistance(Distance.kilometers(.nan), isNull: true) assertGeoDistance(Distance.miles(0)) assertGeoDistance(Distance.miles(10)) assertGeoDistance(Distance.miles(-10), isNull: true) assertGeoDistance(Distance.miles(.nan), isNull: true) assertGeoDistance(Distance.degrees(0)) assertGeoDistance(Distance.degrees(90)) assertGeoDistance(Distance.degrees(-90), isNull: true) assertGeoDistance(Distance.degrees(.nan), isNull: true) func assertGeoDistance(_ radius: Distance?, isNull: Bool = false) { if isNull { XCTAssertNil(radius) } else { XCTAssertNotNil(radius) } } } func testGeoCircle() throws { XCTAssertNotNil(GeoCircle(center: GeoPoint(latitude: 0, longitude: 70)!, radius: .radians(0)!)) XCTAssertNotNil(GeoCircle(center: GeoPoint(latitude: 0, longitude: 70)!, radiusInRadians: 500)) XCTAssertNil(GeoCircle(center: GeoPoint(latitude: 0, longitude: 70)!, radiusInRadians: Double.nan)) XCTAssertNil(GeoCircle(center: GeoPoint(latitude: 0, longitude: 70)!, radiusInRadians: -500)) // Using Simplified initialiser XCTAssertNotNil(GeoCircle(center: (0, 70), radiusInRadians: 0)) XCTAssertNil(GeoCircle(center: (0, 70), radiusInRadians: -500)) } func testDistanceFromKilometers() throws { let earthCircumferenceKM: Double = 40075 let distance = Distance.kilometers(earthCircumferenceKM)! XCTAssertEqual(distance.radians, Double.pi * 2, accuracy: distance.radians * 0.0001) XCTAssertEqual(distance.asKilometers(), earthCircumferenceKM, accuracy: distance.asKilometers() * 0.0001) } func testDistanceFromMiles() throws { let earthCircumferenceMiles: Double = 24901 let distance = Distance.miles(earthCircumferenceMiles)! XCTAssertEqual(distance.radians, Double.pi * 2, accuracy: distance.radians * 0.0001) XCTAssertEqual(distance.asMiles(), earthCircumferenceMiles, accuracy: distance.asMiles() * 0.0001) } func testDistanceFromDegrees() throws { let distance = Distance.degrees(180)! XCTAssertEqual(distance.radians, Double.pi, accuracy: distance.radians * 0.0001) XCTAssertEqual(distance.asDegrees(), 180, accuracy: distance.asDegrees() * 0.0001) } func testDistanceFromRadians() throws { let distance = Distance.radians(Double.pi)! XCTAssertEqual(distance.radians, Double.pi) } func testFilterShapes() throws { try populatePersonLocationTable() assertFilterShape(GeoBox(bottomLeft: (55.6281, 12.0826), topRight: (55.6762, 12.5684))!, count: 1, expectedMatches: ["Maria"]) assertFilterShape(GeoBox(bottomLeft: (55.6279, 12.0825), topRight: (55.6762, 12.5684))!, count: 2, expectedMatches: ["Maria", "Tomas"]) assertFilterShape(GeoBox(bottomLeft: (0, -75), topRight: (60, 15))!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) assertFilterShape(GeoBox(bottomLeft: (0, -75), topRight: (60, 15))!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) assertFilterShape(GeoPolygon(outerRing: [(55.6281, 12.0826), (55.6761, 12.0826), (55.6761, 12.5684), (55.6281, 12.5684), (55.6281, 12.0826)])!, count: 1, expectedMatches: ["Maria"]) assertFilterShape(GeoPolygon(outerRing: [(55, 12), (55.67, 12.5), ( 55.67, 11.5), (55, 12)])!, count: 1, expectedMatches: ["Tomas"]) assertFilterShape(GeoPolygon(outerRing: [(40.0096192, -75.5175781), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175781)])!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) assertFilterShape(GeoPolygon(outerRing: [(40.0096192, -75.5175781), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175781)])!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) // GeoPolygon with holes assertFilterShape(GeoPolygon(outerRing: [(50, -80), (61, 21), (21, 21), (-80, -80), (50, -80)], holes: [[(40.0096192, -75.5175781), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175781)]])!, count: 1, expectedMatches: ["Alba"]) assertFilterShape(GeoPolygon(outerRing: [(50, -80), (62, 22), (22, 22), (-80, -80), (50, -80)], holes: [[(40.7129, -75), (40.7129, -74), (40.7126, -74), (40.7129, -75)]])!, count: 3, expectedMatches: ["Maria", "Tomas", "Alba"]) assertFilterShape(GeoPolygon(outerRing: [(50, -80), (62, 22), (22, 22), (-80, -80), (50, -80)], holes: [[(40.7129, -75), (40.7129, -74), (40.7126, -74), (40.7129, -75)], [(-77, -77), (-77, -75), (-75, -75), (-75, -77), (-77, -77)]])!, count: 2, expectedMatches: ["Maria", "Tomas"]) assertFilterShape(GeoPolygon(outerRing: [(50, -80), (62, 22), (22, 22), (-80, -80), (50, -80)], holes: [[(40.7129, -75), (40.7129, -74), (40.7126, -74), (40.7129, -75)], [(-77, -77), (-77, -75), (-75, -75), (-75, -77), (-77, -77)], [(55.6760, 12.5682), (55.6760, 12.5684), (55.6763, 12.5684), (55.6763, 12.5682), (55.6760, 12.5682)]])!, count: 1, expectedMatches: ["Tomas"]) assertFilterShape(GeoPolygon(outerRing: [(50, -80), (62, 22), (22, 22), (-80, -80), (50, -80)], holes: [[(40.7129, -75), (40.7129, -74), (40.7126, -74), (40.7129, -75)], [(-77, -77), (-77, -75), (-75, -75), (-75, -77), (-77, -77)], [(55.6760, 12.5682), (55.6760, 12.5684), (55.6763, 12.5684), (55.6763, 12.5682), (55.6760, 12.5682)], [(55.6279, 12.0825), (55.6279, 12.0827), (55.6281, 12.0827), (55.6281, 12.0825), (55.6279, 12.0825)]])!, count: 0, expectedMatches: []) assertFilterShape(GeoCircle(center: (55.67, 12.56), radiusInRadians: 0.001)!, count: 1, expectedMatches: ["Maria"]) assertFilterShape(GeoCircle(center: (55.67, 12.56), radiusInRadians: 0.001)!, count: 1, expectedMatches: ["Maria"]) assertFilterShape(GeoCircle(center: (55.67, 12.56), radius: .kilometers(10)!)!, count: 1, expectedMatches: ["Maria"]) assertFilterShape(GeoCircle(center: (55.67, 12.56), radius: .kilometers(100)!)!, count: 2, expectedMatches: ["Maria", "Tomas"]) assertFilterShape(GeoCircle(center: (45, -20), radius: .kilometers(5000)!)!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) assertFilterShape(GeoCircle(center: (45, -20), radius: .kilometers(5000)!)!, count: 3, expectedMatches: ["Diana", "Maria", "Tomas"]) func assertFilterShape(_ shape: U, count: Int, expectedMatches: [String]) { let realm = realmWithTestPath() let resultsBox = realm.objects(PersonLocation.self).where { $0.location.geoWithin(shape) } XCTAssertEqual(resultsBox.count, count) expectedMatches.forEach { match in XCTAssertTrue(resultsBox.contains(where: { $0.name == match })) } let resultsBoxFilter = realm.objects(PersonLocation.self).filter("location IN %@", shape) XCTAssertEqual(resultsBoxFilter.count, count) expectedMatches.forEach { match in XCTAssertTrue(resultsBoxFilter.contains(where: { $0.name == match })) } let resultsBoxNSPredicate = realm.objects(PersonLocation.self).filter(NSPredicate(format: "location IN %@", shape as! CVarArg)) XCTAssertEqual(resultsBoxNSPredicate.count, count) expectedMatches.forEach { match in XCTAssertTrue(resultsBoxNSPredicate.contains(where: { $0.name == match })) } } } func testInvalidTypeValueForObjectGeoPoint() throws { try populatePersonLocationTable() let realm = realmWithTestPath() let persons = realm.objects(PersonLocation.self) let shape = GeoBox(bottomLeft: (55.6281, 12.0826), topRight: (55.6762, 12.5684))! // Executing the query will return one object which is in the region of the GeoBox XCTAssertEqual(realm.objects(PersonLocation.self).where { $0.location.geoWithin(shape) }.count, 1) try realm.write { for person in persons { person.location?.type = "Polygon" } } // Even though one of the GeoPoints is within the box regions, having the type set as // Polygon will cause a no return. XCTAssertEqual(realm.objects(PersonLocation.self).where { $0.location.geoWithin(shape) }.count, 0) } func testInvalidObjectTypesForGeoQuery() throws { let realm = realmWithTestPath() // Populate try realm.write { let geoPointCoordinatesEmbedded = CoordinatesGeoPointEmbedded() geoPointCoordinatesEmbedded.coordinates.append(objectsIn: [2, 1]) let geoPointTypeEmbedded = TypeGeoPointEmbedded() let topLevelGeoPoint = TopLevelGeoPoint() let object = PersonWithInvalidTypes() object.geoPointCoordinatesEmbedded = geoPointCoordinatesEmbedded object.geoPointTypeEmbedded = geoPointTypeEmbedded object.geoPoint = topLevelGeoPoint realm.add(object) } let shape = GeoCircle(center: (0, 0), radiusInRadians: 10.0)! assertThrowsFilter(PersonWithInvalidTypes.self, query: { $0.geoPointCoordinatesEmbedded.geoWithin(shape) }, reason: "Query 'geoPointCoordinatesEmbedded GEOWITHIN GeoCircle([0, 0, 0], 10)' links to data in the wrong format for a geoWithin query") assertThrowsFilter(PersonWithInvalidTypes.self, query: { $0.geoPointTypeEmbedded.geoWithin(shape) }, reason: "Query 'geoPointTypeEmbedded GEOWITHIN GeoCircle([0, 0, 0], 10)' links to data in the wrong format for a geoWithin query") // This is only allowed using filter/NSPredicate assertThrows(realm.objects(PersonWithInvalidTypes.self).filter(NSPredicate(format: "geoPoint IN %@", shape)), reason: "A GEOWITHIN query can only operate on a link to an embedded class but 'TopLevelGeoPoint' is at the top level") func assertThrowsFilter(_ object: T.Type, query: ((Query) -> Query), reason: String) { let realm = realmWithTestPath() assertThrows(realm.objects(object).where(query), reason: reason) let (queryStr, constructedValues) = query(Query._constructForTesting())._constructPredicate() assertThrows(realm.objects(object) .filter(queryStr, constructedValues), reason: reason) assertThrows(realm.objects(object) .filter(NSPredicate(format: queryStr, argumentArray: constructedValues)), reason: reason) } } func testGeoPolygonHoleNotContainedInOuterRingThrows() throws { let realm = realmWithTestPath() assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], holes: [[(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 0]}, {[2, 2, 0], [3, 2, 0], [3, 3, 0], [2, 3, 0], [2, 2, 0]})': 'Secondary ring 1 not contained by first exterior ring - secondary rings must be holes in the first ring") assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], holes: [[(0, 0.1), (0.5, 0.1), (0.5, 0.5), (0, 0.5), (0, 0.1)]])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 0]}, {[0.1, 0, 0], [0.1, 0.5, 0], [0.5, 0.5, 0], [0.5, 0, 0], [0.1, 0, 0]})': 'Secondary ring 1 not contained by first exterior ring - secondary rings must be holes in the first ring") assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], holes: [[(0.25, 0.5), (0.75, 0.5), (0.75, 1.5), (0.25, 1.5), (0.25, 0.5)]])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 0]}, {[0.5, 0.25, 0], [0.5, 0.75, 0], [1.5, 0.75, 0], [1.5, 0.25, 0], [0.5, 0.25, 0]})': 'Secondary ring 1 not contained by first exterior ring - secondary rings must be holes in the first ring") } func testGeoPolygonWithEdgesIntersectionThrows() throws { let realm = realmWithTestPath() assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [GeoPoint(latitude: 50, longitude: -50)!, GeoPoint(latitude: 55, longitude: 55)!, GeoPoint(latitude: -50, longitude: 50)!, GeoPoint(latitude: 70, longitude: -25)!, GeoPoint(latitude: 50, longitude: -50)!])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[-50, 50, 0], [55, 55, 0], [50, -50, 0], [-25, 70, 0], [-50, 50, 0]})': 'Ring 0 is not valid: 'Edges 0 and 2 cross. Edge locations in degrees: [50.0000000, -50.0000000]-[55.0000000, 55.0000000] and [-50.0000000, 50.0000000]-[70.0000000, -25.0000000]''") } func testGeoPolygonDuplicateEdgesThrows() throws { let realm = realmWithTestPath() assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [(50, -80), (60, 20), (20, 20), (-80, -80), (50, -80)], holes: [[(40.0096192, -75.5175781), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175781)]])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[-80, 50, 0], [20, 60, 0], [20, 20, 0], [-80, -80, 0], [-80, 50, 0]}, {[-75.5176, 40.0096, 0], [20, 60, 0], [20, 20, 0], [-75.5176, -75.5176, 0], [-75.5176, 40.0096, 0]})': 'Polygon isn't valid: 'Duplicate edge: ring 1, edge 1 and ring 0, edge 1''") } func testGeoPolygonNestedRingsThrows() throws { let realm = realmWithTestPath() assertThrows(realm.objects(PersonLocation.self).where { $0.location.geoWithin(GeoPolygon(outerRing: [(50, -80), (62, 22), (22, 22), (-80, -80), (50, -80)], holes: [[(45, -77), (61, 21), (21, 21), (-77, -77), (45, -77)], [(40.0096192, -75.5175780), (60, 20), (20, 20), (-75.5175781, -75.5175781), (40.0096192, -75.5175780)]])!) }, reason: "Invalid region in GEOWITHIN query for parameter 'GeoPolygon({[-80, 50, 0], [22, 62, 0], [22, 22, 0], [-80, -80, 0], [-80, 50, 0]}, {[-77, 45, 0], [21, 61, 0], [21, 21, 0], [-77, -77, 0], [-77, 45, 0]}, {[-75.5176, 40.0096, 0], [20, 60, 0], [20, 20, 0], [-75.5176, -75.5176, 0], [-75.5176, 40.0096, 0]})': 'Polygon interior rings cannot be nested: 2") } func testGeoEquality() throws { XCTAssertEqual(GeoPoint(latitude: 1, longitude: 1, altitude: 1), GeoPoint(latitude: 1, longitude: 1, altitude: 1)) XCTAssertNotEqual(GeoPoint(latitude: 1, longitude: 1, altitude: 1), GeoPoint(latitude: 2, longitude: 1, altitude: 1)) XCTAssertNotEqual(GeoPoint(latitude: 1, longitude: 1, altitude: 1), GeoPoint(latitude: 1, longitude: 2, altitude: 1)) XCTAssertNotEqual(GeoPoint(latitude: 1, longitude: 1, altitude: 1), GeoPoint(latitude: 1, longitude: 1, altitude: 2)) XCTAssertEqual(GeoBox(bottomLeft: (0, 0), topRight: (1, 1)), GeoBox(bottomLeft: (0, 0), topRight: (1, 1))) XCTAssertNotEqual(GeoBox(bottomLeft: (1, 1), topRight: (0, 0)), GeoBox(bottomLeft: (0, 0), topRight: (1, 1))) XCTAssertEqual(GeoPolygon(outerRing: [(0, 0), (10, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (4, 4), (1, 1)]]), GeoPolygon(outerRing: [(0, 0), (10, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (4, 4), (1, 1)]])) XCTAssertNotEqual(GeoPolygon(outerRing: [(0, 0), (15, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (4, 4), (1, 1)]]), GeoPolygon(outerRing: [(0, 0), (10, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (4, 4), (1, 1)]])) XCTAssertNotEqual(GeoPolygon(outerRing: [(0, 0), (10, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (3, 3), (1, 1)]]), GeoPolygon(outerRing: [(0, 0), (10, 0), (5, 4), (0, 0)], holes: [[(1, 1), (9, 0), (4, 4), (1, 1)]])) XCTAssertEqual(GeoCircle(center: (55.67, 12.56), radiusInRadians: 0.001), GeoCircle(center: (55.67, 12.56), radiusInRadians: 0.001)) XCTAssertNotEqual(GeoCircle(center: (55, 12), radiusInRadians: 1), GeoCircle(center: (55, 13), radiusInRadians: 1)) XCTAssertNotEqual(GeoCircle(center: (55, 12), radiusInRadians: 1), GeoCircle(center: (55, 12), radiusInRadians: 5)) XCTAssertEqual(Distance.kilometers(1), Distance.kilometers(1)) XCTAssertEqual(Distance.miles(1), Distance.miles(1)) XCTAssertEqual(Distance.radians(50), Distance.radians(50)) XCTAssertEqual(Distance.degrees(180), Distance.degrees(180)) XCTAssertNotEqual(Distance.kilometers(25.01), Distance.kilometers(25.02)) XCTAssertNotEqual(Distance.miles(6.055), Distance.miles(6.054)) XCTAssertNotEqual(Distance.degrees(180.04), Distance.degrees(180.05)) XCTAssertNotEqual(Distance.radians(20.00007695), Distance.degrees(20.00007694)) } } ================================================ FILE: RealmSwift/Tests/KVOTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftKVOObject: Object { @objc dynamic var pk = ObjectId.generate() // primary key for equality @objc dynamic var ignored: Int = 0 @objc dynamic var boolCol: Bool = false @objc dynamic var int8Col: Int8 = 1 @objc dynamic var int16Col: Int16 = 2 @objc dynamic var int32Col: Int32 = 3 @objc dynamic var int64Col: Int64 = 4 @objc dynamic var floatCol: Float = 5 @objc dynamic var doubleCol: Double = 6 @objc dynamic var stringCol: String = "" @objc dynamic var binaryCol: Data = Data() @objc dynamic var dateCol: Date = Date(timeIntervalSince1970: 0) @objc dynamic var decimalCol: Decimal128 = Decimal128(number: 1) @objc dynamic var objectIdCol = ObjectId() @objc dynamic var objectCol: SwiftKVOObject? let arrayCol = List() let setCol = MutableSet() let optIntCol = RealmOptional() let optFloatCol = RealmOptional() let optDoubleCol = RealmOptional() let optBoolCol = RealmOptional() let otherIntCol = RealmProperty() let otherFloatCol = RealmProperty() let otherDoubleCol = RealmProperty() let otherBoolCol = RealmProperty() let otherAnyCol = RealmProperty() @objc dynamic var optStringCol: String? @objc dynamic var optBinaryCol: Data? @objc dynamic var optDateCol: Date? @objc dynamic var optDecimalCol: Decimal128? @objc dynamic var optObjectIdCol: ObjectId? let arrayBool = List() let arrayInt8 = List() let arrayInt16 = List() let arrayInt32 = List() let arrayInt64 = List() let arrayFloat = List() let arrayDouble = List() let arrayString = List() let arrayBinary = List() let arrayDate = List() let arrayDecimal = List() let arrayObjectId = List() let arrayAny = List() let arrayOptBool = List() let arrayOptInt8 = List() let arrayOptInt16 = List() let arrayOptInt32 = List() let arrayOptInt64 = List() let arrayOptFloat = List() let arrayOptDouble = List() let arrayOptString = List() let arrayOptBinary = List() let arrayOptDate = List() let arrayOptDecimal = List() let arrayOptObjectId = List() let setBool = MutableSet() let setInt8 = MutableSet() let setInt16 = MutableSet() let setInt32 = MutableSet() let setInt64 = MutableSet() let setFloat = MutableSet() let setDouble = MutableSet() let setString = MutableSet() let setBinary = MutableSet() let setDate = MutableSet() let setDecimal = MutableSet() let setObjectId = MutableSet() let setAny = MutableSet() let setOptBool = MutableSet() let setOptInt8 = MutableSet() let setOptInt16 = MutableSet() let setOptInt32 = MutableSet() let setOptInt64 = MutableSet() let setOptFloat = MutableSet() let setOptDouble = MutableSet() let setOptString = MutableSet() let setOptBinary = MutableSet() let setOptDate = MutableSet() let setOptDecimal = MutableSet() let setOptObjectId = MutableSet() let mapBool = Map() let mapInt8 = Map() let mapInt16 = Map() let mapInt32 = Map() let mapInt64 = Map() let mapFloat = Map() let mapDouble = Map() let mapString = Map() let mapBinary = Map() let mapDate = Map() let mapDecimal = Map() let mapObjectId = Map() let mapAny = Map() let mapOptBool = Map() let mapOptInt8 = Map() let mapOptInt16 = Map() let mapOptInt32 = Map() let mapOptInt64 = Map() let mapOptFloat = Map() let mapOptDouble = Map() let mapOptString = Map() let mapOptBinary = Map() let mapOptDate = Map() let mapOptDecimal = Map() let mapOptObjectId = Map() override class func primaryKey() -> String { return "pk" } override class func ignoredProperties() -> [String] { return ["ignored"] } } // Most of the testing of KVO functionality is done in the obj-c tests // These tests just verify that it also works on Swift types @available(*, deprecated) // Silence deprecation warnings for RealmOptional class KVOTests: TestCase { var realm: Realm! = nil override func setUp() { super.setUp() realm = try! Realm() realm.beginWrite() } override func tearDown() { realm.cancelWrite() realm = nil super.tearDown() } var changeDictionary: [NSKeyValueChangeKey: Any]? override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { changeDictionary = change } // swiftlint:disable:next cyclomatic_complexity func observeChange(_ obj: SwiftKVOObject, _ key: String, _ old: T?, _ new: T?, fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { let kvoOptions: NSKeyValueObservingOptions = [.old, .new] obj.addObserver(self, forKeyPath: key, options: kvoOptions, context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } let actualOld = changeDictionary![.oldKey] as? T let actualNew = changeDictionary![.newKey] as? T XCTAssert(old == actualOld, "Old value: expected \(String(describing: old)), got \(String(describing: actualOld))", file: (fileName), line: lineNumber) XCTAssert(new == actualNew, "New value: expected \(String(describing: new)), got \(String(describing: actualNew))", file: (fileName), line: lineNumber) changeDictionary = nil } func observeChange( _ obj: SwiftKVOObject, _ keyPath: KeyPath, _ old: T, _ new: T, fileName: StaticString = #filePath, lineNumber: UInt = #line, _ block: () -> Void ) { // observe()'s callback is marked as Sendable, but we'll only ever call it from the same thread let kvoOptions: NSKeyValueObservingOptions = [.old, .new] nonisolated(unsafe) var gotNotification = false nonisolated(unsafe) let nonisolatedOld = old nonisolated(unsafe) let nonisolatedNew = new let observation = obj.observe(keyPath, options: kvoOptions) { _, change in XCTAssertEqual(change.oldValue, nonisolatedOld, file: fileName, line: lineNumber) XCTAssertEqual(change.newValue, nonisolatedNew, file: fileName, line: lineNumber) gotNotification = true } block() observation.invalidate() XCTAssertTrue(gotNotification, file: (fileName), line: lineNumber) } func observeChange( _ obj: SwiftKVOObject, _ keyPath: KeyPath, _ old: T?, _ new: T?, fileName: StaticString = #filePath, lineNumber: UInt = #line, _ block: () -> Void ) { let kvoOptions: NSKeyValueObservingOptions = [.old, .new] nonisolated(unsafe) var gotNotification = false nonisolated(unsafe) let nonisolatedOld = old nonisolated(unsafe) let nonisolatedNew = new let observation = obj.observe(keyPath, options: kvoOptions) { _, change in if let oldValue = change.oldValue { XCTAssertEqual(oldValue, nonisolatedOld, file: (fileName), line: lineNumber) } else { XCTAssertNil(nonisolatedOld, file: (fileName), line: lineNumber) } if let newValue = change.newValue { XCTAssertEqual(newValue, nonisolatedNew, file: (fileName), line: lineNumber) } else { XCTAssertNil(nonisolatedNew, file: (fileName), line: lineNumber) } gotNotification = true } block() observation.invalidate() XCTAssertTrue(gotNotification, file: (fileName), line: lineNumber) } func observeListChange(_ obj: NSObject, _ key: String, _ kind: NSKeyValueChange, _ indexes: NSIndexSet = NSIndexSet(index: 0), fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { obj.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } let actualKind = NSKeyValueChange(rawValue: (changeDictionary![NSKeyValueChangeKey.kindKey] as! NSNumber).uintValue)! let actualIndexes = changeDictionary![NSKeyValueChangeKey.indexesKey]! as! NSIndexSet XCTAssert(actualKind == kind, "Change kind: expected \(kind), got \(actualKind)", file: (fileName), line: lineNumber) XCTAssert(actualIndexes.isEqual(indexes), "Changed indexes: expected \(indexes), got \(actualIndexes)", file: (fileName), line: lineNumber) changeDictionary = nil } func observeSetChange(_ obj: SwiftKVOObject, _ key: String, fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { obj.addObserver(self, forKeyPath: key, options: [], context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } } func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) { return (obj, obj) } // Actual tests follow func testAllPropertyTypes() { let (obj, obs) = getObject(SwiftKVOObject()) observeChange(obs, "boolCol", false, true) { obj.boolCol = true } observeChange(obs, "int8Col", 1 as Int8, 10) { obj.int8Col = 10 } observeChange(obs, "int16Col", 2 as Int16, 10) { obj.int16Col = 10 } observeChange(obs, "int32Col", 3 as Int32, 10) { obj.int32Col = 10 } observeChange(obs, "int64Col", 4 as Int64, 10) { obj.int64Col = 10 } observeChange(obs, "floatCol", 5 as Float, 10) { obj.floatCol = 10 } observeChange(obs, "doubleCol", 6 as Double, 10) { obj.doubleCol = 10 } observeChange(obs, "stringCol", "", "abc") { obj.stringCol = "abc" } observeChange(obs, "objectCol", nil, obj) { obj.objectCol = obj } let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)! observeChange(obs, "binaryCol", Data(), data) { obj.binaryCol = data } let date = Date(timeIntervalSince1970: 1) observeChange(obs, "dateCol", Date(timeIntervalSince1970: 0), date) { obj.dateCol = date } let decimal = Decimal128(number: 2) observeChange(obs, "decimalCol", Decimal128(number: 1), decimal) { obj.decimalCol = decimal } let oldObjectId = obj.objectIdCol let objectId = ObjectId() observeChange(obs, "objectIdCol", oldObjectId, objectId) { obj.objectIdCol = objectId } observeListChange(obs, "arrayCol", .insertion) { obj.arrayCol.append(obj) } observeListChange(obs, "arrayCol", .removal) { obj.arrayCol.removeAll() } observeSetChange(obs, "setCol") { obj.setCol.insert(obj) } observeSetChange(obs, "setCol") { obj.setCol.remove(obj) } observeChange(obs, "optIntCol", nil, 10) { obj.optIntCol.value = 10 } observeChange(obs, "optFloatCol", nil, 10.0) { obj.optFloatCol.value = 10 } observeChange(obs, "optDoubleCol", nil, 10.0) { obj.optDoubleCol.value = 10 } observeChange(obs, "optBoolCol", nil, true) { obj.optBoolCol.value = true } observeChange(obs, "optStringCol", nil, "abc") { obj.optStringCol = "abc" } observeChange(obs, "optBinaryCol", nil, data) { obj.optBinaryCol = data } observeChange(obs, "optDateCol", nil, date) { obj.optDateCol = date } observeChange(obs, "optDecimalCol", nil, decimal) { obj.optDecimalCol = decimal } observeChange(obs, "optObjectIdCol", nil, objectId) { obj.optObjectIdCol = objectId } observeChange(obs, "otherIntCol", nil, 10) { obj.otherIntCol.value = 10 } observeChange(obs, "otherFloatCol", nil, 10.0) { obj.otherFloatCol.value = 10 } observeChange(obs, "otherDoubleCol", nil, 10.0) { obj.otherDoubleCol.value = 10 } observeChange(obs, "otherBoolCol", nil, true) { obj.otherBoolCol.value = true } observeChange(obs, "optIntCol", 10, nil) { obj.optIntCol.value = nil } observeChange(obs, "optFloatCol", 10.0, nil) { obj.optFloatCol.value = nil } observeChange(obs, "optDoubleCol", 10.0, nil) { obj.optDoubleCol.value = nil } observeChange(obs, "optBoolCol", true, nil) { obj.optBoolCol.value = nil } observeChange(obs, "optStringCol", "abc", nil) { obj.optStringCol = nil } observeChange(obs, "optBinaryCol", data, nil) { obj.optBinaryCol = nil } observeChange(obs, "optDateCol", date, nil) { obj.optDateCol = nil } observeChange(obs, "optDecimalCol", decimal, nil) { obj.optDecimalCol = nil } observeChange(obs, "optObjectIdCol", objectId, nil) { obj.optObjectIdCol = nil } observeChange(obs, "otherIntCol", 10, nil) { obj.otherIntCol.value = nil } observeChange(obs, "otherFloatCol", 10.0, nil) { obj.otherFloatCol.value = nil } observeChange(obs, "otherDoubleCol", 10.0, nil) { obj.otherDoubleCol.value = nil } observeChange(obs, "otherBoolCol", true, nil) { obj.otherBoolCol.value = nil } observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.append(true) } observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.append(10) } observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.append(10) } observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.append(10) } observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.append(10) } observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.append(10) } observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.append(10) } observeListChange(obs, "arrayString", .insertion) { obj.arrayString.append("abc") } observeListChange(obs, "arrayDecimal", .insertion) { obj.arrayDecimal.append(decimal) } observeListChange(obs, "arrayObjectId", .insertion) { obj.arrayObjectId.append(objectId) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(true) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(10) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(10) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(10) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(10) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(10) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(10) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append("abc") } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(data) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(date) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.append(decimal) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.append(objectId) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(nil, at: 0) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(nil, at: 0) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(nil, at: 0) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert(nil, at: 0) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(nil, at: 0) } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(nil, at: 0) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.insert(nil, at: 0) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.insert(nil, at: 0) } observeSetChange(obs, "setBool") { obj.setBool.insert(true) } observeSetChange(obs, "setInt8") { obj.setInt8.insert(10) } observeSetChange(obs, "setInt16") { obj.setInt16.insert(10) } observeSetChange(obs, "setInt32") { obj.setInt32.insert(10) } observeSetChange(obs, "setInt64") { obj.setInt64.insert(10) } observeSetChange(obs, "setFloat") { obj.setFloat.insert(10) } observeSetChange(obs, "setDouble") { obj.setDouble.insert(10) } observeSetChange(obs, "setString") { obj.setString.insert("abc") } observeSetChange(obs, "setDecimal") { obj.setDecimal.insert(decimal) } observeSetChange(obs, "setObjectId") { obj.setObjectId.insert(objectId) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(true) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(10) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(10) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(10) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(10) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(10) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(10) } observeSetChange(obs, "setOptString") { obj.setOptString.insert("abc") } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(data) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(date) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(decimal) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(objectId) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(nil) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(nil) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(nil) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(nil) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(nil) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(nil) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(nil) } observeSetChange(obs, "setOptString") { obj.setOptString.insert(nil) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(nil) } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(nil) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(nil) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(nil) } observeSetChange(obs, "mapBool") { obj.mapBool["key"] = true } observeSetChange(obs, "mapInt8") { obj.mapInt8["key"] = 10 } observeSetChange(obs, "mapInt16") { obj.mapInt16["key"] = 10 } observeSetChange(obs, "mapInt32") { obj.mapInt32["key"] = 10 } observeSetChange(obs, "mapInt64") { obj.mapInt64["key"] = 10 } observeSetChange(obs, "mapFloat") { obj.mapFloat["key"] = 10 } observeSetChange(obs, "mapDouble") { obj.mapDouble["key"] = 10 } observeSetChange(obs, "mapString") { obj.mapString["key"] = "abc" } observeSetChange(obs, "mapDecimal") { obj.mapDecimal["key"] = decimal } observeSetChange(obs, "mapObjectId") { obj.mapObjectId["key"] = objectId } observeSetChange(obs, "mapOptBool") { obj.mapOptBool["key"] = true } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8["key"] = 10 } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16["key"] = 10 } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32["key"] = 10 } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64["key"] = 10 } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat["key"] = 10 } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble["key"] = 10 } observeSetChange(obs, "mapOptString") { obj.mapOptString["key"] = "abc" } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal["key"] = decimal } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId["key"] = objectId } observeSetChange(obs, "mapOptBool") { obj.mapOptBool["key"] = nil } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8["key"] = nil } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16["key"] = nil } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32["key"] = nil } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64["key"] = nil } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat["key"] = nil } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble["key"] = nil } observeSetChange(obs, "mapOptString") { obj.mapOptString["key"] = nil } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal["key"] = nil } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId["key"] = nil } if obs.realm == nil { return } observeChange(obs, "invalidated", false, true) { self.realm.delete(obj) } let (obj2, obs2) = getObject(SwiftKVOObject()) observeChange(obs2, "arrayCol.invalidated", false, true) { self.realm.delete(obj2) } let (obj3, obs3) = getObject(SwiftKVOObject()) observeChange(obs3, "setCol.invalidated", false, true) { self.realm.delete(obj3) } let (obj4, obs4) = getObject(SwiftKVOObject()) observeChange(obs4, "mapBool.invalidated", false, true) { self.realm.delete(obj4) } } func testCollectionInMixedKVO() { let (obj, obs) = getObject(SwiftKVOObject()) observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value = AnyRealmValue.fromDictionary([ "key1": .int(1234)]) } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.dictionaryValue?["key1"] = .string("hello") } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.dictionaryValue?["key1"] = nil } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value = AnyRealmValue.fromArray([.bool(true)]) } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.listValue?[0] = .float(123.456) } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.listValue?.append(.bool(true)) } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.listValue?.insert(.date(Date()), at: 1) } observeSetChange(obs, "otherAnyCol") { obj.otherAnyCol.value.listValue?.remove(at: 0) } } func testTypedObservation() { let (obj, obs) = getObject(SwiftKVOObject()) // Swift 5.2+ warns when a literal keypath to a non-@objc property is // passed to observe(). This only works when it's passed directly and // not via a helper, so make sure we aren't triggering this warning on // any property types. _ = obs.observe(\.boolCol) { _, _ in } _ = obs.observe(\.int8Col) { _, _ in } _ = obs.observe(\.int16Col) { _, _ in } _ = obs.observe(\.int32Col) { _, _ in } _ = obs.observe(\.int64Col) { _, _ in } _ = obs.observe(\.floatCol) { _, _ in } _ = obs.observe(\.doubleCol) { _, _ in } _ = obs.observe(\.stringCol) { _, _ in } _ = obs.observe(\.binaryCol) { _, _ in } _ = obs.observe(\.dateCol) { _, _ in } _ = obs.observe(\.objectCol) { _, _ in } _ = obs.observe(\.optStringCol) { _, _ in } _ = obs.observe(\.optBinaryCol) { _, _ in } _ = obs.observe(\.optDateCol) { _, _ in } _ = obs.observe(\.optStringCol) { _, _ in } _ = obs.observe(\.optBinaryCol) { _, _ in } _ = obs.observe(\.optDateCol) { _, _ in } _ = obs.observe(\.isInvalidated) { _, _ in } observeChange(obs, \.boolCol, false, true) { obj.boolCol = true } observeChange(obs, \.int8Col, 1 as Int8, 10 as Int8) { obj.int8Col = 10 } observeChange(obs, \.int16Col, 2 as Int16, 10 as Int16) { obj.int16Col = 10 } observeChange(obs, \.int32Col, 3 as Int32, 10 as Int32) { obj.int32Col = 10 } observeChange(obs, \.int64Col, 4 as Int64, 10 as Int64) { obj.int64Col = 10 } observeChange(obs, \.floatCol, 5 as Float, 10 as Float) { obj.floatCol = 10 } observeChange(obs, \.doubleCol, 6 as Double, 10 as Double) { obj.doubleCol = 10 } observeChange(obs, \.stringCol, "", "abc") { obj.stringCol = "abc" } let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)! observeChange(obs, \.binaryCol, Data(), data) { obj.binaryCol = data } let date = Date(timeIntervalSince1970: 1) observeChange(obs, \.dateCol, Date(timeIntervalSince1970: 0), date) { obj.dateCol = date } let decimal = Decimal128(number: 2) observeChange(obs, \.decimalCol, Decimal128(number: 1), decimal) { obj.decimalCol = decimal } let oldObjectId = obj.objectIdCol let objectId = ObjectId() observeChange(obs, \.objectIdCol, oldObjectId, objectId) { obj.objectIdCol = objectId } observeChange(obs, \.objectCol, nil, obj) { obj.objectCol = obj } observeChange(obs, \.optStringCol, nil, "abc") { obj.optStringCol = "abc" } observeChange(obs, \.optBinaryCol, nil, data) { obj.optBinaryCol = data } observeChange(obs, \.optDateCol, nil, date) { obj.optDateCol = date } observeChange(obs, \.optDecimalCol, nil, decimal) { obj.optDecimalCol = decimal } observeChange(obs, \.optObjectIdCol, nil, objectId) { obj.optObjectIdCol = objectId } observeChange(obs, \.optStringCol, "abc", nil) { obj.optStringCol = nil } observeChange(obs, \.optBinaryCol, data, nil) { obj.optBinaryCol = nil } observeChange(obs, \.optDateCol, date, nil) { obj.optDateCol = nil } observeChange(obs, \.optDecimalCol, decimal, nil) { obj.optDecimalCol = nil } observeChange(obs, \.optObjectIdCol, objectId, nil) { obj.optObjectIdCol = nil } if obs.realm == nil { return } observeChange(obs, \.isInvalidated, false, true) { self.realm.delete(obj) } } func testReadSharedSchemaFromObservedObject() { let obj = SwiftKVOObject() obj.addObserver(self, forKeyPath: "boolCol", options: [.old, .new], context: nil) XCTAssertEqual(type(of: obj).sharedSchema(), SwiftKVOObject.sharedSchema()) obj.removeObserver(self, forKeyPath: "boolCol") } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class KVOPersistedTests: KVOTests { override func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) { realm.add(obj) return (obj, obj) } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class KVOMultipleAccessorsTests: KVOTests { override func getObject(_ obj: SwiftKVOObject) -> (SwiftKVOObject, SwiftKVOObject) { realm.add(obj) return (obj, realm.object(ofType: SwiftKVOObject.self, forPrimaryKey: obj.pk)!) } } ================================================ FILE: RealmSwift/Tests/KeyPathTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift import Foundation class KeyPathTests: TestCase { func testModernObjectTopLevel() { XCTAssertEqual(_name(for: \ModernAllTypesObject.pk), "pk") XCTAssertEqual(_name(for: \ModernAllTypesObject.boolCol), "boolCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.intCol), "intCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.int8Col), "int8Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.int16Col), "int16Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.int32Col), "int32Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.int64Col), "int64Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.floatCol), "floatCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.doubleCol), "doubleCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.stringCol), "stringCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.binaryCol), "binaryCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.dateCol), "dateCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.decimalCol), "decimalCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectIdCol), "objectIdCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol), "objectCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayCol), "arrayCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.setCol), "setCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.anyCol), "anyCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.uuidCol), "uuidCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.intEnumCol), "intEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.stringEnumCol), "stringEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optIntCol), "optIntCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optInt8Col), "optInt8Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.optInt16Col), "optInt16Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.optInt32Col), "optInt32Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.optInt64Col), "optInt64Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.optFloatCol), "optFloatCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optDoubleCol), "optDoubleCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optStringCol), "optStringCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optBinaryCol), "optBinaryCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optDateCol), "optDateCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optDecimalCol), "optDecimalCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optObjectIdCol), "optObjectIdCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optUuidCol), "optUuidCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optIntEnumCol), "optIntEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.optStringEnumCol), "optStringEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayBool), "arrayBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayInt), "arrayInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayInt8), "arrayInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayInt16), "arrayInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayInt32), "arrayInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayInt64), "arrayInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayFloat), "arrayFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayDouble), "arrayDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayString), "arrayString") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayBinary), "arrayBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayDate), "arrayDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayDecimal), "arrayDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayObjectId), "arrayObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayAny), "arrayAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayUuid), "arrayUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptBool), "arrayOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptInt), "arrayOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptInt8), "arrayOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptInt16), "arrayOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptInt32), "arrayOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptInt64), "arrayOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptFloat), "arrayOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptDouble), "arrayOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptString), "arrayOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptBinary), "arrayOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptDate), "arrayOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptDecimal), "arrayOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptObjectId), "arrayOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.arrayOptUuid), "arrayOptUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.setBool), "setBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.setInt), "setInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.setInt8), "setInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.setInt16), "setInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.setInt32), "setInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.setInt64), "setInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.setFloat), "setFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.setDouble), "setDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.setString), "setString") XCTAssertEqual(_name(for: \ModernAllTypesObject.setBinary), "setBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.setDate), "setDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.setDecimal), "setDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.setObjectId), "setObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.setAny), "setAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.setUuid), "setUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptBool), "setOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptInt), "setOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptInt8), "setOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptInt16), "setOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptInt32), "setOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptInt64), "setOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptFloat), "setOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptDouble), "setOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptString), "setOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptBinary), "setOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptDate), "setOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptDecimal), "setOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptObjectId), "setOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.setOptUuid), "setOptUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapBool), "mapBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapInt), "mapInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapInt8), "mapInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapInt16), "mapInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapInt32), "mapInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapInt64), "mapInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapFloat), "mapFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapDouble), "mapDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapString), "mapString") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapBinary), "mapBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapDate), "mapDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapDecimal), "mapDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapObjectId), "mapObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapAny), "mapAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapUuid), "mapUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptBool), "mapOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptInt), "mapOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptInt8), "mapOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptInt16), "mapOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptInt32), "mapOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptInt64), "mapOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptFloat), "mapOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptDouble), "mapOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptString), "mapOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptBinary), "mapOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptDate), "mapOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptDecimal), "mapOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptObjectId), "mapOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.mapOptUuid), "mapOptUuid") XCTAssertEqual(_name(for: \ModernKeyPathObject.embeddedCol), "embeddedCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.linkingObjects), "linkingObjects") XCTAssertEqual(_name(for: \ModernAllTypesObject.linkingObjects[0].boolCol), "linkingObjects.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.list), "list") XCTAssertEqual(_name(for: \ModernKeyPathObject.list[0].boolCol), "list.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.list[0].objectCol), "list.objectCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.list[0].objectCol?.linkingObjects), "list.objectCol.linkingObjects") XCTAssertEqual(_name(for: \ModernKeyPathObject.list[0].objectCol?.linkingObjects[0].boolCol), "list.objectCol.linkingObjects.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.set), "set") XCTAssertEqual(_name(for: \ModernKeyPathObject.set[0].boolCol), "set.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.set[0].objectCol), "set.objectCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.set[0].objectCol?.linkingObjects), "set.objectCol.linkingObjects") XCTAssertEqual(_name(for: \ModernKeyPathObject.set[0].objectCol?.linkingObjects[0].boolCol), "set.objectCol.linkingObjects.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.map), "map") XCTAssertEqual(_name(for: \ModernKeyPathObject.map[""]??.boolCol), "map.boolCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.map[""]??.objectCol), "map.objectCol") XCTAssertEqual(_name(for: \ModernKeyPathObject.map[""]??.objectCol?.linkingObjects), "map.objectCol.linkingObjects") XCTAssertEqual(_name(for: \ModernKeyPathObject.map[""]??.objectCol?.linkingObjects[0].boolCol), "map.objectCol.linkingObjects.boolCol") } func testModernObjectNested() { XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.pk), "objectCol.pk") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.boolCol), "objectCol.boolCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.intCol), "objectCol.intCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.int8Col), "objectCol.int8Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.int16Col), "objectCol.int16Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.int32Col), "objectCol.int32Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.int64Col), "objectCol.int64Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.floatCol), "objectCol.floatCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.doubleCol), "objectCol.doubleCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.stringCol), "objectCol.stringCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.binaryCol), "objectCol.binaryCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.dateCol), "objectCol.dateCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.decimalCol), "objectCol.decimalCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.objectIdCol), "objectCol.objectIdCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.objectCol), "objectCol.objectCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayCol), "objectCol.arrayCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setCol), "objectCol.setCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.anyCol), "objectCol.anyCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.uuidCol), "objectCol.uuidCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.intEnumCol), "objectCol.intEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.stringEnumCol), "objectCol.stringEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optIntCol), "objectCol.optIntCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optInt8Col), "objectCol.optInt8Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optInt16Col), "objectCol.optInt16Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optInt32Col), "objectCol.optInt32Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optInt64Col), "objectCol.optInt64Col") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optFloatCol), "objectCol.optFloatCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optDoubleCol), "objectCol.optDoubleCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optStringCol), "objectCol.optStringCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optBinaryCol), "objectCol.optBinaryCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optDateCol), "objectCol.optDateCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optDecimalCol), "objectCol.optDecimalCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optObjectIdCol), "objectCol.optObjectIdCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optUuidCol), "objectCol.optUuidCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optIntEnumCol), "objectCol.optIntEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.optStringEnumCol), "objectCol.optStringEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayBool), "objectCol.arrayBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayInt), "objectCol.arrayInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayInt8), "objectCol.arrayInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayInt16), "objectCol.arrayInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayInt32), "objectCol.arrayInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayInt64), "objectCol.arrayInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayFloat), "objectCol.arrayFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayDouble), "objectCol.arrayDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayString), "objectCol.arrayString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayBinary), "objectCol.arrayBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayDate), "objectCol.arrayDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayDecimal), "objectCol.arrayDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayObjectId), "objectCol.arrayObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayAny), "objectCol.arrayAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayUuid), "objectCol.arrayUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptBool), "objectCol.arrayOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptInt), "objectCol.arrayOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptInt8), "objectCol.arrayOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptInt16), "objectCol.arrayOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptInt32), "objectCol.arrayOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptInt64), "objectCol.arrayOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptFloat), "objectCol.arrayOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptDouble), "objectCol.arrayOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptString), "objectCol.arrayOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptBinary), "objectCol.arrayOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptDate), "objectCol.arrayOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptDecimal), "objectCol.arrayOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptObjectId), "objectCol.arrayOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.arrayOptUuid), "objectCol.arrayOptUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setBool), "objectCol.setBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setInt), "objectCol.setInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setInt8), "objectCol.setInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setInt16), "objectCol.setInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setInt32), "objectCol.setInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setInt64), "objectCol.setInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setFloat), "objectCol.setFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setDouble), "objectCol.setDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setString), "objectCol.setString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setBinary), "objectCol.setBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setDate), "objectCol.setDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setDecimal), "objectCol.setDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setObjectId), "objectCol.setObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setAny), "objectCol.setAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setUuid), "objectCol.setUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptBool), "objectCol.setOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptInt), "objectCol.setOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptInt8), "objectCol.setOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptInt16), "objectCol.setOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptInt32), "objectCol.setOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptInt64), "objectCol.setOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptFloat), "objectCol.setOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptDouble), "objectCol.setOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptString), "objectCol.setOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptBinary), "objectCol.setOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptDate), "objectCol.setOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptDecimal), "objectCol.setOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptObjectId), "objectCol.setOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.setOptUuid), "objectCol.setOptUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapBool), "objectCol.mapBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapInt), "objectCol.mapInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapInt8), "objectCol.mapInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapInt16), "objectCol.mapInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapInt32), "objectCol.mapInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapInt64), "objectCol.mapInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapFloat), "objectCol.mapFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapDouble), "objectCol.mapDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapString), "objectCol.mapString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapBinary), "objectCol.mapBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapDate), "objectCol.mapDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapDecimal), "objectCol.mapDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapObjectId), "objectCol.mapObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapAny), "objectCol.mapAny") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapUuid), "objectCol.mapUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptBool), "objectCol.mapOptBool") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptInt), "objectCol.mapOptInt") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptInt8), "objectCol.mapOptInt8") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptInt16), "objectCol.mapOptInt16") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptInt32), "objectCol.mapOptInt32") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptInt64), "objectCol.mapOptInt64") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptFloat), "objectCol.mapOptFloat") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptDouble), "objectCol.mapOptDouble") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptString), "objectCol.mapOptString") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptBinary), "objectCol.mapOptBinary") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptDate), "objectCol.mapOptDate") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptDecimal), "objectCol.mapOptDecimal") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptObjectId), "objectCol.mapOptObjectId") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.mapOptUuid), "objectCol.mapOptUuid") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.linkingObjects), "objectCol.linkingObjects") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.linkingObjects[0].boolCol), "objectCol.linkingObjects.boolCol") XCTAssertEqual(_name(for: \ModernAllTypesObject.objectCol?.linkingObjects[0].objectCol?.linkingObjects[0].boolCol), "objectCol.linkingObjects.objectCol.linkingObjects.boolCol") } func testOldObjectSyntax() { XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.boolCol), "boolCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.intCol), "intCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.int8Col), "int8Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.int16Col), "int16Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.int32Col), "int32Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.int64Col), "int64Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.intEnumCol), "intEnumCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.floatCol), "floatCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.doubleCol), "doubleCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.stringCol), "stringCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.binaryCol), "binaryCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.dateCol), "dateCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.decimalCol), "decimalCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.objectIdCol), "objectIdCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.objectCol), "objectCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.objectCol?.boolCol), "objectCol.boolCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.uuidCol), "uuidCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.embeddedCol), "embeddedCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.embeddedCol?.boolCol), "embeddedCol.boolCol") // Nested objects will work fine once they can utilize _kvcKeyPathString. XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.embeddedCol?.embeddedCol?.child), "embeddedCol.embeddedCol.child") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.anyCol), "anyCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.arrayCol), "arrayCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.setCol), "setCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.mapCol), "mapCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.arrayObjCol), "arrayObjCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.setObjCol), "setObjCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.mapObjCol), "mapObjCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.arrayEmbeddedCol), "arrayEmbeddedCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesObject.mapEmbeddedCol), "mapEmbeddedCol") XCTAssertEqual(_name(for: \SwiftDogObject.owners), "owners") // Allowing old property syntax objects to do nested key path strings involves an invasive change to `unmanagedGetter` in RLMAccessor. // We would need to prevent the getter function from returning `nil` and instead return a block that appends the property name to the // tracing array in the object. This would break a ton of other stuff and instead it is recommended that a user use @Persisted } func testOldSyntaxEmbeddedObject() { XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.boolCol), "boolCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.intCol), "intCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.int8Col), "int8Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.int16Col), "int16Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.int32Col), "int32Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.int64Col), "int64Col") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.intEnumCol), "intEnumCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.floatCol), "floatCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.doubleCol), "doubleCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.stringCol), "stringCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.binaryCol), "binaryCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.dateCol), "dateCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.decimalCol), "decimalCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.objectIdCol), "objectIdCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.uuidCol), "uuidCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.anyCol), "anyCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.arrayCol), "arrayCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.setCol), "setCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.mapCol), "mapCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.arrayEmbeddedCol), "arrayEmbeddedCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.mapEmbeddedCol), "mapEmbeddedCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.embeddedCol), "embeddedCol") XCTAssertEqual(_name(for: \SwiftOldSyntaxAllTypesEmbeddedObject.embeddedCol?.child), "embeddedCol.child") } func testEmbeddedObject() { XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.boolCol), "boolCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.intCol), "intCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.int8Col), "int8Col") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.int16Col), "int16Col") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.int32Col), "int32Col") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.int64Col), "int64Col") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.intEnumCol), "intEnumCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.floatCol), "floatCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.doubleCol), "doubleCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.stringCol), "stringCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.binaryCol), "binaryCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.dateCol), "dateCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.decimalCol), "decimalCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.objectIdCol), "objectIdCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.uuidCol), "uuidCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.anyCol), "anyCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.arrayCol), "arrayCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.setCol), "setCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapCol), "mapCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.arrayEmbeddedCol), "arrayEmbeddedCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapEmbeddedCol), "mapEmbeddedCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.embeddedCol), "embeddedCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.embeddedCol?.child), "embeddedCol.child") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.embeddedCol?.child?.value), "embeddedCol.child.value") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.arrayEmbeddedCol[0].value), "arrayEmbeddedCol.value") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.arrayEmbeddedCol[0].child?.value), "arrayEmbeddedCol.child.value") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapEmbeddedCol), "mapEmbeddedCol") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapEmbeddedCol[""]??.value), "mapEmbeddedCol.value") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapEmbeddedCol[""]??.child), "mapEmbeddedCol.child") XCTAssertEqual(_name(for: \ModernAllTypesEmbeddedObject.mapEmbeddedCol[""]??.child?.value), "mapEmbeddedCol.child.value") } func testCustomTypes() { XCTAssertEqual(_name(for: \AllCustomPersistableTypes.bool), "bool") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.int), "int") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.int8), "int8") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.int16), "int16") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.int32), "int32") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.int64), "int64") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.float), "float") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.double), "double") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.string), "string") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.binary), "binary") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.date), "date") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.decimal), "decimal") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.objectId), "objectId") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.uuid), "uuid") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.object), "object") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optBool), "optBool") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optInt), "optInt") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optInt8), "optInt8") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optInt16), "optInt16") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optInt32), "optInt32") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optInt64), "optInt64") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optFloat), "optFloat") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optDouble), "optDouble") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optString), "optString") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optBinary), "optBinary") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optDate), "optDate") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optDecimal), "optDecimal") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optObjectId), "optObjectId") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optUuid), "optUuid") XCTAssertEqual(_name(for: \AllCustomPersistableTypes.optObject), "optObject") XCTAssertEqual(_name(for: \CustomPersistableCollections.listBool), "listBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.listInt), "listInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.listInt8), "listInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.listInt16), "listInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.listInt32), "listInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.listInt64), "listInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.listFloat), "listFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.listDouble), "listDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.listString), "listString") XCTAssertEqual(_name(for: \CustomPersistableCollections.listBinary), "listBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.listDate), "listDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.listDecimal), "listDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.listUuid), "listUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.listObjectId), "listObjectId") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptBool), "listOptBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptInt), "listOptInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptInt8), "listOptInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptInt16), "listOptInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptInt32), "listOptInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptInt64), "listOptInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptFloat), "listOptFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptDouble), "listOptDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptString), "listOptString") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptBinary), "listOptBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptDate), "listOptDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptDecimal), "listOptDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptUuid), "listOptUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.listOptObjectId), "listOptObjectId") XCTAssertEqual(_name(for: \CustomPersistableCollections.setBool), "setBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.setInt), "setInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.setInt8), "setInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.setInt16), "setInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.setInt32), "setInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.setInt64), "setInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.setFloat), "setFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.setDouble), "setDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.setString), "setString") XCTAssertEqual(_name(for: \CustomPersistableCollections.setBinary), "setBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.setDate), "setDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.setDecimal), "setDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.setUuid), "setUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.setObjectId), "setObjectId") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptBool), "setOptBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptInt), "setOptInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptInt8), "setOptInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptInt16), "setOptInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptInt32), "setOptInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptInt64), "setOptInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptFloat), "setOptFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptDouble), "setOptDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptString), "setOptString") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptBinary), "setOptBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptDate), "setOptDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptDecimal), "setOptDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptUuid), "setOptUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.setOptObjectId), "setOptObjectId") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapBool), "mapBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapInt), "mapInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapInt8), "mapInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapInt16), "mapInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapInt32), "mapInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapInt64), "mapInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapFloat), "mapFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapDouble), "mapDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapString), "mapString") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapBinary), "mapBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapDate), "mapDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapDecimal), "mapDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapUuid), "mapUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapObjectId), "mapObjectId") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptBool), "mapOptBool") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptInt), "mapOptInt") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptInt8), "mapOptInt8") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptInt16), "mapOptInt16") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptInt32), "mapOptInt32") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptInt64), "mapOptInt64") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptFloat), "mapOptFloat") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptDouble), "mapOptDouble") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptString), "mapOptString") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptBinary), "mapOptBinary") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptDate), "mapOptDate") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptDecimal), "mapOptDecimal") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptUuid), "mapOptUuid") XCTAssertEqual(_name(for: \CustomPersistableCollections.mapOptObjectId), "mapOptObjectId") } } class SwiftOldSyntaxAllTypesObject: Object { @objc dynamic var boolCol = false @objc dynamic var intCol = 123 @objc dynamic var int8Col: Int8 = 123 @objc dynamic var int16Col: Int16 = 123 @objc dynamic var int32Col: Int32 = 123 @objc dynamic var int64Col: Int64 = 123 @objc dynamic var intEnumCol = IntEnum.value1 @objc dynamic var floatCol = 1.23 as Float @objc dynamic var doubleCol = 12.3 @objc dynamic var stringCol = "a" @objc dynamic var binaryCol = Data("a".utf8) @objc dynamic var dateCol = Date(timeIntervalSince1970: 1) @objc dynamic var decimalCol = Decimal128("123e4") @objc dynamic var objectIdCol = ObjectId("1234567890ab1234567890ab") @objc dynamic var objectCol: SwiftBoolObject? = SwiftBoolObject() @objc dynamic var uuidCol: UUID = UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! @objc dynamic var embeddedCol: SwiftOldSyntaxAllTypesEmbeddedObject? = SwiftOldSyntaxAllTypesEmbeddedObject() let anyCol = RealmProperty() let arrayCol = List() let setCol = MutableSet() let mapCol = Map() let arrayObjCol = List() let setObjCol = MutableSet() let mapObjCol = Map() let arrayEmbeddedCol = List() let mapEmbeddedCol = Map() } class SwiftOldSyntaxAllTypesEmbeddedObject: EmbeddedObject { @objc dynamic var boolCol = false @objc dynamic var intCol = 123 @objc dynamic var int8Col: Int8 = 123 @objc dynamic var int16Col: Int16 = 123 @objc dynamic var int32Col: Int32 = 123 @objc dynamic var int64Col: Int64 = 123 @objc dynamic var intEnumCol = IntEnum.value1 @objc dynamic var floatCol = 1.23 as Float @objc dynamic var doubleCol = 12.3 @objc dynamic var stringCol = "a" @objc dynamic var binaryCol = Data("a".utf8) @objc dynamic var dateCol = Date(timeIntervalSince1970: 1) @objc dynamic var decimalCol = Decimal128("123e4") @objc dynamic var objectIdCol = ObjectId("1234567890ab1234567890ab") @objc dynamic var uuidCol: UUID = UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! let anyCol = RealmProperty() @objc dynamic var embeddedCol: EmbeddedTreeObject1? let arrayCol = List() let setCol = MutableSet() let mapCol = Map() let arrayEmbeddedCol = List() let mapEmbeddedCol = Map() } class ModernKeyPathObject: Object { @Persisted var embeddedCol: ModernAllTypesEmbeddedObject? @Persisted var list: List @Persisted var listEmbedded: List @Persisted var set: MutableSet @Persisted var map: Map @Persisted var mapEmbedded: Map } class ModernAllTypesEmbeddedObject: EmbeddedObject { @Persisted var boolCol = false @Persisted var intCol = 123 @Persisted var int8Col: Int8 = 123 @Persisted var int16Col: Int16 = 123 @Persisted var int32Col: Int32 = 123 @Persisted var int64Col: Int64 = 123 @Persisted var intEnumCol: ModernIntEnum @Persisted var floatCol = 1.23 as Float @Persisted var doubleCol = 12.3 @Persisted var stringCol = "a" @Persisted var binaryCol = Data("a".utf8) @Persisted var dateCol = Date(timeIntervalSince1970: 1) @Persisted var decimalCol = Decimal128("123e4") @Persisted var objectIdCol = ObjectId("1234567890ab1234567890ab") @Persisted var uuidCol: UUID = UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! @Persisted var anyCol: AnyRealmValue @Persisted var embeddedCol: ModernEmbeddedTreeObject2? @Persisted var arrayCol: List @Persisted var setCol: MutableSet @Persisted var mapCol: Map @Persisted var arrayEmbeddedCol: List @Persisted var mapEmbeddedCol: Map } ================================================ FILE: RealmSwift/Tests/ListTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif class ListTests: TestCase { var str1: SwiftStringObject? var str2: SwiftStringObject? var arrayObject: SwiftArrayPropertyObject! var array: List? func createArray() -> SwiftArrayPropertyObject { fatalError("abstract") } func createArrayWithLinks() -> SwiftListOfSwiftObject { fatalError("abstract") } func createEmbeddedArray() -> List { fatalError("abstract") } override func setUp() { super.setUp() let str1 = SwiftStringObject() str1.stringCol = "1" self.str1 = str1 let str2 = SwiftStringObject() str2.stringCol = "2" self.str2 = str2 arrayObject = createArray() array = arrayObject.array let realm = realmWithTestPath() try! realm.write { realm.add(str1) realm.add(str2) } realm.beginWrite() } override func tearDown() { try! realmWithTestPath().commitWrite() str1 = nil str2 = nil arrayObject = nil array = nil super.tearDown() } override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(ListTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func testPrimitive() { let obj = SwiftListObject() obj.int.append(5) XCTAssertEqual(obj.int.first!, 5) XCTAssertEqual(obj.int.last!, 5) XCTAssertEqual(obj.int[0], 5) obj.int.append(objectsIn: [6, 7, 8] as [Int]) XCTAssertEqual(obj.int.index(of: 6), 1) XCTAssertEqual(2, obj.int.index(matching: NSPredicate(format: "self == 7"))) XCTAssertNil(obj.int.index(matching: NSPredicate(format: "self == 9"))) XCTAssertEqual(2, obj.int.index(matching: { $0 == 7 && $0 < 456 })) XCTAssertNil(obj.int.index(matching: { $0 == 9 })) XCTAssertEqual(obj.int.max(), 8) XCTAssertEqual(obj.int.sum(), 26) obj.string.append("str") XCTAssertEqual(obj.string.first!, "str") XCTAssertEqual(obj.string[0], "str") } func testPrimitiveIterationAcrossNil() { let obj = SwiftListObject() XCTAssertFalse(obj.int.contains(5)) XCTAssertFalse(obj.int8.contains(5)) XCTAssertFalse(obj.int16.contains(5)) XCTAssertFalse(obj.int32.contains(5)) XCTAssertFalse(obj.int64.contains(5)) XCTAssertFalse(obj.float.contains(3.141592)) XCTAssertFalse(obj.double.contains(3.141592)) XCTAssertFalse(obj.string.contains("foobar")) XCTAssertFalse(obj.data.contains(Data())) XCTAssertFalse(obj.date.contains(Date())) XCTAssertFalse(obj.decimal.contains(Decimal128())) XCTAssertFalse(obj.objectId.contains(ObjectId())) XCTAssertFalse(obj.uuidOpt.contains(UUID())) XCTAssertFalse(obj.intOpt.contains { $0 == nil }) XCTAssertFalse(obj.int8Opt.contains { $0 == nil }) XCTAssertFalse(obj.int16Opt.contains { $0 == nil }) XCTAssertFalse(obj.int32Opt.contains { $0 == nil }) XCTAssertFalse(obj.int64Opt.contains { $0 == nil }) XCTAssertFalse(obj.floatOpt.contains { $0 == nil }) XCTAssertFalse(obj.doubleOpt.contains { $0 == nil }) XCTAssertFalse(obj.stringOpt.contains { $0 == nil }) XCTAssertFalse(obj.dataOpt.contains { $0 == nil }) XCTAssertFalse(obj.dateOpt.contains { $0 == nil }) XCTAssertFalse(obj.decimalOpt.contains { $0 == nil }) XCTAssertFalse(obj.objectIdOpt.contains { $0 == nil }) XCTAssertFalse(obj.uuidOpt.contains { $0 == nil }) } func testInvalidated() { guard let array = array else { fatalError("Test precondition failure") } XCTAssertFalse(array.isInvalidated) if let realm = arrayObject.realm { realm.delete(arrayObject) XCTAssertTrue(array.isInvalidated) } } func testFastEnumerationWithMutation() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2]) var str = "" for obj in array { str += obj.stringCol array.append(objectsIn: [str1]) } XCTAssertEqual(str, "12121212121212121212") } func testAppendObject() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } for str in [str1, str2, str1] { array.append(str) } XCTAssertEqual(Int(3), array.count) assertEqual(str1, array[0]) assertEqual(str2, array[1]) assertEqual(str1, array[2]) } func testAppendArray() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2, str1]) XCTAssertEqual(Int(3), array.count) assertEqual(str1, array[0]) assertEqual(str2, array[1]) assertEqual(str1, array[2]) } func testAppendResults() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: realmWithTestPath().objects(SwiftStringObject.self)) XCTAssertEqual(Int(2), array.count) assertEqual(str1, array[0]) assertEqual(str2, array[1]) } func testInsert() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } XCTAssertEqual(Int(0), array.count) array.insert(str1, at: 0) XCTAssertEqual(Int(1), array.count) assertEqual(str1, array[0]) array.insert(str2, at: 0) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str1, array[1]) assertThrows(array.insert(str2, at: 200)) assertThrows(array.insert(str2, at: -200)) } func testRemoveAtIndex() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2, str1]) array.remove(at: 1) assertEqual(str1, array[0]) assertEqual(str1, array[1]) assertThrows(array.remove(at: 2)) assertThrows(array.remove(at: -2)) } func testRemoveAtOffsets() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2, str1]) array.remove(atOffsets: [0, 2]) XCTAssertEqual(array.count, 1) assertEqual(str2, array[0]) array.remove(atOffsets: []) XCTAssertEqual(array.count, 1) assertThrows(array.remove(atOffsets: [1])) } func testRemoveLast() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2]) array.removeLast() XCTAssertEqual(Int(1), array.count) assertEqual(str1, array[0]) array.removeLast() XCTAssertEqual(Int(0), array.count) assertThrows(array.removeLast()) // Should throw if already empty } func testRemoveAll() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2]) array.removeAll() XCTAssertEqual(Int(0), array.count) array.removeAll() // should be a no-op XCTAssertEqual(Int(0), array.count) } func testReplace() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str1]) array.replace(index: 0, object: str2) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str1, array[1]) array.replace(index: 1, object: str2) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str2, array[1]) assertThrows(array.replace(index: 200, object: str2)) assertThrows(array.replace(index: -200, object: str2)) } func testMove() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2]) array.move(from: 1, to: 0) XCTAssertEqual(array[0].stringCol, "2") XCTAssertEqual(array[1].stringCol, "1") array.move(from: 0, to: 1) XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "2") array.move(from: 0, to: 0) XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "2") assertThrows(array.move(from: 0, to: 2)) assertThrows(array.move(from: 2, to: 0)) } func testMoveFromOffsets() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } let str3 = SwiftStringObject(value: ["3"]) array.append(objectsIn: [str1, str2, str3]) array.move(fromOffsets: IndexSet([1]), toOffset: 0) // [2, 1, 3] XCTAssertEqual(array[0].stringCol, "2") XCTAssertEqual(array[1].stringCol, "1") XCTAssertEqual(array[2].stringCol, "3") array.move(fromOffsets: IndexSet([0]), toOffset: 3) // [1, 3, 2] XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "3") XCTAssertEqual(array[2].stringCol, "2") array.move(fromOffsets: IndexSet([0]), toOffset: 1) // [1, 3, 2] XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "3") XCTAssertEqual(array[2].stringCol, "2") array.move(fromOffsets: IndexSet([2]), toOffset: 1) // [1, 2, 3] XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "2") XCTAssertEqual(array[2].stringCol, "3") assertThrows(array.move(fromOffsets: IndexSet([0]), toOffset: 4)) assertThrows(array.move(fromOffsets: IndexSet([4]), toOffset: 0)) array.move(fromOffsets: IndexSet([]), toOffset: 1) // [1, 2, 3] XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "2") XCTAssertEqual(array[2].stringCol, "3") array.move(fromOffsets: IndexSet([0, 2]), toOffset: 1) // [1, 3, 2] XCTAssertEqual(array[0].stringCol, "1") XCTAssertEqual(array[1].stringCol, "3") XCTAssertEqual(array[2].stringCol, "2") } func testReplaceRange() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str1]) array.replaceSubrange(0..<1, with: [str2]) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str1, array[1]) array.replaceSubrange(1..<2, with: [str2]) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str2, array[1]) array.replaceSubrange(0..<0, with: [str2]) XCTAssertEqual(Int(3), array.count) assertEqual(str2, array[0]) assertEqual(str2, array[1]) assertEqual(str2, array[2]) array.replaceSubrange(0..<3, with: []) XCTAssertEqual(Int(0), array.count) assertThrows(array.replaceSubrange(200..<201, with: [str2])) assertThrows(array.replaceSubrange(-200..<200, with: [str2])) assertThrows(array.replaceSubrange(0..<200, with: [str2])) } func testSwapAt() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } array.append(objectsIn: [str1, str2]) array.swapAt(0, 1) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str1, array[1]) array.swapAt(1, 1) XCTAssertEqual(Int(2), array.count) assertEqual(str2, array[0]) assertEqual(str1, array[1]) assertThrows(array.swapAt(-1, 0)) assertThrows(array.swapAt(0, -1)) assertThrows(array.swapAt(1000, 0)) assertThrows(array.swapAt(0, 1000)) } func testChangesArePersisted() { guard let array = array, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } if let realm = array.realm { array.append(objectsIn: [str1, str2]) let otherArray = realm.objects(SwiftArrayPropertyObject.self).first!.array XCTAssertEqual(Int(2), otherArray.count) } } func testPopulateEmptyArray() { guard let array = array else { fatalError("Test precondition failure") } XCTAssertEqual(array.count, 0, "Should start with no array elements.") let obj = SwiftStringObject() obj.stringCol = "a" array.append(obj) array.append(realmWithTestPath().create(SwiftStringObject.self, value: ["b"])) array.append(obj) XCTAssertEqual(array.count, 3) XCTAssertEqual(array[0].stringCol, "a") XCTAssertEqual(array[1].stringCol, "b") XCTAssertEqual(array[2].stringCol, "a") // Make sure we can enumerate for obj in array { XCTAssertTrue(obj.description.utf16.count > 0, "Object should have description") } } func testEnumeratingListWithListProperties() { let arrayObject = createArrayWithLinks() arrayObject.realm?.beginWrite() for _ in 0..<10 { arrayObject.array.append(SwiftObject()) } try! arrayObject.realm?.commitWrite() XCTAssertEqual(10, arrayObject.array.count) for object in arrayObject.array { XCTAssertEqual(123, object.intCol) XCTAssertEqual(false, object.objectCol!.boolCol) XCTAssertEqual(0, object.arrayCol.count) } } func testValueForKey() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let listObject = SwiftListOfSwiftObject() let object = SwiftObject() object.intCol = value object.doubleCol = Double(value) object.stringCol = String(value) object.decimalCol = Decimal128(number: value as NSNumber) object.objectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) listObject.array.append(object) realm.add(listObject) } } let listObjects = realm.objects(SwiftListOfSwiftObject.self) let listsOfObjects = listObjects.value(forKeyPath: "array") as! [List] let objects = realm.objects(SwiftObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftObject) -> T) { let properties: [T] = Array(listObjects.flatMap { $0.array.map(fn) }) let kvcProperties: [T] = Array(listsOfObjects.flatMap { $0.map(fn) }) XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftObject) -> T) { let properties = Array(objects.compactMap(fn)) let listsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(listsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0.intCol } testProperty { $0.doubleCol } testProperty { $0.stringCol } testProperty { $0.decimalCol } testProperty { $0.objectIdCol } testProperty("intCol") { $0.intCol } testProperty("doubleCol") { $0.doubleCol } testProperty("stringCol") { $0.stringCol } testProperty("decimalCol") { $0.decimalCol } testProperty("objectIdCol") { $0.objectIdCol } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional func testValueForKeyOptional() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let listObject = SwiftListOfSwiftOptionalObject() let object = SwiftOptionalObject() object.optIntCol.value = value object.optInt8Col.value = Int8(value) object.optDoubleCol.value = Double(value) object.optStringCol = String(value) object.optNSStringCol = NSString(format: "%d", value) object.optDecimalCol = Decimal128(number: value as NSNumber) object.optObjectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) listObject.array.append(object) realm.add(listObject) } } let listObjects = realm.objects(SwiftListOfSwiftOptionalObject.self) let listsOfObjects = listObjects.value(forKeyPath: "array") as! [List] let objects = realm.objects(SwiftOptionalObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftOptionalObject) -> T) { let properties: [T] = Array(listObjects.flatMap { $0.array.map(fn) }) let kvcProperties: [T] = Array(listsOfObjects.flatMap { $0.map(fn) }) XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftOptionalObject) -> T) { let properties = Array(objects.compactMap(fn)) let listsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(listsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0.optIntCol.value } testProperty { $0.optInt8Col.value } testProperty { $0.optDoubleCol.value } testProperty { $0.optStringCol } testProperty { $0.optNSStringCol } testProperty { $0.optDecimalCol } testProperty { $0.optObjectCol } testProperty("optIntCol") { $0.optIntCol.value } testProperty("optInt8Col") { $0.optInt8Col.value } testProperty("optDoubleCol") { $0.optDoubleCol.value } testProperty("optStringCol") { $0.optStringCol } testProperty("optNSStringCol") { $0.optNSStringCol } testProperty("optDecimalCol") { $0.optDecimalCol } testProperty("optObjectCol") { $0.optObjectCol } } func testAppendEmbedded() { let list = createEmbeddedArray() list.realm?.beginWrite() for i in 0..<10 { list.append(EmbeddedTreeObject1(value: [i])) } XCTAssertEqual(10, list.count) for (i, object) in list.enumerated() { XCTAssertEqual(i, object.value) XCTAssertEqual(list.realm, object.realm) } if list.realm != nil { assertThrows(list.append(list[0]), reason: "Cannot add an existing managed embedded object to a List.") } list.realm?.cancelWrite() } func testSetEmbedded() { let list = createEmbeddedArray() list.realm?.beginWrite() list.append(EmbeddedTreeObject1(value: [0])) let oldObj = list[0] let obj = EmbeddedTreeObject1(value: [1]) list[0] = obj XCTAssertTrue(list[0].isSameObject(as: obj)) XCTAssertEqual(obj.value, 1) XCTAssertEqual(obj.realm, list.realm) if list.realm != nil { XCTAssertTrue(oldObj.isInvalidated) assertThrows(list[0] = obj, reason: "Cannot add an existing managed embedded object to a List.") } list.realm?.cancelWrite() } func testUnmanagedListComparison() { let obj = SwiftIntObject() obj.intCol = 5 let obj2 = SwiftIntObject() obj2.intCol = 6 let obj3 = SwiftIntObject() obj3.intCol = 8 let objects = [obj, obj2, obj3] let objects2 = [obj, obj2] let list1 = List() let list2 = List() XCTAssertEqual(list1, list2, "Empty instances should be equal by `==` operator") list1.append(objectsIn: objects) list2.append(objectsIn: objects) let list3 = List() list3.append(objectsIn: objects2) XCTAssertTrue(list1 !== list2, "instances should not be identical") XCTAssertEqual(list1, list2, "instances should be equal by `==` operator") XCTAssertNotEqual(list1, list3, "instances should be equal by `==` operator") XCTAssertTrue(list1.isEqual(list2), "instances should be equal by `isEqual` method") XCTAssertTrue(!list1.isEqual(list3), "instances should be equal by `isEqual` method") XCTAssertEqual(Array(list1), Array(list2), "instances converted to Swift.Array should be equal") XCTAssertNotEqual(Array(list1), Array(list3), "instances converted to Swift.Array should be equal") list3.append(obj3) XCTAssertEqual(list1, list3, "instances should be equal by `==` operator") } } class ListStandaloneTests: ListTests { override func createArray() -> SwiftArrayPropertyObject { let array = SwiftArrayPropertyObject() XCTAssertNil(array.realm) return array } override func createArrayWithLinks() -> SwiftListOfSwiftObject { let array = SwiftListOfSwiftObject() XCTAssertNil(array.realm) return array } override func createEmbeddedArray() -> List { return List() } } class ListNewlyAddedTests: ListTests { override func createArray() -> SwiftArrayPropertyObject { let array = SwiftArrayPropertyObject() array.name = "name" let realm = realmWithTestPath() try! realm.write { realm.add(array) } XCTAssertNotNil(array.realm) return array } override func createArrayWithLinks() -> SwiftListOfSwiftObject { let array = SwiftListOfSwiftObject() let realm = try! Realm() try! realm.write { realm.add(array) } XCTAssertNotNil(array.realm) return array } override func createEmbeddedArray() -> List { let parent = EmbeddedParentObject() let list = parent.array let realm = try! Realm() try! realm.write { realm.add(parent) } return list } } class ListNewlyCreatedTests: ListTests { override func createArray() -> SwiftArrayPropertyObject { let realm = realmWithTestPath() realm.beginWrite() let array = realm.create(SwiftArrayPropertyObject.self, value: ["name"]) try! realm.commitWrite() XCTAssertNotNil(array.realm) return array } override func createArrayWithLinks() -> SwiftListOfSwiftObject { let realm = try! Realm() realm.beginWrite() let array = realm.create(SwiftListOfSwiftObject.self) try! realm.commitWrite() XCTAssertNotNil(array.realm) return array } override func createEmbeddedArray() -> List { let realm = try! Realm() return try! realm.write { realm.create(EmbeddedParentObject.self).array } } } class ListRetrievedTests: ListTests { override func createArray() -> SwiftArrayPropertyObject { let realm = realmWithTestPath() realm.beginWrite() realm.create(SwiftArrayPropertyObject.self, value: ["name"]) try! realm.commitWrite() let array = realm.objects(SwiftArrayPropertyObject.self).first! XCTAssertNotNil(array.realm) return array } override func createArrayWithLinks() -> SwiftListOfSwiftObject { let realm = try! Realm() realm.beginWrite() realm.create(SwiftListOfSwiftObject.self) try! realm.commitWrite() let array = realm.objects(SwiftListOfSwiftObject.self).first! XCTAssertNotNil(array.realm) return array } override func createEmbeddedArray() -> List { let realm = try! Realm() try! realm.write { realm.create(EmbeddedParentObject.self) } return realm.objects(EmbeddedParentObject.self).first!.array } } /// Ensure the range replaceable collection methods behave correctly when emulated for Swift 4 and later. class ListRRCMethodsTests: TestCase { private func compare(array: [Int], with list: List) { guard array.count == list.count else { XCTFail("Array and list have different sizes (\(array.count) and \(list.count), respectively).") return } for i in 0..) -> [Int] { return list.map { $0.intCol } } private func makeSwiftIntObjects(from array: [Int]) -> [SwiftIntObject] { return array.map { SwiftIntObject(value: [$0]) } } private func createListObject(_ values: [Int] = [0, 1, 2, 3, 4, 5, 6]) -> SwiftArrayPropertyObject { let object = SwiftArrayPropertyObject() XCTAssertNil(object.realm) object.intArray.append(objectsIn: makeSwiftIntObjects(from: values)) return object } private var array: [Int]! private var list: List! override func setUp() { super.setUp() list = createListObject().intArray array = makeArray(from: list) } func testSubscript() { list[0] = SwiftIntObject(value: [5]) list[1..<4] = createListObject([10, 11, 12]).intArray[0..<2] array[0] = 5 array[1..<4] = [10, 11] compare(array: array, with: list) } func testReplaceWithCollectionIndices() { let newElements = [1, 2, 3] list.replaceSubrange(list.indices, with: makeSwiftIntObjects(from: newElements)) array.replaceSubrange(array.indices, with: newElements) compare(array: array, with: list) } func testRemoveWithCollectionIndices() { list.removeSubrange(list.indices) XCTAssertTrue(list.isEmpty) } func testRemoveFirst() { list.removeFirst() array.removeFirst() compare(array: array, with: list) } func testRemoveFirstFew() { list.removeFirst(3) array.removeFirst(3) compare(array: array, with: list) } func testRemoveFirstInvalid() { assertThrows(list.removeFirst(-1)) assertThrows(list.removeFirst(100)) } func testRemoveLastFew() { list.removeLast(3) array.removeLast(3) compare(array: array, with: list) } func testInsert() { let newElements = [10, 11, 12, 13] list.insert(contentsOf: makeSwiftIntObjects(from: newElements), at: 2) array.insert(contentsOf: newElements, at: 2) compare(array: array, with: list) } func testRemoveClosedSubrange() { let subrange: ClosedRange = 1...3 list.removeSubrange(subrange) array.removeSubrange(subrange) compare(array: array, with: list) } func testRemoveOpenSubrange() { let subrange: Range = 1..<3 list.removeSubrange(subrange) array.removeSubrange(subrange) compare(array: array, with: list) } func testReplaceClosedSubrange() { let subrange: ClosedRange = 2...5 let newElements = [10, 11, 12, 13, 14, 15, 16] list.replaceSubrange(subrange, with: makeSwiftIntObjects(from: newElements)) array.replaceSubrange(subrange, with: newElements) compare(array: array, with: list) } func testReplaceOpenSubrange() { let subrange: Range = 2..<5 let newElements = [10, 11, 12, 13, 14, 15, 16] list.replaceSubrange(subrange, with: makeSwiftIntObjects(from: newElements)) array.replaceSubrange(subrange, with: newElements) compare(array: array, with: list) } } ================================================ FILE: RealmSwift/Tests/MapTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift class MapTests: TestCase { var str1: SwiftStringObject! var str2: SwiftStringObject! var realm: Realm! func createMap() -> Map { fatalError("abstract") } func createMapObject() -> SwiftMapPropertyObject { fatalError("abstract") } func createEmbeddedMap() -> Map { fatalError("abstract") } override func setUp() { super.setUp() let str1 = SwiftStringObject() str1.stringCol = "1" self.str1 = str1 let str2 = SwiftStringObject() str2.stringCol = "2" self.str2 = str2 realm = realmWithTestPath() try! realm.write { realm.add(str1) realm.add(str2) } realm.beginWrite() } override func tearDown() { if realm.isInWriteTransaction { realm.cancelWrite() } str1 = nil str2 = nil realm = nil super.tearDown() } override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(MapTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func testPrimitive() { let obj = SwiftMapObject() obj.int["key"] = 5 XCTAssertEqual(obj.int["key"]!, 5) XCTAssertNil(obj.int["doesntExist"]) obj.int["keyB"] = 6 obj.int["keyC"] = 7 obj.int["keyD"] = 8 XCTAssertEqual(obj.int.max(), 8) XCTAssertEqual(obj.int.min(), 5) XCTAssertEqual(obj.int.sum(), 26) XCTAssertEqual(obj.int.average(), 6.5) obj.string["key"] = "str" XCTAssertEqual(obj.string["key"], "str") } func testPrimitiveIterationAcrossNil() { let obj = SwiftMapObject() XCTAssertFalse(obj.int.contains(where: { $0 == "0" || $1 == 5 })) XCTAssertFalse(obj.int8.contains(where: { $0 == "0" || $1 == 5 })) XCTAssertFalse(obj.int16.contains(where: { $0 == "0" || $1 == 5 })) XCTAssertFalse(obj.int32.contains(where: { $0 == "0" || $1 == 5 })) XCTAssertFalse(obj.int64.contains(where: { $0 == "0" || $1 == 5 })) XCTAssertFalse(obj.float.contains(where: { $0 == "0" || $1 == 3.141592 })) XCTAssertFalse(obj.double.contains(where: { $0 == "0" || $1 == 3.141592 })) XCTAssertFalse(obj.string.contains(where: { $0 == "0" || $1 == "foobar" })) XCTAssertFalse(obj.data.contains(where: { $0 == "0" || $1 == Data() })) XCTAssertFalse(obj.date.contains(where: { $0 == "0" || $1 == Date() })) XCTAssertFalse(obj.decimal.contains(where: { $0 == "0" || $1 == Decimal128() })) XCTAssertFalse(obj.objectId.contains(where: { $0 == "0" || $1 == ObjectId() })) XCTAssertFalse(obj.uuid.contains(where: { $0 == "0" || $1 == UUID() })) XCTAssertFalse(obj.object.contains(where: { $0 == "0" || $1 == SwiftStringObject() })) XCTAssertFalse(obj.intOpt.contains { $1 == nil }) XCTAssertFalse(obj.int8Opt.contains { $1 == nil }) XCTAssertFalse(obj.int16Opt.contains { $1 == nil }) XCTAssertFalse(obj.int32Opt.contains { $1 == nil }) XCTAssertFalse(obj.int64Opt.contains { $1 == nil }) XCTAssertFalse(obj.floatOpt.contains { $1 == nil }) XCTAssertFalse(obj.doubleOpt.contains { $1 == nil }) XCTAssertFalse(obj.stringOpt.contains { $1 == nil }) XCTAssertFalse(obj.dataOpt.contains { $1 == nil }) XCTAssertFalse(obj.dateOpt.contains { $1 == nil }) XCTAssertFalse(obj.decimalOpt.contains { $1 == nil }) XCTAssertFalse(obj.objectIdOpt.contains { $1 == nil }) XCTAssertFalse(obj.uuidOpt.contains { $1 == nil }) } func testInvalidated() { let mapObject = SwiftMapOfSwiftObject() realm.add(mapObject) XCTAssertFalse(mapObject.map.isInvalidated) if let realm = mapObject.realm { realm.delete(mapObject) XCTAssertTrue(mapObject.map.isInvalidated) } } func testFastEnumerationWithMutation() { let map = createMap() for i in 0...5 { map["key\(i)"] = str1 } var str = "" for obj in map { str += obj.value!.stringCol map[obj.key] = str2 } XCTAssertEqual(str, "111111") } func testMapDescription() { let map = createMap() for i in 0...5 { map["key\(i)"] = SwiftStringObject(value: [String(i)]) } XCTAssertTrue(map.description.hasPrefix("Map")) XCTAssertTrue(map.description.contains("[key3]: SwiftStringObject {\n\t\tstringCol = 3;\n\t}")) } func testAppendObject() { let map = createMap() map["key1"] = str1 map["key2"] = str2 XCTAssertEqual(2, map.count) XCTAssertEqual("1", map["key1"]!!.stringCol) XCTAssertEqual("2", map["key2"]!!.stringCol) } func testInsert() { let map = createMap() XCTAssertEqual(0, map.count) XCTAssertNil(map[str1.stringCol] as Any?) map[str1.stringCol] = str1 XCTAssertEqual(1, map.count) XCTAssertNil(map[str2.stringCol] as Any?) map[str2.stringCol] = str2 XCTAssertEqual(2, map.count) } func testRemove() { let map = createMap() map[str1.stringCol] = str1 XCTAssertEqual(1, map.count) XCTAssertNotNil(map[str1.stringCol] as Any?) map.removeObject(for: str1.stringCol) XCTAssertEqual(0, map.count) XCTAssertNil(map[str1.stringCol] as Any?) map[str1.stringCol] = str1 XCTAssertEqual(1, map.count) XCTAssertNotNil(map[str1.stringCol] as Any?) map[str1.stringCol] = nil XCTAssertEqual(0, map.count) XCTAssertNil(map[str1.stringCol] as Any?) } func testRemoveAll() { let map = createMap() for i in 0..<5 { map[String(i)] = str1 } XCTAssertEqual(5, map.count) map.removeAll() XCTAssertEqual(0, map.count) } func testDeleteObjectFromMap() { let map = createMap() if let realm = map.realm { map["key"] = str1 XCTAssertEqual(map["key"]!!.stringCol, str1.stringCol) realm.delete(str1) XCTAssertNil(map["key"] ?? nil) } } func testMerge() { let map = createMap() map["a"] = str1 map.merge(["b": str2], uniquingKeysWith: { (old, _) in XCTFail("combine called with no duplicates") return old }) XCTAssertEqual(map.count, 2) XCTAssertTrue(str1.isSameObject(as: map["a"]!!)) XCTAssertTrue(str2.isSameObject(as: map["b"]!!)) // Does not actually update because the combine function picks the existing value map.merge(["b": str1], uniquingKeysWith: { (old, _) in return old }) XCTAssertEqual(map.count, 2) XCTAssertTrue(str1.isSameObject(as: map["a"]!!)) XCTAssertTrue(str2.isSameObject(as: map["b"]!!)) // Does actually update map.merge(["b": str1], uniquingKeysWith: { (_, new) in return new }) XCTAssertEqual(map.count, 2) XCTAssertTrue(str1.isSameObject(as: map["a"]!!)) XCTAssertTrue(str1.isSameObject(as: map["b"]!!)) // Creating an entirely new value is valid too map.merge(["a": str1], uniquingKeysWith: { (_, _) in return SwiftStringObject(value: ["c"]) }) XCTAssertEqual(map.count, 2) XCTAssertEqual(map["a"]!!.stringCol, "c") XCTAssertTrue(str1.isSameObject(as: map["b"]!!)) } func testChangesArePersisted() { let map = createMap() map["key"] = str1 map["key2"] = str2 if let realm = map.realm { let mapFromResults = realm.objects(SwiftMapPropertyObject.self).first!.map XCTAssertEqual(map["key"]!!.stringCol, mapFromResults["key"]!!.stringCol) XCTAssertEqual(map["key2"]!!.stringCol, mapFromResults["key2"]!!.stringCol) } } func testPopulateEmptyMap() { let map = createMap() XCTAssertEqual(map.count, 0, "Should start with no array elements.") map["a"] = SwiftStringObject(value: ["a"]) map["b"] = realmWithTestPath().create(SwiftStringObject.self, value: ["b"]) map[str1.stringCol] = str1 XCTAssertEqual(map.count, 3) XCTAssertEqual(map["a"]!!.stringCol, "a") XCTAssertEqual(map["b"]!!.stringCol, "b") XCTAssertEqual(map[str1.stringCol]!!.stringCol, str1.stringCol) var count = 0 for object in map { XCTAssertTrue(object.key.description.utf16.count > 0, "Object should have description") XCTAssertTrue(object.value!.description.utf16.count > 0, "Object should have description") count += 1 } XCTAssertEqual(count, 3) } func testEnumeratingMap() { let map = createMap() for i in 0..<10 { map["key\(i)"] = SwiftStringObject(value: ["key\(i)"]) } XCTAssertEqual(10, map.count) var expected = Set((0..<10).map { "key\($0)" }) for element in map { XCTAssertEqual(element.key, element.value!.stringCol) expected.remove(element.key) } XCTAssertEqual(expected.count, 0) expected = Set((0..<10).map { "key\($0)" }) for (key, value) in map { XCTAssertEqual(key, value!.stringCol) expected.remove(key) } XCTAssertEqual(expected.count, 0) } func testValueForKey() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let mapObject = SwiftMapOfSwiftObject() let object = SwiftObject() object.intCol = value object.doubleCol = Double(value) object.stringCol = String(value) object.decimalCol = Decimal128(number: value as NSNumber) object.objectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) mapObject.map["key"] = object realm.add(mapObject) } } let mapObjects = realm.objects(SwiftMapOfSwiftObject.self) let mapsOfObjects = mapObjects.value(forKeyPath: "map") as! [Map] let objects = realm.objects(SwiftObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftObject?) -> T) { let properties: [T] = Array(mapObjects.flatMap { $0.map.values.map(fn) }) let kvcProperties: [T] = Array(mapsOfObjects.flatMap { $0.values.map(fn) }) XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftObject?) -> T) { let properties = Array(objects.compactMap(fn)) let mapsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(mapsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0!.intCol } testProperty { $0!.doubleCol } testProperty { $0!.stringCol } testProperty { $0!.decimalCol } testProperty { $0!.objectIdCol } testProperty("intCol") { $0!.intCol } testProperty("doubleCol") { $0!.doubleCol } testProperty("stringCol") { $0!.stringCol } testProperty("decimalCol") { $0!.decimalCol } testProperty("objectIdCol") { $0!.objectIdCol } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional func testValueForKeyOptional() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let mapObject = SwiftMapOfSwiftOptionalObject() let object = SwiftOptionalObject() object.optIntCol.value = value object.optInt8Col.value = Int8(value) object.optDoubleCol.value = Double(value) object.optStringCol = String(value) object.optNSStringCol = NSString(format: "%d", value) object.optDecimalCol = Decimal128(number: value as NSNumber) object.optObjectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) mapObject.map["key"] = object realm.add(mapObject) } } let mapObjects = realm.objects(SwiftMapOfSwiftOptionalObject.self) let mapsOfObjects = mapObjects.value(forKeyPath: "map") as! [Map] let objects = realm.objects(SwiftOptionalObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftOptionalObject?) -> T) { let properties: [T] = mapObjects.flatMap { $0.map.values.map(fn) } let kvcProperties: [T] = mapsOfObjects.flatMap { $0.values.map(fn) } XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftOptionalObject?) -> T) { let properties = Array(objects.compactMap(fn)) let mapsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(mapsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0!.optIntCol.value } testProperty { $0!.optInt8Col.value } testProperty { $0!.optDoubleCol.value } testProperty { $0!.optStringCol } testProperty { $0!.optNSStringCol } testProperty { $0!.optDecimalCol } testProperty { $0!.optObjectCol } testProperty("optIntCol") { $0!.optIntCol.value } testProperty("optInt8Col") { $0!.optInt8Col.value } testProperty("optDoubleCol") { $0!.optDoubleCol.value } testProperty("optStringCol") { $0!.optStringCol } testProperty("optNSStringCol") { $0!.optNSStringCol } testProperty("optDecimalCol") { $0!.optDecimalCol } testProperty("optObjectCol") { $0!.optObjectCol } } func testAppendEmbedded() { let map = createEmbeddedMap() for i in 0..<10 { map["\(i)"] = EmbeddedTreeObject1(value: [i]) } XCTAssertEqual(10, map.count) for element in map { XCTAssertEqual(Int(element.key), element.value!.value) XCTAssertEqual(map.realm, element.value!.realm) } if map.realm != nil { // This message comes from Core and is incorrect for Dictionary. assertThrows((map["unassigned"] = map["0"]), reason: "Cannot add an existing managed embedded object to a Dictionary.") } realm.cancelWrite() } func testSetEmbedded() { let map = createEmbeddedMap() map["key"] = EmbeddedTreeObject1(value: [0]) let oldObj = map["key"] let obj = EmbeddedTreeObject1(value: [1]) map["key"] = obj XCTAssertTrue(map["key"]!!.isSameObject(as: obj)) XCTAssertEqual(obj.value, 1) XCTAssertEqual(obj.realm, map.realm) if map.realm != nil { XCTAssertTrue(oldObj!!.isInvalidated) assertThrows(map["key"] = obj, reason: "Cannot add an existing managed embedded object to a Dictionary.") } realm.cancelWrite() } func testUnmanagedMapComparison() { let obj = SwiftIntObject() obj.intCol = 5 let obj2 = SwiftIntObject() obj2.intCol = 6 let obj3 = SwiftIntObject() obj3.intCol = 8 let objects = ["obj": obj, "obj2": obj2, "obj3": obj3] let objects2 = ["obj": obj, "obj2": obj2] let map1 = Map() let map2 = Map() XCTAssertEqual(map1, map2, "Empty instances should be equal by `==` operator") map1.merge(objects) { (a, _) in a } map2.merge(objects) { (a, _) in a } let map3 = Map() map3.merge(objects2) { (a, _) in a } XCTAssertTrue(map1 !== map2, "instances should not be identical") XCTAssertEqual(map1, map2, "instances should be equal by `==` operator") XCTAssertNotEqual(map1, map3, "instances should be equal by `==` operator") XCTAssertTrue(map1.isEqual(map2), "instances should be equal by `isEqual` method") XCTAssertTrue(!map1.isEqual(map3), "instances should be equal by `isEqual` method") map3["obj3"] = obj3 XCTAssertEqual(map1, map3, "instances should be equal by `==` operator") XCTAssertEqual(Dictionary(_immutableCocoaDictionary: map1), Dictionary(_immutableCocoaDictionary: map2), "instances converted to Swift.Dictionary should be equal") XCTAssertEqual(Dictionary(_immutableCocoaDictionary: map1), Dictionary(_immutableCocoaDictionary: map3), "instances converted to Swift.Dictionary should be equal") map3["obj3"] = nil map1["obj3"] = nil XCTAssertEqual(Dictionary(_immutableCocoaDictionary: map1), Dictionary(_immutableCocoaDictionary: map3), "instances should be equal by `==` operator") } func testFilter() { let map = createMap() map["key"] = SwiftStringObject(value: ["apples"]) map["key2"] = SwiftStringObject(value: ["bananas"]) map["key3"] = SwiftStringObject(value: ["cockroach"]) if map.realm != nil { let results: Results = map.filter(NSPredicate(format: "stringCol = 'apples'")) XCTAssertEqual(results.count, 1) XCTAssertEqual(results.first!!.stringCol, "apples") let results2: Results = map.sorted(byKeyPath: "stringCol", ascending: true) XCTAssertEqual(results2.count, 3) XCTAssertEqual(results2[0]!.stringCol, "apples") XCTAssertEqual(results2[1]!.stringCol, "bananas") XCTAssertEqual(results2[2]!.stringCol, "cockroach") } else { assertThrows(map.filter(NSPredicate(format: "stringCol = 'apples'"))) assertThrows(map.sorted(byKeyPath: "stringCol", ascending: false)) } } func testKeyedSortable() { let map = createMap() map["key"] = SwiftStringObject(value: ["apples"]) map["key2"] = SwiftStringObject(value: ["bananas"]) map["key3"] = SwiftStringObject(value: ["cockroach"]) if map.realm != nil { let results2: Results = map.sorted(by: \.stringCol, ascending: true) XCTAssertEqual(results2.count, 3) XCTAssertEqual(results2[0]?.stringCol, "apples") XCTAssertEqual(results2[1]?.stringCol, "bananas") XCTAssertEqual(results2[2]?.stringCol, "cockroach") } else { assertThrows(map.sorted(by: \.stringCol, ascending: true)) } } func testKeyedAggregatable() { let map = SwiftMapPropertyObject() map.intMap["key"] = SwiftIntObject(value: [1]) map.intMap["key2"] = SwiftIntObject(value: [2]) map.intMap["key3"] = SwiftIntObject(value: [3]) func assertAggregations() { let min = map.intMap.min(of: \.intCol) let max = map.intMap.max(of: \.intCol) let sum = map.intMap.sum(of: \.intCol) let avg = map.intMap.average(of: \.intCol) XCTAssertEqual(min, 1) XCTAssertEqual(max, 3) XCTAssertEqual(sum, 6) XCTAssertEqual(avg, 2) } // unmanaged assertAggregations() realm.add(map) try! realm.commitWrite() // managed assertAggregations() } func testAllKeysQuery() { let map = createMap() if let realm = map.realm { func test(on key: String, value: T) { XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys = 'aKey'").count, 0) let o = SwiftMapObject() (o.value(forKey: key) as! Map)["aKey"] = value let o2 = SwiftMapObject() (o2.value(forKey: key) as! Map)["aKey2"] = value let o3 = SwiftMapObject() (o3.value(forKey: key) as! Map)["aKey3"] = value let o4 = SwiftMapObject() // this object should be visible from `ANY \(key).@allKeys != 'aKey'` // as the dictionary contains more than one key. (o4.value(forKey: key) as! Map)["aKey"] = value (o4.value(forKey: key) as! Map)["aKey4"] = value realm.add([o, o2, o3, o4]) XCTAssertEqual(realm.objects(SwiftMapObject.self).count, 4) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys = 'aKey'").count, 2) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys != 'aKey'").count, 3) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys =[c] 'akey'").count, 2) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys !=[c] 'akey'").count, 3) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys =[cd] 'akéy'").count, 2) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allKeys !=[cd] 'akéy'").count, 3) realm.delete([o, o2, o3, o4]) } test(on: "int", value: Int(123)) test(on: "int8", value: Int8(127)) test(on: "int16", value: Int16(789)) test(on: "int32", value: Int32(789)) test(on: "int64", value: Int64(789)) test(on: "float", value: Float(789.123)) test(on: "double", value: Double(789.123)) test(on: "string", value: "Hello") test(on: "data", value: Data(count: 16)) test(on: "date", value: Date()) test(on: "decimal", value: Decimal128(floatLiteral: 123.456)) test(on: "objectId", value: ObjectId()) test(on: "uuid", value: UUID()) test(on: "object", value: Optional(SwiftStringObject(value: ["hello"]))) } } func testAllValuesQuery() { let map = createMap() if let realm = map.realm { func test(on key: String, values: T...) { XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues = %@", values[0]).count, 0) let o = SwiftMapObject() (o.value(forKey: key) as! Map)["aKey"] = values[0] let o2 = SwiftMapObject() (o2.value(forKey: key) as! Map)["aKey2"] = values[1] let o3 = SwiftMapObject() (o3.value(forKey: key) as! Map)["aKey3"] = values[2] realm.add([o, o2, o3]) if T.self is Object.Type { let stringObj = realm.objects(SwiftStringObject.self).filter("stringCol == 'hello'").first! XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues = %@", stringObj).count, 1) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues != %@", stringObj).count, 2) } else { XCTAssertEqual(realm.objects(SwiftMapObject.self).count, 3) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues = %@", values[0]).count, 1) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues != %@", values[0]).count, 2) } if T.self is String.Type { XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues =[c] %@", values[0]).count, 2) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues !=[c] %@", values[0]).count, 1) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues =[cd] %@", values[0]).count, 3) XCTAssertEqual(realm.objects(SwiftMapObject.self).filter("ANY \(key).@allValues !=[cd] %@", values[0]).count, 0) } realm.delete([o, o2, o3]) } test(on: "int", values: Int(123), Int(456), Int(789)) test(on: "int8", values: Int8(127), Int8(0), Int8(64)) test(on: "int16", values: Int16(789), Int16(345), Int16(567)) test(on: "int32", values: Int32(789), Int32(132), Int32(345)) test(on: "int64", values: Int64(789), Int64(234), Int64(345)) test(on: "float", values: Float(789.123), Float(123.123), Float(234.123)) test(on: "double", values: Double(789.123), Double(123.123), Double(234.123)) test(on: "string", values: "Hello", "Héllo", "hello") test(on: "data", values: Data(count: 16), Data(count: 32), Data(count: 64)) test(on: "date", values: Date(timeIntervalSince1970: 2000), Date(timeIntervalSince1970: 4000), Date(timeIntervalSince1970: 8000)) test(on: "decimal", values: Decimal128(floatLiteral: 123.456), Decimal128(floatLiteral: 234.456), Decimal128(floatLiteral: 345.456)) test(on: "objectId", values: ObjectId("507f1f77bcf86cd799439011"), ObjectId("507f1f77bcf86cd799439012"), ObjectId("507f1f77bcf86cd799439013")) test(on: "uuid", values: UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")!, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD88")!, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD87")!) test(on: "object", values: Optional(SwiftStringObject(value: ["hello"])), Optional(SwiftStringObject(value: ["there"])), Optional(SwiftStringObject(value: ["bye"]))) } } // MARK: Notification Tests func testNotificationSentInitially() { let mapObj = createMap() try! mapObj.realm!.commitWrite() let ex = expectation(description: "does receive notification") let token = mapObj.observe(on: queue) { change in switch change { case .initial(let map): XCTAssertNotNil(map) ex.fulfill() default: XCTFail("should not get here for this test") } } wait(for: [ex], timeout: 2.0) token.invalidate() queue.sync { } } func testNotificationSentAfterCommit() { let mapObj = createMap() try! mapObj.realm!.commitWrite() var exp = expectation(description: "does receive notification") var didInsert = false var didModify = false var didDelete = false let token = mapObj.observe(on: queue) { change in switch change { case .initial(let map): XCTAssertNotNil(map) exp.fulfill() case let .update(map, deletions: deletions, insertions: insertions, modifications: modifications): XCTAssertNotNil(map) if didModify && !didDelete { XCTAssertEqual(deletions, ["myNewKey"]) didDelete.toggle() } else if didInsert { XCTAssertEqual(modifications, ["myNewKey"]) didModify.toggle() } else { XCTAssertEqual(insertions, ["anotherNewKey", "myNewKey"]) didInsert.toggle() } exp.fulfill() case .error: XCTFail("should not get here for this test") } } wait(for: [exp], timeout: 2.0) exp = expectation(description: "does receive notification") try! realm.write { mapObj["myNewKey"] = SwiftStringObject(value: ["one"]) mapObj["anotherNewKey"] = SwiftStringObject(value: ["two"]) } wait(for: [exp], timeout: 2.0) XCTAssertTrue(didInsert) exp = expectation(description: "does receive notification") try! realm.write { mapObj["myNewKey"] = SwiftStringObject(value: ["three"]) } wait(for: [exp], timeout: 2.0) exp = expectation(description: "does receive notification") XCTAssertTrue(didModify) try! realm.write { mapObj["myNewKey"] = nil } wait(for: [exp], timeout: 2.0) XCTAssertTrue(didDelete) token.invalidate() queue.sync { } } /* Expect notification on "intCol" key path when intCol is changed */ func testObserveKeyPath() { let mapObj = createMapObject() mapObj.swiftObjectMap["first"] = SwiftObject(value: ["intCol": 1, "stringCol": "one"]) mapObj.swiftObjectMap["second"] = SwiftObject(value: ["intCol": 2, "stringCol": "two"]) try! mapObj.realm!.commitWrite() var ex = expectation(description: "initial notification") let token = mapObj.swiftObjectMap.observe(keyPaths: ["intCol"]) { (changes: RealmMapChange) in switch changes { case .initial(let map): XCTAssertEqual(map.count, 2) case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, ["first"]) case .error: XCTFail("error not expected") } ex.fulfill() } wait(for: [ex], timeout: 2.0) /* Expect notification on "intCol" key path when intCol is changed */ ex = expectation(description: "change notification") dispatchSyncBackground { unsafeSelf in let realm = unsafeSelf.realmWithTestPath() realm.beginWrite() let obj = realm.objects(SwiftMapPropertyObject.self).first! let value = obj.swiftObjectMap["first"]!! value.intCol = 8 try! realm.commitWrite() } wait(for: [ex], timeout: 2.0) token.invalidate() } /* Expect no notification on "intCol" key path when stringCol is changed */ func testObserveKeyPathNoChange() { let mapObj = createMapObject() mapObj.swiftObjectMap["first"] = SwiftObject(value: ["intCol": 1, "stringCol": "one"]) mapObj.swiftObjectMap["second"] = SwiftObject(value: ["intCol": 2, "stringCol": "two"]) try! mapObj.realm!.commitWrite() var ex = expectation(description: "initial notification") let token = mapObj.swiftObjectMap.observe(keyPaths: ["intCol"]) { (changes: RealmMapChange) in switch changes { case .initial(let map): XCTAssertEqual(map.count, 2) case .update: XCTFail("update not expected") case .error: XCTFail("error not expected") } ex.fulfill() } wait(for: [ex], timeout: 0.1) /* Expect no notification on "intCol" key path when stringCol is changed */ ex = expectation(description: "NO change notification") ex.isInverted = true dispatchSyncBackground { unsafeSelf in let realm = unsafeSelf.realmWithTestPath() realm.beginWrite() let obj = realm.objects(SwiftMapPropertyObject.self).first! let value = obj.swiftObjectMap["first"]!! value.stringCol = "new string" try! realm.commitWrite() } wait(for: [ex], timeout: 0.1) token.invalidate() } /* Expect notification delete notification on "intCol" key path when object is removed from map. */ func testObserveKeyPathRemoved() { let mapObj = createMapObject() mapObj.swiftObjectMap["first"] = SwiftObject(value: ["intCol": 1, "stringCol": "one"]) mapObj.swiftObjectMap["second"] = SwiftObject(value: ["intCol": 2, "stringCol": "two"]) try! mapObj.realm!.commitWrite() var ex = expectation(description: "initial notification") let token = mapObj.swiftObjectMap.observe(keyPaths: ["intCol"]) { (changes: RealmMapChange) in switch changes { case .initial(let map): XCTAssertEqual(map.count, 2) case .update(let map, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, ["first"]) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, []) XCTAssertEqual(map.count, 1) case .error: XCTFail("error not expected") } ex.fulfill() } wait(for: [ex], timeout: 0.1) /* Expect notification on "intCol" key path when intCol is changed */ ex = expectation(description: "change notification") dispatchSyncBackground { unsafeSelf in let realm = unsafeSelf.realmWithTestPath() realm.beginWrite() let obj = realm.objects(SwiftMapPropertyObject.self).first! obj.swiftObjectMap.removeObject(for: "first") try! realm.commitWrite() } wait(for: [ex], timeout: 0.1) token.invalidate() } /* Expect modification notification on "intCol" key path when object is deleted from realm. */ func testObserveKeyPathDeleted() { let mapObj = createMapObject() mapObj.swiftObjectMap["first"] = SwiftObject(value: ["intCol": 1, "stringCol": "one"]) mapObj.swiftObjectMap["second"] = SwiftObject(value: ["intCol": 2, "stringCol": "two"]) try! mapObj.realm!.commitWrite() var ex = expectation(description: "initial notification") let token = mapObj.swiftObjectMap.observe(keyPaths: ["intCol"]) { (changes: RealmMapChange) in switch changes { case .initial(let map): XCTAssertEqual(map.count, 2) case .update(let map, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, ["first"]) XCTAssertEqual(map.count, 2) case .error: XCTFail("error not expected") } ex.fulfill() } wait(for: [ex], timeout: 0.1) ex = expectation(description: "change notification") dispatchSyncBackground { unsafeSelf in let realm = unsafeSelf.realmWithTestPath() realm.beginWrite() let obj = realm.objects(SwiftMapPropertyObject.self).first! let value = obj.swiftObjectMap["first"]!! realm.delete(value) try! realm.commitWrite() } wait(for: [ex], timeout: 0.1) token.invalidate() } /* Expect notification on "owners.name" (a backlink property) key path when name is modified. */ func testObserveKeyPathBacklink() { let mapObj = createMapObject() let person = realm.create(SwiftOwnerObject.self, value: ["name": "Moe"]) let dog = SwiftDogObject() person.dog = dog mapObj.dogMap["first"] = dog try! mapObj.realm!.commitWrite() var ex = expectation(description: "initial notification") let token = mapObj.dogMap.observe(keyPaths: ["owners.name"]) { (changes: RealmMapChange) in switch changes { case .initial(let map): XCTAssertEqual(map.count, 1) XCTAssertEqual(map["first"]!!.owners.first?.name, "Moe") case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, ["first"]) case .error: XCTFail("error not expected") } ex.fulfill() } wait(for: [ex], timeout: 0.1) ex = expectation(description: "change notification") dispatchSyncBackground { unsafeSelf in let realm = unsafeSelf.realmWithTestPath() realm.beginWrite() let obj = realm.objects(SwiftOwnerObject.self).first! obj.name = "Curley" try! realm.commitWrite() } wait(for: [ex], timeout: 0.1) token.invalidate() } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @MainActor func testObserveOnActor() async throws { let map = createMapObject().swiftObjectMap map["a"] = SwiftObject() try realm.commitWrite() let initialEx = expectation(description: "initial") let changeEx = expectation(description: "change") let token = await map.observe(keyPaths: ["intCol"], on: MainActor.shared) { _, change in switch change { case .initial: initialEx.fulfill() case .update: changeEx.fulfill() case .error(let error): XCTFail("Unexpected error \(error)") } } await fulfillment(of: [initialEx]) // A write which should not produce a notification and will over-fulfill // the expectation if it does try realm.write { map["a"]??.boolCol = true } try realm.write { map["a"]??.intCol += 1 } await fulfillment(of: [changeEx]) token.invalidate() } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @MainActor func testObserveOnActorTypedKeyPath() async throws { let map = createMapObject().swiftObjectMap map["a"] = SwiftObject() try realm.commitWrite() let initialEx = expectation(description: "initial") let changeEx = expectation(description: "change") let token = await map.observe(keyPaths: [\.intCol], on: MainActor.shared) { _, change in switch change { case .initial: initialEx.fulfill() case .update: changeEx.fulfill() case .error(let error): XCTFail("Unexpected error \(error)") } } await fulfillment(of: [initialEx]) // A write which should not produce a notification and will over-fulfill // the expectation if it does try realm.write { map["a"]??.boolCol = true } try realm.write { map["a"]??.intCol += 1 } await fulfillment(of: [changeEx]) token.invalidate() } } class MapStandaloneTests: MapTests { override func createMap() -> Map { return createMapObject().map } override func createMapObject() -> SwiftMapPropertyObject { return SwiftMapPropertyObject() } override func createEmbeddedMap() -> Map { return Map() } override func testNotificationSentInitially() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testNotificationSentAfterCommit() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testObserveKeyPath() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testObserveKeyPathNoChange() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testObserveKeyPathRemoved() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testObserveKeyPathDeleted() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } override func testObserveKeyPathBacklink() { let mapObj = createMap() assertThrows(mapObj.observe {_ in }) } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @MainActor override func testObserveOnActor() async throws { } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @MainActor override func testObserveOnActorTypedKeyPath() async throws { } } class MapNewlyAddedTests: MapTests { override func createMap() -> Map { let mapObj = SwiftMapPropertyObject() realm.add(mapObj) XCTAssertNotNil(mapObj.realm) return mapObj.map } override func createMapObject() -> SwiftMapPropertyObject { let mapObj = SwiftMapPropertyObject() realm.add(mapObj) XCTAssertNotNil(mapObj.realm) return mapObj } override func createEmbeddedMap() -> Map { let parent = EmbeddedParentObject() let map = parent.map realm.add(parent) return map } } class MapNewlyCreatedTests: MapTests { override func createMap() -> Map { let mapObj = realm.create(SwiftMapPropertyObject.self, value: ["name"]) try! realm.commitWrite() realm.beginWrite() XCTAssertNotNil(mapObj.realm) return mapObj.map } override func createMapObject() -> SwiftMapPropertyObject { let mapObj = realm.create(SwiftMapPropertyObject.self) try! realm.commitWrite() realm.beginWrite() XCTAssertNotNil(mapObj.realm) return mapObj } override func createEmbeddedMap() -> Map { return realm.create(EmbeddedParentObject.self).map } } class MapRetrievedTests: MapTests { override func createMap() -> Map { realm.create(SwiftMapPropertyObject.self, value: ["name"]) try! realm.commitWrite() let mapObj = realm.objects(SwiftMapPropertyObject.self).first! XCTAssertNotNil(mapObj.realm) realm.beginWrite() return mapObj.map } override func createMapObject() -> SwiftMapPropertyObject { realm.create(SwiftMapPropertyObject.self) try! realm.commitWrite() let mapObj = realm.objects(SwiftMapPropertyObject.self).first! XCTAssertNotNil(mapObj.realm) realm.beginWrite() return mapObj } override func createEmbeddedMap() -> Map { realm.create(EmbeddedParentObject.self) return realm.objects(EmbeddedParentObject.self).first!.map } } ================================================ FILE: RealmSwift/Tests/MigrationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import RealmSwift import Realm import Realm.Dynamic import Foundation #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif @discardableResult private func realmWithSingleClassProperties(_ fileURL: URL, className: String, properties: [AnyObject]) -> RLMRealm { let schema = RLMSchema() let objectSchema = RLMObjectSchema(className: className, objectClass: MigrationObject.self, properties: properties) schema.objectSchema = [objectSchema] let config = RLMRealmConfiguration() config.fileURL = fileURL config.customSchema = schema return try! RLMRealm(configuration: config) } private func dynamicRealm(_ fileURL: URL) -> RLMRealm { let config = RLMRealmConfiguration() config.fileURL = fileURL config.dynamic = true return try! RLMRealm(configuration: config) } class MigrationTests: TestCase { private func createDefaultRealm() throws { let config = Realm.Configuration(fileURL: defaultRealmURL()) try autoreleasepool { _ = try Realm(configuration: config) } XCTAssertEqual(0, try schemaVersionAtURL(config.fileURL!)) } private func testMigration(shouldRun: Bool = true, schemaVersion: UInt64 = 1, block: MigrationBlock? = nil, validation: ((Realm, RLMSchema) -> Void)? = nil) throws { let didRun = Locked(false) let config = Realm.Configuration(fileURL: testRealmURL(), schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in if let block = block { block(migration, oldSchemaVersion) } didRun.value = true }) let fm = FileManager.default func withTestFile(_ fn: () throws -> Void) throws { try fm.copyItem(at: defaultRealmURL(), to: testRealmURL()) try autoreleasepool { try fn() XCTAssertEqual(didRun.value, shouldRun) } XCTAssertEqual(schemaVersion, try schemaVersionAtURL(testRealmURL())) if let validation = validation { try autoreleasepool { let schema = autoreleasepool { dynamicRealm(testRealmURL()).schema } validation(try Realm(configuration: .init(fileURL: testRealmURL(), schemaVersion: schemaVersion)), schema) } } XCTAssertTrue(try Realm.deleteFiles(for: config)) } try withTestFile { _ = try Realm(configuration: config) } try withTestFile { try Realm.performMigration(for: config) } try withTestFile { let old = Realm.Configuration.defaultConfiguration defer { Realm.Configuration.defaultConfiguration = old } Realm.Configuration.defaultConfiguration = config _ = try Realm() } try withTestFile { let ex = expectation(description: "did async open") Realm.asyncOpen(configuration: config) { _ in ex.fulfill() } wait(for: [ex], timeout: 2.0) } } // MARK: Test cases func testSchemaVersionAtURL() { assertFails(.invalidDatabase, defaultRealmURL(), "Realm at path '\(defaultRealmURL().path)' has not been initialized.") { // Version should throw before Realm creation try schemaVersionAtURL(defaultRealmURL()) } _ = try! Realm() XCTAssertEqual(0, try! schemaVersionAtURL(defaultRealmURL()), "Initial version should be 0") do { _ = try schemaVersionAtURL(URL(fileURLWithPath: "/dev/null")) XCTFail("Expected .filePermissionDenied or .fileAccess, but no error was raised") } catch Realm.Error.filePermissionDenied { // Success! } catch Realm.Error.fileAccess { // Success! } catch { XCTFail("Expected .filePermissionDenied or .fileAccess, got \(error)") } } func testBasic() throws { try createDefaultRealm() try testMigration() } func testMigrationProperties() throws { let prop = RLMProperty(name: "stringCol", type: RLMPropertyType.int, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) _ = autoreleasepool { realmWithSingleClassProperties(defaultRealmURL(), className: "SwiftStringObject", properties: [prop]) } try testMigration { migration, _ in XCTAssertEqual(migration.oldSchema.objectSchema.count, 1) XCTAssertGreaterThan(migration.newSchema.objectSchema.count, 1) XCTAssertEqual(migration.oldSchema.objectSchema[0].properties.count, 1) XCTAssertEqual(migration.newSchema["SwiftStringObject"]!.properties.count, 1) XCTAssertEqual(migration.oldSchema["SwiftStringObject"]!.properties[0].type, PropertyType.int) XCTAssertEqual(migration.newSchema["SwiftStringObject"]!["stringCol"]!.type, PropertyType.string) } } func testEnumerate() throws { try createDefaultRealm() try testMigration { migration, _ in migration.enumerateObjects(ofType: "SwiftStringObject", { _, _ in XCTFail("No objects to enumerate") }) migration.enumerateObjects(ofType: "NoSuchClass", { _, _ in }) // shouldn't throw } try autoreleasepool { // add object let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["string"]) } } nonisolated(unsafe) let unsafeSelf = self try testMigration(schemaVersion: 2) { migration, _ in var count = 0 migration.enumerateObjects(ofType: "SwiftStringObject", { oldObj, newObj in guard let oldObj = oldObj, let newObj = newObj else { return XCTFail("did not get objects in enumerate") } XCTAssertEqual(newObj.objectSchema.className, "SwiftStringObject") XCTAssertEqual(oldObj.objectSchema.className, "SwiftStringObject") XCTAssertEqual((newObj["stringCol"] as! String), "string") XCTAssertEqual((oldObj["stringCol"] as! String), "string") unsafeSelf.assertThrows(oldObj["noSuchCol"] as! String) unsafeSelf.assertThrows(newObj["noSuchCol"] as! String) count += 1 }) XCTAssertEqual(count, 1) } try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftArrayPropertyObject.self, value: ["string", [["array"]], [[2]]]) realm.create(SwiftMutableSetPropertyObject.self, value: ["string", [["set"]], [[2]]]) realm.create(SwiftMapPropertyObject.self, value: ["string", ["key": ["value"]]]) } } try testMigration(schemaVersion: 3) { migration, _ in migration.enumerateObjects(ofType: "SwiftArrayPropertyObject") { oldObject, newObject in XCTAssertTrue(oldObject! as AnyObject is MigrationObject) XCTAssertTrue(newObject! as AnyObject is MigrationObject) XCTAssertTrue(oldObject!["array"]! is List) XCTAssertTrue(newObject!["array"]! is List) } migration.enumerateObjects(ofType: "SwiftMutableSetPropertyObject") { oldObject, newObject in XCTAssertTrue(oldObject! as AnyObject is MigrationObject) XCTAssertTrue(newObject! as AnyObject is MigrationObject) XCTAssertTrue(oldObject!["set"]! is MutableSet) XCTAssertTrue(newObject!["set"]! is MutableSet) } migration.enumerateObjects(ofType: "SwiftMapPropertyObject") { oldObject, newObject in XCTAssertTrue(oldObject! as AnyObject is MigrationObject) XCTAssertTrue(newObject! as AnyObject is MigrationObject) XCTAssertTrue(oldObject!["map"]! is Map) XCTAssertTrue(newObject!["map"]! is Map) } } } func testBasicTypesInEnumerate() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.add(SwiftObject()) } } try testMigration { migration, _ in migration.enumerateObjects(ofType: "SwiftObject") { oldObject, newObject in XCTAssertTrue(oldObject!.boolCol is Bool) XCTAssertTrue(newObject!.boolCol is Bool) XCTAssertTrue(oldObject!.intCol is Int) XCTAssertTrue(newObject!.intCol is Int) XCTAssertTrue(oldObject!.int8Col is Int) XCTAssertTrue(newObject!.int8Col is Int) XCTAssertTrue(oldObject!.int16Col is Int) XCTAssertTrue(newObject!.int16Col is Int) XCTAssertTrue(oldObject!.int32Col is Int) XCTAssertTrue(newObject!.int32Col is Int) XCTAssertTrue(oldObject!.int64Col is Int) XCTAssertTrue(newObject!.int64Col is Int) XCTAssertTrue(oldObject!.intEnumCol is Int) XCTAssertTrue(newObject!.intEnumCol is Int) XCTAssertTrue(oldObject!.floatCol is Float) XCTAssertTrue(newObject!.floatCol is Float) XCTAssertTrue(oldObject!.doubleCol is Double) XCTAssertTrue(newObject!.doubleCol is Double) XCTAssertTrue(oldObject!.stringCol is String) XCTAssertTrue(newObject!.stringCol is String) XCTAssertTrue(oldObject!.binaryCol is Data) XCTAssertTrue(newObject!.binaryCol is Data) XCTAssertTrue(oldObject!.dateCol is Date) XCTAssertTrue(newObject!.dateCol is Date) XCTAssertTrue(oldObject!.decimalCol is Decimal128) XCTAssertTrue(newObject!.decimalCol is Decimal128) XCTAssertTrue(oldObject!.objectIdCol is ObjectId) XCTAssertTrue(newObject!.objectIdCol is ObjectId) XCTAssertTrue(oldObject!.objectCol is DynamicObject) XCTAssertTrue(newObject!.objectCol is DynamicObject) XCTAssertTrue(oldObject!.uuidCol is UUID) XCTAssertTrue(newObject!.uuidCol is UUID) XCTAssertNil(oldObject!.anyCol) XCTAssertNil(newObject!.anyCol) } } } func testAnyInEnumerate() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.add(SwiftObject()) } } var version = UInt64(1) func write(_ value: @autoclosure () -> AnyRealmValue, test: @escaping @Sendable (Any?, Any?) -> Void) throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.objects(SwiftObject.self).first!.anyCol.value = value() } } try testMigration(schemaVersion: version) { migration, _ in migration.enumerateObjects(ofType: "SwiftObject") { oldObject, newObject in test(oldObject!.anyCol, newObject!.anyCol) } } version += 1 } try write(.int(1)) { oldValue, newValue in XCTAssertTrue(oldValue is Int) XCTAssertTrue(newValue is Int) } try write(.float(1)) { oldValue, newValue in XCTAssertTrue(oldValue is Float) XCTAssertTrue(newValue is Float) } try write(.double(1)) { oldValue, newValue in XCTAssertTrue(oldValue is Double) XCTAssertTrue(newValue is Double) } try write(.bool(true)) { oldValue, newValue in XCTAssertTrue(oldValue is Bool) XCTAssertTrue(newValue is Bool) } try write(.string("")) { oldValue, newValue in XCTAssertTrue(oldValue is String) XCTAssertTrue(newValue is String) } try write(.data(Data())) { oldValue, newValue in XCTAssertTrue(oldValue is Data) XCTAssertTrue(newValue is Data) } try write(.date(Date())) { oldValue, newValue in XCTAssertTrue(oldValue is Date) XCTAssertTrue(newValue is Date) } try write(.objectId(ObjectId())) { oldValue, newValue in XCTAssertTrue(oldValue is ObjectId) XCTAssertTrue(newValue is ObjectId) } try write(.decimal128(Decimal128())) { oldValue, newValue in XCTAssertTrue(oldValue is Decimal128) XCTAssertTrue(newValue is Decimal128) } try write(.uuid(UUID())) { oldValue, newValue in XCTAssertTrue(oldValue is UUID) XCTAssertTrue(newValue is UUID) } try write(.object(SwiftIntObject())) { oldValue, newValue in XCTAssertTrue(oldValue! is DynamicObject) XCTAssertTrue(newValue! is DynamicObject) } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional func testOptionalsInEnumerate() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.add(SwiftOptionalObject()) } } try testMigration { migration, _ in migration.enumerateObjects(ofType: "SwiftOptionalObject") { oldObject, newObject in XCTAssertTrue(oldObject! as AnyObject is MigrationObject) XCTAssertTrue(newObject! as AnyObject is MigrationObject) XCTAssertNil(oldObject!.optNSStringCol) XCTAssertNil(newObject!.optNSStringCol) XCTAssertNil(oldObject!.optStringCol) XCTAssertNil(newObject!.optStringCol) XCTAssertNil(oldObject!.optBinaryCol) XCTAssertNil(newObject!.optBinaryCol) XCTAssertNil(oldObject!.optDateCol) XCTAssertNil(newObject!.optDateCol) XCTAssertNil(oldObject!.optIntCol) XCTAssertNil(newObject!.optIntCol) XCTAssertNil(oldObject!.optInt8Col) XCTAssertNil(newObject!.optInt8Col) XCTAssertNil(oldObject!.optInt16Col) XCTAssertNil(newObject!.optInt16Col) XCTAssertNil(oldObject!.optInt32Col) XCTAssertNil(newObject!.optInt32Col) XCTAssertNil(oldObject!.optInt64Col) XCTAssertNil(newObject!.optInt64Col) XCTAssertNil(oldObject!.optFloatCol) XCTAssertNil(newObject!.optFloatCol) XCTAssertNil(oldObject!.optDoubleCol) XCTAssertNil(newObject!.optDoubleCol) XCTAssertNil(oldObject!.optBoolCol) XCTAssertNil(newObject!.optBoolCol) XCTAssertNil(oldObject!.optDecimalCol) XCTAssertNil(newObject!.optDecimalCol) XCTAssertNil(oldObject!.optObjectIdCol) XCTAssertNil(newObject!.optObjectIdCol) } } try autoreleasepool { let realm = try Realm() try realm.write { let soo = realm.objects(SwiftOptionalObject.self).first! soo.optNSStringCol = "NSString" soo.optStringCol = "String" soo.optBinaryCol = Data() soo.optDateCol = Date() soo.optIntCol.value = 1 soo.optInt8Col.value = 2 soo.optInt16Col.value = 3 soo.optInt32Col.value = 4 soo.optInt64Col.value = 5 soo.optFloatCol.value = 6.1 soo.optDoubleCol.value = 7.2 soo.optDecimalCol = 8.3 soo.optObjectIdCol = ObjectId("1234567890bc1234567890bc") soo.optBoolCol.value = true } } try testMigration(schemaVersion: 2) { migration, _ in migration.enumerateObjects(ofType: "SwiftOptionalObject") { oldObject, newObject in XCTAssertTrue(oldObject! as AnyObject is MigrationObject) XCTAssertTrue(newObject! as AnyObject is MigrationObject) XCTAssertTrue(oldObject!.optNSStringCol! is NSString) XCTAssertTrue(newObject!.optNSStringCol! is NSString) XCTAssertTrue(oldObject!.optStringCol! is String) XCTAssertTrue(newObject!.optStringCol! is String) XCTAssertTrue(oldObject!.optBinaryCol! is Data) XCTAssertTrue(newObject!.optBinaryCol! is Data) XCTAssertTrue(oldObject!.optDateCol! is Date) XCTAssertTrue(newObject!.optDateCol! is Date) XCTAssertTrue(oldObject!.optIntCol! is Int) XCTAssertTrue(newObject!.optIntCol! is Int) XCTAssertTrue(oldObject!.optInt8Col! is Int) XCTAssertTrue(newObject!.optInt8Col! is Int) XCTAssertTrue(oldObject!.optInt16Col! is Int) XCTAssertTrue(newObject!.optInt16Col! is Int) XCTAssertTrue(oldObject!.optInt32Col! is Int) XCTAssertTrue(newObject!.optInt32Col! is Int) XCTAssertTrue(oldObject!.optInt64Col! is Int) XCTAssertTrue(newObject!.optInt64Col! is Int) XCTAssertTrue(oldObject!.optFloatCol! is Float) XCTAssertTrue(newObject!.optFloatCol! is Float) XCTAssertTrue(oldObject!.optDoubleCol! is Double) XCTAssertTrue(newObject!.optDoubleCol! is Double) XCTAssertTrue(oldObject!.optBoolCol! is Bool) XCTAssertTrue(newObject!.optBoolCol! is Bool) XCTAssertTrue(oldObject!.optDecimalCol! is Decimal128) XCTAssertTrue(newObject!.optDecimalCol! is Decimal128) XCTAssertTrue(oldObject!.optObjectIdCol! is ObjectId) XCTAssertTrue(newObject!.optObjectIdCol! is ObjectId) } } } func testEnumerateObjectsAfterDeleteObjects() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["1"]) realm.create(SwiftStringObject.self, value: ["2"]) realm.create(SwiftStringObject.self, value: ["3"]) realm.create(SwiftIntObject.self, value: [1]) realm.create(SwiftIntObject.self, value: [2]) realm.create(SwiftIntObject.self, value: [3]) realm.create(SwiftInt8Object.self, value: [Int8(1)]) realm.create(SwiftInt8Object.self, value: [Int8(2)]) realm.create(SwiftInt8Object.self, value: [Int8(3)]) realm.create(SwiftInt16Object.self, value: [Int16(1)]) realm.create(SwiftInt16Object.self, value: [Int16(2)]) realm.create(SwiftInt16Object.self, value: [Int16(3)]) realm.create(SwiftInt32Object.self, value: [Int32(1)]) realm.create(SwiftInt32Object.self, value: [Int32(2)]) realm.create(SwiftInt32Object.self, value: [Int32(3)]) realm.create(SwiftInt64Object.self, value: [Int64(1)]) realm.create(SwiftInt64Object.self, value: [Int64(2)]) realm.create(SwiftInt64Object.self, value: [Int64(3)]) realm.create(SwiftBoolObject.self, value: [true]) realm.create(SwiftBoolObject.self, value: [false]) realm.create(SwiftBoolObject.self, value: [true]) } } try testMigration(schemaVersion: 1) { migration, _ in var count = 0 migration.enumerateObjects(ofType: "SwiftStringObject") { oldObj, newObj in XCTAssertEqual(newObj!["stringCol"] as! String, oldObj!["stringCol"] as! String) if oldObj!["stringCol"] as! String == "2" { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftStringObject") { oldObj, newObj in XCTAssertEqual(newObj!["stringCol"] as! String, oldObj!["stringCol"] as! String) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftIntObject") { oldObj, newObj in XCTAssertEqual(newObj!["intCol"] as! Int, oldObj!["intCol"] as! Int) if oldObj!["intCol"] as! Int == 1 { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftIntObject") { oldObj, newObj in XCTAssertEqual(newObj!["intCol"] as! Int, oldObj!["intCol"] as! Int) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt8Object") { oldObj, newObj in XCTAssertEqual(newObj!["int8Col"] as! Int8, oldObj!["int8Col"] as! Int8) if oldObj!["int8Col"] as! Int8 == 1 { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftInt8Object") { oldObj, newObj in XCTAssertEqual(newObj!["int8Col"] as! Int8, oldObj!["int8Col"] as! Int8) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt16Object") { oldObj, newObj in XCTAssertEqual(newObj!["int16Col"] as! Int16, oldObj!["int16Col"] as! Int16) if oldObj!["int16Col"] as! Int16 == 1 { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftInt16Object") { oldObj, newObj in XCTAssertEqual(newObj!["int16Col"] as! Int16, oldObj!["int16Col"] as! Int16) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt32Object") { oldObj, newObj in XCTAssertEqual(newObj!["int32Col"] as! Int32, oldObj!["int32Col"] as! Int32) if oldObj!["int32Col"] as! Int32 == 1 { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftInt32Object") { oldObj, newObj in XCTAssertEqual(newObj!["int32Col"] as! Int32, oldObj!["int32Col"] as! Int32) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt64Object") { oldObj, newObj in XCTAssertEqual(newObj!["int64Col"] as! Int64, oldObj!["int64Col"] as! Int64) if oldObj!["int64Col"] as! Int64 == 1 { migration.delete(newObj!) } } migration.enumerateObjects(ofType: "SwiftInt64Object") { oldObj, newObj in XCTAssertEqual(newObj!["int64Col"] as! Int64, oldObj!["int64Col"] as! Int64) count += 1 } XCTAssertEqual(count, 2) migration.enumerateObjects(ofType: "SwiftBoolObject") { oldObj, newObj in XCTAssertEqual(newObj!["boolCol"] as! Bool, oldObj!["boolCol"] as! Bool) migration.delete(newObj!) } migration.enumerateObjects(ofType: "SwiftBoolObject") { _, _ in XCTFail("This line should not executed since all objects have been deleted.") } } } func testEnumerateObjectsAfterDeleteInsertObjects() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["1"]) realm.create(SwiftStringObject.self, value: ["2"]) realm.create(SwiftStringObject.self, value: ["3"]) realm.create(SwiftIntObject.self, value: [1]) realm.create(SwiftIntObject.self, value: [2]) realm.create(SwiftIntObject.self, value: [3]) realm.create(SwiftInt8Object.self, value: [Int8(1)]) realm.create(SwiftInt8Object.self, value: [Int8(2)]) realm.create(SwiftInt8Object.self, value: [Int8(3)]) realm.create(SwiftInt16Object.self, value: [Int16(1)]) realm.create(SwiftInt16Object.self, value: [Int16(2)]) realm.create(SwiftInt16Object.self, value: [Int16(3)]) realm.create(SwiftInt32Object.self, value: [Int32(1)]) realm.create(SwiftInt32Object.self, value: [Int32(2)]) realm.create(SwiftInt32Object.self, value: [Int32(3)]) realm.create(SwiftInt64Object.self, value: [Int64(1)]) realm.create(SwiftInt64Object.self, value: [Int64(2)]) realm.create(SwiftInt64Object.self, value: [Int64(3)]) realm.create(SwiftBoolObject.self, value: [true]) realm.create(SwiftBoolObject.self, value: [false]) realm.create(SwiftBoolObject.self, value: [true]) } } try testMigration(schemaVersion: 1) { migration, _ in var count = 0 migration.enumerateObjects(ofType: "SwiftStringObject") { oldObj, newObj in XCTAssertEqual(newObj!["stringCol"] as! String, oldObj!["stringCol"] as! String) if oldObj!["stringCol"] as! String == "2" { migration.delete(newObj!) migration.create("SwiftStringObject", value: ["A"]) } } migration.enumerateObjects(ofType: "SwiftStringObject") { oldObj, newObj in XCTAssertEqual(newObj!["stringCol"] as! String, oldObj!["stringCol"] as! String) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftIntObject") { oldObj, newObj in XCTAssertEqual(newObj!["intCol"] as! Int, oldObj!["intCol"] as! Int) if oldObj!["intCol"] as! Int == 1 { migration.delete(newObj!) migration.create("SwiftIntObject", value: [0]) } } migration.enumerateObjects(ofType: "SwiftIntObject") { oldObj, newObj in XCTAssertEqual(newObj!["intCol"] as! Int, oldObj!["intCol"] as! Int) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt8Object") { oldObj, newObj in XCTAssertEqual(newObj!["int8Col"] as! Int8, oldObj!["int8Col"] as! Int8) if oldObj!["int8Col"] as! Int8 == 1 { migration.delete(newObj!) migration.create("SwiftInt8Object", value: [0]) } } migration.enumerateObjects(ofType: "SwiftInt8Object") { oldObj, newObj in XCTAssertEqual(newObj!["int8Col"] as! Int8, oldObj!["int8Col"] as! Int8) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt16Object") { oldObj, newObj in XCTAssertEqual(newObj!["int16Col"] as! Int16, oldObj!["int16Col"] as! Int16) if oldObj!["int16Col"] as! Int16 == 1 { migration.delete(newObj!) migration.create("SwiftInt16Object", value: [0]) } } migration.enumerateObjects(ofType: "SwiftInt16Object") { oldObj, newObj in XCTAssertEqual(newObj!["int16Col"] as! Int16, oldObj!["int16Col"] as! Int16) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt32Object") { oldObj, newObj in XCTAssertEqual(newObj!["int32Col"] as! Int32, oldObj!["int32Col"] as! Int32) if oldObj!["int32Col"] as! Int32 == 1 { migration.delete(newObj!) migration.create("SwiftInt32Object", value: [0]) } } migration.enumerateObjects(ofType: "SwiftInt32Object") { oldObj, newObj in XCTAssertEqual(newObj!["int32Col"] as! Int32, oldObj!["int32Col"] as! Int32) count += 1 } XCTAssertEqual(count, 2) count = 0 migration.enumerateObjects(ofType: "SwiftInt64Object") { oldObj, newObj in XCTAssertEqual(newObj!["int64Col"] as! Int64, oldObj!["int64Col"] as! Int64) if oldObj!["int64Col"] as! Int64 == 1 { migration.delete(newObj!) migration.create("SwiftInt64Object", value: [0]) } } migration.enumerateObjects(ofType: "SwiftInt64Object") { oldObj, newObj in XCTAssertEqual(newObj!["int64Col"] as! Int64, oldObj!["int64Col"] as! Int64) count += 1 } XCTAssertEqual(count, 2) migration.enumerateObjects(ofType: "SwiftBoolObject") { oldObj, newObj in XCTAssertEqual(newObj!["boolCol"] as! Bool, oldObj!["boolCol"] as! Bool) migration.delete(newObj!) migration.create("SwiftBoolObject", value: [false]) } migration.enumerateObjects(ofType: "SwiftBoolObject") { _, _ in XCTFail("This line should not executed since all objects have been deleted.") } } } func testEnumerateObjectsAfterDeleteData() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["1"]) realm.create(SwiftStringObject.self, value: ["2"]) realm.create(SwiftStringObject.self, value: ["3"]) } } try testMigration { migration, _ in var count = 0 migration.enumerateObjects(ofType: "SwiftStringObject") { _, _ in count += 1 } XCTAssertEqual(count, 3) migration.deleteData(forType: "SwiftStringObject") migration.create("SwiftStringObject", value: ["A"]) count = 0 migration.enumerateObjects(ofType: "SwiftStringObject") { _, _ in count += 1 } XCTAssertEqual(count, 0) } } func testCreate() throws { try createDefaultRealm() nonisolated(unsafe) let unsafeSelf = self try testMigration { migration, _ in migration.create("SwiftStringObject", value: ["string1"]) migration.create("SwiftStringObject", value: ["stringCol": "string2"]) migration.create("SwiftStringObject", value: ["stringCol": ModernStringEnum.value1]) migration.create("SwiftStringObject", value: ["stringCol": StringWrapper(persistedValue: "string3")]) migration.create("SwiftStringObject") unsafeSelf.assertThrows(migration.create("NoSuchObject")) } validation: { realm, _ in let objects = realm.objects(SwiftStringObject.self) XCTAssertEqual(objects.count, 5) XCTAssertEqual(objects[0].stringCol, "string1") XCTAssertEqual(objects[1].stringCol, "string2") XCTAssertEqual(objects[2].stringCol, ModernStringEnum.value1.rawValue) XCTAssertEqual(objects[3].stringCol, "string3") XCTAssertEqual(objects[4].stringCol, "") } } func testDelete() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["string1"]) realm.create(SwiftStringObject.self, value: ["string2"]) } } try testMigration { migration, _ in var deleted = false migration.enumerateObjects(ofType: "SwiftStringObject") { _, newObj in if deleted == false { migration.delete(newObj!) deleted = true } } } validation: { realm, _ in XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) } } func testDeleteData() throws { try autoreleasepool { let prop = RLMProperty(name: "id", type: .int, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) let realm = realmWithSingleClassProperties(defaultRealmURL(), className: "DeletedClass", properties: [prop]) try realm.transaction { realm.createObject("DeletedClass", withValue: [0]) } } try testMigration { migration, oldSchemaVersion in XCTAssertEqual(oldSchemaVersion, 0, "Initial schema version should be 0") XCTAssertTrue(migration.deleteData(forType: "DeletedClass")) XCTAssertFalse(migration.deleteData(forType: "NoSuchClass")) migration.create(SwiftStringObject.className(), value: ["migration"]) XCTAssertTrue(migration.deleteData(forType: SwiftStringObject.className())) } validation: { realm, schema in XCTAssertNil(schema.schema(forClassName: "DeletedClass")) XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) } } func testRenameProperty() throws { try autoreleasepool { let prop = RLMProperty(name: "before_stringCol", type: .string, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) try autoreleasepool { let realm = realmWithSingleClassProperties(defaultRealmURL(), className: "SwiftStringObject", properties: [prop]) try realm.transaction { realm.createObject("SwiftStringObject", withValue: ["a"]) } } try testMigration { migration, _ in XCTAssertEqual(migration.oldSchema.objectSchema[0].properties.count, 1) migration.renameProperty(onType: "SwiftStringObject", from: "before_stringCol", to: "stringCol") } validation: { realm, schema in XCTAssertEqual(schema.schema(forClassName: "SwiftStringObject")!.properties.count, 1) XCTAssertEqual(1, realm.objects(SwiftStringObject.self).count) XCTAssertEqual("a", realm.objects(SwiftStringObject.self).first!.stringCol) } } } // test getting/setting all property types func testMigrationObject() throws { try autoreleasepool { let realm = try Realm() let nulledMapObj = SwiftBoolObject(value: [false]) try realm.write { let object = SwiftObject() object.anyCol.value = .string("hello!") object.boolCol = true object.objectCol = SwiftBoolObject(value: [true]) object.arrayCol.append(SwiftBoolObject(value: [false])) object.setCol.insert(SwiftBoolObject(value: [false])) object.mapCol["key"] = SwiftBoolObject(value: [false]) object.mapCol["nulledObj"] = nulledMapObj realm.add(object) realm.delete(nulledMapObj) } } nonisolated(unsafe) let unsafeSelf = self try testMigration { migration, _ in var enumerated = false migration.enumerateObjects(ofType: "SwiftObject", { oldObj, newObj in guard let oldObj = oldObj, let newObj = newObj else { XCTFail("did not get objects in enumerate") return } XCTAssertEqual((oldObj["boolCol"] as! Bool), true) XCTAssertEqual((newObj["boolCol"] as! Bool), true) XCTAssertEqual((oldObj["intCol"] as! Int), 123) XCTAssertEqual((newObj["intCol"] as! Int), 123) XCTAssertEqual((oldObj["int8Col"] as! Int8), 123) XCTAssertEqual((newObj["int8Col"] as! Int8), 123) XCTAssertEqual((oldObj["int16Col"] as! Int16), 123) XCTAssertEqual((newObj["int16Col"] as! Int16), 123) XCTAssertEqual((oldObj["int32Col"] as! Int32), 123) XCTAssertEqual((newObj["int32Col"] as! Int32), 123) XCTAssertEqual((oldObj["int64Col"] as! Int64), 123) XCTAssertEqual((newObj["int64Col"] as! Int64), 123) XCTAssertEqual((oldObj["intEnumCol"] as! Int), 1) XCTAssertEqual((newObj["intEnumCol"] as! Int), 1) XCTAssertEqual((oldObj["floatCol"] as! Float), 1.23 as Float) XCTAssertEqual((newObj["floatCol"] as! Float), 1.23 as Float) XCTAssertEqual((oldObj["doubleCol"] as! Double), 12.3 as Double) XCTAssertEqual((newObj["doubleCol"] as! Double), 12.3 as Double) XCTAssertEqual((oldObj["decimalCol"] as! Decimal128), 123e4 as Decimal128) XCTAssertEqual((newObj["decimalCol"] as! Decimal128), 123e4 as Decimal128) let binaryCol = Data("a".utf8) XCTAssertEqual((oldObj["binaryCol"] as! Data), binaryCol) XCTAssertEqual((newObj["binaryCol"] as! Data), binaryCol) let dateCol = Date(timeIntervalSince1970: 1) XCTAssertEqual((oldObj["dateCol"] as! Date), dateCol) XCTAssertEqual((newObj["dateCol"] as! Date), dateCol) let objectIdCol = ObjectId("1234567890ab1234567890ab") XCTAssertEqual((oldObj["objectIdCol"] as! ObjectId), objectIdCol) XCTAssertEqual((newObj["objectIdCol"] as! ObjectId), objectIdCol) XCTAssertNil(oldObj["objectCol"] as? SwiftBoolObject) XCTAssertNil(newObj["objectCol"] as? SwiftBoolObject) XCTAssertEqual(((oldObj["objectCol"] as! MigrationObject)["boolCol"] as! Bool), true) XCTAssertEqual(((newObj["objectCol"] as! MigrationObject)["boolCol"] as! Bool), true) XCTAssertEqual((oldObj["arrayCol"] as! List).count, 1) XCTAssertEqual(((oldObj["arrayCol"] as! List)[0]["boolCol"] as! Bool), false) XCTAssertEqual((newObj["arrayCol"] as! List).count, 1) XCTAssertEqual(((newObj["arrayCol"] as! List)[0]["boolCol"] as! Bool), false) XCTAssertEqual((oldObj["setCol"] as! MutableSet).count, 1) XCTAssertEqual(((oldObj["setCol"] as! MutableSet)[0]["boolCol"] as! Bool), false) XCTAssertEqual((newObj["setCol"] as! MutableSet).count, 1) XCTAssertEqual(((newObj["setCol"] as! MutableSet)[0]["boolCol"] as! Bool), false) XCTAssertEqual((oldObj["mapCol"] as! Map).count, 2) XCTAssertEqual(((oldObj["mapCol"] as! Map)["key"]?!["boolCol"] as! Bool), false) XCTAssertEqual((newObj["mapCol"] as! Map).count, 2) XCTAssertEqual(((newObj["mapCol"] as! Map)["key"]?!["boolCol"] as! Bool), false) let uuidCol: UUID = UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! XCTAssertEqual((newObj["uuidCol"] as! UUID), uuidCol) XCTAssertEqual((oldObj["uuidCol"] as! UUID), uuidCol) let anyValue = AnyRealmValue.string("hello!") XCTAssertEqual(((newObj["anyCol"] as! String)), anyValue.stringValue) XCTAssertEqual(((oldObj["anyCol"] as! String)), anyValue.stringValue) // edit all values newObj["boolCol"] = false newObj["intCol"] = 1 newObj["int8Col"] = Int8(1) newObj["int16Col"] = Int16(1) newObj["int32Col"] = Int32(1) newObj["int64Col"] = Int64(1) newObj["intEnumCol"] = IntEnum.value2.rawValue newObj["floatCol"] = 1.0 newObj["doubleCol"] = 10.0 newObj["binaryCol"] = Data(bytes: "b", count: 1) newObj["dateCol"] = Date(timeIntervalSince1970: 2) newObj["decimalCol"] = Decimal128(number: 567e8) newObj["objectIdCol"] = ObjectId("abcdef123456abcdef123456") newObj["anyCol"] = 12345 let falseObj = SwiftBoolObject(value: [false]) newObj["objectCol"] = falseObj var list = newObj["arrayCol"] as! List list[0]["boolCol"] = true list.append(newObj["objectCol"] as! MigrationObject) let trueObj = migration.create(SwiftBoolObject.className(), value: [true]) list.append(trueObj) var set = newObj["setCol"] as! MutableSet set[0]["boolCol"] = true set.insert(newObj["objectCol"] as! MigrationObject) set.insert(trueObj) // verify list property list = newObj["arrayCol"] as! List XCTAssertEqual(list.count, 3) XCTAssertEqual((list[0]["boolCol"] as! Bool), true) XCTAssertEqual((list[1]["boolCol"] as! Bool), false) XCTAssertEqual((list[2]["boolCol"] as! Bool), true) list = newObj.dynamicList("arrayCol") XCTAssertEqual(list.count, 3) XCTAssertEqual((list[0]["boolCol"] as! Bool), true) XCTAssertEqual((list[1]["boolCol"] as! Bool), false) XCTAssertEqual((list[2]["boolCol"] as! Bool), true) // verify set property set = newObj["setCol"] as! MutableSet XCTAssertEqual(set.count, 3) XCTAssertEqual((set[0]["boolCol"] as! Bool), true) XCTAssertEqual((set[1]["boolCol"] as! Bool), false) XCTAssertEqual((set[2]["boolCol"] as! Bool), true) set = newObj.dynamicMutableSet("setCol") XCTAssertEqual(set.count, 3) XCTAssertEqual((set[0]["boolCol"] as! Bool), true) XCTAssertEqual((set[1]["boolCol"] as! Bool), false) XCTAssertEqual((set[2]["boolCol"] as! Bool), true) // verify map property var map = newObj["mapCol"] as! Map XCTAssertEqual(map["key"]?!["boolCol"] as! Bool, false) XCTAssertEqual(map.count, 2) map["key"]?!["boolCol"] = true map = newObj.dynamicMap("mapCol") XCTAssertEqual(map.count, 2) XCTAssertEqual((map["key"]?!["boolCol"] as! Bool), true) unsafeSelf.assertThrows(newObj.value(forKey: "noSuchKey")) unsafeSelf.assertThrows(newObj.setValue(1, forKey: "noSuchKey")) // set it again newObj["arrayCol"] = [falseObj, trueObj] XCTAssertEqual(list.count, 2) newObj["arrayCol"] = [SwiftBoolObject(value: [false])] XCTAssertEqual(list.count, 1) XCTAssertEqual((list[0]["boolCol"] as! Bool), false) newObj["setCol"] = [falseObj, trueObj] XCTAssertEqual(set.count, 2) newObj["setCol"] = [SwiftBoolObject(value: [false])] XCTAssertEqual(set.count, 1) XCTAssertEqual((set[0]["boolCol"] as! Bool), false) // test null in Map XCTAssertEqual(map.count, 2) XCTAssertEqual(map["nulledObj"], Optional.some(nil)) newObj["mapCol"] = ["key": SwiftBoolObject(value: [false])] XCTAssertEqual(map.count, 1) XCTAssertEqual((map["key"]?!["boolCol"] as! Bool), false) let expected = """ SwiftObject \\{ boolCol = 0; intCol = 1; int8Col = 1; int16Col = 1; int32Col = 1; int64Col = 1; intEnumCol = 3; floatCol = 1; doubleCol = 10; stringCol = a; binaryCol = <.*62.*>; dateCol = 1970-01-01 00:00:02 \\+0000; decimalCol = 56700000000; objectIdCol = abcdef123456abcdef123456; objectCol = SwiftBoolObject \\{ boolCol = 0; \\}; uuidCol = 137DECC8-B300-4954-A233-F89909F4FD89; anyCol = 12345; arrayCol = List <0x[0-9a-f]+> \\( \\[0\\] SwiftBoolObject \\{ boolCol = 0; \\} \\); setCol = MutableSet <0x[0-9a-f]+> \\( \\[0\\] SwiftBoolObject \\{ boolCol = 0; \\} \\); mapCol = Map <0x[0-9a-f]+> \\( \\[key\\]: SwiftBoolObject \\{ boolCol = 0; \\} \\); \\} """ unsafeSelf.assertMatches(newObj.description, expected.replacingOccurrences(of: " ", with: "\t")) enumerated = true }) XCTAssertEqual(enumerated, true) let newObj = migration.create(SwiftObject.className()) newObj["anyCol"] = "Some String" let expected = """ SwiftObject \\{ boolCol = 0; intCol = 123; int8Col = 123; int16Col = 123; int32Col = 123; int64Col = 123; intEnumCol = 1; floatCol = 1.23; doubleCol = 12.3; stringCol = a; binaryCol = <.*61.*>; dateCol = 1970-01-01 00:00:01 \\+0000; decimalCol = 1.23E6; objectIdCol = 1234567890ab1234567890ab; objectCol = SwiftBoolObject \\{ boolCol = 0; \\}; uuidCol = 137DECC8-B300-4954-A233-F89909F4FD89; anyCol = Some String; arrayCol = List <0x[0-9a-f]+> \\( \\ \\); setCol = MutableSet <0x[0-9a-f]+> \\( \\ \\); mapCol = Map <0x[0-9a-f]+> \\( \\ \\); \\} """ unsafeSelf.assertMatches(newObj.description, expected.replacingOccurrences(of: " ", with: "\t")) } validation: { realm, _ in let object = realm.objects(SwiftObject.self).first! XCTAssertEqual(object.boolCol, false) XCTAssertEqual(object.intCol, 1) XCTAssertEqual(object.int8Col, Int8(1)) XCTAssertEqual(object.int16Col, Int16(1)) XCTAssertEqual(object.int32Col, Int32(1)) XCTAssertEqual(object.int64Col, Int64(1)) XCTAssertEqual(object.floatCol, 1.0 as Float) XCTAssertEqual(object.doubleCol, 10.0) XCTAssertEqual(object.binaryCol, Data(bytes: "b", count: 1)) XCTAssertEqual(object.dateCol, Date(timeIntervalSince1970: 2)) XCTAssertEqual(object.objectCol!.boolCol, false) XCTAssertEqual(object.arrayCol.count, 1) XCTAssertEqual(object.arrayCol[0].boolCol, false) XCTAssertEqual(object.setCol.count, 1) XCTAssertEqual(object.setCol[0].boolCol, false) XCTAssertEqual(object.mapCol.count, 1) XCTAssertEqual(object.mapCol["key"]!?.boolCol, false) // make sure we added new bool objects as object property and in the list XCTAssertEqual(realm.objects(SwiftBoolObject.self).count, 10) } } func testCollectionAccess() throws { try autoreleasepool { let realm = try Realm() try realm.write { realm.add(ModernAllTypesObject()) } } try testMigration { migration, _ in var enumerated = false migration.enumerateObjects(ofType: "ModernAllTypesObject") { oldObj, _ in guard let oldObj = oldObj else { return XCTFail("did not get objects in enumerate") } XCTAssertEqual((oldObj["arrayCol"] as! List).count, 0) XCTAssertEqual((oldObj["setCol"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["mapCol"] as! Map).count, 0) XCTAssertEqual((oldObj["arrayBool"] as! List).count, 0) XCTAssertEqual((oldObj["arrayInt"] as! List).count, 0) XCTAssertEqual((oldObj["arrayInt8"] as! List).count, 0) XCTAssertEqual((oldObj["arrayInt16"] as! List).count, 0) XCTAssertEqual((oldObj["arrayInt32"] as! List).count, 0) XCTAssertEqual((oldObj["arrayInt64"] as! List).count, 0) XCTAssertEqual((oldObj["arrayFloat"] as! List).count, 0) XCTAssertEqual((oldObj["arrayDouble"] as! List).count, 0) XCTAssertEqual((oldObj["arrayString"] as! List).count, 0) XCTAssertEqual((oldObj["arrayBinary"] as! List).count, 0) XCTAssertEqual((oldObj["arrayDate"] as! List).count, 0) XCTAssertEqual((oldObj["arrayDecimal"] as! List).count, 0) XCTAssertEqual((oldObj["arrayObjectId"] as! List).count, 0) XCTAssertEqual((oldObj["arrayAny"] as! List).count, 0) XCTAssertEqual((oldObj["arrayUuid"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptBool"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptInt"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptInt8"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptInt16"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptInt32"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptInt64"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptFloat"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptDouble"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptString"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptBinary"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptDate"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptDecimal"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptObjectId"] as! List).count, 0) XCTAssertEqual((oldObj["arrayOptUuid"] as! List).count, 0) XCTAssertEqual((oldObj["setBool"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setInt"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setInt8"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setInt16"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setInt32"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setInt64"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setFloat"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setDouble"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setString"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setBinary"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setDate"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setDecimal"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setObjectId"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setAny"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setUuid"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptBool"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptInt"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptInt8"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptInt16"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptInt32"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptInt64"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptFloat"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptDouble"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptString"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptBinary"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptDate"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptDecimal"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptObjectId"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["setOptUuid"] as! MutableSet).count, 0) XCTAssertEqual((oldObj["mapBool"] as! Map).count, 0) XCTAssertEqual((oldObj["mapInt"] as! Map).count, 0) XCTAssertEqual((oldObj["mapInt8"] as! Map).count, 0) XCTAssertEqual((oldObj["mapInt16"] as! Map).count, 0) XCTAssertEqual((oldObj["mapInt32"] as! Map).count, 0) XCTAssertEqual((oldObj["mapInt64"] as! Map).count, 0) XCTAssertEqual((oldObj["mapFloat"] as! Map).count, 0) XCTAssertEqual((oldObj["mapDouble"] as! Map).count, 0) XCTAssertEqual((oldObj["mapString"] as! Map).count, 0) XCTAssertEqual((oldObj["mapBinary"] as! Map).count, 0) XCTAssertEqual((oldObj["mapDate"] as! Map).count, 0) XCTAssertEqual((oldObj["mapDecimal"] as! Map).count, 0) XCTAssertEqual((oldObj["mapObjectId"] as! Map).count, 0) XCTAssertEqual((oldObj["mapAny"] as! Map).count, 0) XCTAssertEqual((oldObj["mapUuid"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptBool"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptInt"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptInt8"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptInt16"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptInt32"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptInt64"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptFloat"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptDouble"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptString"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptBinary"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptDate"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptDecimal"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptObjectId"] as! Map).count, 0) XCTAssertEqual((oldObj["mapOptUuid"] as! Map).count, 0) enumerated = true } XCTAssertTrue(enumerated) } } func testMigrationObjectSchema() throws { let properties: [Property] = autoreleasepool { let realm = try! Realm() return try! realm.write { let obj = SwiftObject() realm.add(obj) return obj.objectSchema.properties } } try testMigration { migration, _ in XCTAssertEqual(migration.oldSchema["SwiftObject"]!.properties, properties) XCTAssertEqual(migration.newSchema["SwiftObject"]!.properties, properties) var enumerated = false migration.enumerateObjects(ofType: "SwiftObject") { oldObj, newObj in guard let oldObj = oldObj, let newObj = newObj else { XCTFail("did not get objects in enumerate") return } enumerated = true XCTAssertEqual(oldObj.objectSchema.properties, properties) XCTAssertEqual(newObj.objectSchema.properties, properties) } XCTAssertTrue(enumerated) } } func testFailOnSchemaMismatch() { let prop = RLMProperty(name: "name", type: RLMPropertyType.string, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) _ = autoreleasepool { realmWithSingleClassProperties(defaultRealmURL(), className: "SwiftEmployeeObject", properties: [prop]) } let config = Realm.Configuration(fileURL: defaultRealmURL(), objectTypes: [SwiftEmployeeObject.self]) assertFails(.schemaMismatch) { try Realm(configuration: config) } } func testDeleteRealmIfMigrationNeededWithSetCustomSchema() { let prop = RLMProperty(name: "name", type: RLMPropertyType.string, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) _ = autoreleasepool { realmWithSingleClassProperties(defaultRealmURL(), className: "SwiftEmployeeObject", properties: [prop]) } var config = Realm.Configuration(fileURL: defaultRealmURL(), objectTypes: [SwiftEmployeeObject.self]) config.migrationBlock = { _, _ in XCTFail("Migration block should not be called") } config.deleteRealmIfMigrationNeeded = true assertSucceeds { _ = try Realm(configuration: config) } } func testDeleteRealmIfMigrationNeeded() throws { try autoreleasepool { _ = try Realm(fileURL: defaultRealmURL()) } let objectSchema = RLMObjectSchema(forObjectClass: SwiftEmployeeObject.self) objectSchema.properties = Array(objectSchema.properties[0..<1]) let metaClass: AnyClass = objc_getMetaClass("RLMSchema") as! AnyClass let imp = imp_implementationWithBlock(unsafeBitCast({ () -> RLMSchema in let schema = RLMSchema() schema.objectSchema = [objectSchema] return schema } as @convention(block)() -> (RLMSchema), to: AnyObject.self)) let originalImp = class_getMethodImplementation(metaClass, #selector(RLMObjectBase.sharedSchema)) class_replaceMethod(metaClass, #selector(RLMObjectBase.sharedSchema), imp, "@@:") assertFails(.schemaMismatch) { try Realm() } let migrationBlock: MigrationBlock = { _, _ in XCTFail("Migration block should not be called") } let config = Realm.Configuration(fileURL: defaultRealmURL(), migrationBlock: migrationBlock, deleteRealmIfMigrationNeeded: true) assertSucceeds { _ = try Realm(configuration: config) } class_replaceMethod(metaClass, #selector(RLMObjectBase.sharedSchema), originalImp!, "@@:") } func testObjectWithCustomColumnNames() throws { try autoreleasepool { let realm = try Realm() try realm.write { let object = ModernCustomObject() object.intCol = 123 object.anyCol = .int(456) object.intEnumCol = .value3 let linkedObject = ModernCustomObject() linkedObject.intCol = 789 object.objectCol = linkedObject object.arrayCol.append(linkedObject) object.setCol.insert(linkedObject) object.mapCol["key"] = linkedObject let embeddedObject = EmbeddedModernCustomObject() embeddedObject.intCol = 102 object.embeddedObject = embeddedObject realm.add(object) } XCTAssertEqual(realm.objects(ModernCustomObject.self).count, 2) } try testMigration { migration, _ in var checkOnce = false migration.enumerateObjects(ofType: "ModernCustomObject") { oldObj, newObj in guard !checkOnce else { return } guard let oldObj = oldObj, let newObj = newObj else { return XCTFail("did not get objects in enumerate") } XCTAssertEqual((oldObj["custom_intCol"] as! Int), 123) XCTAssertEqual((newObj["intCol"] as! Int), 123) let anyValue = AnyRealmValue.int(456) XCTAssertEqual(((oldObj["custom_anyCol"] as! Int)), anyValue.intValue) XCTAssertEqual(((newObj["anyCol"] as! Int)), anyValue.intValue) XCTAssertEqual(((oldObj["custom_intEnumCol"] as! Int)), ModernIntEnum.value3.rawValue) XCTAssertEqual(((newObj["intEnumCol"] as! Int)), ModernIntEnum.value3.rawValue) XCTAssertEqual(((oldObj["custom_objectCol"] as! MigrationObject)["custom_intCol"] as! Int), 789) XCTAssertEqual(((newObj["objectCol"] as! MigrationObject)["intCol"] as! Int), 789) XCTAssertEqual((oldObj["custom_arrayCol"] as! List).count, 1) XCTAssertEqual(((oldObj["custom_arrayCol"] as! List)[0]["custom_intCol"] as! Int), 789) XCTAssertEqual((newObj["arrayCol"] as! List).count, 1) XCTAssertEqual(((newObj["arrayCol"] as! List)[0]["intCol"] as! Int), 789) XCTAssertEqual((oldObj["custom_setCol"] as! MutableSet).count, 1) XCTAssertEqual(((oldObj["custom_setCol"] as! MutableSet)[0]["custom_intCol"] as! Int), 789) XCTAssertEqual((newObj["setCol"] as! MutableSet).count, 1) XCTAssertEqual(((newObj["setCol"] as! MutableSet)[0]["intCol"] as! Int), 789) XCTAssertEqual((oldObj["custom_mapCol"] as! Map).count, 1) XCTAssertEqual(((oldObj["custom_mapCol"] as! Map)["key"]?!["custom_intCol"] as! Int), 789) XCTAssertEqual((newObj["mapCol"] as! Map).count, 1) XCTAssertEqual(((newObj["mapCol"] as! Map)["key"]?!["intCol"] as! Int), 789) XCTAssertEqual(((oldObj["custom_embeddedObject"] as! MigrationObject)["custom_intCol"] as! Int), 102) XCTAssertEqual(((newObj["embeddedObject"] as! MigrationObject)["intCol"] as! Int), 102) checkOnce = true } } validation: { realm, _ in let object = realm.objects(ModernCustomObject.self).first! XCTAssertEqual(object.intCol, 123) XCTAssertEqual(object.anyCol, .int(456)) XCTAssertEqual(object.intEnumCol, .value3) XCTAssertEqual(object.objectCol!.intCol, 789) XCTAssertEqual(object.arrayCol.count, 1) XCTAssertEqual(object.arrayCol[0].intCol, 789) XCTAssertEqual(object.setCol.count, 1) XCTAssertEqual(object.setCol[0].intCol, 789) XCTAssertEqual(object.mapCol.count, 1) XCTAssertEqual(object.mapCol["key"]!?.intCol, 789) XCTAssertEqual(object.embeddedObject?.intCol, 102) XCTAssertEqual(realm.objects(ModernCustomObject.self).count, 2) } } func testCustomColumnDataAfterMigrationRealm() throws { try autoreleasepool { let realm = try Realm() try realm.write { let object = ModernCustomObject() object.intCol = 123 object.anyCol = .int(456) object.intEnumCol = .value3 let linkedObject = ModernCustomObject() linkedObject.intCol = 789 object.objectCol = linkedObject object.setCol.insert(linkedObject) object.mapCol["key"] = linkedObject let embeddedObject = EmbeddedModernCustomObject() embeddedObject.intCol = 102 object.embeddedObject = embeddedObject realm.add(object) } } let config = Realm.Configuration(fileURL: defaultRealmURL(), schemaVersion: 1) let realm = try Realm(configuration: config) XCTAssertEqual(realm.objects(ModernCustomObject.self).count, 2) } func testCustomColumnRenamePropertyToCustom() throws { let prop = RLMProperty(name: "before_intCol", type: .int, objectClassName: nil, linkOriginPropertyName: nil, indexed: false, optional: false) prop.columnName = "custom_before_intCol" try autoreleasepool { let realm = realmWithSingleClassProperties(defaultRealmURL(), className: "ModernCustomObject", properties: [prop]) try realm.transaction { realm.createObject("ModernCustomObject", withValue: [123]) } } try testMigration { migration, _ in XCTAssertEqual(migration.oldSchema.objectSchema[0].properties.count, 1) migration.renameProperty(onType: "ModernCustomObject", from: "custom_before_intCol", to: "custom_intCol") } validation: { realm, schema in XCTAssertEqual(schema.schema(forClassName: "ModernCustomObject")!.properties.count, 12) XCTAssertEqual(1, realm.objects(ModernCustomObject.self).count) let object = realm.objects(ModernCustomObject.self).first! XCTAssertEqual(123, object.intCol) } } } ================================================ FILE: RealmSwift/Tests/MixedCollectionTest.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // 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 import RealmSwift class MixedCollectionTest: TestCase { func testAnyMixedDictionary() throws { let so = SwiftStringObject() so.stringCol = "hello" let d = Date() let oid = ObjectId.generate() let uuid = UUID() func assertObject(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"], .string("hello")) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"], .bool(false)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key3"], .int(234)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key4"], .float(789.123)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key5"], .double(12345.678901)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key6"], .data(Data("a".utf8))) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key7"], .date(d)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key9"], .objectId(oid)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key10"], .uuid(uuid)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key11"], .decimal128(Decimal128(number: 567))) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"]?.stringValue, "hello") XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"]?.boolValue, false) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key3"]?.intValue, 234) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key4"]?.floatValue, 789.123) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key5"]?.doubleValue, 12345.678901) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key6"]?.dataValue, Data("a".utf8)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key7"]?.dateValue, d) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key8"]?.object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key9"]?.objectIdValue, oid) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key10"]?.uuidValue, uuid) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key11"]?.decimal128Value, Decimal128(number: 567)) } let dictionary: Dictionary = [ "key1": .string("hello"), "key2": .bool(false), "key3": .int(234), "key4": .float(789.123), "key5": .double(12345.678901), "key6": .data(Data("a".utf8)), "key7": .date(d), "key8": .object(so), "key9": .objectId(oid), "key10": .uuid(uuid), "key11": .decimal128(Decimal128(number: 567)) ] let dictionary1: Dictionary = [ "key": .none ] let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) assertObject(o) // Unmanaged update o.anyValue.value = AnyRealmValue.fromDictionary(dictionary1) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key"], AnyRealmValue.none) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key"], AnyRealmValue.none) // Update managed object try realm.write { o.anyValue.value = AnyRealmValue.fromDictionary(dictionary1) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key"], AnyRealmValue.none) // Create managed object try realm.write { let object = realm.create(AnyRealmTypeObject.self, value: [ "anyValue": dictionary ]) assertObject(object) } // Results let result = realm.objects(AnyRealmTypeObject.self).last XCTAssertNotNil(result) assertObject(result!) } func testAnyMixedDictionaryUpdateAndDelete() throws { let so = SwiftStringObject() so.stringCol = "hello" let dictionary: Dictionary = [ "key1": .string("hello"), "key2": .bool(false), "key3": .int(234), ] let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"], .string("hello")) // Unmanaged Update o.anyValue.value.dictionaryValue?["key1"] = .object(so) o.anyValue.value.dictionaryValue?["key2"] = .data(Data("a".utf8)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"]?.object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"], .data(Data("a".utf8))) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"]?.object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"], .data(Data("a".utf8))) // Update managed object try realm.write { o.anyValue.value.dictionaryValue?["key1"] = .double(12345.678901) o.anyValue.value.dictionaryValue?["key2"] = .float(789.123) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"], .double(12345.678901)) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"], .float(789.123)) let result = realm.objects(AnyRealmTypeObject.self).last XCTAssertNotNil(result) // Delete try realm.write { result?.anyValue.value.dictionaryValue?["key1"] = nil result?.anyValue.value.dictionaryValue?["key2"] = nil } XCTAssertNil(result?.anyValue.value.dictionaryValue?["key1"]) XCTAssertNil(result?.anyValue.value.dictionaryValue?["key2"]) } func testAnyMixedNestedDictionary() throws { let so = SwiftStringObject() so.stringCol = "hello" func assertDictionary1(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.dictionaryValue?["key2"]?.dictionaryValue?["key3"]?.object(SwiftStringObject.self)?.stringCol, "hello") XCTAssertEqual(o.anyValue.value.dictionaryValue?["key4"]?.boolValue, false) } func assertDictionary2(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.dictionaryValue?["key10"]?.dictionaryValue?["key11"]?.dictionaryValue?["key12"]?.dictionaryValue?["key1"]?.dictionaryValue?["key2"]?.dictionaryValue?["key3"]?.object(SwiftStringObject.self)?.stringCol, "hello") } let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": .object(so) ]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subDict2 ]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subDict3 ]) let dictionary1: Dictionary = [ "key0": subDict4, "key4": .bool(false) ] let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key12": subDict4 ]) let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key11": subDict5 ]) let dictionary2: Dictionary = [ "key10": subDict6, ] let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromDictionary(dictionary2) // Unamanged Read assertDictionary2(o) // Unmanaged update o.anyValue.value = AnyRealmValue.fromDictionary(dictionary1) // Update assert assertDictionary1(o) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } // Add assert assertDictionary1(o) // Update managed object try realm.write { o.anyValue.value = AnyRealmValue.fromDictionary(dictionary2) } // Update assert assertDictionary2(o) try realm.write { let d = [ "key0": [ "key1": [ "key2": [ "key3": AnyRealmValue.object(so)]]], "key4": AnyRealmValue.bool(false)] let object = realm.create(AnyRealmTypeObject.self, value: [ "anyValue": d]) assertDictionary1(object) } // Results let result = realm.objects(AnyRealmTypeObject.self).last // Results assert XCTAssertNotNil(result) assertDictionary1(result!) } func testAnyMixedList() throws { let so = SwiftStringObject() so.stringCol = "hello" let d = Date() let oid = ObjectId.generate() let uuid = UUID() func assertObject(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.listValue?[0], .string("hello")) XCTAssertEqual(o.anyValue.value.listValue?[1], .bool(false)) XCTAssertEqual(o.anyValue.value.listValue?[2], .int(234)) XCTAssertEqual(o.anyValue.value.listValue?[3], .float(789.123)) XCTAssertEqual(o.anyValue.value.listValue?[4], .double(12345.678901)) XCTAssertEqual(o.anyValue.value.listValue?[5], .data(Data("a".utf8))) XCTAssertEqual(o.anyValue.value.listValue?[6], .date(d)) XCTAssertEqual(o.anyValue.value.listValue?[8], .objectId(oid)) XCTAssertEqual(o.anyValue.value.listValue?[9], .uuid(uuid)) XCTAssertEqual(o.anyValue.value.listValue?[10], .decimal128(Decimal128(number: 567))) XCTAssertEqual(o.anyValue.value.listValue?[0].stringValue, "hello") XCTAssertEqual(o.anyValue.value.listValue?[1].boolValue, false) XCTAssertEqual(o.anyValue.value.listValue?[2].intValue, 234) XCTAssertEqual(o.anyValue.value.listValue?[3].floatValue, 789.123) XCTAssertEqual(o.anyValue.value.listValue?[4].doubleValue, 12345.678901) XCTAssertEqual(o.anyValue.value.listValue?[5].dataValue, Data("a".utf8)) XCTAssertEqual(o.anyValue.value.listValue?[6].dateValue, d) XCTAssertEqual(o.anyValue.value.listValue?[7].object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.listValue?[8].objectIdValue, oid) XCTAssertEqual(o.anyValue.value.listValue?[9].uuidValue, uuid) XCTAssertEqual(o.anyValue.value.listValue?[10].decimal128Value, Decimal128(number: 567)) } let list: Array = [ .string("hello"), .bool(false), .int(234), .float(789.123), .double(12345.678901), .data(Data("a".utf8)), .date(d), .object(so), .objectId(oid), .uuid(uuid), .decimal128(Decimal128(number: 567)) ] let list1: Array = [ .none ] let o = AnyRealmTypeObject() // Unmanaged Set let l = AnyRealmValue.fromArray(list) o.anyValue.value = l assertObject(o) // Unmanaged update o.anyValue.value = AnyRealmValue.fromArray(list1) XCTAssertEqual(o.anyValue.value.listValue?[0], AnyRealmValue.none) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.listValue?[0], AnyRealmValue.none) // Update managed object try realm.write { o.anyValue.value = AnyRealmValue.fromArray(list1) } XCTAssertEqual(o.anyValue.value.listValue?[0], AnyRealmValue.none) try realm.write { let object = realm.create(AnyRealmTypeObject.self, value: [ "anyValue": list ]) assertObject(object) } // Results let result = realm.objects(AnyRealmTypeObject.self).last XCTAssertNotNil(result) assertObject(result!) } func testAnyMixedListUpdateAndDelete() throws { let so = SwiftStringObject() so.stringCol = "hello" let list: Array = [ .string("hello"), .bool(false), .int(234), ] let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromArray(list) XCTAssertEqual(o.anyValue.value.listValue?[0], .string("hello")) // Unmanaged Update o.anyValue.value.listValue?[0] = .object(so) o.anyValue.value.listValue?[1] = .data(Data("a".utf8)) XCTAssertEqual(o.anyValue.value.listValue?[0].object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.listValue?[1], .data(Data("a".utf8))) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.listValue?[0].object(SwiftStringObject.self)?.stringCol, so.stringCol) XCTAssertEqual(o.anyValue.value.listValue?[1], .data(Data("a".utf8))) // Update managed object try realm.write { o.anyValue.value.listValue?[0] = .double(12345.678901) o.anyValue.value.listValue?[1] = .float(789.123) } XCTAssertEqual(o.anyValue.value.listValue?[0], .double(12345.678901)) XCTAssertEqual(o.anyValue.value.listValue?[1], .float(789.123)) let result = realm.objects(AnyRealmTypeObject.self).last XCTAssertNotNil(result) XCTAssertEqual(result?.anyValue.value.listValue?.count, 3) // Delete try realm.write { result?.anyValue.value.listValue?.remove(at: 0) result?.anyValue.value.listValue?.remove(at: 0) } XCTAssertNotEqual(result?.anyValue.value.listValue?[0], .double(12345.678901)) XCTAssertEqual(result?.anyValue.value.listValue?[0], .int(234)) XCTAssertEqual(result?.anyValue.value.listValue?.count, 1) } func testAnyMixedNestedArray() throws { let so = SwiftStringObject() so.stringCol = "hello" func assertArray1(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.listValue?[0].listValue?[0].listValue?[0].listValue?[0].object(SwiftStringObject.self)?.stringCol, "hello") XCTAssertEqual(o.anyValue.value.listValue?[1].boolValue, false) } func assertArray2(_ o: AnyRealmTypeObject) { XCTAssertEqual(o.anyValue.value.listValue?[0].listValue?[0].listValue?[0].listValue?[0].listValue?[0].listValue?[0].object(SwiftStringObject.self)?.stringCol, "hello") } let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so) ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2 ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subArray3 ]) let array1: Array = [ subArray4, .bool(false) ] let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subArray4 ]) let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subArray5 ]) let array2: Array = [ subArray6 ] let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromArray(array2) // Unamanged Read assertArray2(o) // Unmanaged update o.anyValue.value = AnyRealmValue.fromArray(array1) // Update assert assertArray1(o) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } // Add assert assertArray1(o) // Update managed object try realm.write { o.anyValue.value = AnyRealmValue.fromArray(array2) } // Update assert assertArray2(o) try realm.write { let d = [[[[ AnyRealmValue.object(so)]]], AnyRealmValue.bool(false)] let object = realm.create(AnyRealmTypeObject.self, value: [ "anyValue": d]) assertArray1(object) } // Results let result = realm.objects(AnyRealmTypeObject.self).last XCTAssertNotNil(result) assertArray1(result!) } func testMixedNestedCollection() throws { let so = SwiftStringObject() so.stringCol = "hello" let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so) ]) let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, subDict2]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subArray3 ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subDict3 ]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subArray4 ]) let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subDict4 ]) let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": subArray5 ]) let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subDict5 ]) let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": subArray6 ]) let dictionary: Dictionary = [ "key0": subDict6, ] func assertMixed(_ o: AnyRealmTypeObject) { let baseNested: List? = o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0].dictionaryValue?["key4"]?.listValue?[0].dictionaryValue?["key3"]?.listValue let nested1: String? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[0].listValue?[0].object(SwiftStringObject.self)?.stringCol XCTAssertEqual(nested1, "hello") let nested2: String? = baseNested?[0].dictionaryValue?["key2"]?.listValue?[1].dictionaryValue?["key1"]?.listValue?[0].object(SwiftStringObject.self)?.stringCol XCTAssertEqual(nested2, "hello") } let o = AnyRealmTypeObject() // Unmanaged Set o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) // Unamanged Read assertMixed(o) // Unmanaged update o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) // Update assert assertMixed(o) // Add mixed collection to object let realm = realmWithTestPath() try realm.write { realm.add(o) } // Add assert assertMixed(o) // Update managed object try realm.write { o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) } // Update assert assertMixed(o) try realm.write { let d = ["key0": ["key5": [["key4": [["key3": [["key2": [[AnyRealmValue.object(so)], ["key1": [AnyRealmValue.object(so)]]]]]]]]]]] let object = realm.create(AnyRealmTypeObject.self, value: [ "anyValue": d]) assertMixed(object) } // Results let result = realm.objects(AnyRealmTypeObject.self).last // Results assert XCTAssertNotNil(result) assertMixed(result!) } func testMixedCollectionEquality() throws { let so = SwiftStringObject() so.stringCol = "hello" let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so) ]) let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, subDict2]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subArray3 ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subDict3 ]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subArray4 ]) let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subDict4 ]) let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": subArray5 ]) let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subDict5 ]) let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": subArray6 ]) let dictionary: Dictionary = [ "key0": subDict6, ] XCTAssertEqual(AnyRealmValue.fromDictionary([:]), AnyRealmValue.fromDictionary([:])) // Empty mixed lists should be equal XCTAssertEqual(AnyRealmValue.fromDictionary(dictionary), AnyRealmValue.fromDictionary(dictionary)) // Unamanged mixed list should be equal var dictionary2 = dictionary dictionary2["newKey"] = .bool(false) XCTAssertNotEqual(AnyRealmValue.fromDictionary(dictionary), AnyRealmValue.fromDictionary(dictionary2)) let anyValue = AnyRealmValue.fromDictionary(dictionary) let anyValue2 = AnyRealmValue.fromDictionary(dictionary) anyValue2.dictionaryValue?["newKey"] = .bool(false) XCTAssertNotEqual(anyValue, anyValue2) let mixedObject = AnyRealmTypeObject() mixedObject.anyValue.value = anyValue let mixedObject2 = mixedObject XCTAssertEqual(mixedObject.anyValue, mixedObject2.anyValue) XCTAssertEqual(mixedObject.anyValue.value, mixedObject2.anyValue.value) XCTAssertEqual(mixedObject.anyValue, mixedObject2.anyValue, "instances should be equal by `==` operator") XCTAssertTrue(mixedObject.isEqual(mixedObject2), "instances should be equal by `isEqual` method") XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue, mixedObject.anyValue.value.dictionaryValue) let realm = realmWithTestPath() try realm.write { realm.add(mixedObject) realm.add(mixedObject2) } XCTAssertEqual(mixedObject.anyValue.value, mixedObject2.anyValue.value) XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key0"], mixedObject2.anyValue.value.dictionaryValue?["key0"]) XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0], mixedObject2.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0]) let mixedObject3 = AnyRealmTypeObject() mixedObject3.anyValue.value = AnyRealmValue.fromDictionary(dictionary) try realm.write { realm.add(mixedObject3) } XCTAssertNotEqual(mixedObject.anyValue.value, mixedObject3.anyValue.value) XCTAssertNotEqual(mixedObject.anyValue.value.dictionaryValue?["key0"], mixedObject3.anyValue.value.dictionaryValue?["key0"]) XCTAssertNotEqual(mixedObject.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0], mixedObject3.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0]) } func testMixedCollectionModernObjectEquality() throws { let so = SwiftStringObject() so.stringCol = "hello" let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so) ]) let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, subDict2]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subArray3 ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subDict3 ]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subArray4 ]) let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subDict4 ]) let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": subArray5 ]) let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subDict5 ]) let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": subArray6 ]) let dictionary: Dictionary = [ "key0": subDict6, ] XCTAssertEqual(AnyRealmValue.fromDictionary([:]), AnyRealmValue.fromDictionary([:])) // Empty mixed lists should be equal XCTAssertEqual(AnyRealmValue.fromDictionary(dictionary), AnyRealmValue.fromDictionary(dictionary)) // Unamanged mixed list should be equal var dictionary2 = dictionary dictionary2["newKey"] = .bool(false) XCTAssertNotEqual(AnyRealmValue.fromDictionary(dictionary), AnyRealmValue.fromDictionary(dictionary2)) let anyValue = AnyRealmValue.fromDictionary(dictionary) let anyValue2 = AnyRealmValue.fromDictionary(dictionary) anyValue2.dictionaryValue?["newKey"] = .bool(false) XCTAssertNotEqual(anyValue, anyValue2) // Unmanaged equality let mixedObject = ModernAllTypesObject() mixedObject.anyCol = anyValue XCTAssertEqual(mixedObject.anyCol, anyValue) XCTAssertEqual(mixedObject.anyCol, AnyRealmValue.fromDictionary(dictionary)) let mixedObject2 = mixedObject XCTAssertEqual(mixedObject2.anyCol, anyValue) XCTAssertEqual(mixedObject2.anyCol, AnyRealmValue.fromDictionary(dictionary)) XCTAssertEqual(mixedObject.anyCol, mixedObject2.anyCol) XCTAssertTrue(mixedObject.anyCol == mixedObject2.anyCol, "instances should be equal by `==` operator") XCTAssertTrue(mixedObject.isEqual(mixedObject2), "instances should be equal by `isEqual` method") XCTAssertEqual(mixedObject.anyCol.dictionaryValue, mixedObject.anyCol.dictionaryValue) let realm = realmWithTestPath() try realm.write { realm.add(mixedObject) realm.add(mixedObject2) } XCTAssertEqual(mixedObject.anyCol, mixedObject2.anyCol) XCTAssertTrue(mixedObject.anyCol == mixedObject2.anyCol, "instances should be equal by `==` operator") XCTAssertTrue(mixedObject.isEqual(mixedObject2), "instances should be equal by `isEqual` method") let mixedObject3 = ModernAllTypesObject() mixedObject3.anyCol = AnyRealmValue.fromDictionary(dictionary) try realm.write { realm.add(mixedObject3) } XCTAssertNotEqual(mixedObject.anyCol, mixedObject3.anyCol) XCTAssertNotEqual(mixedObject.anyCol.dictionaryValue?["key0"], mixedObject3.anyCol.dictionaryValue?["key0"]) XCTAssertNotEqual(mixedObject.anyCol.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0], mixedObject3.anyCol.dictionaryValue?["key0"]?.dictionaryValue?["key5"]?.listValue?[0]) } @MainActor func testMixedCollectionObjectNotifications() throws { let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ .int(3) ]) let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ subArray3 ]) let subDict1: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) let dictionary: Dictionary = [ "key0": subDict1, ] func expectChange(_ name: String) -> ((ObjectChange) -> Void) { let exp = expectation(description: "Object changes for mixed collections") return { change in if case .change(_, let properties) = change { XCTAssertEqual(properties.count, 1) if let prop = properties.first { XCTAssertEqual(prop.name, name) } } else { XCTFail("expected .change, got \(change)") } exp.fulfill() } } func assertObjectNotification(_ object: Object, block: @escaping () -> Void) { let token = object.observe(expectChange("anyValue")) let realm = realmWithTestPath() try! realm.write { block() } waitForExpectations(timeout: 2) token.invalidate() } let o = AnyRealmTypeObject() let realm = realmWithTestPath() try realm.write { realm.add(o) } assertObjectNotification(o) { o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.listValue?[0].listValue?[0] = .bool(true) } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.listValue?.append(.float(33.33)) } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.listValue?.removeLast() } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.listValue?.removeAll() } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key2"] = .string("nowhere") } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key2"] = nil } assertObjectNotification(o) { o.anyValue.value.dictionaryValue?.removeAll() } } @MainActor func testMixedCollectionDictionaryNotifications() throws { let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": .float(43) ]) let subDict1: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subDict2 ]) let dictionary: Dictionary = [ "key0": subDict1, "key3": .decimal128(Decimal128(1)), ] func expectChanges(_ deletions: [String], _ insertions: [String], _ modifications: [String]) -> ((RealmMapChange>) -> Void) { let exp = expectation(description: "Dictionary changes for mixed collections") return { change in switch change { case .initial: break case .update(_, deletions: let d, insertions: let i, modifications: let m): XCTAssertEqual(d.count, deletions.count) XCTAssertEqual(d, deletions) XCTAssertEqual(i.count, insertions.count) XCTAssertEqual(i, insertions) XCTAssertEqual(m.count, modifications.count) XCTAssertEqual(m, modifications) exp.fulfill() case .error(let error): XCTFail("Unexpected error \(error)") } } } func assertDictionaryNotification(_ dictionary: Map?, deletions: [String], insertions: [String], modifications: [String], block: @escaping () -> Void) { let token = dictionary?.observe(expectChanges(deletions, insertions, modifications)) let realm = realmWithTestPath() try! realm.write { block() } waitForExpectations(timeout: 2) token?.invalidate() } let o = AnyRealmTypeObject() o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) let realm = realmWithTestPath() try realm.write { realm.add(o) } assertDictionaryNotification(o.anyValue.value.dictionaryValue, deletions: [], insertions: ["key2"], modifications: []) { o.anyValue.value.dictionaryValue?["key2"] = AnyRealmValue.fromDictionary(dictionary) } assertDictionaryNotification(o.anyValue.value.dictionaryValue, deletions: [], insertions: ["key10"], modifications: []) { o.anyValue.value.dictionaryValue?["key10"] = AnyRealmValue.fromDictionary(dictionary) } assertDictionaryNotification(o.anyValue.value.dictionaryValue?["key10"]?.dictionaryValue, deletions: [], insertions: [], modifications: ["key0"]) { o.anyValue.value.dictionaryValue?["key10"]?.dictionaryValue?["key0"] = .string("new") } assertDictionaryNotification(o.anyValue.value.dictionaryValue, deletions: ["key3"], insertions: [], modifications: []) { o.anyValue.value.dictionaryValue?["key3"] = nil } assertDictionaryNotification(o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.dictionaryValue, deletions: [], insertions: ["key6"], modifications: []) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.dictionaryValue?["key6"] = .date(Date()) } assertDictionaryNotification(o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.dictionaryValue, deletions: ["key5", "key6"], insertions: [], modifications: []) { o.anyValue.value.dictionaryValue?["key0"]?.dictionaryValue?["key1"]?.dictionaryValue?.removeAll() } } @MainActor func testMixedCollectionArrayNotifications() throws { let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .float(43), .string("lunch"), .double(12.34) ]) let subArray1: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, .bool(false) ]) let array: Array = [ subArray1, .decimal128(Decimal128(1)), ] func expectChanges(_ deletions: [Int], _ insertions: [Int], _ modifications: [Int]) -> ((RealmCollectionChange>) -> Void) { let exp = expectation(description: "Dictionary changes for mixed collections") return { change in switch change { case .initial: break case .update(_, deletions: let d, insertions: let i, modifications: let m): XCTAssertEqual(d.count, deletions.count) XCTAssertEqual(d, deletions) XCTAssertEqual(i.count, insertions.count) XCTAssertEqual(i, insertions) XCTAssertEqual(m.count, modifications.count) XCTAssertEqual(m, modifications) exp.fulfill() case .error(let error): XCTFail("Unexpected error \(error)") } } } func assertDictionaryNotification(_ list: List?, deletions: [Int], insertions: [Int], modifications: [Int], block: @escaping () -> Void) { let token = list?.observe(expectChanges(deletions, insertions, modifications)) let realm = realmWithTestPath() try! realm.write { block() } waitForExpectations(timeout: 2) token?.invalidate() } let o = AnyRealmTypeObject() o.anyValue.value = AnyRealmValue.fromArray(array) let realm = realmWithTestPath() try realm.write { realm.add(o) } assertDictionaryNotification(o.anyValue.value.listValue, deletions: [], insertions: [2], modifications: []) { o.anyValue.value.listValue?.append(AnyRealmValue.fromArray(array)) } assertDictionaryNotification(o.anyValue.value.listValue?[0].listValue, deletions: [], insertions: [], modifications: [1]) { o.anyValue.value.listValue?[0].listValue?[1] = .objectId(ObjectId.generate()) } assertDictionaryNotification(o.anyValue.value.listValue?[0].listValue?[0].listValue, deletions: [2], insertions: [], modifications: []) { o.anyValue.value.listValue?[0].listValue?[0].listValue?.removeLast() } assertDictionaryNotification(o.anyValue.value.listValue?[0].listValue?[0].listValue, deletions: [0, 1], insertions: [], modifications: []) { o.anyValue.value.listValue?[0].listValue?[0].listValue?.removeAll() } } func testReassignToMixedList() throws { let list = AnyRealmValue.fromArray([.bool(true), AnyRealmValue.fromDictionary(["key": .int(12)]), .float(13.12)]) let mixedObject = AnyRealmTypeObject() mixedObject.anyValue.value = list let realm = realmWithTestPath() try realm.write { realm.add(mixedObject) } XCTAssertEqual(mixedObject.anyValue.value.listValue?[1].dictionaryValue?["key"], .int(12)) try realm.write { mixedObject.anyValue.value.listValue?.append(AnyRealmValue.fromArray([.double(20.20), .string("hello")])) } XCTAssertEqual(mixedObject.anyValue.value.listValue?[3].listValue?[0], .double(20.20)) try realm.write { let listItem = mixedObject.anyValue.value.listValue?[0] mixedObject.anyValue.value.listValue?.append(listItem!) } XCTAssertEqual(mixedObject.anyValue.value.listValue?[4], .bool(true)) try realm.write { let listItem = mixedObject.anyValue.value.listValue?[3] mixedObject.anyValue.value.listValue?.append(listItem!) } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.listValue?[4].listValue?[0], .double(20.20)) try realm.write { let listItem = mixedObject.anyValue.value.listValue?[3] mixedObject.anyValue.value.listValue?[3] = listItem! } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.listValue?[3].listValue?[0], .double(20.20)) try realm.write { mixedObject.anyValue.value.listValue?[1] = AnyRealmValue.fromDictionary(["new-key": .int(3)]) } XCTAssertEqual(mixedObject.anyValue.value.listValue?[1].dictionaryValue?["new-key"], .int(3)) try realm.write { let listItem = mixedObject.anyValue.value.listValue?[1] mixedObject.anyValue.value.listValue?.append(listItem!) } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.listValue?[3].dictionaryValue?["new-key"], .int(3)) try realm.write { let listItem = mixedObject.anyValue.value.listValue?[1] mixedObject.anyValue.value.listValue?[3] = listItem! } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.listValue?[3].dictionaryValue?["new-key"], .int(3)) } func testReassignToMixedDictionary() throws { let dictionary = AnyRealmValue.fromDictionary(["key1": .bool(true), "key2": AnyRealmValue.fromDictionary(["key4": .int(12)]), "key3": .float(13.12)]) let mixedObject = AnyRealmTypeObject() mixedObject.anyValue.value = dictionary let realm = realmWithTestPath() try realm.write { realm.add(mixedObject) } XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key2"]?.dictionaryValue?["key4"], .int(12)) try realm.write { mixedObject.anyValue.value.dictionaryValue?["key4"] = AnyRealmValue.fromDictionary(["new-key": .int(3)]) } XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key4"]?.dictionaryValue?["new-key"], .int(3)) try realm.write { let dictItem = mixedObject.anyValue.value.dictionaryValue?["key1"] mixedObject.anyValue.value.dictionaryValue?["key5"] = dictItem } XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key5"], .bool(true)) try realm.write { let dictItem = mixedObject.anyValue.value.dictionaryValue?["key2"] mixedObject.anyValue.value.dictionaryValue?["key6"] = dictItem } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key6"]?.dictionaryValue?["key4"], .int(12)) try realm.write { mixedObject.anyValue.value.dictionaryValue?["key7"] = AnyRealmValue.fromArray([.string("hello"), .double(20.20)]) } XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key7"]?.listValue?[0], .string("hello")) try realm.write { let dictItem = mixedObject.anyValue.value.dictionaryValue?["key7"] mixedObject.anyValue.value.dictionaryValue?["key2"] = dictItem } // TODO: Self-assignment - this doesn't work due to https://github.com/realm/realm-core/issues/7422 // XCTAssertEqual(mixedObject.anyValue.value.dictionaryValue?["key2"]?.listValue?[0], .string("hello")) } func testEnumerationNestedCollection() throws { var count = 0 var accessNestedValue = false func iterateNestedCollectionKeyValue(_ value: AnyRealmValue) { count+=1 switch value { case .list(let l): for item in l { iterateNestedCollectionKeyValue(item) } case .dictionary(let d): for (_, val) in d { iterateNestedCollectionKeyValue(val) } default: accessNestedValue = true } } let so = SwiftStringObject() so.stringCol = "hello" let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .object(so) ]) let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary([ "key1": subArray2 ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subDict2 ]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary([ "key2": subArray3 ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subDict3 ]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary([ "key3": subArray4 ]) let subArray5: AnyRealmValue = AnyRealmValue.fromArray([ subDict4 ]) let subDict5: AnyRealmValue = AnyRealmValue.fromDictionary([ "key4": subArray5 ]) let subArray6: AnyRealmValue = AnyRealmValue.fromArray([ subDict5 ]) let subDict6: AnyRealmValue = AnyRealmValue.fromDictionary([ "key5": subArray6 ]) let dictionary: Dictionary = [ "key0": subDict6, ] let o = AnyRealmTypeObject() o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) let realm = realmWithTestPath() try realm.write { realm.add(o) } iterateNestedCollectionKeyValue(o.anyValue.value) XCTAssertEqual(count, 12) XCTAssertTrue(accessNestedValue) var countValue = 0 var accessNestedValueValue = false func iterateNestedCollectionValue(_ value: AnyRealmValue) { countValue+=1 switch value { case .list(let l): for item in l { iterateNestedCollectionValue(item) } case .dictionary(let d): for (val) in d { iterateNestedCollectionValue(val.value) } default: accessNestedValueValue = true } } iterateNestedCollectionValue(o.anyValue.value) XCTAssertEqual(countValue, 12) XCTAssertTrue(accessNestedValueValue) } func testCollectionReassign() throws { let dictionary: Dictionary = [ "key1": .string("hello"), "key2": .bool(false), ] let dictionary1: Dictionary = [ "key1": .string("adios"), ] let o = AnyRealmTypeObject() o.anyValue.value = AnyRealmValue.fromDictionary(dictionary) let realm = realmWithTestPath() try realm.write { realm.add(o) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"], .string("hello")) XCTAssertEqual(o.anyValue.value.dictionaryValue?["key2"], .bool(false)) try realm.write { o.anyValue.value = AnyRealmValue.fromDictionary(dictionary1) } XCTAssertEqual(o.anyValue.value.dictionaryValue?["key1"], .string("adios")) XCTAssertNil(o.anyValue.value.dictionaryValue?["key2"]) } } ================================================ FILE: RealmSwift/Tests/ModernKVOTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift class ModernKVOTests: TestCase { var realm: Realm! = nil override func setUp() { super.setUp() realm = try! Realm() realm.beginWrite() } override func tearDown() { realm.cancelWrite() realm = nil super.tearDown() } var changeDictionary: [NSKeyValueChangeKey: Any]? override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { changeDictionary = change } // swiftlint:disable:next cyclomatic_complexity func observeChange(_ obj: ModernAllTypesObject, _ key: String, _ old: T?, _ new: T?, fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { let kvoOptions: NSKeyValueObservingOptions = [.old, .new] obj.addObserver(self, forKeyPath: key, options: kvoOptions, context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } let actualOld = changeDictionary![.oldKey] as? T let actualNew = changeDictionary![.newKey] as? T XCTAssert(old == actualOld, "Old value: expected \(String(describing: old)), got \(String(describing: actualOld))", file: (fileName), line: lineNumber) XCTAssert(new == actualNew, "New value: expected \(String(describing: new)), got \(String(describing: actualNew))", file: (fileName), line: lineNumber) changeDictionary = nil } func observeListChange(_ obj: NSObject, _ key: String, _ kind: NSKeyValueChange, _ indexes: NSIndexSet = NSIndexSet(index: 0), fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { obj.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } let actualKind = NSKeyValueChange(rawValue: (changeDictionary![NSKeyValueChangeKey.kindKey] as! NSNumber).uintValue)! let actualIndexes = changeDictionary![NSKeyValueChangeKey.indexesKey]! as! NSIndexSet XCTAssert(actualKind == kind, "Change kind: expected \(kind), got \(actualKind)", file: (fileName), line: lineNumber) XCTAssert(actualIndexes.isEqual(indexes), "Changed indexes: expected \(indexes), got \(actualIndexes)", file: (fileName), line: lineNumber) changeDictionary = nil } class CompoundListObserver: NSObject { let key: String let deletions: [Int] let insertions: [Int] public var count = 0 init(_ key: String, _ deletions: [Int], _ insertions: [Int]) { self.key = key self.deletions = deletions self.insertions = insertions } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { XCTAssertEqual(keyPath, key) XCTAssertNotNil(object) XCTAssertNotNil(change) guard let change = change else { return } XCTAssertNotNil(change[.kindKey]) guard let kind = (change[.kindKey] as? UInt).map(NSKeyValueChange.init(rawValue:)) else { return } if count == 0 { XCTAssertEqual(kind, .removal) XCTAssertEqual(Array(change[.indexesKey]! as! NSIndexSet), deletions) } else if count == 1 { XCTAssertEqual(kind, .insertion) XCTAssertEqual(Array(change[.indexesKey]! as! NSIndexSet), insertions) } else { XCTFail("too many notifications") } count += 1 } } func observeCompoundListChange(_ obs: NSObject, _ obj: NSObject, _ key: String, _ values: NSArray, deletions: [Int], insertions: [Int]) { let observer = CompoundListObserver(key, deletions, insertions) if deletions.count == 0 { observer.count = 1 } obs.addObserver(observer, forKeyPath: key, options: [.old, .new], context: nil) obj.setValue(values, forKey: key) obs.removeObserver(observer, forKeyPath: key) if insertions.count > 0 { XCTAssertEqual(observer.count, 2) } else { XCTAssertEqual(observer.count, 1) } } func observeSetChange(_ obj: ModernAllTypesObject, _ key: String, fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) { obj.addObserver(self, forKeyPath: key, options: [], context: nil) block() obj.removeObserver(self, forKeyPath: key) XCTAssert(changeDictionary != nil, "Did not get a notification", file: (fileName), line: lineNumber) guard changeDictionary != nil else { return } } func getObject(_ obj: ModernAllTypesObject) -> (ModernAllTypesObject, ModernAllTypesObject) { return (obj, obj) } // Actual tests follow func testAllPropertyTypes() { let (obj, obs) = getObject(ModernAllTypesObject()) let oldData = obj.binaryCol let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)! let oldDate = obj.dateCol let date = Date(timeIntervalSince1970: 1) let oldDecimal = obj.decimalCol let decimal = Decimal128(number: 2) let oldObjectId = obj.objectIdCol let objectId = ObjectId() let oldUUID = obj.uuidCol let uuid = UUID() observeChange(obs, "boolCol", false, true) { obj.boolCol = true } observeChange(obs, "int8Col", 1 as Int8, 10) { obj.int8Col = 10 } observeChange(obs, "int16Col", 2 as Int16, 10) { obj.int16Col = 10 } observeChange(obs, "int32Col", 3 as Int32, 10) { obj.int32Col = 10 } observeChange(obs, "int64Col", 4 as Int64, 10) { obj.int64Col = 10 } observeChange(obs, "floatCol", 5 as Float, 10) { obj.floatCol = 10 } observeChange(obs, "doubleCol", 6 as Double, 10) { obj.doubleCol = 10 } observeChange(obs, "stringCol", "", "abc") { obj.stringCol = "abc" } observeChange(obs, "objectCol", nil, obj) { obj.objectCol = obj } observeChange(obs, "binaryCol", oldData, data) { obj.binaryCol = data } observeChange(obs, "dateCol", oldDate, date) { obj.dateCol = date } observeChange(obs, "decimalCol", oldDecimal, decimal) { obj.decimalCol = decimal } observeChange(obs, "objectIdCol", oldObjectId, objectId) { obj.objectIdCol = objectId } observeChange(obs, "uuidCol", oldUUID, uuid) { obj.uuidCol = uuid } observeChange(obs, "anyCol", nil, 1) { obj.anyCol = .int(1) } observeListChange(obs, "arrayCol", .insertion) { obj.arrayCol.append(obj) } observeListChange(obs, "arrayCol", .removal) { obj.arrayCol.removeAll() } observeSetChange(obs, "setCol") { obj.setCol.insert(obj) } observeSetChange(obs, "setCol") { obj.setCol.remove(obj) } observeChange(obs, "optIntCol", nil, 10) { obj.optIntCol = 10 } observeChange(obs, "optFloatCol", nil, 10.0) { obj.optFloatCol = 10 } observeChange(obs, "optDoubleCol", nil, 10.0) { obj.optDoubleCol = 10 } observeChange(obs, "optBoolCol", nil, true) { obj.optBoolCol = true } observeChange(obs, "optStringCol", nil, "abc") { obj.optStringCol = "abc" } observeChange(obs, "optBinaryCol", nil, data) { obj.optBinaryCol = data } observeChange(obs, "optDateCol", nil, date) { obj.optDateCol = date } observeChange(obs, "optDecimalCol", nil, decimal) { obj.optDecimalCol = decimal } observeChange(obs, "optObjectIdCol", nil, objectId) { obj.optObjectIdCol = objectId } observeChange(obs, "optUuidCol", nil, uuid) { obj.optUuidCol = uuid } observeChange(obs, "optIntCol", 10, nil) { obj.optIntCol = nil } observeChange(obs, "optFloatCol", 10.0, nil) { obj.optFloatCol = nil } observeChange(obs, "optDoubleCol", 10.0, nil) { obj.optDoubleCol = nil } observeChange(obs, "optBoolCol", true, nil) { obj.optBoolCol = nil } observeChange(obs, "optStringCol", "abc", nil) { obj.optStringCol = nil } observeChange(obs, "optBinaryCol", data, nil) { obj.optBinaryCol = nil } observeChange(obs, "optDateCol", date, nil) { obj.optDateCol = nil } observeChange(obs, "optDecimalCol", decimal, nil) { obj.optDecimalCol = nil } observeChange(obs, "optObjectIdCol", objectId, nil) { obj.optObjectIdCol = nil } observeChange(obs, "optUuidCol", uuid, nil) { obj.optUuidCol = nil } observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.append(true) } observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.append(10) } observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.append(10) } observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.append(10) } observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.append(10) } observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.append(10) } observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.append(10) } observeListChange(obs, "arrayString", .insertion) { obj.arrayString.append("abc") } observeListChange(obs, "arrayDecimal", .insertion) { obj.arrayDecimal.append(decimal) } observeListChange(obs, "arrayObjectId", .insertion) { obj.arrayObjectId.append(objectId) } observeListChange(obs, "arrayUuid", .insertion) { obj.arrayUuid.append(uuid) } observeListChange(obs, "arrayAny", .insertion) { obj.arrayAny.append(.string("a")) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(true) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(10) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(10) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(10) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(10) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(10) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(10) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append("abc") } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(data) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(date) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.append(decimal) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.append(objectId) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.append(uuid) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(nil, at: 0) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(nil, at: 0) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(nil, at: 0) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert(nil, at: 0) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(nil, at: 0) } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(nil, at: 0) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.insert(nil, at: 0) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.insert(nil, at: 0) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.insert(nil, at: 0) } observeSetChange(obs, "setBool") { obj.setBool.insert(true) } observeSetChange(obs, "setInt8") { obj.setInt8.insert(10) } observeSetChange(obs, "setInt16") { obj.setInt16.insert(10) } observeSetChange(obs, "setInt32") { obj.setInt32.insert(10) } observeSetChange(obs, "setInt64") { obj.setInt64.insert(10) } observeSetChange(obs, "setFloat") { obj.setFloat.insert(10) } observeSetChange(obs, "setDouble") { obj.setDouble.insert(10) } observeSetChange(obs, "setString") { obj.setString.insert("abc") } observeSetChange(obs, "setDecimal") { obj.setDecimal.insert(decimal) } observeSetChange(obs, "setObjectId") { obj.setObjectId.insert(objectId) } observeSetChange(obs, "setUuid") { obj.setUuid.insert(uuid) } observeSetChange(obs, "setAny") { obj.setAny.insert(.none) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(true) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(10) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(10) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(10) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(10) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(10) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(10) } observeSetChange(obs, "setOptString") { obj.setOptString.insert("abc") } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(data) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(date) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(decimal) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(objectId) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.insert(uuid) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(nil) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(nil) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(nil) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(nil) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(nil) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(nil) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(nil) } observeSetChange(obs, "setOptString") { obj.setOptString.insert(nil) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(nil) } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(nil) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(nil) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(nil) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.insert(nil) } observeSetChange(obs, "mapBool") { obj.mapBool[""] = true } observeSetChange(obs, "mapInt8") { obj.mapInt8[""] = 10 } observeSetChange(obs, "mapInt16") { obj.mapInt16[""] = 10 } observeSetChange(obs, "mapInt32") { obj.mapInt32[""] = 10 } observeSetChange(obs, "mapInt64") { obj.mapInt64[""] = 10 } observeSetChange(obs, "mapFloat") { obj.mapFloat[""] = 10 } observeSetChange(obs, "mapDouble") { obj.mapDouble[""] = 10 } observeSetChange(obs, "mapString") { obj.mapString[""] = "abc" } observeSetChange(obs, "mapDecimal") { obj.mapDecimal[""] = decimal } observeSetChange(obs, "mapObjectId") { obj.mapObjectId[""] = objectId } observeSetChange(obs, "mapUuid") { obj.mapUuid[""] = uuid } observeSetChange(obs, "mapAny") { obj.mapAny[""] = AnyRealmValue.none } observeSetChange(obs, "mapOptBool") { obj.mapOptBool[""] = true } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8[""] = 10 } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16[""] = 10 } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32[""] = 10 } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64[""] = 10 } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat[""] = 10 } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble[""] = 10 } observeSetChange(obs, "mapOptString") { obj.mapOptString[""] = "abc" } observeSetChange(obs, "mapOptBinary") { obj.mapOptBinary[""] = data } observeSetChange(obs, "mapOptDate") { obj.mapOptDate[""] = date } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal[""] = decimal } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId[""] = objectId } observeSetChange(obs, "mapOptUuid") { obj.mapOptUuid[""] = uuid } observeSetChange(obs, "mapOptBool") { obj.mapOptBool[""] = nil } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8[""] = nil } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16[""] = nil } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32[""] = nil } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64[""] = nil } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat[""] = nil } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble[""] = nil } observeSetChange(obs, "mapOptString") { obj.mapOptString[""] = nil } observeSetChange(obs, "mapOptDate") { obj.mapOptDate[""] = nil } observeSetChange(obs, "mapOptBinary") { obj.mapOptBinary[""] = nil } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal[""] = nil } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId[""] = nil } observeSetChange(obs, "mapOptUuid") { obj.mapOptUuid[""] = nil } obj.arrayInt32.removeAll() observeCompoundListChange(obj, obs, "arrayInt32", [1], deletions: [], insertions: [0]) observeCompoundListChange(obj, obs, "arrayInt32", [1], deletions: [0], insertions: [0]) observeCompoundListChange(obj, obs, "arrayInt32", [1, 2, 3], deletions: [0], insertions: [0, 1, 2]) observeCompoundListChange(obj, obs, "arrayInt32", [], deletions: [0, 1, 2], insertions: []) observeCompoundListChange(obj, obs, "arrayInt32", [], deletions: [], insertions: []) if obs.realm == nil { return } observeChange(obs, "invalidated", false, true) { self.realm.delete(obj) } let (obj2, obs2) = getObject(ModernAllTypesObject()) observeChange(obs2, "arrayCol.invalidated", false, true) { self.realm.delete(obj2) } let (obj3, obs3) = getObject(ModernAllTypesObject()) observeChange(obs3, "setCol.invalidated", false, true) { self.realm.delete(obj3) } let (obj4, obs4) = getObject(ModernAllTypesObject()) observeChange(obs4, "mapAny.invalidated", false, true) { self.realm.delete(obj4) } } func testCollectionInMixedKVO() { let (obj, obs) = getObject(ModernAllTypesObject()) observeSetChange(obs, "anyCol") { obj.anyCol = AnyRealmValue.fromDictionary([ "key1": .int(1234)]) } observeSetChange(obs, "anyCol") { obj.anyCol.dictionaryValue?["key1"] = .string("hello") } observeSetChange(obs, "anyCol") { obj.anyCol.dictionaryValue?["key1"] = nil } observeSetChange(obs, "anyCol") { obj.anyCol = AnyRealmValue.fromArray([ .int(1234)]) } observeSetChange(obs, "anyCol") { obj.anyCol.listValue?[0] = .float(123.456) } observeSetChange(obs, "anyCol") { obj.anyCol.listValue?.append(.bool(true)) } observeSetChange(obs, "anyCol") { obj.anyCol.listValue?.insert(.date(Date()), at: 1) } observeSetChange(obs, "anyCol") { obj.anyCol.listValue?.remove(at: 0) } } func testReadSharedSchemaFromObservedObject() { let obj = ModernAllTypesObject() obj.addObserver(self, forKeyPath: "boolCol", options: [.old, .new], context: nil) XCTAssertEqual(type(of: obj).sharedSchema(), ModernAllTypesObject.sharedSchema()) obj.removeObserver(self, forKeyPath: "boolCol") } } class ModernKVOPersistedTests: ModernKVOTests { override func getObject(_ obj: ModernAllTypesObject) -> (ModernAllTypesObject, ModernAllTypesObject) { realm.add(obj) return (obj, obj) } } class ModernKVOMultipleAccessorsTests: ModernKVOTests { override func getObject(_ obj: ModernAllTypesObject) -> (ModernAllTypesObject, ModernAllTypesObject) { realm.add(obj) return (obj, realm.object(ofType: ModernAllTypesObject.self, forPrimaryKey: obj.pk)!) } } ================================================ FILE: RealmSwift/Tests/ModernObjectAccessorTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import Realm.Private import RealmSwift import Foundation class ModernObjectAccessorTests: TestCase { let data = "b".data(using: .utf8, allowLossyConversion: false)! let date = Date(timeIntervalSinceReferenceDate: 2) let oid1 = ObjectId("1234567890ab1234567890ab") let oid2 = ObjectId("abcdef123456abcdef123456") let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" let uuid = UUID() func setAndTestAllPropertiesViaNormalAccess(_ object: ModernAllTypesObject) { func test(_ keyPath: ReferenceWritableKeyPath, _ values: T...) { for value in values { object[keyPath: keyPath] = value XCTAssertEqual(object[keyPath: keyPath], value) } } test(\.boolCol, true, false) test(\.intCol, -1, 0, 1) test(\.int8Col, -1, 0, 1) test(\.int16Col, -1, 0, 1) test(\.int32Col, -1, 0, 1) test(\.int64Col, -1, 0, 1) test(\.floatCol, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2) test(\.doubleCol, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217) test(\.stringCol, "", utf8TestString) test(\.binaryCol, data) test(\.dateCol, date) test(\.decimalCol, "inf", 1, 0, "0", -1, "-inf") test(\.objectIdCol, oid1, oid2) test(\.uuidCol, uuid) test(\.objectCol, ModernAllTypesObject(), nil) test(\.intEnumCol, .value1, .value2) test(\.stringEnumCol, .value1, .value2) test(\.optBoolCol, true, false, nil) test(\.optIntCol, Int.min, 0, Int.max, nil) test(\.optInt8Col, Int8.min, 0, Int8.max, nil) test(\.optInt16Col, Int16.min, 0, Int16.max, nil) test(\.optInt32Col, Int32.min, 0, Int32.max, nil) test(\.optInt64Col, Int64.min, 0, Int64.max, nil) test(\.optFloatCol, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2, nil) test(\.optDoubleCol, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217, nil) test(\.optStringCol, "", utf8TestString, nil) test(\.optBinaryCol, data, nil) test(\.optDateCol, date, nil) test(\.optDecimalCol, "inf", 1, 0, "0", -1, "-inf", nil) test(\.optObjectIdCol, oid1, oid2, nil) test(\.optUuidCol, uuid, nil) test(\.optIntEnumCol, .value1, .value2, nil) test(\.optStringEnumCol, .value1, .value2, nil) test(\.anyCol, .none, .int(1), .bool(false), .float(2.2), .double(3.3), .string("str"), .data(data), .date(date), .object(ModernAllTypesObject()), .objectId(oid1), .decimal128(5), .uuid(UUID())) object.decimalCol = "nan" XCTAssertTrue(object.decimalCol.isNaN) object.optDecimalCol = "nan" XCTAssertTrue(object.optDecimalCol!.isNaN) object["optIntEnumCol"] = 10 XCTAssertNil(object.optIntEnumCol) object.objectCol = ModernAllTypesObject() if object.realm == nil { XCTAssertEqual(object.objectCol!.linkingObjects.count, 0) } else { XCTAssertEqual(object.objectCol!.linkingObjects.count, 1) XCTAssertEqual(object.objectCol!.linkingObjects[0], object) } } func setAndTestAllPropertiesViaSubscript(_ object: ModernAllTypesObject) { func testNoConversion(_ keyPath: String, _ type: T.Type, _ values: T...) { for value in values { object[keyPath] = value XCTAssertEqual(object[keyPath] as! T, value) } for value in values { object.setValue(value, forKey: keyPath) XCTAssertEqual(object.value(forKey: keyPath) as! T, value) } } testNoConversion("boolCol", Bool.self, true, false) testNoConversion("intCol", Int.self, -1, 0, 1) testNoConversion("int8Col", Int8.self, -1, 0, 1) testNoConversion("int16Col", Int16.self, -1, 0, 1) testNoConversion("int32Col", Int32.self, -1, 0, 1) testNoConversion("int64Col", Int64.self, -1, 0, 1) testNoConversion("floatCol", Float.self, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2) testNoConversion("doubleCol", Double.self, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217) testNoConversion("stringCol", String.self, "", utf8TestString) testNoConversion("binaryCol", Data.self, data) testNoConversion("dateCol", Date.self, date) testNoConversion("decimalCol", Decimal128.self, "inf", 1, 0, "0", -1, "-inf") testNoConversion("objectIdCol", ObjectId.self, oid1, oid2) testNoConversion("uuidCol", UUID.self, uuid) testNoConversion("objectCol", ModernAllTypesObject?.self, ModernAllTypesObject(), nil) testNoConversion("optBoolCol", Bool?.self, true, false, nil) testNoConversion("optIntCol", Int?.self, Int.min, 0, Int.max, nil) testNoConversion("optInt8Col", Int8?.self, Int8.min, 0, Int8.max, nil) testNoConversion("optInt16Col", Int16?.self, Int16.min, 0, Int16.max, nil) testNoConversion("optInt32Col", Int32?.self, Int32.min, 0, Int32.max, nil) testNoConversion("optInt64Col", Int64?.self, Int64.min, 0, Int64.max, nil) testNoConversion("optFloatCol", Float?.self, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2, nil) testNoConversion("optDoubleCol", Double?.self, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217, nil) testNoConversion("optStringCol", String?.self, "", utf8TestString, nil) testNoConversion("optBinaryCol", Data?.self, data, nil) testNoConversion("optDateCol", Date?.self, date, nil) testNoConversion("optDecimalCol", Decimal128?.self, "inf", 1, 0, "0", -1, "-inf", nil) testNoConversion("optObjectIdCol", ObjectId?.self, oid1, oid2, nil) testNoConversion("optUuidCol", UUID?.self, uuid, nil) func testConversion(_ keyPath: String, _ value: Any, _ expected: Expected) { object[keyPath] = value XCTAssertEqual(object[keyPath] as! Expected, expected) object.setValue(value, forKey: keyPath) XCTAssertEqual(object.value(forKey: keyPath) as! Expected, expected) } testConversion("decimalCol", 1, 1 as Decimal128) testConversion("decimalCol", 2.2 as Float, Decimal128(value: 2.2 as Float)) testConversion("decimalCol", 3.3 as Double, Decimal128(value: 3.3 as Double)) testConversion("decimalCol", "4.4", 4.4 as Decimal128) testConversion("decimalCol", Decimal(5.5), 5.5 as Decimal128) testConversion("intEnumCol", ModernIntEnum.value1, ModernIntEnum.value1.rawValue) testConversion("intEnumCol", ModernIntEnum.value2, ModernIntEnum.value2.rawValue) testConversion("stringEnumCol", ModernStringEnum.value1, ModernStringEnum.value1.rawValue) testConversion("stringEnumCol", ModernStringEnum.value2, ModernStringEnum.value2.rawValue) testConversion("optDecimalCol", 1, 1 as Decimal128) testConversion("optDecimalCol", 2.2 as Float, Decimal128(value: 2.2 as Float)) testConversion("optDecimalCol", 3.3 as Double, Decimal128(value: 3.3 as Double)) testConversion("optDecimalCol", "4.4", 4.4 as Decimal128) testConversion("optDecimalCol", Decimal(5.5), 5.5 as Decimal128) testConversion("optDecimalCol", NSNull(), nil as Decimal128?) testConversion("optIntEnumCol", ModernIntEnum.value2, ModernIntEnum.value2.rawValue) testConversion("optIntEnumCol", nil as ModernIntEnum? as Any, nil as ModernIntEnum?) testConversion("optStringEnumCol", ModernStringEnum.value1, ModernStringEnum.value1.rawValue) testConversion("optStringEnumCol", nil as ModernStringEnum? as Any, nil as ModernStringEnum?) let obj = ModernAllTypesObject() testConversion("anyCol", AnyRealmValue.int(1), 1) testConversion("anyCol", AnyRealmValue.bool(false), false) testConversion("anyCol", AnyRealmValue.float(2.2), 2.2 as Float) testConversion("anyCol", AnyRealmValue.double(3.3), 3.3) testConversion("anyCol", AnyRealmValue.string("str"), "str") testConversion("anyCol", AnyRealmValue.data(data), data) testConversion("anyCol", AnyRealmValue.date(date), date) testConversion("anyCol", AnyRealmValue.object(obj), obj) testConversion("anyCol", AnyRealmValue.objectId(oid1), oid1) testConversion("anyCol", AnyRealmValue.decimal128(5), Decimal128(5)) testConversion("anyCol", AnyRealmValue.uuid(uuid), uuid) object["anyCol"] = AnyRealmValue.none if case Optional.none = object["anyCol"] { } else { XCTFail("\(String(describing: object["anyCol"])) should be nil") } object.setValue(AnyRealmValue.none, forKey: "anyCol") if case Optional.none = object["anyCol"] { } else { XCTFail("\(String(describing: object["anyCol"])) should be nil") } object["decimalCol"] = Decimal128("nan") XCTAssertTrue((object["decimalCol"] as! Decimal128).isNaN) object["optDecimalCol"] = Decimal128("nan") XCTAssertTrue((object["optDecimalCol"] as! Decimal128).isNaN) object["optIntEnumCol"] = 10 XCTAssertNil(object["optIntEnumCol"]) object.objectCol = ModernAllTypesObject() let linkingObjects = (object["objectCol"]! as! ModernAllTypesObject)["linkingObjects"] as! LinkingObjects if object.realm == nil { XCTAssertEqual(linkingObjects.count, 0) } else { XCTAssertEqual(linkingObjects.count, 1) XCTAssertEqual(linkingObjects[0], object) } } func get(_ object: ObjectBase, _ propertyName: String) -> Any { let prop = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == propertyName }! return prop.swiftAccessor!.get(prop, on: object) } func set(_ object: ObjectBase, _ propertyName: String, _ value: Any) { let prop = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == propertyName }! prop.swiftAccessor!.set(prop, on: object, to: value) } func assertEqual(_ lhs: Any, _ rhs: T) { if rhs is NSNull { XCTAssertTrue(lhs is NSNull) } else if lhs is NSNull { XCTAssertEqual((T.self as! ExpressibleByNilLiteral.Type).init(nilLiteral: ()) as! T, rhs) } else { XCTAssertEqual(lhs as! T, rhs) } } func setAndTestAllPropertiesViaAccessor(_ object: ModernAllTypesObject) { func testNoConversion(_ keyPath: String, _ type: T.Type, _ values: T...) { for value in values { set(object, keyPath, value) assertEqual(get(object, keyPath), value) } } testNoConversion("boolCol", Bool.self, true, false) testNoConversion("intCol", Int.self, -1, 0, 1) testNoConversion("int8Col", Int8.self, -1, 0, 1) testNoConversion("int16Col", Int16.self, -1, 0, 1) testNoConversion("int32Col", Int32.self, -1, 0, 1) testNoConversion("int64Col", Int64.self, -1, 0, 1) testNoConversion("floatCol", Float.self, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2) testNoConversion("doubleCol", Double.self, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217) testNoConversion("stringCol", String.self, "", utf8TestString) testNoConversion("binaryCol", Data.self, data) testNoConversion("dateCol", Date.self, date) testNoConversion("decimalCol", Decimal128.self, "inf", 1, 0, "0", -1, "-inf") testNoConversion("objectIdCol", ObjectId.self, oid1, oid2) testNoConversion("uuidCol", UUID.self, uuid) testNoConversion("objectCol", ModernAllTypesObject?.self, ModernAllTypesObject(), nil) testNoConversion("optBoolCol", Bool?.self, true, false, nil) testNoConversion("optIntCol", Int?.self, Int.min, 0, Int.max, nil) testNoConversion("optInt8Col", Int8?.self, Int8.min, 0, Int8.max, nil) testNoConversion("optInt16Col", Int16?.self, Int16.min, 0, Int16.max, nil) testNoConversion("optInt32Col", Int32?.self, Int32.min, 0, Int32.max, nil) testNoConversion("optInt64Col", Int64?.self, Int64.min, 0, Int64.max, nil) testNoConversion("optFloatCol", Float?.self, -Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, 20, 20.2, nil) testNoConversion("optDoubleCol", Double?.self, -Double.greatestFiniteMagnitude, Double.greatestFiniteMagnitude, 20, 20.2, 16777217, nil) testNoConversion("optStringCol", String?.self, "", utf8TestString, nil) testNoConversion("optBinaryCol", Data?.self, data, nil) testNoConversion("optDateCol", Date?.self, date, nil) testNoConversion("optDecimalCol", Decimal128?.self, "inf", 1, 0, "0", -1, "-inf", nil) testNoConversion("optObjectIdCol", ObjectId?.self, oid1, oid2, nil) testNoConversion("optUuidCol", UUID?.self, uuid, nil) func testAny(_ value: T) { set(object, "anyCol", value) XCTAssertEqual(get(object, "anyCol") as! T, value) } testAny(NSNull()) testAny(1) testAny(false) testAny(2.2 as Float) testAny(3.3) testAny("str") testAny(data) testAny(date) testAny(ModernAllTypesObject()) testAny(oid1) testAny(Decimal128(5)) testAny(uuid) func testConversion(_ keyPath: String, _ value: Any, _ expected: Expected) { set(object, keyPath, value) assertEqual(get(object, keyPath), expected) } testConversion("decimalCol", 1, 1 as Decimal128) testConversion("decimalCol", 2.2 as Float, Decimal128(value: 2.2 as Float)) testConversion("decimalCol", 3.3 as Double, Decimal128(value: 3.3 as Double)) testConversion("decimalCol", "4.4", 4.4 as Decimal128) testConversion("decimalCol", Decimal(5.5), 5.5 as Decimal128) testConversion("intEnumCol", ModernIntEnum.value1, ModernIntEnum.value1.rawValue) testConversion("intEnumCol", ModernIntEnum.value2, ModernIntEnum.value2.rawValue) testConversion("stringEnumCol", ModernStringEnum.value1, ModernStringEnum.value1.rawValue) testConversion("stringEnumCol", ModernStringEnum.value2, ModernStringEnum.value2.rawValue) testConversion("optDecimalCol", 1, 1 as Decimal128) testConversion("optDecimalCol", 2.2 as Float, Decimal128(value: 2.2 as Float)) testConversion("optDecimalCol", 3.3 as Double, Decimal128(value: 3.3 as Double)) testConversion("optDecimalCol", "4.4", 4.4 as Decimal128) testConversion("optDecimalCol", Decimal(5.5), 5.5 as Decimal128) testConversion("optDecimalCol", NSNull(), nil as Decimal128?) testConversion("optIntEnumCol", ModernIntEnum.value2, ModernIntEnum.value2.rawValue) testConversion("optIntEnumCol", nil as ModernIntEnum? as Any, nil as ModernIntEnum?) testConversion("optStringEnumCol", ModernStringEnum.value1, ModernStringEnum.value1.rawValue) testConversion("optStringEnumCol", nil as ModernStringEnum? as Any, nil as ModernStringEnum?) let obj = ModernAllTypesObject() testConversion("anyCol", AnyRealmValue.none, NSNull()) testConversion("anyCol", AnyRealmValue.int(1), 1) testConversion("anyCol", AnyRealmValue.bool(false), false) testConversion("anyCol", AnyRealmValue.float(2.2), 2.2 as Float) testConversion("anyCol", AnyRealmValue.double(3.3), 3.3) testConversion("anyCol", AnyRealmValue.string("str"), "str") testConversion("anyCol", AnyRealmValue.data(data), data) testConversion("anyCol", AnyRealmValue.date(date), date) testConversion("anyCol", AnyRealmValue.object(obj), obj) testConversion("anyCol", AnyRealmValue.objectId(oid1), oid1) testConversion("anyCol", AnyRealmValue.decimal128(5), Decimal128(5)) testConversion("anyCol", AnyRealmValue.uuid(uuid), uuid) object["decimalCol"] = Decimal128("nan") XCTAssertTrue((object["decimalCol"] as! Decimal128).isNaN) object["optDecimalCol"] = Decimal128("nan") XCTAssertTrue((object["optDecimalCol"] as! Decimal128).isNaN) } func setAndTestList(_ object: ModernAllTypesObject) { func test(_ name: String, _ keyPath: ReferenceWritableKeyPath>, _ values: T...) { // Getter should return correct type XCTAssertTrue(get(object, name) is List) // Getter should return the same object each time XCTAssertTrue(get(object, name) as AnyObject === get(object, name) as AnyObject) // Which should be the same object as is obtained from reading the property directly let list = object[keyPath: keyPath] XCTAssertTrue(get(object, name) as! List === list) // Assigning a list to the property should copy the contents of the list, and not set // the property pointing to the assigned list let list2 = List() list2.append(objectsIn: values) object[keyPath: keyPath] = list2 XCTAssertEqual(Array(list), values) XCTAssertFalse(list2 === get(object, name) as AnyObject) // Self-assignment should be a no-op and not clear the list object[keyPath: keyPath] = object[keyPath: keyPath] XCTAssertEqual(Array(list), values) object.setValue(object.value(forKey: name), forKey: name) XCTAssertEqual(Array(list), values) // Setting via the accessor directly should do the same thing as assigning to the // property list.removeAll() set(object, name, list2) XCTAssertEqual(Array(list), values) XCTAssertFalse(list2 === get(object, name) as AnyObject) set(object, name, get(object, name)) XCTAssertEqual(Array(list), values) set(object, name, RLMDynamicGetByName(object, name)!) XCTAssertEqual(Array(list), values) // The accessor should accept any enumerable type and not just List, so we should be // able to assign an array directly list.removeAll() set(object, name, values) XCTAssertEqual(Array(list), values) XCTAssertTrue(get(object, name) as! List === list) // Assigning null to a List clears it set(object, name, NSNull()) XCTAssertEqual(list.count, 0) } test("arrayBool", \.arrayBool, false, true) test("arrayInt", \.arrayInt, Int.min, 0, Int.max) test("arrayInt8", \.arrayInt8, Int8.min, 0, Int8.max) test("arrayInt16", \.arrayInt16, Int16.min, 0, Int16.max) test("arrayInt32", \.arrayInt32, Int32.min, 0, Int32.max) test("arrayInt64", \.arrayInt64, Int64.min, 0, Int64.max) test("arrayFloat", \.arrayFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude) test("arrayDouble", \.arrayDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude) test("arrayString", \.arrayString, "a", "b", "c") test("arrayBinary", \.arrayBinary, data) test("arrayDate", \.arrayDate, date) test("arrayDecimal", \.arrayDecimal, Decimal128(1), Decimal128(2)) test("arrayObjectId", \.arrayObjectId, oid1, oid2) test("arrayUuid", \.arrayUuid, uuid) test("arrayOptBool", \.arrayOptBool, false, true, nil) test("arrayOptInt", \.arrayOptInt, Int.min, 0, Int.max, nil) test("arrayOptInt8", \.arrayOptInt8, Int8.min, 0, Int8.max, nil) test("arrayOptInt16", \.arrayOptInt16, Int16.min, 0, Int16.max, nil) test("arrayOptInt32", \.arrayOptInt32, Int32.min, 0, Int32.max, nil) test("arrayOptInt64", \.arrayOptInt64, Int64.min, 0, Int64.max, nil) test("arrayOptFloat", \.arrayOptFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude, nil) test("arrayOptDouble", \.arrayOptDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude, nil) test("arrayOptString", \.arrayOptString, "a", "b", "c", nil) test("arrayOptBinary", \.arrayOptBinary, data, nil) test("arrayOptDate", \.arrayOptDate, date, nil) test("arrayOptDecimal", \.arrayOptDecimal, Decimal128(1), Decimal128(2), nil) test("arrayOptObjectId", \.arrayOptObjectId, oid1, oid2, nil) test("arrayOptUuid", \.arrayOptUuid, uuid, nil) let obj = ModernAllTypesObject() test("arrayAny", \.arrayAny, .none, .int(1), .bool(false), .float(2.2), .double(3.3), .string("str"), .data(data), .date(date), .object(obj), .objectId(oid1), .decimal128(5), .uuid(uuid)) } func assertSetEquals(_ set: MutableSet, _ expected: Array) { XCTAssertEqual(set.count, expected.count) XCTAssertEqual(Set(set), Set(expected)) } func setAndTestSet(_ object: ModernAllTypesObject) { func test(_ name: String, _ keyPath: ReferenceWritableKeyPath>, _ values: T...) { // Getter should return correct type XCTAssertTrue(get(object, name) is MutableSet) // Getter should return the same object each time XCTAssertTrue(get(object, name) as AnyObject === get(object, name) as AnyObject) // Which should be the same object as is obtained from reading the property directly let collection = object[keyPath: keyPath] XCTAssertTrue(get(object, name) as! MutableSet === collection) // Assigning a collection to the property should copy the contents of the list, and not set // the property pointing to the assigned collection let collection2 = MutableSet() collection2.insert(objectsIn: values) object[keyPath: keyPath] = collection2 assertSetEquals(collection, values) XCTAssertFalse(collection2 === get(object, name) as AnyObject) // Self-assignment should be a no-op and not clear the collection object[keyPath: keyPath] = object[keyPath: keyPath] assertSetEquals(collection, values) object.setValue(object.value(forKey: name), forKey: name) assertSetEquals(collection, values) // Setting via the accessor directly should do the same thing as assigning to the // property collection.removeAll() set(object, name, collection2) assertSetEquals(collection, values) XCTAssertFalse(collection2 === get(object, name) as AnyObject) set(object, name, get(object, name)) assertSetEquals(collection, values) // The accessor should accept any enumerable type and not just Set, so we should be // able to assign an set directly collection.removeAll() set(object, name, values) assertSetEquals(collection, values) XCTAssertTrue(get(object, name) as! MutableSet === collection) // Assigning null to a Set clears it set(object, name, NSNull()) XCTAssertEqual(collection.count, 0) } test("setBool", \.setBool, false, true) test("setInt", \.setInt, Int.min, 0, Int.max) test("setInt8", \.setInt8, Int8.min, 0, Int8.max) test("setInt16", \.setInt16, Int16.min, 0, Int16.max) test("setInt32", \.setInt32, Int32.min, 0, Int32.max) test("setInt64", \.setInt64, Int64.min, 0, Int64.max) test("setFloat", \.setFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude) test("setDouble", \.setDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude) test("setString", \.setString, "a", "b", "c") test("setBinary", \.setBinary, data) test("setDate", \.setDate, date) test("setDecimal", \.setDecimal, Decimal128(1), Decimal128(2)) test("setObjectId", \.setObjectId, oid1, oid2) test("setUuid", \.setUuid, uuid) test("setOptBool", \.setOptBool, false, true, nil) test("setOptInt", \.setOptInt, Int.min, 0, Int.max, nil) test("setOptInt8", \.setOptInt8, Int8.min, 0, Int8.max, nil) test("setOptInt16", \.setOptInt16, Int16.min, 0, Int16.max, nil) test("setOptInt32", \.setOptInt32, Int32.min, 0, Int32.max, nil) test("setOptInt64", \.setOptInt64, Int64.min, 0, Int64.max, nil) test("setOptFloat", \.setOptFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude, nil) test("setOptDouble", \.setOptDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude, nil) test("setOptString", \.setOptString, "a", "b", "c", nil) test("setOptBinary", \.setOptBinary, data, nil) test("setOptDate", \.setOptDate, date, nil) test("setOptDecimal", \.setOptDecimal, Decimal128(1), Decimal128(2), nil) test("setOptObjectId", \.setOptObjectId, oid1, oid2, nil) test("setOptUuid", \.setOptUuid, uuid, nil) let obj = ModernAllTypesObject() test("setAny", \.setAny, .none, .int(1), .bool(false), .float(2.2), .double(3.3), .string("str"), .data(data), .date(date), .object(obj), .objectId(oid1), .decimal128(5), .uuid(uuid)) } func assertMapEquals(_ map: Map, _ expected: Array) { XCTAssertEqual(map.count, expected.count) for (i, value) in expected.enumerated() { XCTAssertEqual(map["\(i)"], value) } } func setAndTestMap(_ object: ModernAllTypesObject) { func test(_ name: String, _ keyPath: ReferenceWritableKeyPath>, _ values: T...) { var dictValues = [String: T]() for (i, value) in values.enumerated() { dictValues["\(i)"] = value } // Getter should return correct type XCTAssertTrue(get(object, name) is Map) // Getter should return the same object each time XCTAssertTrue(get(object, name) as AnyObject === get(object, name) as AnyObject) // Which should be the same object as is obtained from reading the property directly let collection = object[keyPath: keyPath] XCTAssertTrue(get(object, name) as! Map === collection) // Assigning a collection to the property should copy the contents of the list, and not set // the property pointing to the assigned collection let collection2 = Map() collection2.merge(dictValues) { $1 } object[keyPath: keyPath] = collection2 assertMapEquals(collection, values) XCTAssertFalse(collection2 === get(object, name) as AnyObject) // Self-assignment should be a no-op and not clear the collection object[keyPath: keyPath] = object[keyPath: keyPath] assertMapEquals(collection, values) object.setValue(object.value(forKey: name), forKey: name) assertMapEquals(collection, values) // setting via the accessor directly should do the same thing as assigning to the // property collection.removeAll() set(object, name, collection2) assertMapEquals(collection, values) XCTAssertFalse(collection2 === get(object, name) as AnyObject) set(object, name, get(object, name)) assertMapEquals(collection, values) // The accessor should accept any enumerable type and not just map, so we should be // able to assign a dictionary directly collection.removeAll() set(object, name, dictValues) assertMapEquals(collection, values) XCTAssertTrue(get(object, name) as! Map === collection) // Assigning null to a map clears it set(object, name, NSNull()) XCTAssertEqual(collection.count, 0) } test("mapBool", \.mapBool, false, true) test("mapInt", \.mapInt, Int.min, 0, Int.max) test("mapInt8", \.mapInt8, Int8.min, 0, Int8.max) test("mapInt16", \.mapInt16, Int16.min, 0, Int16.max) test("mapInt32", \.mapInt32, Int32.min, 0, Int32.max) test("mapInt64", \.mapInt64, Int64.min, 0, Int64.max) test("mapFloat", \.mapFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude) test("mapDouble", \.mapDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude) test("mapString", \.mapString, "a", "b", "c") test("mapBinary", \.mapBinary, data) test("mapDate", \.mapDate, date) test("mapDecimal", \.mapDecimal, Decimal128(1), Decimal128(2)) test("mapObjectId", \.mapObjectId, oid1, oid2) test("mapUuid", \.mapUuid, uuid) test("mapOptBool", \.mapOptBool, false, true, nil) test("mapOptInt", \.mapOptInt, Int.min, 0, Int.max, nil) test("mapOptInt8", \.mapOptInt8, Int8.min, 0, Int8.max, nil) test("mapOptInt16", \.mapOptInt16, Int16.min, 0, Int16.max, nil) test("mapOptInt32", \.mapOptInt32, Int32.min, 0, Int32.max, nil) test("mapOptInt64", \.mapOptInt64, Int64.min, 0, Int64.max, nil) test("mapOptFloat", \.mapOptFloat, -Float.greatestFiniteMagnitude, 0, Float.greatestFiniteMagnitude, nil) test("mapOptDouble", \.mapOptDouble, -Double.greatestFiniteMagnitude, 0, Double.greatestFiniteMagnitude, nil) test("mapOptString", \.mapOptString, "a", "b", "c", nil) test("mapOptBinary", \.mapOptBinary, data, nil) test("mapOptDate", \.mapOptDate, date, nil) test("mapOptDecimal", \.mapOptDecimal, Decimal128(1), Decimal128(2), nil) test("mapOptObjectId", \.mapOptObjectId, oid1, oid2, nil) test("mapOptUuid", \.mapOptUuid, uuid, nil) let obj = ModernAllTypesObject() test("mapAny", \.mapAny, .none, .int(1), .bool(false), .float(2.2), .double(3.3), .string("str"), .data(data), .date(date), .object(obj), .objectId(oid1), .decimal128(5), .uuid(uuid)) } func setAndTestFailableCustomMappings(_ obj: FailableCustomObject) { obj["int"] = 2 XCTAssertEqual(obj["int"] as! Int, 2) XCTAssertEqual(get(obj, "int") as! Int, 2) XCTAssertEqual(obj.int.persistableValue, 2) if obj.realm == nil { // Unmanaged objects convert to the mapped type on set as the value // is stored as the wrapped type let reason = "Could not convert value '1' to type 'IntFailableWrapper'." assertThrows(obj["int"] = 1, reason: reason) assertThrows(set(obj, "int", 1), reason: reason) } else { // Managed objects convert on read obj["int"] = 1 let reason = "Failed to convert persisted value '1' to type 'IntFailableWrapper' in a non-optional context." assertThrows(obj["int"] as! Int, reason: reason) assertThrows(get(obj, "int") as! Int, reason: reason) assertThrows(obj.int, reason: reason) } obj["optInt"] = 2 XCTAssertEqual(obj["optInt"] as! Int, 2) XCTAssertEqual(get(obj, "optInt") as! Int, 2) XCTAssertEqual(obj.optInt!.persistableValue, 2) obj["optInt"] = 1 XCTAssertNil(obj["optInt"]) XCTAssertTrue(get(obj, "optInt") is NSNull) XCTAssertNil(obj.optInt) } func testUnmanagedAccessors() { setAndTestAllPropertiesViaNormalAccess(ModernAllTypesObject()) setAndTestAllPropertiesViaSubscript(ModernAllTypesObject()) setAndTestAllPropertiesViaAccessor(ModernAllTypesObject()) setAndTestList(ModernAllTypesObject()) setAndTestSet(ModernAllTypesObject()) setAndTestMap(ModernAllTypesObject()) setAndTestFailableCustomMappings(FailableCustomObject()) } func testManagedAccessorsReadFromRealm() { let realm = try! Realm() realm.beginWrite() let object = realm.create(ModernAllTypesObject.self) setAndTestAllPropertiesViaNormalAccess(object) setAndTestAllPropertiesViaSubscript(object) setAndTestAllPropertiesViaAccessor(object) setAndTestList(object) setAndTestSet(object) setAndTestMap(object) setAndTestFailableCustomMappings(realm.create(FailableCustomObject.self)) realm.cancelWrite() } func testManagedAccessorsAddedToRealm() { let realm = try! Realm() realm.beginWrite() let object = ModernAllTypesObject() realm.add(object) setAndTestAllPropertiesViaNormalAccess(object) setAndTestAllPropertiesViaSubscript(object) setAndTestAllPropertiesViaAccessor(object) setAndTestList(object) setAndTestSet(object) setAndTestMap(object) realm.cancelWrite() } func testThreadChecking() { let realm = try! Realm() nonisolated(unsafe) var obj: ModernAllTypesObject! try! realm.write { obj = realm.create(ModernAllTypesObject.self) // Create the lazily-initialized List to test the cached codepath obj.arrayInt.removeAll() obj.arrayInt8.removeAll() } dispatchSyncBackground { unsafeSelf in unsafeSelf.assertThrows(_ = obj.intCol, reason: "incorrect thread") unsafeSelf.assertThrows(obj.arrayInt.removeAll(), reason: "incorrect thread") unsafeSelf.assertThrows(obj.int8Col = 5, reason: "incorrect thread") unsafeSelf.assertThrows(obj.arrayInt8 = List(), reason: "incorrect thread") } } func testInvalidationChecking() { let realm = try! Realm() var obj: ModernAllTypesObject! try! realm.write { obj = realm.create(ModernAllTypesObject.self) // Create the lazily-initialized List to test the cached codepath obj.arrayInt.removeAll() obj.arrayInt8.removeAll() } realm.invalidate() self.assertThrows(_ = obj.intCol, reason: "invalidated") self.assertThrows(obj.arrayInt.removeAll(), reason: "invalidated") self.assertThrows(obj.int8Col = 5, reason: "invalidated") self.assertThrows(obj.arrayInt8 = List(), reason: "invalidated") } func testObjectWithArcMethodFamilies() { let obj = ObjectWithArcMethodCategoryNames() obj.allocValue = "a" obj.initValue = "b" obj.copyValue = "c" obj.mutableCopyValue = "d" obj.newValue = "e" XCTAssertEqual(obj.allocValue, "a") XCTAssertEqual(obj.initValue, "b") XCTAssertEqual(obj.copyValue, "c") XCTAssertEqual(obj.mutableCopyValue, "d") XCTAssertEqual(obj.newValue, "e") } func testReadInvalidEnumValue() { let realm = try! Realm() realm.beginWrite() let obj = realm.create(ModernAllTypesObject.self) obj["optIntEnumCol"] = 10 XCTAssertNil(obj.optIntEnumCol) obj["optStringEnumCol"] = "10" XCTAssertNil(obj.optStringEnumCol) let collectionsObj = realm.create(ModernCollectionsOfEnums.self) (collectionsObj.listIntOpt._rlmCollection as! RLMArray).add(NSNumber(value: 10)) XCTAssertNil(collectionsObj.listIntOpt[0]) (collectionsObj.setStringOpt._rlmCollection as! RLMSet).add("abc" as AnyObject) XCTAssertNil(collectionsObj.setStringOpt[0]) (collectionsObj.mapStringOpt._rlmCollection as! RLMDictionary).setObject("abc" as AnyObject, forKey: "key") XCTAssertEqual(collectionsObj.mapStringOpt["key"], EnumString??.some(nil)) realm.cancelWrite() } } ================================================ FILE: RealmSwift/Tests/ModernObjectCreationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift import Realm.Private class ModernObjectCreationTests: TestCase { var values: [String: Any]! override func setUp() { values = [ "boolCol": true, "intCol": 10, "int8Col": 11 as Int8, "int16Col": 12 as Int16, "int32Col": 13 as Int32, "int64Col": 14 as Int64, "floatCol": 15 as Float, "doubleCol": 16 as Double, "stringCol": "a", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 17), "decimalCol": 18 as Decimal128, "objectIdCol": ObjectId.generate(), "objectCol": ModernAllTypesObject(value: ["intCol": 1]), "arrayCol": [ ModernAllTypesObject(value: ["intCol": 2]), ModernAllTypesObject(value: ["intCol": 3]) ], "setCol": [ ModernAllTypesObject(value: ["intCol": 4]), ModernAllTypesObject(value: ["intCol": 5]), ModernAllTypesObject(value: ["intCol": 6]) ], "anyCol": AnyRealmValue.int(20), "uuidCol": UUID(), "intEnumCol": ModernIntEnum.value2, "stringEnumCol": ModernStringEnum.value3, "optBoolCol": false, "optIntCol": 30, "optInt8Col": 31 as Int8, "optInt16Col": 32 as Int16, "optInt32Col": 33 as Int32, "optInt64Col": 34 as Int64, "optFloatCol": 35 as Float, "optDoubleCol": 36 as Double, "optStringCol": "c", "optBinaryCol": Data("d".utf8), "optDateCol": Date(timeIntervalSince1970: 37), "optDecimalCol": 38 as Decimal128, "optObjectIdCol": ObjectId.generate(), "optUuidCol": UUID(), "optIntEnumCol": ModernIntEnum.value1, "optStringEnumCol": ModernStringEnum.value1, "arrayBool": [true, false] as [Bool], "arrayInt": [1, 1, 2, 3] as [Int], "arrayInt8": [1, 2, 3, 1] as [Int8], "arrayInt16": [1, 2, 3, 1] as [Int16], "arrayInt32": [1, 2, 3, 1] as [Int32], "arrayInt64": [1, 2, 3, 1] as [Int64], "arrayFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float], "arrayDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double], "arrayString": ["a", "b", "c"] as [String], "arrayBinary": [Data("a".utf8)] as [Data], "arrayDate": [Date(), Date()] as [Date], "arrayDecimal": [1 as Decimal128, 2 as Decimal128], "arrayObjectId": [ObjectId.generate(), ObjectId.generate()], "arrayAny": [.none, .int(1), .string("a"), .none] as [AnyRealmValue], "arrayUuid": [UUID(), UUID(), UUID()], "arrayOptBool": [true, false, nil] as [Bool?], "arrayOptInt": [1, 1, 2, 3, nil] as [Int?], "arrayOptInt8": [1, 2, 3, 1, nil] as [Int8?], "arrayOptInt16": [1, 2, 3, 1, nil] as [Int16?], "arrayOptInt32": [1, 2, 3, 1, nil] as [Int32?], "arrayOptInt64": [1, 2, 3, 1, nil] as [Int64?], "arrayOptFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float, nil], "arrayOptDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double, nil], "arrayOptString": ["a", "b", "c", nil], "arrayOptBinary": [Data("a".utf8), nil], "arrayOptDate": [Date(), Date(), nil], "arrayOptDecimal": [1 as Decimal128, 2 as Decimal128, nil], "arrayOptObjectId": [ObjectId.generate(), ObjectId.generate(), nil], "arrayOptUuid": [UUID(), UUID(), UUID(), nil], "setBool": [true, false] as [Bool], "setInt": [1, 1, 2, 3] as [Int], "setInt8": [1, 2, 3, 1] as [Int8], "setInt16": [1, 2, 3, 1] as [Int16], "setInt32": [1, 2, 3, 1] as [Int32], "setInt64": [1, 2, 3, 1] as [Int64], "setFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float], "setDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double], "setString": ["a", "b", "c"] as [String], "setBinary": [Data("a".utf8)] as [Data], "setDate": [Date(), Date()] as [Date], "setDecimal": [1 as Decimal128, 2 as Decimal128], "setObjectId": [ObjectId.generate(), ObjectId.generate()], "setAny": [.none, .int(1), .string("a"), .none] as [AnyRealmValue], "setUuid": [UUID(), UUID(), UUID()], "setOptBool": [true, false, nil] as [Bool?], "setOptInt": [1, 1, 2, 3, nil] as [Int?], "setOptInt8": [1, 2, 3, 1, nil] as [Int8?], "setOptInt16": [1, 2, 3, 1, nil] as [Int16?], "setOptInt32": [1, 2, 3, 1, nil] as [Int32?], "setOptInt64": [1, 2, 3, 1, nil] as [Int64?], "setOptFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float, nil], "setOptDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double, nil], "setOptString": ["a", "b", "c", nil], "setOptBinary": [Data("a".utf8), nil], "setOptDate": [Date(), Date(), nil], "setOptDecimal": [1 as Decimal128, 2 as Decimal128, nil], "setOptObjectId": [ObjectId.generate(), ObjectId.generate(), nil], "setOptUuid": [UUID(), UUID(), UUID(), nil], "mapBool": ["1": true, "2": false] as [String: Bool], "mapInt": ["1": 1, "2": 1, "3": 2, "4": 3] as [String: Int], "mapInt8": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int8], "mapInt16": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int16], "mapInt32": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int32], "mapInt64": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int64], "mapFloat": ["1": 1 as Float, "2": 2 as Float, "3": 3 as Float, "4": 1 as Float], "mapDouble": ["1": 1 as Double, "2": 2 as Double, "3": 3 as Double, "4": 1 as Double], "mapString": ["1": "a", "2": "b", "3": "c"] as [String: String], "mapBinary": ["1": Data("a".utf8)] as [String: Data], "mapDate": ["1": Date(), "2": Date()] as [String: Date], "mapDecimal": ["1": 1 as Decimal128, "2": 2 as Decimal128], "mapObjectId": ["1": ObjectId.generate(), "2": ObjectId.generate()], "mapAny": ["1": .none, "2": .int(1), "3": .string("a"), "4": .none] as [String: AnyRealmValue], "mapUuid": ["1": UUID(), "2": UUID(), "3": UUID()], "mapOptBool": ["1": true, "2": false, "3": nil] as [String: Bool?], "mapOptInt": ["1": 1, "2": 1, "3": 2, "4": 3, "5": nil] as [String: Int?], "mapOptInt8": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int8?], "mapOptInt16": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int16?], "mapOptInt32": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int32?], "mapOptInt64": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int64?], "mapOptFloat": ["1": 1 as Float, "2": 2 as Float, "3": 3 as Float, "4": 1 as Float, "5": nil], "mapOptDouble": ["1": 1 as Double, "2": 2 as Double, "3": 3 as Double, "4": 1 as Double, "5": nil], "mapOptString": ["1": "a", "2": "b", "3": "c", "4": nil], "mapOptBinary": ["1": Data("a".utf8), "2": nil], "mapOptDate": ["1": Date(), "2": Date(), "3": nil], "mapOptDecimal": ["1": 1 as Decimal128, "2": 2 as Decimal128, "3": nil], "mapOptObjectId": ["1": ObjectId.generate(), "2": ObjectId.generate(), "3": nil], "mapOptUuid": ["1": UUID(), "2": UUID(), "3": UUID(), "4": nil], ] super.setUp() } override func tearDown() { values = nil super.tearDown() } func nullValues() -> [String: Any] { return values.merging([ "objectCol": NSNull(), "anyCol": AnyRealmValue.none, "optBoolCol": NSNull(), "optIntCol": NSNull(), "optInt8Col": NSNull(), "optInt16Col": NSNull(), "optInt32Col": NSNull(), "optInt64Col": NSNull(), "optFloatCol": NSNull(), "optDoubleCol": NSNull(), "optStringCol": NSNull(), "optBinaryCol": NSNull(), "optDateCol": NSNull(), "optDecimalCol": NSNull(), "optObjectIdCol": NSNull(), "optUuidCol": NSNull(), "optIntEnumCol": NSNull(), "optStringEnumCol": NSNull(), "arrayAny": [AnyRealmValue.none], "arrayOptBool": [NSNull()], "arrayOptInt": [NSNull()], "arrayOptInt8": [NSNull()], "arrayOptInt16": [NSNull()], "arrayOptInt32": [NSNull()], "arrayOptInt64": [NSNull()], "arrayOptFloat": [NSNull()], "arrayOptDouble": [NSNull()], "arrayOptString": [NSNull()], "arrayOptBinary": [NSNull()], "arrayOptDate": [NSNull()], "arrayOptDecimal": [NSNull()], "arrayOptObjectId": [NSNull()], "arrayOptUuid": [NSNull()], "setAny": [AnyRealmValue.none], "setOptBool": [NSNull()], "setOptInt": [NSNull()], "setOptInt8": [NSNull()], "setOptInt16": [NSNull()], "setOptInt32": [NSNull()], "setOptInt64": [NSNull()], "setOptFloat": [NSNull()], "setOptDouble": [NSNull()], "setOptString": [NSNull()], "setOptBinary": [NSNull()], "setOptDate": [NSNull()], "setOptDecimal": [NSNull()], "setOptObjectId": [NSNull()], "setOptUuid": [NSNull()], "mapAny": ["1": AnyRealmValue.none], "mapOptBool": ["1": NSNull()], "mapOptInt": ["1": NSNull()], "mapOptInt8": ["1": NSNull()], "mapOptInt16": ["1": NSNull()], "mapOptInt32": ["1": NSNull()], "mapOptInt64": ["1": NSNull()], "mapOptFloat": ["1": NSNull()], "mapOptDouble": ["1": NSNull()], "mapOptString": ["1": NSNull()], "mapOptBinary": ["1": NSNull()], "mapOptDate": ["1": NSNull()], "mapOptDecimal": ["1": NSNull()], "mapOptObjectId": ["1": NSNull()], "mapOptUuid": ["1": NSNull()] ] as [String: Any]) { _, null in null } } func assertSetEquals(_ set: MutableSet, _ expected: Array) { XCTAssertEqual(set.count, Set(expected).count) XCTAssertEqual(Set(set), Set(expected)) } func assertEquivalent(_ actual: AnyRealmCollection, _ expected: Array, expectedShouldBeCopy: Bool) { XCTAssertEqual(actual.count, expected.count) for obj in expected { if expectedShouldBeCopy { XCTAssertTrue(actual.contains { $0.pk == obj.pk }) } else { XCTAssertTrue(actual.contains(obj)) } } } func assertMapEquals(_ actual: Map, _ expected: Dictionary) { XCTAssertEqual(actual.count, expected.count) for (key, value) in expected { XCTAssertEqual(actual[key], value) } } func verifyObject(_ obj: ModernAllTypesObject, expectedShouldBeCopy: Bool = true) { XCTAssertEqual(obj.boolCol, values["boolCol"] as! Bool) XCTAssertEqual(obj.intCol, values["intCol"] as! Int) XCTAssertEqual(obj.int8Col, values["int8Col"] as! Int8) XCTAssertEqual(obj.int16Col, values["int16Col"] as! Int16) XCTAssertEqual(obj.int32Col, values["int32Col"] as! Int32) XCTAssertEqual(obj.int64Col, values["int64Col"] as! Int64) XCTAssertEqual(obj.floatCol, values["floatCol"] as! Float) XCTAssertEqual(obj.doubleCol, values["doubleCol"] as! Double) XCTAssertEqual(obj.stringCol, values["stringCol"] as! String) XCTAssertEqual(obj.binaryCol, values["binaryCol"] as! Data) XCTAssertEqual(obj.dateCol, values["dateCol"] as! Date) XCTAssertEqual(obj.decimalCol, values["decimalCol"] as! Decimal128) XCTAssertEqual(obj.objectIdCol, values["objectIdCol"] as! ObjectId) XCTAssertEqual(obj.objectCol!.pk, (values["objectCol"] as! ModernAllTypesObject?)!.pk) assertEquivalent(AnyRealmCollection(obj.arrayCol), values["arrayCol"] as! [ModernAllTypesObject], expectedShouldBeCopy: expectedShouldBeCopy) assertEquivalent(AnyRealmCollection(obj.setCol), values["setCol"] as! [ModernAllTypesObject], expectedShouldBeCopy: expectedShouldBeCopy) XCTAssertEqual(obj.anyCol, values["anyCol"] as! AnyRealmValue) XCTAssertEqual(obj.uuidCol, values["uuidCol"] as! UUID) XCTAssertEqual(obj.intEnumCol, values["intEnumCol"] as! ModernIntEnum) XCTAssertEqual(obj.stringEnumCol, values["stringEnumCol"] as! ModernStringEnum) XCTAssertEqual(obj.optBoolCol, values["optBoolCol"] as! Bool?) XCTAssertEqual(obj.optIntCol, values["optIntCol"] as! Int?) XCTAssertEqual(obj.optInt8Col, values["optInt8Col"] as! Int8?) XCTAssertEqual(obj.optInt16Col, values["optInt16Col"] as! Int16?) XCTAssertEqual(obj.optInt32Col, values["optInt32Col"] as! Int32?) XCTAssertEqual(obj.optInt64Col, values["optInt64Col"] as! Int64?) XCTAssertEqual(obj.optFloatCol, values["optFloatCol"] as! Float?) XCTAssertEqual(obj.optDoubleCol, values["optDoubleCol"] as! Double?) XCTAssertEqual(obj.optStringCol, values["optStringCol"] as! String?) XCTAssertEqual(obj.optBinaryCol, values["optBinaryCol"] as! Data?) XCTAssertEqual(obj.optDateCol, values["optDateCol"] as! Date?) XCTAssertEqual(obj.optDecimalCol, values["optDecimalCol"] as! Decimal128?) XCTAssertEqual(obj.optObjectIdCol, values["optObjectIdCol"] as! ObjectId?) XCTAssertEqual(obj.optUuidCol, values["optUuidCol"] as! UUID?) XCTAssertEqual(obj.optIntEnumCol, values["optIntEnumCol"] as! ModernIntEnum?) XCTAssertEqual(obj.optStringEnumCol, values["optStringEnumCol"] as! ModernStringEnum?) XCTAssertEqual(Array(obj.arrayBool), values["arrayBool"] as! [Bool]) XCTAssertEqual(Array(obj.arrayInt), values["arrayInt"] as! [Int]) XCTAssertEqual(Array(obj.arrayInt8), values["arrayInt8"] as! [Int8]) XCTAssertEqual(Array(obj.arrayInt16), values["arrayInt16"] as! [Int16]) XCTAssertEqual(Array(obj.arrayInt32), values["arrayInt32"] as! [Int32]) XCTAssertEqual(Array(obj.arrayInt64), values["arrayInt64"] as! [Int64]) XCTAssertEqual(Array(obj.arrayFloat), values["arrayFloat"] as! [Float]) XCTAssertEqual(Array(obj.arrayDouble), values["arrayDouble"] as! [Double]) XCTAssertEqual(Array(obj.arrayString), values["arrayString"] as! [String]) XCTAssertEqual(Array(obj.arrayBinary), values["arrayBinary"] as! [Data]) XCTAssertEqual(Array(obj.arrayDate), values["arrayDate"] as! [Date]) XCTAssertEqual(Array(obj.arrayDecimal), values["arrayDecimal"] as! [Decimal128]) XCTAssertEqual(Array(obj.arrayObjectId), values["arrayObjectId"] as! [ObjectId]) XCTAssertEqual(Array(obj.arrayAny), values["arrayAny"] as! [AnyRealmValue]) XCTAssertEqual(Array(obj.arrayUuid), values["arrayUuid"] as! [UUID]) XCTAssertEqual(Array(obj.arrayOptBool), values["arrayOptBool"] as! [Bool?]) XCTAssertEqual(Array(obj.arrayOptInt), values["arrayOptInt"] as! [Int?]) XCTAssertEqual(Array(obj.arrayOptInt8), values["arrayOptInt8"] as! [Int8?]) XCTAssertEqual(Array(obj.arrayOptInt16), values["arrayOptInt16"] as! [Int16?]) XCTAssertEqual(Array(obj.arrayOptInt32), values["arrayOptInt32"] as! [Int32?]) XCTAssertEqual(Array(obj.arrayOptInt64), values["arrayOptInt64"] as! [Int64?]) XCTAssertEqual(Array(obj.arrayOptFloat), values["arrayOptFloat"] as! [Float?]) XCTAssertEqual(Array(obj.arrayOptDouble), values["arrayOptDouble"] as! [Double?]) XCTAssertEqual(Array(obj.arrayOptString), values["arrayOptString"] as! [String?]) XCTAssertEqual(Array(obj.arrayOptBinary), values["arrayOptBinary"] as! [Data?]) XCTAssertEqual(Array(obj.arrayOptDate), values["arrayOptDate"] as! [Date?]) XCTAssertEqual(Array(obj.arrayOptDecimal), values["arrayOptDecimal"] as! [Decimal128?]) XCTAssertEqual(Array(obj.arrayOptObjectId), values["arrayOptObjectId"] as! [ObjectId?]) XCTAssertEqual(Array(obj.arrayOptUuid), values["arrayOptUuid"] as! [UUID?]) assertSetEquals(obj.setBool, values["setBool"] as! [Bool]) assertSetEquals(obj.setInt, values["setInt"] as! [Int]) assertSetEquals(obj.setInt8, values["setInt8"] as! [Int8]) assertSetEquals(obj.setInt16, values["setInt16"] as! [Int16]) assertSetEquals(obj.setInt32, values["setInt32"] as! [Int32]) assertSetEquals(obj.setInt64, values["setInt64"] as! [Int64]) assertSetEquals(obj.setFloat, values["setFloat"] as! [Float]) assertSetEquals(obj.setDouble, values["setDouble"] as! [Double]) assertSetEquals(obj.setString, values["setString"] as! [String]) assertSetEquals(obj.setBinary, values["setBinary"] as! [Data]) assertSetEquals(obj.setDate, values["setDate"] as! [Date]) assertSetEquals(obj.setDecimal, values["setDecimal"] as! [Decimal128]) assertSetEquals(obj.setObjectId, values["setObjectId"] as! [ObjectId]) assertSetEquals(obj.setAny, values["setAny"] as! [AnyRealmValue]) assertSetEquals(obj.setUuid, values["setUuid"] as! [UUID]) assertSetEquals(obj.setOptBool, values["setOptBool"] as! [Bool?]) assertSetEquals(obj.setOptInt, values["setOptInt"] as! [Int?]) assertSetEquals(obj.setOptInt8, values["setOptInt8"] as! [Int8?]) assertSetEquals(obj.setOptInt16, values["setOptInt16"] as! [Int16?]) assertSetEquals(obj.setOptInt32, values["setOptInt32"] as! [Int32?]) assertSetEquals(obj.setOptInt64, values["setOptInt64"] as! [Int64?]) assertSetEquals(obj.setOptFloat, values["setOptFloat"] as! [Float?]) assertSetEquals(obj.setOptDouble, values["setOptDouble"] as! [Double?]) assertSetEquals(obj.setOptString, values["setOptString"] as! [String?]) assertSetEquals(obj.setOptBinary, values["setOptBinary"] as! [Data?]) assertSetEquals(obj.setOptDate, values["setOptDate"] as! [Date?]) assertSetEquals(obj.setOptDecimal, values["setOptDecimal"] as! [Decimal128?]) assertSetEquals(obj.setOptObjectId, values["setOptObjectId"] as! [ObjectId?]) assertSetEquals(obj.setOptUuid, values["setOptUuid"] as! [UUID?]) assertMapEquals(obj.mapBool, values["mapBool"] as! [String: Bool]) assertMapEquals(obj.mapInt, values["mapInt"] as! [String: Int]) assertMapEquals(obj.mapInt8, values["mapInt8"] as! [String: Int8]) assertMapEquals(obj.mapInt16, values["mapInt16"] as! [String: Int16]) assertMapEquals(obj.mapInt32, values["mapInt32"] as! [String: Int32]) assertMapEquals(obj.mapInt64, values["mapInt64"] as! [String: Int64]) assertMapEquals(obj.mapFloat, values["mapFloat"] as! [String: Float]) assertMapEquals(obj.mapDouble, values["mapDouble"] as! [String: Double]) assertMapEquals(obj.mapString, values["mapString"] as! [String: String]) assertMapEquals(obj.mapBinary, values["mapBinary"] as! [String: Data]) assertMapEquals(obj.mapDate, values["mapDate"] as! [String: Date]) assertMapEquals(obj.mapDecimal, values["mapDecimal"] as! [String: Decimal128]) assertMapEquals(obj.mapObjectId, values["mapObjectId"] as! [String: ObjectId]) assertMapEquals(obj.mapAny, values["mapAny"] as! [String: AnyRealmValue]) assertMapEquals(obj.mapUuid, values["mapUuid"] as! [String: UUID]) assertMapEquals(obj.mapOptBool, values["mapOptBool"] as! [String: Bool?]) assertMapEquals(obj.mapOptInt, values["mapOptInt"] as! [String: Int?]) assertMapEquals(obj.mapOptInt8, values["mapOptInt8"] as! [String: Int8?]) assertMapEquals(obj.mapOptInt16, values["mapOptInt16"] as! [String: Int16?]) assertMapEquals(obj.mapOptInt32, values["mapOptInt32"] as! [String: Int32?]) assertMapEquals(obj.mapOptInt64, values["mapOptInt64"] as! [String: Int64?]) assertMapEquals(obj.mapOptFloat, values["mapOptFloat"] as! [String: Float?]) assertMapEquals(obj.mapOptDouble, values["mapOptDouble"] as! [String: Double?]) assertMapEquals(obj.mapOptString, values["mapOptString"] as! [String: String?]) assertMapEquals(obj.mapOptBinary, values["mapOptBinary"] as! [String: Data?]) assertMapEquals(obj.mapOptDate, values["mapOptDate"] as! [String: Date?]) assertMapEquals(obj.mapOptDecimal, values["mapOptDecimal"] as! [String: Decimal128?]) assertMapEquals(obj.mapOptObjectId, values["mapOptObjectId"] as! [String: ObjectId?]) assertMapEquals(obj.mapOptUuid, values["mapOptUuid"] as! [String: UUID?]) } func verifyDefault(_ obj: ModernAllTypesObject) { XCTAssertEqual(obj.boolCol, false) XCTAssertEqual(obj.intCol, 0) XCTAssertEqual(obj.int8Col, 1) XCTAssertEqual(obj.int16Col, 2) XCTAssertEqual(obj.int32Col, 3) XCTAssertEqual(obj.int64Col, 4) XCTAssertEqual(obj.floatCol, 5) XCTAssertEqual(obj.doubleCol, 6) XCTAssertEqual(obj.stringCol, "") XCTAssertEqual(obj.binaryCol, Data()) XCTAssertEqual(obj.decimalCol, 0) XCTAssertNotEqual(obj.objectIdCol, ObjectId()) // should have generated a random ObjectId XCTAssertEqual(obj.objectCol, nil) XCTAssertEqual(obj.arrayCol.count, 0) XCTAssertEqual(obj.setCol.count, 0) XCTAssertEqual(obj.anyCol, .none) XCTAssertNotEqual(obj.uuidCol, UUID()) // should have generated a random UUID XCTAssertEqual(obj.intEnumCol, .value1) XCTAssertEqual(obj.stringEnumCol, .value1) XCTAssertNil(obj.optIntCol) XCTAssertNil(obj.optInt8Col) XCTAssertNil(obj.optInt16Col) XCTAssertNil(obj.optInt32Col) XCTAssertNil(obj.optInt64Col) XCTAssertNil(obj.optFloatCol) XCTAssertNil(obj.optDoubleCol) XCTAssertNil(obj.optBoolCol) XCTAssertNil(obj.optStringCol) XCTAssertNil(obj.optBinaryCol) XCTAssertNil(obj.optDateCol) XCTAssertNil(obj.optDecimalCol) XCTAssertNil(obj.optObjectIdCol) XCTAssertNil(obj.optUuidCol) XCTAssertNil(obj.optIntEnumCol) XCTAssertNil(obj.optStringEnumCol) XCTAssertEqual(obj.arrayBool.count, 0) XCTAssertEqual(obj.arrayInt.count, 0) XCTAssertEqual(obj.arrayInt8.count, 0) XCTAssertEqual(obj.arrayInt16.count, 0) XCTAssertEqual(obj.arrayInt32.count, 0) XCTAssertEqual(obj.arrayInt64.count, 0) XCTAssertEqual(obj.arrayFloat.count, 0) XCTAssertEqual(obj.arrayDouble.count, 0) XCTAssertEqual(obj.arrayString.count, 0) XCTAssertEqual(obj.arrayBinary.count, 0) XCTAssertEqual(obj.arrayDate.count, 0) XCTAssertEqual(obj.arrayDecimal.count, 0) XCTAssertEqual(obj.arrayObjectId.count, 0) XCTAssertEqual(obj.arrayAny.count, 0) XCTAssertEqual(obj.arrayUuid.count, 0) XCTAssertEqual(obj.arrayOptBool.count, 0) XCTAssertEqual(obj.arrayOptInt.count, 0) XCTAssertEqual(obj.arrayOptInt8.count, 0) XCTAssertEqual(obj.arrayOptInt16.count, 0) XCTAssertEqual(obj.arrayOptInt32.count, 0) XCTAssertEqual(obj.arrayOptInt64.count, 0) XCTAssertEqual(obj.arrayOptFloat.count, 0) XCTAssertEqual(obj.arrayOptDouble.count, 0) XCTAssertEqual(obj.arrayOptString.count, 0) XCTAssertEqual(obj.arrayOptBinary.count, 0) XCTAssertEqual(obj.arrayOptDate.count, 0) XCTAssertEqual(obj.arrayOptDecimal.count, 0) XCTAssertEqual(obj.arrayOptObjectId.count, 0) XCTAssertEqual(obj.arrayOptUuid.count, 0) XCTAssertEqual(obj.setBool.count, 0) XCTAssertEqual(obj.setInt.count, 0) XCTAssertEqual(obj.setInt8.count, 0) XCTAssertEqual(obj.setInt16.count, 0) XCTAssertEqual(obj.setInt32.count, 0) XCTAssertEqual(obj.setInt64.count, 0) XCTAssertEqual(obj.setFloat.count, 0) XCTAssertEqual(obj.setDouble.count, 0) XCTAssertEqual(obj.setString.count, 0) XCTAssertEqual(obj.setBinary.count, 0) XCTAssertEqual(obj.setDate.count, 0) XCTAssertEqual(obj.setDecimal.count, 0) XCTAssertEqual(obj.setObjectId.count, 0) XCTAssertEqual(obj.setAny.count, 0) XCTAssertEqual(obj.setUuid.count, 0) XCTAssertEqual(obj.setOptBool.count, 0) XCTAssertEqual(obj.setOptInt.count, 0) XCTAssertEqual(obj.setOptInt8.count, 0) XCTAssertEqual(obj.setOptInt16.count, 0) XCTAssertEqual(obj.setOptInt32.count, 0) XCTAssertEqual(obj.setOptInt64.count, 0) XCTAssertEqual(obj.setOptFloat.count, 0) XCTAssertEqual(obj.setOptDouble.count, 0) XCTAssertEqual(obj.setOptString.count, 0) XCTAssertEqual(obj.setOptBinary.count, 0) XCTAssertEqual(obj.setOptDate.count, 0) XCTAssertEqual(obj.setOptDecimal.count, 0) XCTAssertEqual(obj.setOptObjectId.count, 0) XCTAssertEqual(obj.setOptUuid.count, 0) } func verifyNil(_ obj: ModernAllTypesObject) { // "anyCol": .none, XCTAssertNil(obj.objectCol) XCTAssertNil(obj.optBoolCol) XCTAssertNil(obj.optIntCol) XCTAssertNil(obj.optInt8Col) XCTAssertNil(obj.optInt16Col) XCTAssertNil(obj.optInt32Col) XCTAssertNil(obj.optInt64Col) XCTAssertNil(obj.optFloatCol) XCTAssertNil(obj.optDoubleCol) XCTAssertNil(obj.optStringCol) XCTAssertNil(obj.optBinaryCol) XCTAssertNil(obj.optDateCol) XCTAssertNil(obj.optDecimalCol) XCTAssertNil(obj.optObjectIdCol) XCTAssertNil(obj.optUuidCol) XCTAssertNil(obj.optIntEnumCol) XCTAssertNil(obj.optStringEnumCol) XCTAssertEqual(obj.arrayAny[0], .none) XCTAssertNil(obj.arrayOptBool[0]) XCTAssertNil(obj.arrayOptInt[0]) XCTAssertNil(obj.arrayOptInt8[0]) XCTAssertNil(obj.arrayOptInt16[0]) XCTAssertNil(obj.arrayOptInt32[0]) XCTAssertNil(obj.arrayOptInt64[0]) XCTAssertNil(obj.arrayOptFloat[0]) XCTAssertNil(obj.arrayOptDouble[0]) XCTAssertNil(obj.arrayOptString[0]) XCTAssertNil(obj.arrayOptBinary[0]) XCTAssertNil(obj.arrayOptDate[0]) XCTAssertNil(obj.arrayOptDecimal[0]) XCTAssertNil(obj.arrayOptObjectId[0]) XCTAssertNil(obj.arrayOptUuid[0]) XCTAssertEqual(obj.setAny.first!, .none) XCTAssertNil(obj.setOptBool.first!) XCTAssertNil(obj.setOptInt.first!) XCTAssertNil(obj.setOptInt8.first!) XCTAssertNil(obj.setOptInt16.first!) XCTAssertNil(obj.setOptInt32.first!) XCTAssertNil(obj.setOptInt64.first!) XCTAssertNil(obj.setOptFloat.first!) XCTAssertNil(obj.setOptDouble.first!) XCTAssertNil(obj.setOptString.first!) XCTAssertNil(obj.setOptBinary.first!) XCTAssertNil(obj.setOptDate.first!) XCTAssertNil(obj.setOptDecimal.first!) XCTAssertNil(obj.setOptObjectId.first!) XCTAssertNil(obj.setOptUuid.first!) XCTAssertEqual(obj.mapAny["1"], .some(.none)) XCTAssertEqual(obj.mapOptBool["1"], .some(nil)) XCTAssertEqual(obj.mapOptInt["1"], .some(nil)) XCTAssertEqual(obj.mapOptInt8["1"], .some(nil)) XCTAssertEqual(obj.mapOptInt16["1"], .some(nil)) XCTAssertEqual(obj.mapOptInt32["1"], .some(nil)) XCTAssertEqual(obj.mapOptInt64["1"], .some(nil)) XCTAssertEqual(obj.mapOptFloat["1"], .some(nil)) XCTAssertEqual(obj.mapOptDouble["1"], .some(nil)) XCTAssertEqual(obj.mapOptString["1"], .some(nil)) XCTAssertEqual(obj.mapOptBinary["1"], .some(nil)) XCTAssertEqual(obj.mapOptDate["1"], .some(nil)) XCTAssertEqual(obj.mapOptDecimal["1"], .some(nil)) XCTAssertEqual(obj.mapOptObjectId["1"], .some(nil)) XCTAssertEqual(obj.mapOptUuid["1"], .some(nil)) } func testInitDefault() { verifyDefault(ModernAllTypesObject()) } func testInitWithArray() { var arrayValues = ModernAllTypesObject.sharedSchema()!.properties.map { values[$0.name] } arrayValues[0] = ObjectId.generate() verifyObject(ModernAllTypesObject(value: arrayValues), expectedShouldBeCopy: false) } func testInitWithDictionary() { verifyObject(ModernAllTypesObject(value: values!), expectedShouldBeCopy: false) } func testInitWithObject() { let obj = ModernAllTypesObject(value: values!) verifyObject(ModernAllTypesObject(value: obj), expectedShouldBeCopy: false) } func testInitNil() { verifyNil(ModernAllTypesObject(value: nullValues())) } func testCreateDefault() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernAllTypesObject.self) } verifyDefault(obj) } func testCreateWithArray() { let realm = try! Realm() var arrayValues = ModernAllTypesObject.sharedSchema()!.properties.map { values[$0.name] } arrayValues[0] = ObjectId.generate() let obj = try! realm.write { return realm.create(ModernAllTypesObject.self, value: arrayValues) } verifyObject(obj) } func testCreateWithDictionary() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernAllTypesObject.self, value: values!) } verifyObject(obj) } func testCreateWithObject() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernAllTypesObject.self, value: ModernAllTypesObject(value: values!)) } verifyObject(obj) } func testCreateNil() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernAllTypesObject.self, value: nullValues()) } verifyNil(obj) } func testAddDefault() { let obj = ModernAllTypesObject() let realm = try! Realm() try! realm.write { realm.add(obj) } verifyDefault(obj) } func testAdd() { let obj = ModernAllTypesObject(value: values!) let realm = try! Realm() try! realm.write { realm.add(obj) } verifyObject(obj, expectedShouldBeCopy: false) } func testAddNil() { let obj = ModernAllTypesObject(value: nullValues()) let realm = try! Realm() try! realm.write { realm.add(obj) } verifyNil(obj) } func testCreateEmbeddedWithDictionary() { let realm = try! Realm() realm.beginWrite() let parent = realm.create(ModernEmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) XCTAssertEqual(parent.object!.value, 5) XCTAssertEqual(parent.object!.child!.value, 6) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 7) XCTAssertEqual(parent.object!.children[1].value, 8) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 9) XCTAssertEqual(parent.array[1].value, 10) XCTAssertTrue(parent.isSameObject(as: parent.object!.parent1.first!)) XCTAssertTrue(parent.isSameObject(as: parent.array[0].parent2.first!)) XCTAssertTrue(parent.isSameObject(as: parent.array[1].parent2.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.child!.parent3.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.children[0].parent4.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.children[1].parent4.first!)) realm.cancelWrite() } func testCreateEmbeddedWithUnmanagedObjects() { let sourceObject = ModernEmbeddedParentObject() sourceObject.object = .init(value: [5]) sourceObject.object!.child = .init(value: [6]) sourceObject.object!.children.append(.init(value: [7])) sourceObject.object!.children.append(.init(value: [8])) sourceObject.array.append(.init(value: [9])) sourceObject.array.append(.init(value: [10])) let realm = try! Realm() realm.beginWrite() let parent = realm.create(ModernEmbeddedParentObject.self, value: sourceObject) XCTAssertNil(sourceObject.realm) XCTAssertEqual(parent.object!.value, 5) XCTAssertEqual(parent.object!.child!.value, 6) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 7) XCTAssertEqual(parent.object!.children[1].value, 8) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 9) XCTAssertEqual(parent.array[1].value, 10) realm.cancelWrite() } func testCreateEmbeddedFromManagedObjectInSameRealm() { let realm = try! Realm() realm.beginWrite() let parent = realm.create(ModernEmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) let copy = realm.create(ModernEmbeddedParentObject.self, value: parent) XCTAssertNotEqual(parent, copy) XCTAssertEqual(copy.object!.value, 5) XCTAssertEqual(copy.object!.child!.value, 6) XCTAssertEqual(copy.object!.children.count, 2) XCTAssertEqual(copy.object!.children[0].value, 7) XCTAssertEqual(copy.object!.children[1].value, 8) XCTAssertEqual(copy.array.count, 2) XCTAssertEqual(copy.array[0].value, 9) XCTAssertEqual(copy.array[1].value, 10) realm.cancelWrite() } func testCreateEmbeddedFromManagedObjectInDifferentRealm() { let realmA = realmWithTestPath() let realmB = try! Realm() realmA.beginWrite() let parent = realmA.create(ModernEmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) try! realmA.commitWrite() realmB.beginWrite() let copy = realmB.create(ModernEmbeddedParentObject.self, value: parent) XCTAssertNotEqual(parent, copy) XCTAssertEqual(copy.object!.value, 5) XCTAssertEqual(copy.object!.child!.value, 6) XCTAssertEqual(copy.object!.children.count, 2) XCTAssertEqual(copy.object!.children[0].value, 7) XCTAssertEqual(copy.object!.children[1].value, 8) XCTAssertEqual(copy.array.count, 2) XCTAssertEqual(copy.array[0].value, 9) XCTAssertEqual(copy.array[1].value, 10) realmB.cancelWrite() } // MARK: - Add tests class ModernEmbeddedObjectFactory { private var value = 0 var objects = [EmbeddedObject]() func create() -> T { let obj = T() obj.value = value value += 1 objects.append(obj) return obj } } func testAddEmbedded() { let objectFactory = ModernEmbeddedObjectFactory() let parent = ModernEmbeddedParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) XCTAssertEqual((object as! ModernEmbeddedTreeObject).value, i) } XCTAssertEqual(parent.object!.value, 0) XCTAssertEqual(parent.object!.child!.value, 1) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 2) XCTAssertEqual(parent.object!.children[1].value, 3) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 4) XCTAssertEqual(parent.array[1].value, 5) realm.cancelWrite() } func testAddAndUpdateEmbedded() { let objectFactory = ModernEmbeddedObjectFactory() let parent = ModernEmbeddedPrimaryParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let parent2 = ModernEmbeddedPrimaryParentObject() parent2.object = objectFactory.create() parent2.object!.child = objectFactory.create() parent2.object!.children.append(objectFactory.create()) parent2.object!.children.append(objectFactory.create()) parent2.array.append(objectFactory.create()) parent2.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) realm.add(parent2, update: .all) // update all deletes the old embedded objects and creates new ones for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) if i < 6 { XCTAssertTrue(object.isInvalidated) } else { XCTAssertEqual((object as! ModernEmbeddedTreeObject).value, i) } } XCTAssertTrue(parent.isSameObject(as: parent2)) XCTAssertEqual(parent.object!.value, 6) XCTAssertEqual(parent.object!.child!.value, 7) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 8) XCTAssertEqual(parent.object!.children[1].value, 9) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 10) XCTAssertEqual(parent.array[1].value, 11) realm.cancelWrite() } func testAddAndUpdateChangedWithExisingNestedObjects() { let realm = try! Realm() realm.beginWrite() let existingObject = realm.create(SwiftPrimaryStringObject.self, value: ["primary", 1]) try! realm.commitWrite() realm.beginWrite() let object = SwiftLinkToPrimaryStringObject(value: ["primary", ["primary", 2] as [Any]]) realm.add(object, update: .modified) try! realm.commitWrite() XCTAssertNotNil(object.realm) XCTAssertEqual(object.object!, existingObject) // the existing object should be updated XCTAssertEqual(existingObject.intCol, 2) } func testAddAndUpdateChangedEmbedded() { let objectFactory = ModernEmbeddedObjectFactory() let parent = ModernEmbeddedPrimaryParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let parent2 = ModernEmbeddedPrimaryParentObject() parent2.object = objectFactory.create() parent2.object!.child = objectFactory.create() parent2.object!.children.append(objectFactory.create()) parent2.object!.children.append(objectFactory.create()) parent2.array.append(objectFactory.create()) parent2.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) realm.add(parent2, update: .modified) // update modified modifies the existing embedded objects for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) XCTAssertEqual((object as! ModernEmbeddedTreeObject).value, i < 6 ? i + 6 : i) } XCTAssertTrue(parent.isSameObject(as: parent2)) XCTAssertEqual(parent.object!.value, 6) XCTAssertEqual(parent.object!.child!.value, 7) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 8) XCTAssertEqual(parent.object!.children[1].value, 9) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 10) XCTAssertEqual(parent.array[1].value, 11) realm.cancelWrite() } func testAddObjectCycle() { weak var weakObj1: ModernCircleObject?, weakObj2: ModernCircleObject? autoreleasepool { let obj1 = ModernCircleObject() let obj2 = ModernCircleObject(value: [obj1, [obj1]]) obj1.obj = obj2 obj1.array.append(obj2) weakObj1 = obj1 weakObj2 = obj2 let realm = try! Realm() try! realm.write { realm.add(obj1) } XCTAssertEqual(obj1.realm, realm) XCTAssertEqual(obj2.realm, realm) } XCTAssertNil(weakObj1) XCTAssertNil(weakObj2) } } private func mapValues(_ values: [T]) -> [String: T] { var map = [String: T]() for (i, v) in values.enumerated() { map["\(i)"] = v } return map } class ModernEnumObjectCreationTests: TestCase { let values: [String: Any] = [ "listInt": EnumInt.values(), "listInt8": EnumInt8.values(), "listInt16": EnumInt16.values(), "listInt32": EnumInt32.values(), "listInt64": EnumInt64.values(), "listFloat": EnumFloat.values(), "listDouble": EnumDouble.values(), "listString": EnumString.values(), "listIntOpt": EnumInt?.values(), "listInt8Opt": EnumInt8?.values(), "listInt16Opt": EnumInt16?.values(), "listInt32Opt": EnumInt32?.values(), "listInt64Opt": EnumInt64?.values(), "listFloatOpt": EnumFloat?.values(), "listDoubleOpt": EnumDouble?.values(), "listStringOpt": EnumString?.values(), "setInt": EnumInt.values(), "setInt8": EnumInt8.values(), "setInt16": EnumInt16.values(), "setInt32": EnumInt32.values(), "setInt64": EnumInt64.values(), "setFloat": EnumFloat.values(), "setDouble": EnumDouble.values(), "setString": EnumString.values(), "setIntOpt": EnumInt?.values(), "setInt8Opt": EnumInt8?.values(), "setInt16Opt": EnumInt16?.values(), "setInt32Opt": EnumInt32?.values(), "setInt64Opt": EnumInt64?.values(), "setFloatOpt": EnumFloat?.values(), "setDoubleOpt": EnumDouble?.values(), "setStringOpt": EnumString?.values(), "mapInt": mapValues(EnumInt.values()), "mapInt8": mapValues(EnumInt8.values()), "mapInt16": mapValues(EnumInt16.values()), "mapInt32": mapValues(EnumInt32.values()), "mapInt64": mapValues(EnumInt64.values()), "mapFloat": mapValues(EnumFloat.values()), "mapDouble": mapValues(EnumDouble.values()), "mapString": mapValues(EnumString.values()), "mapIntOpt": mapValues(EnumInt?.values()), "mapInt8Opt": mapValues(EnumInt8?.values()), "mapInt16Opt": mapValues(EnumInt16?.values()), "mapInt32Opt": mapValues(EnumInt32?.values()), "mapInt64Opt": mapValues(EnumInt64?.values()), "mapFloatOpt": mapValues(EnumFloat?.values()), "mapDoubleOpt": mapValues(EnumDouble?.values()), "mapStringOpt": mapValues(EnumString?.values()) ] func assertMapEqual(_ actual: Map, _ expected: Dictionary) { XCTAssertEqual(actual.count, expected.count) for (key, value) in expected { XCTAssertEqual(actual[key], value) } } func verifyObject(_ obj: ModernCollectionsOfEnums) { XCTAssertEqual(Array(obj.listInt), EnumInt.values()) XCTAssertEqual(Array(obj.listInt8), EnumInt8.values()) XCTAssertEqual(Array(obj.listInt16), EnumInt16.values()) XCTAssertEqual(Array(obj.listInt32), EnumInt32.values()) XCTAssertEqual(Array(obj.listInt64), EnumInt64.values()) XCTAssertEqual(Array(obj.listFloat), EnumFloat.values()) XCTAssertEqual(Array(obj.listDouble), EnumDouble.values()) XCTAssertEqual(Array(obj.listString), EnumString.values()) XCTAssertEqual(Array(obj.listIntOpt), EnumInt?.values()) XCTAssertEqual(Array(obj.listInt8Opt), EnumInt8?.values()) XCTAssertEqual(Array(obj.listInt16Opt), EnumInt16?.values()) XCTAssertEqual(Array(obj.listInt32Opt), EnumInt32?.values()) XCTAssertEqual(Array(obj.listInt64Opt), EnumInt64?.values()) XCTAssertEqual(Array(obj.listFloatOpt), EnumFloat?.values()) XCTAssertEqual(Array(obj.listDoubleOpt), EnumDouble?.values()) XCTAssertEqual(Array(obj.listStringOpt), EnumString?.values()) XCTAssertEqual(Set(obj.setInt), Set(EnumInt.values())) XCTAssertEqual(Set(obj.setInt8), Set(EnumInt8.values())) XCTAssertEqual(Set(obj.setInt16), Set(EnumInt16.values())) XCTAssertEqual(Set(obj.setInt32), Set(EnumInt32.values())) XCTAssertEqual(Set(obj.setInt64), Set(EnumInt64.values())) XCTAssertEqual(Set(obj.setFloat), Set(EnumFloat.values())) XCTAssertEqual(Set(obj.setDouble), Set(EnumDouble.values())) XCTAssertEqual(Set(obj.setString), Set(EnumString.values())) XCTAssertEqual(Set(obj.setIntOpt), Set(EnumInt?.values())) XCTAssertEqual(Set(obj.setInt8Opt), Set(EnumInt8?.values())) XCTAssertEqual(Set(obj.setInt16Opt), Set(EnumInt16?.values())) XCTAssertEqual(Set(obj.setInt32Opt), Set(EnumInt32?.values())) XCTAssertEqual(Set(obj.setInt64Opt), Set(EnumInt64?.values())) XCTAssertEqual(Set(obj.setFloatOpt), Set(EnumFloat?.values())) XCTAssertEqual(Set(obj.setDoubleOpt), Set(EnumDouble?.values())) XCTAssertEqual(Set(obj.setStringOpt), Set(EnumString?.values())) assertMapEqual(obj.mapInt, mapValues(EnumInt.values())) assertMapEqual(obj.mapInt8, mapValues(EnumInt8.values())) assertMapEqual(obj.mapInt16, mapValues(EnumInt16.values())) assertMapEqual(obj.mapInt32, mapValues(EnumInt32.values())) assertMapEqual(obj.mapInt64, mapValues(EnumInt64.values())) assertMapEqual(obj.mapFloat, mapValues(EnumFloat.values())) assertMapEqual(obj.mapDouble, mapValues(EnumDouble.values())) assertMapEqual(obj.mapString, mapValues(EnumString.values())) assertMapEqual(obj.mapIntOpt, mapValues(EnumInt?.values())) assertMapEqual(obj.mapInt8Opt, mapValues(EnumInt8?.values())) assertMapEqual(obj.mapInt16Opt, mapValues(EnumInt16?.values())) assertMapEqual(obj.mapInt32Opt, mapValues(EnumInt32?.values())) assertMapEqual(obj.mapInt64Opt, mapValues(EnumInt64?.values())) assertMapEqual(obj.mapFloatOpt, mapValues(EnumFloat?.values())) assertMapEqual(obj.mapDoubleOpt, mapValues(EnumDouble?.values())) assertMapEqual(obj.mapStringOpt, mapValues(EnumString?.values())) } func verifyDefault(_ obj: ModernCollectionsOfEnums) { XCTAssertEqual(obj.listInt.count, 0) XCTAssertEqual(obj.listInt8.count, 0) XCTAssertEqual(obj.listInt16.count, 0) XCTAssertEqual(obj.listInt32.count, 0) XCTAssertEqual(obj.listInt64.count, 0) XCTAssertEqual(obj.listFloat.count, 0) XCTAssertEqual(obj.listDouble.count, 0) XCTAssertEqual(obj.listString.count, 0) XCTAssertEqual(obj.listIntOpt.count, 0) XCTAssertEqual(obj.listInt8Opt.count, 0) XCTAssertEqual(obj.listInt16Opt.count, 0) XCTAssertEqual(obj.listInt32Opt.count, 0) XCTAssertEqual(obj.listInt64Opt.count, 0) XCTAssertEqual(obj.listFloatOpt.count, 0) XCTAssertEqual(obj.listDoubleOpt.count, 0) XCTAssertEqual(obj.listStringOpt.count, 0) XCTAssertEqual(obj.setInt.count, 0) XCTAssertEqual(obj.setInt8.count, 0) XCTAssertEqual(obj.setInt16.count, 0) XCTAssertEqual(obj.setInt32.count, 0) XCTAssertEqual(obj.setInt64.count, 0) XCTAssertEqual(obj.setFloat.count, 0) XCTAssertEqual(obj.setDouble.count, 0) XCTAssertEqual(obj.setString.count, 0) XCTAssertEqual(obj.setIntOpt.count, 0) XCTAssertEqual(obj.setInt8Opt.count, 0) XCTAssertEqual(obj.setInt16Opt.count, 0) XCTAssertEqual(obj.setInt32Opt.count, 0) XCTAssertEqual(obj.setInt64Opt.count, 0) XCTAssertEqual(obj.setFloatOpt.count, 0) XCTAssertEqual(obj.setDoubleOpt.count, 0) XCTAssertEqual(obj.setStringOpt.count, 0) XCTAssertEqual(obj.mapInt.count, 0) XCTAssertEqual(obj.mapInt8.count, 0) XCTAssertEqual(obj.mapInt16.count, 0) XCTAssertEqual(obj.mapInt32.count, 0) XCTAssertEqual(obj.mapInt64.count, 0) XCTAssertEqual(obj.mapFloat.count, 0) XCTAssertEqual(obj.mapDouble.count, 0) XCTAssertEqual(obj.mapString.count, 0) XCTAssertEqual(obj.mapIntOpt.count, 0) XCTAssertEqual(obj.mapInt8Opt.count, 0) XCTAssertEqual(obj.mapInt16Opt.count, 0) XCTAssertEqual(obj.mapInt32Opt.count, 0) XCTAssertEqual(obj.mapInt64Opt.count, 0) XCTAssertEqual(obj.mapFloatOpt.count, 0) XCTAssertEqual(obj.mapDoubleOpt.count, 0) XCTAssertEqual(obj.mapStringOpt.count, 0) } func testInitDefault() { verifyDefault(ModernCollectionsOfEnums()) } func testInitWithArray() { let arrayValues = ModernCollectionsOfEnums.sharedSchema()!.properties.map { values[$0.name] } verifyObject(ModernCollectionsOfEnums(value: arrayValues)) } func testInitWithDictionary() { verifyObject(ModernCollectionsOfEnums(value: values)) } func testInitWithObject() { let obj = ModernCollectionsOfEnums(value: values) verifyObject(ModernCollectionsOfEnums(value: obj)) } func testCreateDefault() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernCollectionsOfEnums.self) } verifyDefault(obj) } func testCreateWithArray() { let realm = try! Realm() let arrayValues = ModernCollectionsOfEnums.sharedSchema()!.properties.map { values[$0.name] } let obj = try! realm.write { return realm.create(ModernCollectionsOfEnums.self, value: arrayValues) } verifyObject(obj) } func testCreateWithDictionary() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernCollectionsOfEnums.self, value: values) } verifyObject(obj) } func testCreateWithObject() { let realm = try! Realm() let obj = try! realm.write { return realm.create(ModernCollectionsOfEnums.self, value: ModernCollectionsOfEnums(value: values)) } verifyObject(obj) } func testAddDefault() { let obj = ModernCollectionsOfEnums() let realm = try! Realm() try! realm.write { realm.add(obj) } verifyDefault(obj) } func testAdd() { let obj = ModernCollectionsOfEnums(value: values) let realm = try! Realm() try! realm.write { realm.add(obj) } verifyObject(obj) } } ================================================ FILE: RealmSwift/Tests/ModernObjectTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift import Foundation private nonisolated(unsafe) var dynamicDefaultSeed = 0 private func nextDynamicDefaultSeed() -> Int { dynamicDefaultSeed += 1 return dynamicDefaultSeed } class ModernDynamicDefaultObject: Object { @Persisted(primaryKey: true) var intCol = nextDynamicDefaultSeed() @Persisted var floatCol = Float(nextDynamicDefaultSeed()) @Persisted var doubleCol = Double(nextDynamicDefaultSeed()) @Persisted var dateCol = Date(timeIntervalSinceReferenceDate: TimeInterval(nextDynamicDefaultSeed())) @Persisted var stringCol = UUID().uuidString @Persisted var binaryCol = UUID().uuidString.data(using: .utf8) } class ModernObjectTests: TestCase { // init() Tests are in ObjectCreationTests.swift // init(value:) tests are in ObjectCreationTests.swift func testObjectSchema() { let object = ModernAllTypesObject() let schema = object.objectSchema XCTAssert(schema as AnyObject is ObjectSchema) XCTAssert(schema.properties as AnyObject is [Property]) XCTAssertEqual(schema.className, "ModernAllTypesObject") XCTAssertEqual(schema.properties.map { $0.name }, ["pk", "boolCol", "intCol", "int8Col", "int16Col", "int32Col", "int64Col", "floatCol", "doubleCol", "stringCol", "binaryCol", "dateCol", "decimalCol", "objectIdCol", "objectCol", "arrayCol", "setCol", "mapCol", "anyCol", "uuidCol", "intEnumCol", "stringEnumCol", "optIntCol", "optInt8Col", "optInt16Col", "optInt32Col", "optInt64Col", "optFloatCol", "optDoubleCol", "optBoolCol", "optStringCol", "optBinaryCol", "optDateCol", "optDecimalCol", "optObjectIdCol", "optUuidCol", "optIntEnumCol", "optStringEnumCol", "arrayBool", "arrayInt", "arrayInt8", "arrayInt16", "arrayInt32", "arrayInt64", "arrayFloat", "arrayDouble", "arrayString", "arrayBinary", "arrayDate", "arrayDecimal", "arrayObjectId", "arrayAny", "arrayUuid", "arrayOptBool", "arrayOptInt", "arrayOptInt8", "arrayOptInt16", "arrayOptInt32", "arrayOptInt64", "arrayOptFloat", "arrayOptDouble", "arrayOptString", "arrayOptBinary", "arrayOptDate", "arrayOptDecimal", "arrayOptObjectId", "arrayOptUuid", "setBool", "setInt", "setInt8", "setInt16", "setInt32", "setInt64", "setFloat", "setDouble", "setString", "setBinary", "setDate", "setDecimal", "setObjectId", "setAny", "setUuid", "setOptBool", "setOptInt", "setOptInt8", "setOptInt16", "setOptInt32", "setOptInt64", "setOptFloat", "setOptDouble", "setOptString", "setOptBinary", "setOptDate", "setOptDecimal", "setOptObjectId", "setOptUuid", "mapBool", "mapInt", "mapInt8", "mapInt16", "mapInt32", "mapInt64", "mapFloat", "mapDouble", "mapString", "mapBinary", "mapDate", "mapDecimal", "mapObjectId", "mapAny", "mapUuid", "mapOptBool", "mapOptInt", "mapOptInt8", "mapOptInt16", "mapOptInt32", "mapOptInt64", "mapOptFloat", "mapOptDouble", "mapOptString", "mapOptBinary", "mapOptDate", "mapOptDecimal", "mapOptObjectId", "mapOptUuid"]) } func testObjectSchemaForObjectWithConvenienceInitializer() { let object = ModernConvenienceInitializerObject(stringCol: "abc") let schema = object.objectSchema XCTAssert(schema as AnyObject is ObjectSchema) XCTAssert(schema.properties as AnyObject is [Property]) XCTAssertEqual(schema.className, "ModernConvenienceInitializerObject") XCTAssertEqual(schema.properties.map { $0.name }, ["stringCol"]) } func testCannotUpdatePrimaryKey() { let primaryKeyReason = "Primary key can't be changed.* after an object is inserted." let realm = self.realmWithTestPath() realm.beginWrite() func test(_ object: O, _ v1: O.PrimaryKey, _ v2: O.PrimaryKey) { // Unmanaged objects can mutate the primary key object.pk = v1 XCTAssertEqual(object.pk, v1) object.pk = v2 XCTAssertEqual(object.pk, v2) object["pk"] = v1 XCTAssertEqual(object.pk, v1) object.setValue(v2, forKey: "pk") XCTAssertEqual(object.pk, v2) // Managed objects cannot mutate the pk realm.add(object) assertThrows(object.pk = v2, reasonMatching: primaryKeyReason) assertThrows(object["pk"] = v2, reasonMatching: primaryKeyReason) assertThrows(object.setValue(v2, forKey: "pk"), reasonMatching: primaryKeyReason) } test(ModernPrimaryIntObject(), 1, 2) test(ModernPrimaryInt8Object(), 1, 2) test(ModernPrimaryInt16Object(), 1, 2) test(ModernPrimaryInt32Object(), 1, 2) test(ModernPrimaryInt64Object(), 1, 2) test(ModernPrimaryOptionalIntObject(), 1, nil) test(ModernPrimaryOptionalInt8Object(), 1, nil) test(ModernPrimaryOptionalInt16Object(), 1, nil) test(ModernPrimaryOptionalInt32Object(), 1, nil) test(ModernPrimaryOptionalInt64Object(), 1, nil) test(ModernPrimaryStringObject(), "a", "b") test(ModernPrimaryOptionalStringObject(), "a", nil) test(ModernPrimaryUUIDObject(), UUID(), UUID()) test(ModernPrimaryOptionalUUIDObject(), UUID(), nil) test(ModernPrimaryObjectIdObject(), ObjectId.generate(), ObjectId.generate()) test(ModernPrimaryOptionalObjectIdObject(), ObjectId.generate(), nil) test(ModernPrimaryIntEnumObject(), .value1, .value2) test(ModernPrimaryOptionalIntEnumObject(), .value1, nil) realm.cancelWrite() } func testDynamicDefaultPropertyValues() { func assertDifferentPropertyValues(_ obj1: ModernDynamicDefaultObject, _ obj2: ModernDynamicDefaultObject) { XCTAssertNotEqual(obj1.intCol, obj2.intCol) XCTAssertNotEqual(obj1.floatCol, obj2.floatCol) XCTAssertNotEqual(obj1.doubleCol, obj2.doubleCol) XCTAssertNotEqual(obj1.dateCol.timeIntervalSinceReferenceDate, obj2.dateCol.timeIntervalSinceReferenceDate, accuracy: 0.01) XCTAssertNotEqual(obj1.stringCol, obj2.stringCol) XCTAssertNotEqual(obj1.binaryCol, obj2.binaryCol) } assertDifferentPropertyValues(ModernDynamicDefaultObject(), ModernDynamicDefaultObject()) let realm = try! Realm() try! realm.write { assertDifferentPropertyValues(realm.create(ModernDynamicDefaultObject.self), realm.create(ModernDynamicDefaultObject.self)) } } func testWillSetDidSet() { let obj = SetterObservers() var calls = 0 obj.willSetCallback = { XCTAssertEqual(obj.value, 0) calls += 1 } obj.didSetCallback = { XCTAssertEqual(obj.value, 1) calls += 1 } obj.value = 1 XCTAssertEqual(calls, 2) let realm = try! Realm() realm.beginWrite() realm.add(obj) obj.willSetCallback = { XCTAssertEqual(obj.value, 1) calls += 1 } obj.didSetCallback = { XCTAssertEqual(obj.value, 2) calls += 1 } obj.value = 2 XCTAssertEqual(calls, 4) realm.cancelWrite() // The callbacks form a circular reference and so need to be removed obj.willSetCallback = nil obj.didSetCallback = nil } func testAddingObjectReusesExistingCollectionObjects() { let obj = ModernAllTypesObject() let list = obj.arrayCol let set = obj.setCol let dictionary = obj.mapAny XCTAssertNil(list.realm) XCTAssertNil(set.realm) XCTAssertNil(dictionary.realm) XCTAssertTrue(list === obj.arrayCol) XCTAssertTrue(set === obj.setCol) XCTAssertTrue(dictionary === obj.mapAny) let realm = try! Realm() try! realm.write { realm.add(obj) } XCTAssertNotNil(list.realm) XCTAssertNotNil(set.realm) XCTAssertNotNil(dictionary.realm) XCTAssertTrue(list === obj.arrayCol) XCTAssertTrue(set === obj.setCol) XCTAssertTrue(dictionary === obj.mapAny) } func testAddingPreviouslyObservedObjectReusesExistingCollectionObjects() { let obj = ModernAllTypesObject() let list = obj.arrayCol let set = obj.setCol let dictionary = obj.mapAny // We don't allow adding an object with active observers to the Realm // (it causes problems with the subclass KVO creates at runtime), but // once a property has been observed it stays in the observed state forever. obj.addObserver(self, forKeyPath: "arrayCol", context: nil) obj.addObserver(self, forKeyPath: "setCol", context: nil) obj.addObserver(self, forKeyPath: "mapAny", context: nil) obj.removeObserver(self, forKeyPath: "arrayCol", context: nil) obj.removeObserver(self, forKeyPath: "setCol", context: nil) obj.removeObserver(self, forKeyPath: "mapAny", context: nil) XCTAssertNil(list.realm) XCTAssertNil(set.realm) XCTAssertNil(dictionary.realm) XCTAssertTrue(list === obj.arrayCol) XCTAssertTrue(set === obj.setCol) XCTAssertTrue(dictionary === obj.mapAny) let realm = try! Realm() try! realm.write { realm.add(obj) } XCTAssertNotNil(list.realm) XCTAssertNotNil(set.realm) XCTAssertNotNil(dictionary.realm) XCTAssertTrue(list === obj.arrayCol) XCTAssertTrue(set === obj.setCol) XCTAssertTrue(dictionary === obj.mapAny) } } ================================================ FILE: RealmSwift/Tests/ModernTestObjects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import RealmSwift import Realm class ModernAllTypesObject: Object { @Persisted(primaryKey: true) var pk: ObjectId var ignored: Int = 1 @Persisted var boolCol: Bool @Persisted var intCol: Int @Persisted var int8Col: Int8 = 1 @Persisted var int16Col: Int16 = 2 @Persisted var int32Col: Int32 = 3 @Persisted var int64Col: Int64 = 4 @Persisted var floatCol: Float = 5 @Persisted var doubleCol: Double = 6 @Persisted var stringCol: String @Persisted var binaryCol: Data @Persisted var dateCol: Date @Persisted var decimalCol: Decimal128 @Persisted var objectIdCol: ObjectId @Persisted var objectCol: ModernAllTypesObject? @Persisted var arrayCol: List @Persisted var setCol: MutableSet @Persisted var mapCol: Map @Persisted var anyCol: AnyRealmValue @Persisted var uuidCol: UUID @Persisted var intEnumCol: ModernIntEnum @Persisted var stringEnumCol: ModernStringEnum @Persisted var optIntCol: Int? @Persisted var optInt8Col: Int8? @Persisted var optInt16Col: Int16? @Persisted var optInt32Col: Int32? @Persisted var optInt64Col: Int64? @Persisted var optFloatCol: Float? @Persisted var optDoubleCol: Double? @Persisted var optBoolCol: Bool? @Persisted var optStringCol: String? @Persisted var optBinaryCol: Data? @Persisted var optDateCol: Date? @Persisted var optDecimalCol: Decimal128? @Persisted var optObjectIdCol: ObjectId? @Persisted var optUuidCol: UUID? @Persisted var optIntEnumCol: ModernIntEnum? @Persisted var optStringEnumCol: ModernStringEnum? @Persisted var arrayBool: List @Persisted var arrayInt: List @Persisted var arrayInt8: List @Persisted var arrayInt16: List @Persisted var arrayInt32: List @Persisted var arrayInt64: List @Persisted var arrayFloat: List @Persisted var arrayDouble: List @Persisted var arrayString: List @Persisted var arrayBinary: List @Persisted var arrayDate: List @Persisted var arrayDecimal: List @Persisted var arrayObjectId: List @Persisted var arrayAny: List @Persisted var arrayUuid: List @Persisted var arrayOptBool: List @Persisted var arrayOptInt: List @Persisted var arrayOptInt8: List @Persisted var arrayOptInt16: List @Persisted var arrayOptInt32: List @Persisted var arrayOptInt64: List @Persisted var arrayOptFloat: List @Persisted var arrayOptDouble: List @Persisted var arrayOptString: List @Persisted var arrayOptBinary: List @Persisted var arrayOptDate: List @Persisted var arrayOptDecimal: List @Persisted var arrayOptObjectId: List @Persisted var arrayOptUuid: List @Persisted var setBool: MutableSet @Persisted var setInt: MutableSet @Persisted var setInt8: MutableSet @Persisted var setInt16: MutableSet @Persisted var setInt32: MutableSet @Persisted var setInt64: MutableSet @Persisted var setFloat: MutableSet @Persisted var setDouble: MutableSet @Persisted var setString: MutableSet @Persisted var setBinary: MutableSet @Persisted var setDate: MutableSet @Persisted var setDecimal: MutableSet @Persisted var setObjectId: MutableSet @Persisted var setAny: MutableSet @Persisted var setUuid: MutableSet @Persisted var setOptBool: MutableSet @Persisted var setOptInt: MutableSet @Persisted var setOptInt8: MutableSet @Persisted var setOptInt16: MutableSet @Persisted var setOptInt32: MutableSet @Persisted var setOptInt64: MutableSet @Persisted var setOptFloat: MutableSet @Persisted var setOptDouble: MutableSet @Persisted var setOptString: MutableSet @Persisted var setOptBinary: MutableSet @Persisted var setOptDate: MutableSet @Persisted var setOptDecimal: MutableSet @Persisted var setOptObjectId: MutableSet @Persisted var setOptUuid: MutableSet @Persisted var mapBool: Map @Persisted var mapInt: Map @Persisted var mapInt8: Map @Persisted var mapInt16: Map @Persisted var mapInt32: Map @Persisted var mapInt64: Map @Persisted var mapFloat: Map @Persisted var mapDouble: Map @Persisted var mapString: Map @Persisted var mapBinary: Map @Persisted var mapDate: Map @Persisted var mapDecimal: Map @Persisted var mapObjectId: Map @Persisted var mapAny: Map @Persisted var mapUuid: Map @Persisted var mapOptBool: Map @Persisted var mapOptInt: Map @Persisted var mapOptInt8: Map @Persisted var mapOptInt16: Map @Persisted var mapOptInt32: Map @Persisted var mapOptInt64: Map @Persisted var mapOptFloat: Map @Persisted var mapOptDouble: Map @Persisted var mapOptString: Map @Persisted var mapOptBinary: Map @Persisted var mapOptDate: Map @Persisted var mapOptDecimal: Map @Persisted var mapOptObjectId: Map @Persisted var mapOptUuid: Map @Persisted(originProperty: "objectCol") var linkingObjects: LinkingObjects } class LinkToModernAllTypesObject: Object { @Persisted var object: ModernAllTypesObject? @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } enum ModernIntEnum: Int, Codable, PersistableEnum { case value1 = 1 case value2 = 3 case value3 = 5 } enum ModernStringEnum: String, Codable, PersistableEnum { case value1 = "a" case value2 = "c" case value3 = "e" case value4 = "Foó" } class ModernImplicitlyUnwrappedOptionalObject: Object { @Persisted var optStringCol: String! @Persisted var optBinaryCol: Data! @Persisted var optDateCol: Date! @Persisted var optDecimalCol: Decimal128! @Persisted var optObjectIdCol: ObjectId! @Persisted var optObjectCol: ModernImplicitlyUnwrappedOptionalObject! @Persisted var optUuidCol: UUID! } class ModernLinkToPrimaryStringObject: Object { @Persisted var pk = "" @Persisted var object: ModernPrimaryStringObject? @Persisted var objects: List override class func primaryKey() -> String? { return "pk" } } class ModernUTF8Object: Object { // swiftlint:disable:next identifier_name @Persisted var 柱колоéнǢкƱаم👍 = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" } protocol ModernPrimaryKeyObject: Object { associatedtype PrimaryKey: Equatable var pk: PrimaryKey { get set } } class ModernPrimaryStringObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: String } class ModernPrimaryOptionalStringObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: String? } class ModernPrimaryIntObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int } class ModernPrimaryOptionalIntObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int? } class ModernPrimaryInt8Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int8 } class ModernPrimaryOptionalInt8Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int8? } class ModernPrimaryInt16Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int16 } class ModernPrimaryOptionalInt16Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int16? } class ModernPrimaryInt32Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int32 } class ModernPrimaryOptionalInt32Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int32? } class ModernPrimaryInt64Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int64 } class ModernPrimaryOptionalInt64Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int64? } class ModernPrimaryUUIDObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: UUID } class ModernPrimaryOptionalUUIDObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: UUID? } class ModernPrimaryObjectIdObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ObjectId } class ModernPrimaryOptionalObjectIdObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ObjectId? } class ModernPrimaryIntEnumObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ModernIntEnum } class ModernPrimaryOptionalIntEnumObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ModernIntEnum? } class ModernIndexedIntEnumObject: Object { @Persisted(indexed: true) var value: ModernIntEnum } class ModernIndexedOptionalIntEnumObject: Object { @Persisted(indexed: true) var value: ModernIntEnum? } class ModernCustomInitializerObject: Object { @Persisted var stringCol: String init(stringVal: String) { stringCol = stringVal super.init() } required override init() { stringCol = "" super.init() } } class ModernConvenienceInitializerObject: Object { @Persisted var stringCol = "" convenience init(stringCol: String) { self.init() self.stringCol = stringCol } } @objc(ModernObjcRenamedObject) class ModernObjcRenamedObject: Object { @Persisted var stringCol = "" } @objc(ModernObjcRenamedObjectWithTotallyDifferentName) class ModernObjcArbitrarilyRenamedObject: Object { @Persisted var boolCol = false } class ModernIntAndStringObject: Object { @Persisted var intCol: Int @Persisted var optIntCol: Int? @Persisted var stringCol: String @Persisted var optStringCol: String? } class ModernCollectionObject: Object { @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } class ModernCircleObject: Object { @Persisted var obj: ModernCircleObject? @Persisted var array: List } class ModernEmbeddedParentObject: Object { @Persisted var object: ModernEmbeddedTreeObject1? @Persisted var array: List } class ModernEmbeddedPrimaryParentObject: Object { @Persisted(primaryKey: true) var pk: Int = 0 @Persisted var object: ModernEmbeddedTreeObject1? @Persisted var array: List } protocol ModernEmbeddedTreeObject: EmbeddedObject { var value: Int { get set } } class ModernEmbeddedTreeObject1: EmbeddedObject, ModernEmbeddedTreeObject { @Persisted var value = 0 @Persisted var bool = false @Persisted var child: ModernEmbeddedTreeObject2? @Persisted var children: List @Persisted(originProperty: "object") var parent1: LinkingObjects @Persisted(originProperty: "array") var parent2: LinkingObjects } class ModernEmbeddedTreeObject2: EmbeddedObject, ModernEmbeddedTreeObject { @Persisted var value = 0 @Persisted var child: ModernEmbeddedTreeObject3? @Persisted var children: List @Persisted(originProperty: "child") var parent3: LinkingObjects @Persisted(originProperty: "children") var parent4: LinkingObjects } class ModernEmbeddedTreeObject3: EmbeddedObject, ModernEmbeddedTreeObject { @Persisted var value = 0 @Persisted(originProperty: "child") var parent3: LinkingObjects @Persisted(originProperty: "children") var parent4: LinkingObjects } class ModernEmbeddedObject: EmbeddedObject { @Persisted var value = 0 } class SetterObservers: Object { @Persisted var value: Int { willSet { willSetCallback?() } didSet { didSetCallback?() } } var willSetCallback: (() -> Void)? var didSetCallback: (() -> Void)? } class ObjectWithArcMethodCategoryNames: Object { // @objc properties with these names would crash with asan (and unreliably // without it) because they would not have the correct behavior for the // inferred ARC method family. @Persisted var newValue: String @Persisted var allocValue: String @Persisted var copyValue: String @Persisted var mutableCopyValue: String @Persisted var initValue: String } class ModernAllIndexableTypesObject: Object { @Persisted(indexed: true) var boolCol: Bool @Persisted(indexed: true) var intCol: Int @Persisted(indexed: true) var int8Col: Int8 = 1 @Persisted(indexed: true) var int16Col: Int16 = 2 @Persisted(indexed: true) var int32Col: Int32 = 3 @Persisted(indexed: true) var int64Col: Int64 = 4 @Persisted(indexed: true) var stringCol: String @Persisted(indexed: true) var dateCol: Date @Persisted(indexed: true) var uuidCol: UUID @Persisted(indexed: true) var objectIdCol: ObjectId @Persisted(indexed: true) var intEnumCol: ModernIntEnum @Persisted(indexed: true) var stringEnumCol: ModernStringEnum @Persisted(indexed: true) var optIntCol: Int? @Persisted(indexed: true) var optInt8Col: Int8? @Persisted(indexed: true) var optInt16Col: Int16? @Persisted(indexed: true) var optInt32Col: Int32? @Persisted(indexed: true) var optInt64Col: Int64? @Persisted(indexed: true) var optBoolCol: Bool? @Persisted(indexed: true) var optStringCol: String? @Persisted(indexed: true) var optDateCol: Date? @Persisted(indexed: true) var optUuidCol: UUID? @Persisted(indexed: true) var optObjectIdCol: ObjectId? @Persisted(indexed: true) var optIntEnumCol: ModernIntEnum? @Persisted(indexed: true) var optStringEnumCol: ModernStringEnum? } class ModernAllIndexableButNotIndexedObject: Object { @Persisted(indexed: false) var boolCol: Bool @Persisted(indexed: false) var intCol: Int @Persisted(indexed: false) var int8Col: Int8 = 1 @Persisted(indexed: false) var int16Col: Int16 = 2 @Persisted(indexed: false) var int32Col: Int32 = 3 @Persisted(indexed: false) var int64Col: Int64 = 4 @Persisted(indexed: false) var stringCol: String @Persisted(indexed: false) var dateCol: Date @Persisted(indexed: false) var uuidCol: UUID @Persisted(indexed: false) var objectIdCol: ObjectId @Persisted(indexed: false) var intEnumCol: ModernIntEnum @Persisted(indexed: false) var stringEnumCol: ModernStringEnum @Persisted(indexed: false) var optIntCol: Int? @Persisted(indexed: false) var optInt8Col: Int8? @Persisted(indexed: false) var optInt16Col: Int16? @Persisted(indexed: false) var optInt32Col: Int32? @Persisted(indexed: false) var optInt64Col: Int64? @Persisted(indexed: false) var optBoolCol: Bool? @Persisted(indexed: false) var optStringCol: String? @Persisted(indexed: false) var optDateCol: Date? @Persisted(indexed: false) var optUuidCol: UUID? @Persisted(indexed: false) var optObjectIdCol: ObjectId? @Persisted(indexed: false) var optIntEnumCol: ModernIntEnum? @Persisted(indexed: false) var optStringEnumCol: ModernStringEnum? } class ModernCollectionsOfEnums: Object { @Persisted var listInt: List @Persisted var listInt8: List @Persisted var listInt16: List @Persisted var listInt32: List @Persisted var listInt64: List @Persisted var listFloat: List @Persisted var listDouble: List @Persisted var listString: List @Persisted var listIntOpt: List @Persisted var listInt8Opt: List @Persisted var listInt16Opt: List @Persisted var listInt32Opt: List @Persisted var listInt64Opt: List @Persisted var listFloatOpt: List @Persisted var listDoubleOpt: List @Persisted var listStringOpt: List @Persisted var setInt: MutableSet @Persisted var setInt8: MutableSet @Persisted var setInt16: MutableSet @Persisted var setInt32: MutableSet @Persisted var setInt64: MutableSet @Persisted var setFloat: MutableSet @Persisted var setDouble: MutableSet @Persisted var setString: MutableSet @Persisted var setIntOpt: MutableSet @Persisted var setInt8Opt: MutableSet @Persisted var setInt16Opt: MutableSet @Persisted var setInt32Opt: MutableSet @Persisted var setInt64Opt: MutableSet @Persisted var setFloatOpt: MutableSet @Persisted var setDoubleOpt: MutableSet @Persisted var setStringOpt: MutableSet @Persisted var mapInt: Map @Persisted var mapInt8: Map @Persisted var mapInt16: Map @Persisted var mapInt32: Map @Persisted var mapInt64: Map @Persisted var mapFloat: Map @Persisted var mapDouble: Map @Persisted var mapString: Map @Persisted var mapIntOpt: Map @Persisted var mapInt8Opt: Map @Persisted var mapInt16Opt: Map @Persisted var mapInt32Opt: Map @Persisted var mapInt64Opt: Map @Persisted var mapFloatOpt: Map @Persisted var mapDoubleOpt: Map @Persisted var mapStringOpt: Map } class LinkToModernCollectionsOfEnums: Object { @Persisted var object: ModernCollectionsOfEnums? @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } class ModernListAnyRealmValueObject: Object { @Persisted var value: List } class ModernAllTypesProjection: Projection { @Projected(\ModernAllTypesObject.boolCol) var boolCol @Projected(\ModernAllTypesObject.intCol) var intCol: Int @Projected(\ModernAllTypesObject.floatCol) var floatCol: Float @Projected(\ModernAllTypesObject.doubleCol) var doubleCol: Double @Projected(\ModernAllTypesObject.stringCol) var stringCol: String @Projected(\ModernAllTypesObject.binaryCol) var binaryCol: Data @Projected(\ModernAllTypesObject.dateCol) var dateCol: Date @Projected(\ModernAllTypesObject.decimalCol) var decimalCol: Decimal128 @Projected(\ModernAllTypesObject.objectIdCol) var objectIdCol: ObjectId @Projected(\ModernAllTypesObject.objectCol) var objectCol: ModernAllTypesObject? @Projected(\ModernAllTypesObject.arrayCol) var arrayCol: List @Projected(\ModernAllTypesObject.setCol) var setCol: MutableSet @Projected(\ModernAllTypesObject.mapCol) var mapCol: Map @Projected(\ModernAllTypesObject.anyCol) var anyCol: AnyRealmValue @Projected(\ModernAllTypesObject.uuidCol) var uuidCol: UUID @Projected(\ModernAllTypesObject.intEnumCol) var intEnumCol: ModernIntEnum @Projected(\ModernAllTypesObject.stringEnumCol) var stringEnumCol: ModernStringEnum @Projected(\ModernAllTypesObject.optIntCol) var optIntCol: Int? @Projected(\ModernAllTypesObject.optFloatCol) var optFloatCol: Float? @Projected(\ModernAllTypesObject.optDoubleCol) var optDoubleCol: Double? @Projected(\ModernAllTypesObject.optBoolCol) var optBoolCol: Bool? @Projected(\ModernAllTypesObject.optStringCol) var optStringCol: String? @Projected(\ModernAllTypesObject.optBinaryCol) var optBinaryCol: Data? @Projected(\ModernAllTypesObject.optDateCol) var optDateCol: Date? @Projected(\ModernAllTypesObject.optDecimalCol) var optDecimalCol: Decimal128? @Projected(\ModernAllTypesObject.optObjectIdCol) var optObjectIdCol: ObjectId? @Projected(\ModernAllTypesObject.optUuidCol) var optUuidCol: UUID? @Projected(\ModernAllTypesObject.optIntEnumCol) var optIntEnumCol: ModernIntEnum? @Projected(\ModernAllTypesObject.optStringEnumCol) var optStringEnumCol: ModernStringEnum? @Projected(\ModernAllTypesObject.arrayBool) var arrayBool: List @Projected(\ModernAllTypesObject.arrayInt) var arrayInt: List @Projected(\ModernAllTypesObject.arrayFloat) var arrayFloat: List @Projected(\ModernAllTypesObject.arrayDouble) var arrayDouble: List @Projected(\ModernAllTypesObject.arrayString) var arrayString: List @Projected(\ModernAllTypesObject.arrayBinary) var arrayBinary: List @Projected(\ModernAllTypesObject.arrayDate) var arrayDate: List @Projected(\ModernAllTypesObject.arrayDecimal) var arrayDecimal: List @Projected(\ModernAllTypesObject.arrayObjectId) var arrayObjectId: List @Projected(\ModernAllTypesObject.arrayAny) var arrayAny: List @Projected(\ModernAllTypesObject.arrayUuid) var arrayUuid: List @Projected(\ModernAllTypesObject.arrayOptBool) var arrayOptBool: List @Projected(\ModernAllTypesObject.arrayOptInt) var arrayOptInt: List @Projected(\ModernAllTypesObject.arrayOptFloat) var arrayOptFloat: List @Projected(\ModernAllTypesObject.arrayOptDouble) var arrayOptDouble: List @Projected(\ModernAllTypesObject.arrayOptString) var arrayOptString: List @Projected(\ModernAllTypesObject.arrayOptBinary) var arrayOptBinary: List @Projected(\ModernAllTypesObject.arrayOptDate) var arrayOptDate: List @Projected(\ModernAllTypesObject.arrayOptDecimal) var arrayOptDecimal: List @Projected(\ModernAllTypesObject.arrayOptObjectId) var arrayOptObjectId: List @Projected(\ModernAllTypesObject.arrayOptUuid) var arrayOptUuid: List @Projected(\ModernAllTypesObject.setBool) var setBool: MutableSet @Projected(\ModernAllTypesObject.setInt) var setInt: MutableSet @Projected(\ModernAllTypesObject.setFloat) var setFloat: MutableSet @Projected(\ModernAllTypesObject.setDouble) var setDouble: MutableSet @Projected(\ModernAllTypesObject.setString) var setString: MutableSet @Projected(\ModernAllTypesObject.setBinary) var setBinary: MutableSet @Projected(\ModernAllTypesObject.setDate) var setDate: MutableSet @Projected(\ModernAllTypesObject.setDecimal) var setDecimal: MutableSet @Projected(\ModernAllTypesObject.setObjectId) var setObjectId: MutableSet @Projected(\ModernAllTypesObject.setAny) var setAny: MutableSet @Projected(\ModernAllTypesObject.setUuid) var setUuid: MutableSet @Projected(\ModernAllTypesObject.setOptBool) var setOptBool: MutableSet @Projected(\ModernAllTypesObject.setOptInt) var setOptInt: MutableSet @Projected(\ModernAllTypesObject.setOptFloat) var setOptFloat: MutableSet @Projected(\ModernAllTypesObject.setOptDouble) var setOptDouble: MutableSet @Projected(\ModernAllTypesObject.setOptString) var setOptString: MutableSet @Projected(\ModernAllTypesObject.setOptBinary) var setOptBinary: MutableSet @Projected(\ModernAllTypesObject.setOptDate) var setOptDate: MutableSet @Projected(\ModernAllTypesObject.setOptDecimal) var setOptDecimal: MutableSet @Projected(\ModernAllTypesObject.setOptObjectId) var setOptObjectId: MutableSet @Projected(\ModernAllTypesObject.setOptUuid) var setOptUuid: MutableSet @Projected(\ModernAllTypesObject.mapBool) var mapBool: Map @Projected(\ModernAllTypesObject.mapInt) var mapInt: Map @Projected(\ModernAllTypesObject.mapFloat) var mapFloat: Map @Projected(\ModernAllTypesObject.mapDouble) var mapDouble: Map @Projected(\ModernAllTypesObject.mapString) var mapString: Map @Projected(\ModernAllTypesObject.mapBinary) var mapBinary: Map @Projected(\ModernAllTypesObject.mapDate) var mapDate: Map @Projected(\ModernAllTypesObject.mapDecimal) var mapDecimal: Map @Projected(\ModernAllTypesObject.mapObjectId) var mapObjectId: Map @Projected(\ModernAllTypesObject.mapAny) var mapAny: Map @Projected(\ModernAllTypesObject.mapUuid) var mapUuid: Map @Projected(\ModernAllTypesObject.mapOptBool) var mapOptBool: Map @Projected(\ModernAllTypesObject.mapOptInt) var mapOptInt: Map @Projected(\ModernAllTypesObject.mapOptFloat) var mapOptFloat: Map @Projected(\ModernAllTypesObject.mapOptDouble) var mapOptDouble: Map @Projected(\ModernAllTypesObject.mapOptString) var mapOptString: Map @Projected(\ModernAllTypesObject.mapOptBinary) var mapOptBinary: Map @Projected(\ModernAllTypesObject.mapOptDate) var mapOptDate: Map @Projected(\ModernAllTypesObject.mapOptDecimal) var mapOptDecimal: Map @Projected(\ModernAllTypesObject.mapOptObjectId) var mapOptObjectId: Map @Projected(\ModernAllTypesObject.mapOptUuid) var mapOptUuid: Map @Projected(\ModernAllTypesObject.linkingObjects) var linkingObjects: LinkingObjects } ================================================ FILE: RealmSwift/Tests/MutableSetTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import RealmSwift class MutableSetTests: TestCase { var str1: SwiftStringObject? var str2: SwiftStringObject? var str3: SwiftStringObject? var setObject: SwiftMutableSetPropertyObject! var setObject2: SwiftMutableSetPropertyObject! var set: MutableSet? var set2: MutableSet? func createSet() -> SwiftMutableSetPropertyObject { fatalError("abstract") } func createSetWithLinks() -> SwiftMutableSetOfSwiftObject { fatalError("abstract") } override func setUp() { super.setUp() let str1 = SwiftStringObject() str1.stringCol = "1" self.str1 = str1 let str2 = SwiftStringObject() str2.stringCol = "2" self.str2 = str2 let str3 = SwiftStringObject() str3.stringCol = "3" self.str3 = str3 setObject = createSet() setObject2 = createSet() set = setObject.set set2 = setObject2.set let realm = realmWithTestPath() try! realm.write { realm.add(str1) realm.add(str2) realm.add(str3) } realm.beginWrite() } override func tearDown() { try! realmWithTestPath().commitWrite() str1 = nil str2 = nil str3 = nil setObject = nil setObject2 = nil set = nil set2 = nil super.tearDown() } override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(MutableSetTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func testPrimitive() { let obj = SwiftMutableSetObject() obj.int.insert(5) XCTAssertEqual(obj.int.first!, 5) XCTAssertEqual(obj.int.last!, 5) XCTAssertEqual(obj.int[0], 5) obj.int.insert(objectsIn: [6, 7, 8] as [Int]) XCTAssertEqual(obj.int.max(), 8) XCTAssertEqual(obj.int.sum(), 26) obj.string.insert("str") XCTAssertEqual(obj.string.first!, "str") XCTAssertEqual(obj.string[0], "str") } func testPrimitiveIterationAcrossNil() { let obj = SwiftMutableSetObject() XCTAssertFalse(obj.int.contains(5)) XCTAssertFalse(obj.int8.contains(5)) XCTAssertFalse(obj.int16.contains(5)) XCTAssertFalse(obj.int32.contains(5)) XCTAssertFalse(obj.int64.contains(5)) XCTAssertFalse(obj.float.contains(3.141592)) XCTAssertFalse(obj.double.contains(3.141592)) XCTAssertFalse(obj.string.contains("foobar")) XCTAssertFalse(obj.data.contains(Data())) XCTAssertFalse(obj.date.contains(Date())) XCTAssertFalse(obj.decimal.contains(Decimal128())) XCTAssertFalse(obj.objectId.contains(ObjectId())) XCTAssertFalse(obj.uuidOpt.contains(UUID())) XCTAssertFalse(obj.intOpt.contains { $0 == nil }) XCTAssertFalse(obj.int8Opt.contains { $0 == nil }) XCTAssertFalse(obj.int16Opt.contains { $0 == nil }) XCTAssertFalse(obj.int32Opt.contains { $0 == nil }) XCTAssertFalse(obj.int64Opt.contains { $0 == nil }) XCTAssertFalse(obj.floatOpt.contains { $0 == nil }) XCTAssertFalse(obj.doubleOpt.contains { $0 == nil }) XCTAssertFalse(obj.stringOpt.contains { $0 == nil }) XCTAssertFalse(obj.dataOpt.contains { $0 == nil }) XCTAssertFalse(obj.dateOpt.contains { $0 == nil }) XCTAssertFalse(obj.decimalOpt.contains { $0 == nil }) XCTAssertFalse(obj.objectIdOpt.contains { $0 == nil }) XCTAssertFalse(obj.uuidOpt.contains { $0 == nil }) } func testInvalidated() { guard let set = set else { fatalError("Test precondition failure") } XCTAssertFalse(set.isInvalidated) if let realm = setObject.realm { realm.delete(setObject) XCTAssertTrue(set.isInvalidated) } } func testFastEnumerationWithMutation() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.insert(objectsIn: [str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2, str1, str2]) var str = "" for obj in set { str += obj.stringCol set.insert(objectsIn: [str1]) } XCTAssertTrue(set.contains(str1)) XCTAssertTrue(set.contains(str2)) } func testAppendObject() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } for str in [str1, str2, str1] { set.insert(str) } XCTAssertEqual(Int(2), set.count) XCTAssertTrue(set.contains(str1)) XCTAssertTrue(set.contains(str2)) } func testInsert() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.insert(objectsIn: [str1, str2, str1]) XCTAssertEqual(Int(2), set.count) XCTAssertTrue(set.contains(str1)) XCTAssertTrue(set.contains(str2)) } func testAppendResults() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.insert(objectsIn: realmWithTestPath().objects(SwiftStringObject.self)) XCTAssertEqual(Int(3), set.count) // The unmanaged NSSet backing object won't work with MutableSet.contains(:) set.forEach { // Ordering is not guaranteed so we can't subscript XCTAssertTrue($0.isSameObject(as: str1) || $0.isSameObject(as: str2) || $0.isSameObject(as: str3)) } } func testRemoveAll() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.insert(objectsIn: [str1, str2]) set.removeAll() XCTAssertEqual(Int(0), set.count) set.removeAll() // should be a no-op XCTAssertEqual(Int(0), set.count) } func testRemoveObject() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.insert(objectsIn: [str1, str2]) XCTAssertTrue(set.contains(str1)) XCTAssertEqual(Int(2), set.count) set.remove(str1) XCTAssertFalse(set.contains(str1)) XCTAssertEqual(Int(1), set.count) set.removeAll() XCTAssertEqual(Int(0), set.count) XCTAssertFalse(set.contains(str1)) XCTAssertFalse(set.contains(str2)) } func testChangesArePersisted() { guard let set = set, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } if let realm = set.realm { set.insert(objectsIn: [str1, str2]) let otherSet = realm.objects(SwiftMutableSetPropertyObject.self).first!.set XCTAssertEqual(Int(2), otherSet.count) } } func testPopulateEmptySet() { guard let set = set else { fatalError("Test precondition failure") } XCTAssertEqual(set.count, 0, "Should start with no set elements.") let obj = SwiftStringObject() obj.stringCol = "a" set.insert(obj) set.insert(realmWithTestPath().create(SwiftStringObject.self, value: ["b"])) set.insert(obj) XCTAssertEqual(set.count, 2) set.forEach { XCTAssertTrue($0.stringCol == "a" || $0.stringCol == "b") } // Make sure we can enumerate for obj in set { XCTAssertTrue(obj.description.utf16.count > 0, "Object should have description") } } func testEnumeratingSetWithSetProperties() { let setObject = createSetWithLinks() setObject.realm?.beginWrite() for _ in 0..<10 { setObject.set.insert(SwiftObject()) } try! setObject.realm?.commitWrite() XCTAssertEqual(10, setObject.set.count) for object in setObject.set { XCTAssertEqual(123, object.intCol) XCTAssertEqual(false, object.objectCol!.boolCol) XCTAssertEqual(0, object.setCol.count) } } func testValueForKey() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let setObject = SwiftMutableSetOfSwiftObject() let object = SwiftObject() object.intCol = value object.doubleCol = Double(value) object.stringCol = String(value) object.decimalCol = Decimal128(number: value as NSNumber) object.objectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) setObject.set.insert(object) realm.add(setObject) } } let setObjects = realm.objects(SwiftMutableSetOfSwiftObject.self) let setsOfObjects = setObjects.value(forKeyPath: "set") as! [MutableSet] let objects = realm.objects(SwiftObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftObject) -> T) { let properties: [T] = Array(setObjects.flatMap { $0.set.map(fn) }) let kvcProperties: [T] = Array(setsOfObjects.flatMap { $0.map(fn) }) XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftObject) -> T) { let properties = Array(objects.compactMap(fn)) let setsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(setsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0.intCol } testProperty { $0.doubleCol } testProperty { $0.stringCol } testProperty { $0.decimalCol } testProperty { $0.objectIdCol } testProperty("intCol") { $0.intCol } testProperty("doubleCol") { $0.doubleCol } testProperty("stringCol") { $0.stringCol } testProperty("decimalCol") { $0.decimalCol } testProperty("objectIdCol") { $0.objectIdCol } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional func testValueForKeyOptional() { let realm = try! Realm() try! realm.write { for value in [1, 2] { let setObject = SwiftMutableSetOfSwiftOptionalObject() let object = SwiftOptionalObject() object.optIntCol.value = value object.optInt8Col.value = Int8(value) object.optDoubleCol.value = Double(value) object.optStringCol = String(value) object.optNSStringCol = NSString(format: "%d", value) object.optDecimalCol = Decimal128(number: value as NSNumber) object.optObjectIdCol = try! ObjectId(string: String(repeating: String(value), count: 24)) setObject.set.insert(object) realm.add(setObject) } } let setObjects = realm.objects(SwiftMutableSetOfSwiftOptionalObject.self) let setOfObjects = setObjects.value(forKeyPath: "set") as! [MutableSet] let objects = realm.objects(SwiftOptionalObject.self) func testProperty(line: UInt = #line, fn: @escaping (SwiftOptionalObject) -> T) { let properties: [T] = Array(setObjects.flatMap { $0.set.map(fn) }) let kvcProperties: [T] = Array(setOfObjects.flatMap { $0.map(fn) }) XCTAssertEqual(properties, kvcProperties, line: line) } func testProperty(_ name: String, line: UInt = #line, fn: @escaping (SwiftOptionalObject) -> T) { let properties = Array(objects.compactMap(fn)) let setsOfObjects = objects.value(forKeyPath: name) as! [T] let kvcProperties = Array(setsOfObjects.compactMap { $0 }) XCTAssertEqual(properties, kvcProperties, line: line) } testProperty { $0.optIntCol.value } testProperty { $0.optInt8Col.value } testProperty { $0.optDoubleCol.value } testProperty { $0.optStringCol } testProperty { $0.optNSStringCol } testProperty { $0.optDecimalCol } testProperty { $0.optObjectCol } testProperty("optIntCol") { $0.optIntCol.value } testProperty("optInt8Col") { $0.optInt8Col.value } testProperty("optDoubleCol") { $0.optDoubleCol.value } testProperty("optStringCol") { $0.optStringCol } testProperty("optNSStringCol") { $0.optNSStringCol } testProperty("optDecimalCol") { $0.optDecimalCol } testProperty("optObjectCol") { $0.optObjectCol } } func testUnmanagedSetComparison() { let obj = SwiftIntObject() obj.intCol = 5 let obj2 = SwiftIntObject() obj2.intCol = 6 let obj3 = SwiftIntObject() obj3.intCol = 8 let objects = [obj, obj2, obj3] let objects2 = [obj, obj2] let set1 = MutableSet() let set2 = MutableSet() XCTAssertEqual(set1, set2, "Empty instances should be equal by `==` operator") set1.insert(objectsIn: objects) set2.insert(objectsIn: objects) let set3 = MutableSet() set3.insert(objectsIn: objects2) XCTAssertTrue(set1 !== set2, "instances should not be identical") XCTAssertEqual(set1, set2, "instances should be equal by `==` operator") XCTAssertNotEqual(set1, set3, "instances should be equal by `==` operator") XCTAssertTrue(set1.isEqual(set2), "instances should be equal by `isEqual` method") XCTAssertTrue(!set1.isEqual(set3), "instances should be equal by `isEqual` method") XCTAssertEqual(Array(set1), Array(set2), "instances converted to Swift.Array should be equal") XCTAssertNotEqual(Array(set1), Array(set3), "instances converted to Swift.Array should be equal") XCTAssertEqual(Set(set1), Set(set2), "instances converted to Swift.Array should be equal") XCTAssertNotEqual(Set(set1), Set(set3), "instances converted to Swift.Array should be equal") set3.insert(obj3) XCTAssertEqual(set1, set3, "instances should be equal by `==` operator") } func testSubset() { guard let set = set, let set2 = set2, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.removeAll() set2.removeAll() XCTAssertEqual(Int(0), set.count) XCTAssertEqual(Int(0), set2.count) set.insert(objectsIn: [str1, str2, str1]) set2.insert(objectsIn: [str1]) XCTAssertEqual(Int(2), set.count) XCTAssertEqual(Int(1), set2.count) XCTAssertTrue(set2.isSubset(of: set)) XCTAssertFalse(set.isSubset(of: set2)) } func testUnion() { guard let set = set, let set2 = set2, let str1 = str1, let str2 = str2 else { fatalError("Test precondition failure") } set.removeAll() set2.removeAll() set.insert(objectsIn: [str1, str2, str1]) set2.insert(objectsIn: [str1]) XCTAssertEqual(Int(2), set.count) XCTAssertEqual(Int(1), set2.count) XCTAssertTrue(set2.isSubset(of: set)) XCTAssertFalse(set.isSubset(of: set2)) } func testIntersection() { guard let set = set, let set2 = set2, let str1 = str1, let str2 = str2, let str3 = str3 else { fatalError("Test precondition failure") } set.removeAll() set2.removeAll() set.insert(objectsIn: [str1, str2]) set2.insert(objectsIn: [str2, str3]) XCTAssertEqual(Int(2), set.count) XCTAssertEqual(Int(2), set2.count) XCTAssertTrue(set.intersects(set2)) XCTAssertTrue(set2.intersects(set)) set.formIntersection(set2) XCTAssertTrue(set.intersects(set2)) XCTAssertTrue(set2.intersects(set)) XCTAssertEqual(Int(1), set.count) } func testSubtract() { guard let set = set, let set2 = set2, let str1 = str1, let str2 = str2, let str3 = str3 else { fatalError("Test precondition failure") } set.removeAll() set2.removeAll() set.insert(objectsIn: [str1, str2]) set2.insert(objectsIn: [str2, str3]) XCTAssertEqual(Int(2), set.count) XCTAssertEqual(Int(2), set2.count) set.subtract(set2) XCTAssertEqual(Int(1), set.count) XCTAssertTrue(set.contains(str1)) } } class MutableSetStandaloneTests: MutableSetTests { override func createSet() -> SwiftMutableSetPropertyObject { let set = SwiftMutableSetPropertyObject() XCTAssertNil(set.realm) return set } override func createSetWithLinks() -> SwiftMutableSetOfSwiftObject { let set = SwiftMutableSetOfSwiftObject() XCTAssertNil(set.realm) return set } } class MutableSetNewlyAddedTests: MutableSetTests { override func createSet() -> SwiftMutableSetPropertyObject { let set = SwiftMutableSetPropertyObject() set.name = "name" let realm = realmWithTestPath() try! realm.write { realm.add(set) } XCTAssertNotNil(set.realm) return set } override func createSetWithLinks() -> SwiftMutableSetOfSwiftObject { let set = SwiftMutableSetOfSwiftObject() let realm = try! Realm() try! realm.write { realm.add(set) } XCTAssertNotNil(set.realm) return set } } class MutableSetNewlyCreatedTests: MutableSetTests { override func createSet() -> SwiftMutableSetPropertyObject { let realm = realmWithTestPath() realm.beginWrite() let set = realm.create(SwiftMutableSetPropertyObject.self, value: ["name"]) try! realm.commitWrite() XCTAssertNotNil(set.realm) return set } override func createSetWithLinks() -> SwiftMutableSetOfSwiftObject { let realm = try! Realm() realm.beginWrite() let set = realm.create(SwiftMutableSetOfSwiftObject.self) try! realm.commitWrite() XCTAssertNotNil(set.realm) return set } } class MutableSetRetrievedTests: MutableSetTests { override func createSet() -> SwiftMutableSetPropertyObject { let realm = realmWithTestPath() realm.beginWrite() realm.create(SwiftMutableSetPropertyObject.self, value: ["name"]) try! realm.commitWrite() let set = realm.objects(SwiftMutableSetPropertyObject.self).last! XCTAssertNotNil(set.realm) return set } override func createSetWithLinks() -> SwiftMutableSetOfSwiftObject { let realm = try! Realm() realm.beginWrite() realm.create(SwiftMutableSetOfSwiftObject.self) try! realm.commitWrite() let set = realm.objects(SwiftMutableSetOfSwiftObject.self).first! XCTAssertNotNil(set.realm) return set } } ================================================ FILE: RealmSwift/Tests/ObjectAccessorTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import Realm.Private import RealmSwift import Foundation @available(*, deprecated) // Silence deprecation warnings for RealmOptional class ObjectAccessorTests: TestCase { func setAndTestAllPropertiesViaNormalAccess(_ object: SwiftObject, _ optObject: SwiftOptionalObject) { object.boolCol = true XCTAssertEqual(object.boolCol, true) object.boolCol = false XCTAssertEqual(object.boolCol, false) object.intCol = -1 XCTAssertEqual(object.intCol, -1) object.intCol = 0 XCTAssertEqual(object.intCol, 0) object.intCol = 1 XCTAssertEqual(object.intCol, 1) object.int8Col = -1 XCTAssertEqual(object.int8Col, -1) object.int8Col = 0 XCTAssertEqual(object.int8Col, 0) object.int8Col = 1 XCTAssertEqual(object.int8Col, 1) object.int16Col = -1 XCTAssertEqual(object.int16Col, -1) object.int16Col = 0 XCTAssertEqual(object.int16Col, 0) object.int16Col = 1 XCTAssertEqual(object.int16Col, 1) object.int32Col = -1 XCTAssertEqual(object.int32Col, -1) object.int32Col = 0 XCTAssertEqual(object.int32Col, 0) object.int32Col = 1 XCTAssertEqual(object.int32Col, 1) object.int64Col = -1 XCTAssertEqual(object.int64Col, -1) object.int64Col = 0 XCTAssertEqual(object.int64Col, 0) object.int64Col = 1 XCTAssertEqual(object.int64Col, 1) object.floatCol = 20 XCTAssertEqual(object.floatCol, 20 as Float) object.floatCol = 20.2 XCTAssertEqual(object.floatCol, 20.2 as Float) object.floatCol = 16777217 XCTAssertEqual(Double(object.floatCol), 16777216.0 as Double) object.doubleCol = 20 XCTAssertEqual(object.doubleCol, 20) object.doubleCol = 20.2 XCTAssertEqual(object.doubleCol, 20.2) object.doubleCol = 16777217 XCTAssertEqual(object.doubleCol, 16777217) object.stringCol = "" XCTAssertEqual(object.stringCol, "") let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" object.stringCol = utf8TestString XCTAssertEqual(object.stringCol, utf8TestString) let data = "b".data(using: String.Encoding.utf8, allowLossyConversion: false)! object.binaryCol = data XCTAssertEqual(object.binaryCol, data) let date = Date(timeIntervalSinceReferenceDate: 2) object.dateCol = date XCTAssertEqual(object.dateCol, date) object.objectCol = SwiftBoolObject(value: [true]) XCTAssertEqual(object.objectCol!.boolCol, true) object.intEnumCol = .value1 XCTAssertEqual(object.intEnumCol, .value1) object.intEnumCol = .value2 XCTAssertEqual(object.intEnumCol, .value2) object.decimalCol = "inf" XCTAssertEqual(object.decimalCol, "inf") object.decimalCol = "-inf" XCTAssertEqual(object.decimalCol, "-inf") object.decimalCol = "0" XCTAssertEqual(object.decimalCol, "0") object.decimalCol = "nan" XCTAssertTrue(object.decimalCol.isNaN) let oid1 = ObjectId("1234567890ab1234567890ab") let oid2 = ObjectId("abcdef123456abcdef123456") object.objectIdCol = oid1 XCTAssertEqual(object.objectIdCol, oid1) object.objectIdCol = oid2 XCTAssertEqual(object.objectIdCol, oid2) object.anyCol.value = .string("hello") XCTAssertEqual(object.anyCol.value.stringValue, "hello") // Optional properties optObject.optNSStringCol = "" XCTAssertEqual(optObject.optNSStringCol!, "") optObject.optNSStringCol = utf8TestString as NSString? XCTAssertEqual(optObject.optNSStringCol! as String, utf8TestString) optObject.optNSStringCol = nil XCTAssertNil(optObject.optNSStringCol) optObject.optStringCol = "" XCTAssertEqual(optObject.optStringCol!, "") optObject.optStringCol = utf8TestString XCTAssertEqual(optObject.optStringCol!, utf8TestString) optObject.optStringCol = nil XCTAssertNil(optObject.optStringCol) optObject.optBinaryCol = data XCTAssertEqual(optObject.optBinaryCol!, data) optObject.optBinaryCol = nil XCTAssertNil(optObject.optBinaryCol) optObject.optDateCol = date XCTAssertEqual(optObject.optDateCol!, date) optObject.optDateCol = nil XCTAssertNil(optObject.optDateCol) optObject.optIntCol.value = Int.min XCTAssertEqual(optObject.optIntCol.value!, Int.min) optObject.optIntCol.value = 0 XCTAssertEqual(optObject.optIntCol.value!, 0) optObject.optIntCol.value = Int.max XCTAssertEqual(optObject.optIntCol.value!, Int.max) optObject.optIntCol.value = nil XCTAssertNil(optObject.optIntCol.value) optObject.optInt8Col.value = Int8.min XCTAssertEqual(optObject.optInt8Col.value!, Int8.min) optObject.optInt8Col.value = 0 XCTAssertEqual(optObject.optInt8Col.value!, 0) optObject.optInt8Col.value = Int8.max XCTAssertEqual(optObject.optInt8Col.value!, Int8.max) optObject.optInt8Col.value = nil XCTAssertNil(optObject.optInt8Col.value) optObject.optInt16Col.value = Int16.min XCTAssertEqual(optObject.optInt16Col.value!, Int16.min) optObject.optInt16Col.value = 0 XCTAssertEqual(optObject.optInt16Col.value!, 0) optObject.optInt16Col.value = Int16.max XCTAssertEqual(optObject.optInt16Col.value!, Int16.max) optObject.optInt16Col.value = nil XCTAssertNil(optObject.optInt16Col.value) optObject.optInt32Col.value = Int32.min XCTAssertEqual(optObject.optInt32Col.value!, Int32.min) optObject.optInt32Col.value = 0 XCTAssertEqual(optObject.optInt32Col.value!, 0) optObject.optInt32Col.value = Int32.max XCTAssertEqual(optObject.optInt32Col.value!, Int32.max) optObject.optInt32Col.value = nil XCTAssertNil(optObject.optInt32Col.value) optObject.optInt64Col.value = Int64.min XCTAssertEqual(optObject.optInt64Col.value!, Int64.min) optObject.optInt64Col.value = 0 XCTAssertEqual(optObject.optInt64Col.value!, 0) optObject.optInt64Col.value = Int64.max XCTAssertEqual(optObject.optInt64Col.value!, Int64.max) optObject.optInt64Col.value = nil XCTAssertNil(optObject.optInt64Col.value) optObject.optFloatCol.value = -Float.greatestFiniteMagnitude XCTAssertEqual(optObject.optFloatCol.value!, -Float.greatestFiniteMagnitude) optObject.optFloatCol.value = 0 XCTAssertEqual(optObject.optFloatCol.value!, 0) optObject.optFloatCol.value = Float.greatestFiniteMagnitude XCTAssertEqual(optObject.optFloatCol.value!, Float.greatestFiniteMagnitude) optObject.optFloatCol.value = nil XCTAssertNil(optObject.optFloatCol.value) optObject.optDoubleCol.value = -Double.greatestFiniteMagnitude XCTAssertEqual(optObject.optDoubleCol.value!, -Double.greatestFiniteMagnitude) optObject.optDoubleCol.value = 0 XCTAssertEqual(optObject.optDoubleCol.value!, 0) optObject.optDoubleCol.value = Double.greatestFiniteMagnitude XCTAssertEqual(optObject.optDoubleCol.value!, Double.greatestFiniteMagnitude) optObject.optDoubleCol.value = nil XCTAssertNil(optObject.optDoubleCol.value) optObject.optBoolCol.value = true XCTAssertEqual(optObject.optBoolCol.value!, true) optObject.optBoolCol.value = false XCTAssertEqual(optObject.optBoolCol.value!, false) optObject.optBoolCol.value = nil XCTAssertNil(optObject.optBoolCol.value) optObject.optObjectCol = SwiftBoolObject(value: [true]) XCTAssertEqual(optObject.optObjectCol!.boolCol, true) optObject.optObjectCol = nil XCTAssertNil(optObject.optObjectCol) optObject.optEnumCol.value = .value1 XCTAssertEqual(optObject.optEnumCol.value, .value1) optObject.optEnumCol.value = .value2 XCTAssertEqual(optObject.optEnumCol.value, .value2) optObject.optEnumCol.value = nil XCTAssertNil(optObject.optEnumCol.value) } func setAndTestAllPropertiesViaSubscript(_ object: SwiftObject, _ optObject: SwiftOptionalObject) { object["boolCol"] = true XCTAssertEqual(object["boolCol"] as! Bool, true) object["boolCol"] = false XCTAssertEqual(object["boolCol"] as! Bool, false) object["intCol"] = -1 XCTAssertEqual(object["intCol"] as! Int, -1) object["intCol"] = 0 XCTAssertEqual(object["intCol"] as! Int, 0) object["intCol"] = 1 XCTAssertEqual(object["intCol"] as! Int, 1) object["int8Col"] = -1 XCTAssertEqual(object["int8Col"] as! Int, -1) object["int8Col"] = 0 XCTAssertEqual(object["int8Col"] as! Int, 0) object["int8Col"] = 1 XCTAssertEqual(object["int8Col"] as! Int, 1) object["int16Col"] = -1 XCTAssertEqual(object["int16Col"] as! Int, -1) object["int16Col"] = 0 XCTAssertEqual(object["int16Col"] as! Int, 0) object["int16Col"] = 1 XCTAssertEqual(object["int16Col"] as! Int, 1) object["int32Col"] = -1 XCTAssertEqual(object["int32Col"] as! Int, -1) object["int32Col"] = 0 XCTAssertEqual(object["int32Col"] as! Int, 0) object["int32Col"] = 1 XCTAssertEqual(object["int32Col"] as! Int, 1) object["int64Col"] = -1 XCTAssertEqual(object["int64Col"] as! Int, -1) object["int64Col"] = 0 XCTAssertEqual(object["int64Col"] as! Int, 0) object["int64Col"] = 1 XCTAssertEqual(object["int64Col"] as! Int, 1) object["floatCol"] = 20 XCTAssertEqual(object["floatCol"] as! Float, 20 as Float) object["floatCol"] = 20.2 XCTAssertEqual(object["floatCol"] as! Float, 20.2 as Float) object["floatCol"] = 16777217 XCTAssertEqual(object["floatCol"] as! Float, 16777216 as Float) object["doubleCol"] = 20 XCTAssertEqual(object["doubleCol"] as! Double, 20) object["doubleCol"] = 20.2 XCTAssertEqual(object["doubleCol"] as! Double, 20.2) object["doubleCol"] = 16777217 XCTAssertEqual(object["doubleCol"] as! Double, 16777217) object["stringCol"] = "" XCTAssertEqual(object["stringCol"] as! String, "") let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" object["stringCol"] = utf8TestString XCTAssertEqual(object["stringCol"] as! String, utf8TestString) let data = "b".data(using: String.Encoding.utf8, allowLossyConversion: false)! object["binaryCol"] = data XCTAssertEqual(object["binaryCol"] as! Data, data) let date = Date(timeIntervalSinceReferenceDate: 2) object["dateCol"] = date XCTAssertEqual(object["dateCol"] as! Date, date) object["objectCol"] = SwiftBoolObject(value: [true]) XCTAssertEqual((object["objectCol"]! as! SwiftBoolObject).boolCol, true) object["intEnumCol"] = IntEnum.value1 XCTAssertEqual(object["intEnumCol"] as! Int, IntEnum.value1.rawValue) object["intEnumCol"] = IntEnum.value2 XCTAssertEqual(object["intEnumCol"] as! Int, IntEnum.value2.rawValue) object["decimalCol"] = Decimal128("inf") XCTAssertEqual(object["decimalCol"] as! Decimal128, "inf") object["decimalCol"] = Decimal128("-inf") XCTAssertEqual(object["decimalCol"] as! Decimal128, "-inf") object["decimalCol"] = Decimal128("0") XCTAssertEqual(object["decimalCol"] as! Decimal128, "0") object["decimalCol"] = Decimal128("nan") XCTAssertTrue((object["decimalCol"] as! Decimal128).isNaN) let oid1 = ObjectId("1234567890ab1234567890ab") let oid2 = ObjectId("abcdef123456abcdef123456") object["objectIdCol"] = oid1 XCTAssertEqual(object["objectIdCol"] as! ObjectId, oid1) object["objectIdCol"] = oid2 XCTAssertEqual(object["objectIdCol"] as! ObjectId, oid2) object["anyCol"] = AnyRealmValue.string("hello") XCTAssertEqual(object["anyCol"] as! String, "hello") object["anyCol"] = "goodbye" XCTAssertEqual(object["anyCol"] as! String, "goodbye") // Optional properties optObject["optNSStringCol"] = "" XCTAssertEqual(optObject["optNSStringCol"] as! String, "") optObject["optNSStringCol"] = utf8TestString as NSString? XCTAssertEqual(optObject["optNSStringCol"] as! String, utf8TestString) optObject["optNSStringCol"] = nil XCTAssertNil(optObject["optNSStringCol"]) optObject["optStringCol"] = "" XCTAssertEqual(optObject["optStringCol"] as! String, "") optObject["optStringCol"] = utf8TestString XCTAssertEqual(optObject["optStringCol"] as! String, utf8TestString) optObject["optStringCol"] = nil XCTAssertNil(optObject["optStringCol"]) optObject["optBinaryCol"] = data XCTAssertEqual(optObject["optBinaryCol"] as! Data, data) optObject["optBinaryCol"] = nil XCTAssertNil(optObject["optBinaryCol"]) optObject["optDateCol"] = date XCTAssertEqual(optObject["optDateCol"] as! Date, date) optObject["optDateCol"] = nil XCTAssertNil(optObject["optDateCol"]) optObject["optIntCol"] = Int.min XCTAssertEqual(optObject["optIntCol"] as! Int, Int.min) optObject["optIntCol"] = 0 XCTAssertEqual(optObject["optIntCol"] as! Int, 0) optObject["optIntCol"] = Int.max XCTAssertEqual(optObject["optIntCol"] as! Int, Int.max) optObject["optIntCol"] = nil XCTAssertNil(optObject["optIntCol"]) optObject["optInt8Col"] = Int8.min XCTAssertEqual(optObject["optInt8Col"] as! Int8, Int8.min) optObject["optInt8Col"] = 0 XCTAssertEqual(optObject["optInt8Col"] as! Int8, 0) optObject["optInt8Col"] = Int8.max XCTAssertEqual(optObject["optInt8Col"] as! Int8, Int8.max) optObject["optInt8Col"] = nil XCTAssertNil(optObject["optInt8Col"]) optObject["optInt16Col"] = Int16.min XCTAssertEqual(optObject["optInt16Col"] as! Int16, Int16.min) optObject["optInt16Col"] = 0 XCTAssertEqual(optObject["optInt16Col"] as! Int16, 0) optObject["optInt16Col"] = Int16.max XCTAssertEqual(optObject["optInt16Col"] as! Int16, Int16.max) optObject["optInt16Col"] = nil XCTAssertNil(optObject["optInt16Col"]) optObject["optInt32Col"] = Int32.min XCTAssertEqual(optObject["optInt32Col"] as! Int32, Int32.min) optObject["optInt32Col"] = 0 XCTAssertEqual(optObject["optInt32Col"] as! Int32, 0) optObject["optInt32Col"] = Int32.max XCTAssertEqual(optObject["optInt32Col"] as! Int32, Int32.max) optObject["optInt32Col"] = nil XCTAssertNil(optObject["optInt32Col"]) optObject["optInt64Col"] = Int64.min XCTAssertEqual(optObject["optInt64Col"] as! Int64, Int64.min) optObject["optInt64Col"] = 0 XCTAssertEqual(optObject["optInt64Col"] as! Int64, 0) optObject["optInt64Col"] = Int64.max XCTAssertEqual(optObject["optInt64Col"] as! Int64, Int64.max) optObject["optInt64Col"] = nil XCTAssertNil(optObject["optInt64Col"]) optObject["optFloatCol"] = -Float.greatestFiniteMagnitude XCTAssertEqual(optObject["optFloatCol"] as! Float, -Float.greatestFiniteMagnitude) optObject["optFloatCol"] = 0 XCTAssertEqual(optObject["optFloatCol"] as! Float, 0) optObject["optFloatCol"] = Float.greatestFiniteMagnitude XCTAssertEqual(optObject["optFloatCol"] as! Float, Float.greatestFiniteMagnitude) optObject["optFloatCol"] = nil XCTAssertNil(optObject["optFloatCol"]) optObject["optDoubleCol"] = -Double.greatestFiniteMagnitude XCTAssertEqual(optObject["optDoubleCol"] as! Double, -Double.greatestFiniteMagnitude) optObject["optDoubleCol"] = 0 XCTAssertEqual(optObject["optDoubleCol"] as! Double, 0) optObject["optDoubleCol"] = Double.greatestFiniteMagnitude XCTAssertEqual(optObject["optDoubleCol"] as! Double, Double.greatestFiniteMagnitude) optObject["optDoubleCol"] = nil XCTAssertNil(optObject["optDoubleCol"]) optObject["optBoolCol"] = true XCTAssertEqual(optObject["optBoolCol"] as! Bool, true) optObject["optBoolCol"] = false XCTAssertEqual(optObject["optBoolCol"] as! Bool, false) optObject["optBoolCol"] = nil XCTAssertNil(optObject["optBoolCol"]) optObject["optObjectCol"] = SwiftBoolObject(value: [true]) XCTAssertEqual((optObject["optObjectCol"] as! SwiftBoolObject).boolCol, true) optObject["optObjectCol"] = nil XCTAssertNil(optObject["optObjectCol"]) optObject["optEnumCol"] = IntEnum.value1 XCTAssertEqual(optObject["optEnumCol"] as! Int, IntEnum.value1.rawValue) optObject["optEnumCol"] = IntEnum.value2 XCTAssertEqual(optObject["optEnumCol"] as! Int, IntEnum.value2.rawValue) optObject["optEnumCol"] = nil XCTAssertNil(optObject["optEnumCol"]) } func setAndTestAnyViaAccessorObjectWithCoercion(_ object: ObjectBase) { let anyProp = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == "anyCol" }! func get() -> Any { return anyProp.swiftAccessor!.get(anyProp, on: object) } func set(_ value: Any) { anyProp.swiftAccessor!.set(anyProp, on: object, to: value) } set(true) XCTAssertEqual(get() as! Bool, true) set(false) XCTAssertEqual(get() as! Bool, false) set(-1) XCTAssertEqual(get() as! Int, -1) set(0) XCTAssertEqual(get() as! Int, 0) set(1) XCTAssertEqual(get() as! Int, 1) set(-1 as Int8) XCTAssertEqual(get() as! Int, -1) set(0 as Int8) XCTAssertEqual(get() as! Int, 0) set(1 as Int8) XCTAssertEqual(get() as! Int, 1) set(-1 as Int16) XCTAssertEqual(get() as! Int, -1) set(0 as Int16) XCTAssertEqual(get() as! Int, 0) set(1 as Int16) XCTAssertEqual(get() as! Int, 1) set(-1 as Int32) XCTAssertEqual(get() as! Int, -1) set(0 as Int32) XCTAssertEqual(get() as! Int, 0) set(1 as Int32) XCTAssertEqual(get() as! Int, 1) set(-1 as Int64) XCTAssertEqual(get() as! Int, -1) set(0 as Int64) XCTAssertEqual(get() as! Int, 0) set(1 as Int64) XCTAssertEqual(get() as! Int, 1) set(20 as Float) XCTAssertEqual(get() as! Float, 20 as Float) set(20.2 as Float) XCTAssertEqual(get() as! Float, 20.2 as Float) set(20 as Double) XCTAssertEqual(get() as! Double, 20) set(20.2 as Double) XCTAssertEqual(get() as! Double, 20.2) set(16777217 as Double) XCTAssertEqual(get() as! Double, 16777217) set("") XCTAssertEqual(get() as! String, "") let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" set(utf8TestString) XCTAssertEqual(get() as! String, utf8TestString) let data = "b".data(using: String.Encoding.utf8, allowLossyConversion: false)! set(data) XCTAssertEqual(get() as! Data, data) let date = Date(timeIntervalSinceReferenceDate: 2) set(date) XCTAssertEqual(get() as! Date, date) set(SwiftBoolObject(value: [true])) XCTAssertEqual((get() as! SwiftBoolObject).boolCol, true) set(Decimal128("inf")) XCTAssertEqual(get() as! Decimal128, "inf") set(Decimal128("-inf")) XCTAssertEqual(get() as! Decimal128, "-inf") set(Decimal128("0")) XCTAssertEqual(get() as! Decimal128, "0") set(Decimal128("nan")) XCTAssertTrue((get() as! Decimal128).isNaN) let oid1 = ObjectId("1234567890ab1234567890ab") let oid2 = ObjectId("abcdef123456abcdef123456") set(oid1) XCTAssertEqual(get() as! ObjectId, oid1) set(oid2) XCTAssertEqual(get() as! ObjectId, oid2) set(NSNull()) XCTAssertTrue(get() is NSNull) } func setAndTestAnyViaAccessorObjectWithExplicitAnyRealmValue(_ object: ObjectBase) { let anyProp = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == "anyCol" }! func get() -> Any { return anyProp.swiftAccessor!.get(anyProp, on: object) } func set(_ value: AnyRealmValue) { anyProp.swiftAccessor!.set(anyProp, on: object, to: value) } set(.bool(true)) XCTAssertEqual(get() as! Bool, true) set(.bool(false)) XCTAssertEqual(get() as! Bool, false) set(.int(-1)) XCTAssertEqual(get() as! Int, -1) set(.int(0)) XCTAssertEqual(get() as! Int, 0) set(.int(1)) XCTAssertEqual(get() as! Int, 1) set(.float(20)) XCTAssertEqual(get() as! Float, 20 as Float) set(.float(20.2)) XCTAssertEqual(get() as! Float, 20.2 as Float) set(.double(20)) XCTAssertEqual(get() as! Double, 20) set(.double(20.2)) XCTAssertEqual(get() as! Double, 20.2) set(.double(16777217)) XCTAssertEqual(get() as! Double, 16777217) set(.string("")) XCTAssertEqual(get() as! String, "") let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" set(.string(utf8TestString)) XCTAssertEqual(get() as! String, utf8TestString) let data = "b".data(using: String.Encoding.utf8, allowLossyConversion: false)! set(.data(data)) XCTAssertEqual(get() as! Data, data) let date = Date(timeIntervalSinceReferenceDate: 2) set(.date(date)) XCTAssertEqual(get() as! Date, date) set(.object(SwiftBoolObject(value: [true]))) XCTAssertEqual((get() as! SwiftBoolObject).boolCol, true) set(.decimal128(Decimal128("inf"))) XCTAssertEqual(get() as! Decimal128, "inf") set(.decimal128(Decimal128("-inf"))) XCTAssertEqual(get() as! Decimal128, "-inf") set(.decimal128(Decimal128("0"))) XCTAssertEqual(get() as! Decimal128, "0") set(.decimal128(Decimal128("nan"))) XCTAssertTrue((get() as! Decimal128).isNaN) let oid1 = ObjectId("1234567890ab1234567890ab") let oid2 = ObjectId("abcdef123456abcdef123456") set(.objectId(oid1)) XCTAssertEqual(get() as! ObjectId, oid1) set(.objectId(oid2)) XCTAssertEqual(get() as! ObjectId, oid2) set(.none) XCTAssertTrue(get() is NSNull) } func get(_ object: ObjectBase, _ propertyName: String) -> Any { let prop = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == propertyName }! return prop.swiftAccessor!.get(prop, on: object) } func set(_ object: ObjectBase, _ propertyName: String, _ value: Any) { let prop = RLMObjectBaseObjectSchema(object)!.properties.first { $0.name == propertyName }! prop.swiftAccessor!.set(prop, on: object, to: value) } func setAndTestRealmPropertyViaAccessor(_ object: ObjectBase) { set(object, "otherInt", -1) XCTAssertEqual(get(object, "otherInt") as! Int, -1) set(object, "otherInt", 0) XCTAssertEqual(get(object, "otherInt") as! Int, 0) set(object, "otherInt", 1) XCTAssertEqual(get(object, "otherInt") as! Int, 1) set(object, "otherInt", NSNull()) XCTAssertTrue(get(object, "otherInt") is NSNull) set(object, "otherInt8", -1) XCTAssertEqual(get(object, "otherInt8") as! Int, -1) set(object, "otherInt8", 0) XCTAssertEqual(get(object, "otherInt8") as! Int, 0) set(object, "otherInt8", 1) XCTAssertEqual(get(object, "otherInt8") as! Int, 1) set(object, "otherInt16", -1) XCTAssertEqual(get(object, "otherInt16") as! Int, -1) set(object, "otherInt16", 0) XCTAssertEqual(get(object, "otherInt16") as! Int, 0) set(object, "otherInt16", 1) XCTAssertEqual(get(object, "otherInt16") as! Int, 1) set(object, "otherInt32", -1) XCTAssertEqual(get(object, "otherInt32") as! Int, -1) set(object, "otherInt32", 0) XCTAssertEqual(get(object, "otherInt32") as! Int, 0) set(object, "otherInt32", 1) XCTAssertEqual(get(object, "otherInt32") as! Int, 1) set(object, "otherInt64", -1) XCTAssertEqual(get(object, "otherInt64") as! Int, -1) set(object, "otherInt64", 0) XCTAssertEqual(get(object, "otherInt64") as! Int, 0) set(object, "otherInt64", 1) XCTAssertEqual(get(object, "otherInt64") as! Int, 1) set(object, "otherFloat", -20 as Float) XCTAssertEqual(get(object, "otherFloat") as! Float, -20) set(object, "otherFloat", 20.2 as Float) XCTAssertEqual(get(object, "otherFloat") as! Float, 20.2) // 16777217 is not exactly representable as a float set(object, "otherFloat", 16777217 as Double) XCTAssertEqual(get(object, "otherFloat") as! Float, 16777216) set(object, "otherDouble", -20 as Double) XCTAssertEqual(get(object, "otherDouble") as! Double, -20) set(object, "otherDouble", 20.2 as Double) XCTAssertEqual(get(object, "otherDouble") as! Double, 20.2) set(object, "otherDouble", 16777217 as Double) XCTAssertEqual(get(object, "otherDouble") as! Double, 16777217) set(object, "otherBool", true) XCTAssertEqual(get(object, "otherBool") as! Bool, true) set(object, "otherBool", false) XCTAssertEqual(get(object, "otherBool") as! Bool, false) set(object, "otherBool", 1) XCTAssertEqual(get(object, "otherBool") as! Bool, true) set(object, "otherBool", 0) XCTAssertEqual(get(object, "otherBool") as! Bool, false) set(object, "otherEnum", IntEnum.value1) XCTAssertEqual(get(object, "otherEnum") as! Int, IntEnum.value1.rawValue) set(object, "otherEnum", IntEnum.value2) XCTAssertEqual(get(object, "otherEnum") as! Int, IntEnum.value2.rawValue) set(object, "otherEnum", IntEnum.value1.rawValue) XCTAssertEqual(get(object, "otherEnum") as! Int, IntEnum.value1.rawValue) } func setAndTestRealmOptionalViaAccessor(_ object: ObjectBase) { set(object, "optIntCol", -1) XCTAssertEqual(get(object, "optIntCol") as! Int, -1) set(object, "optIntCol", 0) XCTAssertEqual(get(object, "optIntCol") as! Int, 0) set(object, "optIntCol", 1) XCTAssertEqual(get(object, "optIntCol") as! Int, 1) set(object, "optIntCol", NSNull()) XCTAssertTrue(get(object, "optIntCol") is NSNull) set(object, "optInt8Col", -1) XCTAssertEqual(get(object, "optInt8Col") as! Int, -1) set(object, "optInt8Col", 0) XCTAssertEqual(get(object, "optInt8Col") as! Int, 0) set(object, "optInt8Col", 1) XCTAssertEqual(get(object, "optInt8Col") as! Int, 1) set(object, "optInt16Col", -1) XCTAssertEqual(get(object, "optInt16Col") as! Int, -1) set(object, "optInt16Col", 0) XCTAssertEqual(get(object, "optInt16Col") as! Int, 0) set(object, "optInt16Col", 1) XCTAssertEqual(get(object, "optInt16Col") as! Int, 1) set(object, "optInt32Col", -1) XCTAssertEqual(get(object, "optInt32Col") as! Int, -1) set(object, "optInt32Col", 0) XCTAssertEqual(get(object, "optInt32Col") as! Int, 0) set(object, "optInt32Col", 1) XCTAssertEqual(get(object, "optInt32Col") as! Int, 1) set(object, "optInt64Col", -1) XCTAssertEqual(get(object, "optInt64Col") as! Int, -1) set(object, "optInt64Col", 0) XCTAssertEqual(get(object, "optInt64Col") as! Int, 0) set(object, "optInt64Col", 1) XCTAssertEqual(get(object, "optInt64Col") as! Int, 1) set(object, "optFloatCol", -20 as Float) XCTAssertEqual(get(object, "optFloatCol") as! Float, -20) set(object, "optFloatCol", 20.2 as Float) XCTAssertEqual(get(object, "optFloatCol") as! Float, 20.2) // 16777217 is not exactly representable as a float set(object, "optFloatCol", 16777217 as Double) XCTAssertEqual(get(object, "optFloatCol") as! Float, 16777216) set(object, "optDoubleCol", -20 as Double) XCTAssertEqual(get(object, "optDoubleCol") as! Double, -20) set(object, "optDoubleCol", 20.2 as Double) XCTAssertEqual(get(object, "optDoubleCol") as! Double, 20.2) set(object, "optDoubleCol", 16777217 as Double) XCTAssertEqual(get(object, "optDoubleCol") as! Double, 16777217) set(object, "optBoolCol", true) XCTAssertEqual(get(object, "optBoolCol") as! Bool, true) set(object, "optBoolCol", false) XCTAssertEqual(get(object, "optBoolCol") as! Bool, false) set(object, "optBoolCol", 1) XCTAssertEqual(get(object, "optBoolCol") as! Bool, true) set(object, "optBoolCol", 0) XCTAssertEqual(get(object, "optBoolCol") as! Bool, false) set(object, "optEnumCol", IntEnum.value1) XCTAssertEqual(get(object, "optEnumCol") as! Int, IntEnum.value1.rawValue) set(object, "optEnumCol", IntEnum.value2) XCTAssertEqual(get(object, "optEnumCol") as! Int, IntEnum.value2.rawValue) set(object, "optEnumCol", IntEnum.value1.rawValue) XCTAssertEqual(get(object, "optEnumCol") as! Int, IntEnum.value1.rawValue) set(object, "optEnumCol", NSNull()) XCTAssertTrue(get(object, "optEnumCol") is NSNull) } func setAndTestListViaAccessor(_ object: ObjectBase) { XCTAssertTrue(get(object, "int") is List) XCTAssertTrue(get(object, "int8") is List) XCTAssertTrue(get(object, "int16") is List) XCTAssertTrue(get(object, "int32") is List) XCTAssertTrue(get(object, "int64") is List) XCTAssertTrue(get(object, "float") is List) XCTAssertTrue(get(object, "double") is List) XCTAssertTrue(get(object, "string") is List) XCTAssertTrue(get(object, "data") is List) XCTAssertTrue(get(object, "date") is List) XCTAssertTrue(get(object, "decimal") is List) XCTAssertTrue(get(object, "objectId") is List) XCTAssertTrue(get(object, "uuid") is List) XCTAssertTrue(get(object, "any") is List) XCTAssertTrue(get(object, "intOpt") is List) XCTAssertTrue(get(object, "int8Opt") is List) XCTAssertTrue(get(object, "int16Opt") is List) XCTAssertTrue(get(object, "int32Opt") is List) XCTAssertTrue(get(object, "int64Opt") is List) XCTAssertTrue(get(object, "floatOpt") is List) XCTAssertTrue(get(object, "doubleOpt") is List) XCTAssertTrue(get(object, "stringOpt") is List) XCTAssertTrue(get(object, "dataOpt") is List) XCTAssertTrue(get(object, "dateOpt") is List) XCTAssertTrue(get(object, "decimalOpt") is List) XCTAssertTrue(get(object, "objectIdOpt") is List) XCTAssertTrue(get(object, "uuidOpt") is List) set(object, "int", [1, 2, 3]) XCTAssertEqual(Array(get(object, "int") as! List), [1, 2, 3]) set(object, "int", [4, 5, 6]) XCTAssertEqual(Array(get(object, "int") as! List), [4, 5, 6]) set(object, "int8", get(object, "int")) XCTAssertEqual(Array(get(object, "int8") as! List), [4, 5, 6]) set(object, "int", NSNull()) XCTAssertEqual((get(object, "int") as! List).count, 0) } func setAndTestSetViaAccessor(_ object: ObjectBase) { XCTAssertTrue(get(object, "int") is MutableSet) XCTAssertTrue(get(object, "int8") is MutableSet) XCTAssertTrue(get(object, "int16") is MutableSet) XCTAssertTrue(get(object, "int32") is MutableSet) XCTAssertTrue(get(object, "int64") is MutableSet) XCTAssertTrue(get(object, "float") is MutableSet) XCTAssertTrue(get(object, "double") is MutableSet) XCTAssertTrue(get(object, "string") is MutableSet) XCTAssertTrue(get(object, "data") is MutableSet) XCTAssertTrue(get(object, "date") is MutableSet) XCTAssertTrue(get(object, "decimal") is MutableSet) XCTAssertTrue(get(object, "objectId") is MutableSet) XCTAssertTrue(get(object, "uuid") is MutableSet) XCTAssertTrue(get(object, "any") is MutableSet) XCTAssertTrue(get(object, "intOpt") is MutableSet) XCTAssertTrue(get(object, "int8Opt") is MutableSet) XCTAssertTrue(get(object, "int16Opt") is MutableSet) XCTAssertTrue(get(object, "int32Opt") is MutableSet) XCTAssertTrue(get(object, "int64Opt") is MutableSet) XCTAssertTrue(get(object, "floatOpt") is MutableSet) XCTAssertTrue(get(object, "doubleOpt") is MutableSet) XCTAssertTrue(get(object, "stringOpt") is MutableSet) XCTAssertTrue(get(object, "dataOpt") is MutableSet) XCTAssertTrue(get(object, "dateOpt") is MutableSet) XCTAssertTrue(get(object, "decimalOpt") is MutableSet) XCTAssertTrue(get(object, "objectIdOpt") is MutableSet) XCTAssertTrue(get(object, "uuidOpt") is MutableSet) set(object, "int", [1, 2, 3]) XCTAssertEqual(Array(get(object, "int") as! MutableSet).sorted(), [1, 2, 3]) set(object, "int", [4, 5, 6]) XCTAssertEqual(Array(get(object, "int") as! MutableSet).sorted(), [4, 5, 6]) set(object, "int8", get(object, "int")) XCTAssertEqual(Array(get(object, "int8") as! MutableSet).sorted(), [4, 5, 6]) set(object, "int", NSNull()) XCTAssertEqual((get(object, "int") as! MutableSet).count, 0) } func setAndTestMapViaAccessor(_ object: ObjectBase) { XCTAssertTrue(get(object, "int") is Map) XCTAssertTrue(get(object, "int8") is Map) XCTAssertTrue(get(object, "int16") is Map) XCTAssertTrue(get(object, "int32") is Map) XCTAssertTrue(get(object, "int64") is Map) XCTAssertTrue(get(object, "float") is Map) XCTAssertTrue(get(object, "double") is Map) XCTAssertTrue(get(object, "string") is Map) XCTAssertTrue(get(object, "data") is Map) XCTAssertTrue(get(object, "date") is Map) XCTAssertTrue(get(object, "decimal") is Map) XCTAssertTrue(get(object, "objectId") is Map) XCTAssertTrue(get(object, "uuid") is Map) XCTAssertTrue(get(object, "any") is Map) XCTAssertTrue(get(object, "intOpt") is Map) XCTAssertTrue(get(object, "int8Opt") is Map) XCTAssertTrue(get(object, "int16Opt") is Map) XCTAssertTrue(get(object, "int32Opt") is Map) XCTAssertTrue(get(object, "int64Opt") is Map) XCTAssertTrue(get(object, "floatOpt") is Map) XCTAssertTrue(get(object, "doubleOpt") is Map) XCTAssertTrue(get(object, "stringOpt") is Map) XCTAssertTrue(get(object, "dataOpt") is Map) XCTAssertTrue(get(object, "dateOpt") is Map) XCTAssertTrue(get(object, "decimalOpt") is Map) XCTAssertTrue(get(object, "objectIdOpt") is Map) XCTAssertTrue(get(object, "uuidOpt") is Map) set(object, "int", ["one": 1, "two": 2, "three": 3]) XCTAssertEqual((get(object, "int") as! Map)["one"], 1) XCTAssertEqual((get(object, "int") as! Map)["two"], 2) XCTAssertEqual((get(object, "int") as! Map)["three"], 3) set(object, "int", NSNull()) XCTAssertEqual((get(object, "int") as! Map).count, 0) } func testUnmanagedAccessors() { setAndTestAllPropertiesViaNormalAccess(SwiftObject(), SwiftOptionalObject()) setAndTestAllPropertiesViaSubscript(SwiftObject(), SwiftOptionalObject()) setAndTestAnyViaAccessorObjectWithCoercion(SwiftObject()) setAndTestAnyViaAccessorObjectWithExplicitAnyRealmValue(SwiftObject()) setAndTestRealmPropertyViaAccessor(CodableObject()) setAndTestRealmOptionalViaAccessor(SwiftOptionalObject()) setAndTestListViaAccessor(SwiftListObject()) setAndTestSetViaAccessor(SwiftMutableSetObject()) setAndTestMapViaAccessor(SwiftMapObject()) } func testManagedAccessors() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftObject.self) let optionalObject = realm.create(SwiftOptionalObject.self) setAndTestAllPropertiesViaNormalAccess(object, optionalObject) setAndTestAllPropertiesViaSubscript(object, optionalObject) setAndTestAnyViaAccessorObjectWithCoercion(object) setAndTestAnyViaAccessorObjectWithExplicitAnyRealmValue(object) setAndTestRealmPropertyViaAccessor(realm.create(CodableObject.self)) setAndTestRealmOptionalViaAccessor(optionalObject) setAndTestListViaAccessor(realm.create(SwiftListObject.self)) setAndTestSetViaAccessor(realm.create(SwiftMutableSetObject.self)) setAndTestMapViaAccessor(realm.create(SwiftMapObject.self)) realm.cancelWrite() } func testIntSizes() { let realm = realmWithTestPath() let v8 = Int8(1) << 6 let v16 = Int16(1) << 12 let v32 = Int32(1) << 30 // 1 << 40 doesn't auto-promote to Int64 on 32-bit platforms let v64 = Int64(1) << 40 try! realm.write { let obj = SwiftAllIntSizesObject() let testObject: () -> Void = { obj.objectSchema.properties.map { $0.name }.forEach { obj[$0] = 0 } obj["int8"] = NSNumber(value: v8) XCTAssertEqual((obj["int8"]! as! Int), Int(v8)) obj["int16"] = NSNumber(value: v16) XCTAssertEqual((obj["int16"]! as! Int), Int(v16)) obj["int32"] = NSNumber(value: v32) XCTAssertEqual((obj["int32"]! as! Int), Int(v32)) obj["int64"] = NSNumber(value: v64) XCTAssertEqual((obj["int64"]! as! NSNumber), NSNumber(value: v64)) obj.objectSchema.properties.map { $0.name }.forEach { obj[$0] = 0 } obj.setValue(NSNumber(value: v8), forKey: "int8") XCTAssertEqual((obj.value(forKey: "int8")! as! Int), Int(v8)) obj.setValue(NSNumber(value: v16), forKey: "int16") XCTAssertEqual((obj.value(forKey: "int16")! as! Int), Int(v16)) obj.setValue(NSNumber(value: v32), forKey: "int32") XCTAssertEqual((obj.value(forKey: "int32")! as! Int), Int(v32)) obj.setValue(NSNumber(value: v64), forKey: "int64") XCTAssertEqual((obj.value(forKey: "int64")! as! NSNumber), NSNumber(value: v64)) obj.objectSchema.properties.map { $0.name }.forEach { obj[$0] = 0 } obj.int8 = v8 XCTAssertEqual(obj.int8, v8) obj.int16 = v16 XCTAssertEqual(obj.int16, v16) obj.int32 = v32 XCTAssertEqual(obj.int32, v32) obj.int64 = v64 XCTAssertEqual(obj.int64, v64) } testObject() realm.add(obj) testObject() } let obj = realm.objects(SwiftAllIntSizesObject.self).first! XCTAssertEqual(obj.int8, v8) XCTAssertEqual(obj.int16, v16) XCTAssertEqual(obj.int32, v32) XCTAssertEqual(obj.int64, v64) } func testLongType() { let longNumber: Int64 = 17179869184 let intNumber: Int64 = 2147483647 let negativeLongNumber: Int64 = -17179869184 let updatedLongNumber: Int64 = 8589934592 let realm = realmWithTestPath() realm.beginWrite() realm.create(SwiftLongObject.self, value: [NSNumber(value: longNumber)]) realm.create(SwiftLongObject.self, value: [NSNumber(value: intNumber)]) realm.create(SwiftLongObject.self, value: [NSNumber(value: negativeLongNumber)]) try! realm.commitWrite() let objects = realm.objects(SwiftLongObject.self) XCTAssertEqual(objects.count, Int(3), "3 rows expected") XCTAssertEqual(objects[0].longCol, longNumber, "2 ^ 34 expected") XCTAssertEqual(objects[1].longCol, intNumber, "2 ^ 31 - 1 expected") XCTAssertEqual(objects[2].longCol, negativeLongNumber, "-2 ^ 34 expected") realm.beginWrite() objects[0].longCol = updatedLongNumber try! realm.commitWrite() XCTAssertEqual(objects[0].longCol, updatedLongNumber, "After update: 2 ^ 33 expected") } func testCollectionsDuringResultsFastEnumeration() { let realm = realmWithTestPath() let object1 = SwiftObject() let object2 = SwiftObject() let trueObject = SwiftBoolObject() trueObject.boolCol = true let falseObject = SwiftBoolObject() falseObject.boolCol = false object1.arrayCol.append(trueObject) object1.arrayCol.append(falseObject) object2.arrayCol.append(trueObject) object2.arrayCol.append(falseObject) object1.setCol.insert(trueObject) object1.setCol.insert(falseObject) object2.setCol.insert(trueObject) object2.setCol.insert(falseObject) try! realm.write { realm.add(object1) realm.add(object2) } let objects = realm.objects(SwiftObject.self) let firstObject = objects.first XCTAssertEqual(2, firstObject!.arrayCol.count) XCTAssertEqual(2, firstObject!.setCol.count) let lastObject = objects.last XCTAssertEqual(2, lastObject!.arrayCol.count) XCTAssertEqual(2, lastObject!.setCol.count) var iterator = objects.makeIterator() let next = iterator.next()! XCTAssertEqual(next.arrayCol.count, 2) XCTAssertEqual(next.setCol.count, 2) for obj in objects { XCTAssertEqual(2, obj.arrayCol.count) XCTAssertEqual(2, obj.setCol.count) } } func testSettingOptionalPropertyOnDeletedObjectsThrows() { let realm = try! Realm() try! realm.write { let obj = realm.create(SwiftOptionalObject.self) let copy = realm.objects(SwiftOptionalObject.self).first! realm.delete(obj) self.assertThrows(copy.optIntCol.value = 1) self.assertThrows(copy.optIntCol.value = nil) self.assertThrows(obj.optIntCol.value = 1) self.assertThrows(obj.optIntCol.value = nil) } } func testLinkingObjectsDynamicGet() { let fido = SwiftDogObject() let owner = SwiftOwnerObject() owner.dog = fido owner.name = "JP" let realm = try! Realm() try! realm.write { realm.add([fido, owner]) } // Get the linking objects property via subscript. let dynamicOwners = fido["owners"] guard let owners = dynamicOwners else { XCTFail("Got an unexpected nil for fido[\"owners\"]") return } XCTAssertTrue(owners is LinkingObjects) // Make sure the results actually functions. guard let firstOwner = (owners as? LinkingObjects)?.first else { XCTFail("Was not able to get first owner") return } XCTAssertEqual(firstOwner.name, "JP") } func testRenamedProperties() { let obj = SwiftRenamedProperties1() obj.propA = 5 obj.propB = "a" let link = LinkToSwiftRenamedProperties1() link.linkA = obj link.array1.append(obj) link.set1.insert(obj) let realm = try! Realm() try! realm.write { realm.add(link) } XCTAssertEqual(obj.propA, 5) XCTAssertEqual(obj.propB, "a") XCTAssertTrue(link.linkA!.isSameObject(as: obj)) XCTAssertTrue(link.array1[0].isSameObject(as: obj)) XCTAssertTrue(link.set1.contains(obj)) XCTAssertTrue(obj.linking1[0].isSameObject(as: link)) XCTAssertEqual(obj["propA"]! as! Int, 5) XCTAssertEqual(obj["propB"]! as! String, "a") XCTAssertTrue((link["linkA"]! as! SwiftRenamedProperties1).isSameObject(as: obj)) XCTAssertTrue((link["array1"]! as! List)[0].isSameObject(as: obj)) XCTAssertTrue((link["set1"]! as! MutableSet).contains(obj)) XCTAssertTrue((obj["linking1"]! as! LinkingObjects)[0].isSameObject(as: link)) XCTAssertTrue(link.dynamicList("array1")[0].isSameObject(as: obj)) XCTAssertTrue(link.dynamicMutableSet("set1")[0].isSameObject(as: obj)) let obj2 = realm.objects(SwiftRenamedProperties2.self).first! let link2 = realm.objects(LinkToSwiftRenamedProperties2.self).first! XCTAssertEqual(obj2.propC, 5) XCTAssertEqual(obj2.propD, "a") XCTAssertTrue(link2.linkC!.isSameObject(as: obj)) XCTAssertTrue(link2.array2[0].isSameObject(as: obj)) XCTAssertTrue(link2.set2[0].isSameObject(as: obj)) XCTAssertTrue(obj2.linking1[0].isSameObject(as: link)) XCTAssertEqual(obj2["propC"]! as! Int, 5) XCTAssertEqual(obj2["propD"]! as! String, "a") XCTAssertTrue((link2["linkC"]! as! SwiftRenamedProperties1).isSameObject(as: obj)) XCTAssertTrue((link2["array2"]! as! List)[0].isSameObject(as: obj)) XCTAssertTrue((link2["set2"]! as! MutableSet)[0].isSameObject(as: obj)) XCTAssertTrue((obj2["linking1"]! as! LinkingObjects)[0].isSameObject(as: link)) XCTAssertTrue(link2.dynamicList("array2")[0].isSameObject(as: obj)) XCTAssertTrue(link2.dynamicMutableSet("set2")[0].isSameObject(as: obj)) } func testPropertiesOutlivingParentObject() { var optional: RealmOptional! var realmProperty: RealmProperty! var list: List! var set: MutableSet! let realm = try! Realm() try! realm.write { autoreleasepool { let optObject = realm.create(SwiftOptionalObject.self, value: ["optIntCol": 1, "otherIntCol": 1]) optional = optObject.optIntCol realmProperty = optObject.otherIntCol list = realm.create(SwiftListObject.self, value: ["int": [1]]).int set = realm.create(SwiftMutableSetObject.self, value: ["int": [1]]).int } } // Verify that we can still read the correct value XCTAssertEqual(optional.value, 1) XCTAssertEqual(realmProperty.value, 1) XCTAssertEqual(list.count, 1) XCTAssertEqual(list[0], 1) XCTAssertEqual(set.count, 1) XCTAssertEqual(set[0], 1) // Verify that we can modify the values via the standalone property objects and // have it properly update the parent try! realm.write { optional.value = 2 realmProperty.value = 2 list.append(2) set.insert(2) } XCTAssertEqual(optional.value, 2) XCTAssertEqual(realmProperty.value, 2) XCTAssertEqual(list.count, 2) XCTAssertEqual(list[0], 1) XCTAssertEqual(list[1], 2) XCTAssertEqual(set.count, 2) XCTAssertEqual(set[0], 1) XCTAssertEqual(set[1], 2) autoreleasepool { XCTAssertEqual(realm.objects(SwiftOptionalObject.self).first!.optIntCol.value, 2) XCTAssertEqual(realm.objects(SwiftOptionalObject.self).first!.otherIntCol.value, 2) XCTAssertEqual(Array(realm.objects(SwiftListObject.self).first!.int), [1, 2]) XCTAssertEqual(Array(realm.objects(SwiftMutableSetObject.self).first!.int), [1, 2]) } try! realm.write { optional.value = nil realmProperty.value = nil list.removeAll() set.removeAll() } XCTAssertEqual(optional.value, nil) XCTAssertEqual(realmProperty.value, nil) XCTAssertEqual(list.count, 0) XCTAssertEqual(set.count, 0) autoreleasepool { XCTAssertEqual(realm.objects(SwiftOptionalObject.self).first!.optIntCol.value, nil) XCTAssertEqual(realm.objects(SwiftOptionalObject.self).first!.otherIntCol.value, nil) XCTAssertEqual(Array(realm.objects(SwiftListObject.self).first!.int), []) XCTAssertEqual(Array(realm.objects(SwiftMutableSetObject.self).first!.int), []) } } func testSetEmbeddedLink() { let realm = try! Realm() realm.beginWrite() let parent = EmbeddedParentObject() realm.add(parent) let child1 = EmbeddedTreeObject1() parent.object = child1 XCTAssertEqual(child1.realm, realm) XCTAssertNoThrow(parent.object = child1) let child2 = EmbeddedTreeObject1() parent.object = child2 XCTAssertEqual(child1.realm, realm) XCTAssertTrue(child1.isInvalidated) let child3 = EmbeddedTreeObject1() parent.array.append(child3) assertThrows(parent.object = child3, reason: "Can't set link to existing managed embedded object") } } ================================================ FILE: RealmSwift/Tests/ObjectCreationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift import Realm.Private #if canImport(RealmTestSupport) import RealmSwiftTestSupport #endif class ObjectWithPrivateOptionals: Object { private var nilInt: Int? private var nilFloat: Float? private var nilString: String? private var int: Int? = 123 private var float: Float? = 1.23 private var string: String? = "123" @objc dynamic var value = 5 } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class ObjectCreationTests: TestCase { // MARK: - Init tests func testInitWithDefaults() { // test all properties are defaults let object = SwiftObject() XCTAssertNil(object.realm) // test defaults values verifySwiftObjectWithDictionaryLiteral(object, dictionary: SwiftObject.defaultValues(), boolObjectValue: false, boolObjectListValues: []) // test realm properties are nil for standalone XCTAssertNil(object.realm) XCTAssertNil(object.objectCol!.realm) XCTAssertNil(object.arrayCol.realm) XCTAssertNil(object.setCol.realm) XCTAssertNil(object.mapCol.realm) } func testInitWithOptionalWithoutDefaults() { let object = SwiftOptionalObject() for prop in object.objectSchema.properties { let value = object[prop.name] if let value = value as? RLMSwiftValueStorage { XCTAssertNil(RLMGetSwiftValueStorage(value)) } else { XCTAssertNil(value) } } } func testInitWithOptionalDefaults() { let object = SwiftOptionalDefaultValuesObject() verifySwiftOptionalObjectWithDictionaryLiteral(object, dictionary: SwiftOptionalDefaultValuesObject.defaultValues(), boolObjectValue: true) } func testInitWithDictionary() { // dictionary with all values specified let baselineValues: [String: Any] = ["boolCol": true, "intCol": 1, "int8Col": 1 as Int8, "int16Col": 1 as Int16, "int32Col": 1 as Int32, "int64Col": 1 as Int64, "floatCol": 1.1 as Float, "doubleCol": 11.1, "stringCol": "b", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 2), "decimalCol": 3 as Decimal128, "objectIdCol": ObjectId.generate(), "objectCol": SwiftBoolObject(value: [true]), "uuidCol": UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")!, "anyCol": AnyRealmValue.string("hello"), "arrayCol": [SwiftBoolObject(value: [true]), SwiftBoolObject()], "setCol": [SwiftBoolObject(value: [true]), SwiftBoolObject()], "mapCol": ["trueVal": SwiftBoolObject(value: [true]), "falseVal": SwiftBoolObject(value: [false])] ] // test with valid dictionary literals let props = try! Realm().schema["SwiftObject"]!.properties for propNum in 0..() -> T { return T() } let obj1: SwiftBoolObject = createObject() let obj2 = SwiftBoolObject() XCTAssertEqual(obj1.boolCol, obj2.boolCol, "object created via generic initializer should equal object created by calling initializer directly") } func testInitWithObjcName() { // Test that init doesn't crash going into non-swift init logic for renamed Swift classes. _ = SwiftObjcRenamedObject() _ = SwiftObjcArbitrarilyRenamedObject() } // MARK: - Creation tests func testCreateWithDefaults() { let realm = try! Realm() assertThrows(realm.create(SwiftObject.self), "Must be in write transaction") var object: SwiftObject! let objects = realm.objects(SwiftObject.self) XCTAssertEqual(0, objects.count) try! realm.write { // test create with all defaults object = realm.create(SwiftObject.self) return } verifySwiftObjectWithDictionaryLiteral(object, dictionary: SwiftObject.defaultValues(), boolObjectValue: false, boolObjectListValues: []) // test realm properties are populated correctly XCTAssertEqual(object.realm!, realm) XCTAssertEqual(object.objectCol!.realm!, realm) XCTAssertEqual(object.arrayCol.realm!, realm) } func testCreateWithOptionalWithoutDefaults() { let realm = try! Realm() try! realm.write { let object = realm.create(SwiftOptionalObject.self) for prop in object.objectSchema.properties { XCTAssertNil(object[prop.name]) } } } func testCreateWithOptionalDefaults() { let realm = try! Realm() try! realm.write { let object = realm.create(SwiftOptionalDefaultValuesObject.self) self.verifySwiftOptionalObjectWithDictionaryLiteral(object, dictionary: SwiftOptionalDefaultValuesObject.defaultValues(), boolObjectValue: true) } } func testCreateWithOptionalIgnoredProperties() { let realm = try! Realm() try! realm.write { let object = realm.create(SwiftOptionalIgnoredPropertiesObject.self) let properties = object.objectSchema.properties XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].name, "id") } } func testCreateWithDictionary() { // dictionary with all values specified let baselineValues: [String: Any] = [ "boolCol": true, "intCol": 1, "int8Col": 1 as Int8, "int16Col": 1 as Int16, "int32Col": 1 as Int32, "int64Col": 1 as Int64, "floatCol": 1.1 as Float, "doubleCol": 11.1, "stringCol": "b", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 2), "decimalCol": 3 as Decimal128, "objectIdCol": ObjectId.generate(), "objectCol": SwiftBoolObject(value: [true]), "arrayCol": [SwiftBoolObject(value: [true]), SwiftBoolObject()], "setCol": [SwiftBoolObject(value: [true]), SwiftBoolObject()], "mapCol": ["trueVal": ["boolCol": true], "falseVal": ["boolCol": false]], "anyCol": AnyRealmValue.double(10) ] // test with valid dictionary literals let realm = try! Realm() let props = realm.schema["SwiftObject"]!.properties for propNum in 0.. SwiftLinkToPrimaryStringObject in realm.create(SwiftLinkToPrimaryStringObject.self, value: ["primary", ["10", 10] as [Any], [["11", 11] as [Any]]]) return realm.create(SwiftLinkToPrimaryStringObject.self, value: otherRealmObject, update: .all) } XCTAssertNotEqual(otherRealmObject, object) // the object from the other realm should be copied into this realm XCTAssertEqual(realm.objects(SwiftLinkToPrimaryStringObject.self).count, 1) XCTAssertEqual(realm.objects(SwiftPrimaryStringObject.self).count, 4) } func testUpdateChangedWithObjectsFromAnotherRealm() throws { let testRealm = realmWithTestPath() let otherRealmObject = try testRealm.write { testRealm.create(SwiftLinkToPrimaryStringObject.self, value: ["primary", NSNull(), [["2", 2] as [Any], ["4", 4]]]) } let realm = try Realm() let object = try realm.write { () -> SwiftLinkToPrimaryStringObject in realm.create(SwiftLinkToPrimaryStringObject.self, value: ["primary", ["10", 10] as [Any], [["11", 11] as [Any]]]) return realm.create(SwiftLinkToPrimaryStringObject.self, value: otherRealmObject, update: .modified) } XCTAssertNotEqual(otherRealmObject, object) // the object from the other realm should be copied into this realm XCTAssertEqual(realm.objects(SwiftLinkToPrimaryStringObject.self).count, 1) XCTAssertEqual(realm.objects(SwiftPrimaryStringObject.self).count, 4) } func testCreateWithNSNullLinks() { let values: [String: Any] = [ "boolCol": true, "intCol": 1, "floatCol": 1.1, "doubleCol": 11.1, "stringCol": "b", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 2), "decimalCol": 3 as Decimal128, "objectIdCol": ObjectId.generate(), "objectCol": NSNull(), "arrayCol": NSNull(), "setCol": NSNull() ] let realm = realmWithTestPath() realm.beginWrite() let object = realm.create(SwiftObject.self, value: values) try! realm.commitWrite() XCTAssertNil(object.objectCol) XCTAssertEqual(object.arrayCol.count, 0) XCTAssertEqual(object.setCol.count, 0) } func testCreateWithObjcName() { let realm = try! Realm() try! realm.write { let object = realm.create(SwiftObjcRenamedObject.self) object.stringCol = "string" } XCTAssertEqual(realm.objects(SwiftObjcRenamedObject.self).count, 1) try! realm.write { realm.delete(realm.objects(SwiftObjcRenamedObject.self)) } } func testCreateWithDifferentObjcName() { let realm = try! Realm() try! realm.write { let object = realm.create(SwiftObjcArbitrarilyRenamedObject.self) object.boolCol = true } XCTAssertEqual(realm.objects(SwiftObjcArbitrarilyRenamedObject.self).count, 1) try! realm.write { realm.delete(realm.objects(SwiftObjcArbitrarilyRenamedObject.self)) } } func testCreateOrUpdateNil() { let realm = try! Realm() realm.beginWrite() // Create with all fields nil let object = realm.create(SwiftOptionalPrimaryObject.self, value: SwiftOptionalPrimaryObject(), update: .all) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) // Try to switch to non-nil let object2 = SwiftOptionalPrimaryObject() object2.optIntCol.value = 1 object2.optInt8Col.value = 1 object2.optInt16Col.value = 1 object2.optInt32Col.value = 1 object2.optInt64Col.value = 1 object2.optFloatCol.value = 1 object2.optDoubleCol.value = 1 object2.optBoolCol.value = true object2.optDateCol = Date() object2.optStringCol = "" object2.optNSStringCol = "" object2.optBinaryCol = Data() object2.optObjectCol = SwiftBoolObject() realm.create(SwiftOptionalPrimaryObject.self, value: object2, update: .all) XCTAssertNil(object.id.value) XCTAssertNotNil(object.optIntCol.value) XCTAssertNotNil(object.optInt8Col.value) XCTAssertNotNil(object.optInt16Col.value) XCTAssertNotNil(object.optInt32Col.value) XCTAssertNotNil(object.optInt64Col.value) XCTAssertNotNil(object.optBoolCol.value) XCTAssertNotNil(object.optFloatCol.value) XCTAssertNotNil(object.optDoubleCol.value) XCTAssertNotNil(object.optDateCol) XCTAssertNotNil(object.optStringCol) XCTAssertNotNil(object.optNSStringCol) XCTAssertNotNil(object.optBinaryCol) XCTAssertNotNil(object.optObjectCol) // Try to switch back to nil realm.create(SwiftOptionalPrimaryObject.self, value: SwiftOptionalPrimaryObject(), update: .all) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) realm.cancelWrite() } func testCreateOrUpdateModifiedNil() { let realm = try! Realm() realm.beginWrite() // Create with all fields nil let object = realm.create(SwiftOptionalPrimaryObject.self, value: SwiftOptionalPrimaryObject(), update: .modified) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) // Try to switch to non-nil let object2 = SwiftOptionalPrimaryObject() object2.optIntCol.value = 1 object2.optInt8Col.value = 1 object2.optInt16Col.value = 1 object2.optInt32Col.value = 1 object2.optInt64Col.value = 1 object2.optFloatCol.value = 1 object2.optDoubleCol.value = 1 object2.optBoolCol.value = true object2.optDateCol = Date() object2.optStringCol = "" object2.optNSStringCol = "" object2.optBinaryCol = Data() object2.optObjectCol = SwiftBoolObject() realm.create(SwiftOptionalPrimaryObject.self, value: object2, update: .modified) XCTAssertNil(object.id.value) XCTAssertNotNil(object.optIntCol.value) XCTAssertNotNil(object.optInt8Col.value) XCTAssertNotNil(object.optInt16Col.value) XCTAssertNotNil(object.optInt32Col.value) XCTAssertNotNil(object.optInt64Col.value) XCTAssertNotNil(object.optBoolCol.value) XCTAssertNotNil(object.optFloatCol.value) XCTAssertNotNil(object.optDoubleCol.value) XCTAssertNotNil(object.optDateCol) XCTAssertNotNil(object.optStringCol) XCTAssertNotNil(object.optNSStringCol) XCTAssertNotNil(object.optBinaryCol) XCTAssertNotNil(object.optObjectCol) // Try to switch back to nil realm.create(SwiftOptionalPrimaryObject.self, value: SwiftOptionalPrimaryObject(), update: .modified) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) realm.cancelWrite() } func testCreateOrUpdateDynamicUnmanagedType() { let realm = try! Realm() let unmanagedValue = SwiftOptionalPrimaryObject() // Shouldn't throw. realm.beginWrite() _ = realm.create(type(of: unmanagedValue), value: unmanagedValue, update: .modified) realm.cancelWrite() } func testCreateOrUpdateDynamicManagedType() { let realm = try! Realm() let managedValue = SwiftOptionalPrimaryObject() try! realm.write { realm.add(managedValue) } // Shouldn't throw. realm.beginWrite() _ = realm.create(type(of: managedValue), value: managedValue, update: .all) realm.cancelWrite() } func testCreateOrUpdateModifiedDynamicManagedType() { let realm = try! Realm() let managedValue = SwiftOptionalPrimaryObject() try! realm.write { realm.add(managedValue) } // Shouldn't throw. realm.beginWrite() _ = realm.create(type(of: managedValue), value: managedValue, update: .modified) realm.cancelWrite() } func testCreateOrUpdateWithMismatchedStaticAndDynamicTypes() { let realm = try! Realm() let obj: Object = SwiftOptionalPrimaryObject() try! realm.write { let obj2 = realm.create(type(of: obj), value: obj) XCTAssertEqual(obj2.objectSchema.className, "SwiftOptionalPrimaryObject") let obj3 = realm.create(type(of: obj), value: obj, update: .all) XCTAssertEqual(obj3.objectSchema.className, "SwiftOptionalPrimaryObject") } } func testDynamicCreateEmbeddedDirectly() { let realm = try! Realm() realm.beginWrite() assertThrows(realm.dynamicCreate("EmbeddedTreeObject1"), reasonMatching: "Embedded objects cannot be created directly") realm.cancelWrite() } func testCreateEmbeddedWithDictionary() { let realm = try! Realm() realm.beginWrite() let parent = realm.create(EmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) XCTAssertEqual(parent.object!.value, 5) XCTAssertEqual(parent.object!.child!.value, 6) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 7) XCTAssertEqual(parent.object!.children[1].value, 8) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 9) XCTAssertEqual(parent.array[1].value, 10) XCTAssertTrue(parent.isSameObject(as: parent.object!.parent1.first!)) XCTAssertTrue(parent.isSameObject(as: parent.array[0].parent2.first!)) XCTAssertTrue(parent.isSameObject(as: parent.array[1].parent2.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.child!.parent3.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.children[0].parent4.first!)) XCTAssertTrue(parent.object!.isSameObject(as: parent.object!.children[1].parent4.first!)) realm.cancelWrite() } func testCreateEmbeddedWithUnmanagedObjects() { let sourceObject = EmbeddedParentObject() sourceObject.object = .init(value: [5]) sourceObject.object!.child = .init(value: [6]) sourceObject.object!.children.append(.init(value: [7])) sourceObject.object!.children.append(.init(value: [8])) sourceObject.array.append(.init(value: [9])) sourceObject.array.append(.init(value: [10])) let realm = try! Realm() realm.beginWrite() let parent = realm.create(EmbeddedParentObject.self, value: sourceObject) XCTAssertNil(sourceObject.realm) XCTAssertEqual(parent.object!.value, 5) XCTAssertEqual(parent.object!.child!.value, 6) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 7) XCTAssertEqual(parent.object!.children[1].value, 8) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 9) XCTAssertEqual(parent.array[1].value, 10) realm.cancelWrite() } func testCreateEmbeddedFromManagedObjectInSameRealm() { let realm = try! Realm() realm.beginWrite() let parent = realm.create(EmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) let copy = realm.create(EmbeddedParentObject.self, value: parent) XCTAssertNotEqual(parent, copy) XCTAssertEqual(copy.object!.value, 5) XCTAssertEqual(copy.object!.child!.value, 6) XCTAssertEqual(copy.object!.children.count, 2) XCTAssertEqual(copy.object!.children[0].value, 7) XCTAssertEqual(copy.object!.children[1].value, 8) XCTAssertEqual(copy.array.count, 2) XCTAssertEqual(copy.array[0].value, 9) XCTAssertEqual(copy.array[1].value, 10) realm.cancelWrite() } func testCreateEmbeddedFromManagedObjectInDifferentRealm() { let realmA = realmWithTestPath() let realmB = try! Realm() realmA.beginWrite() let parent = realmA.create(EmbeddedParentObject.self, value: [ "object": ["value": 5, "child": ["value": 6], "children": [[7], [8]]] as [String: Any], "array": [[9], [10]] ]) try! realmA.commitWrite() realmB.beginWrite() let copy = realmB.create(EmbeddedParentObject.self, value: parent) XCTAssertNotEqual(parent, copy) XCTAssertEqual(copy.object!.value, 5) XCTAssertEqual(copy.object!.child!.value, 6) XCTAssertEqual(copy.object!.children.count, 2) XCTAssertEqual(copy.object!.children[0].value, 7) XCTAssertEqual(copy.object!.children[1].value, 8) XCTAssertEqual(copy.array.count, 2) XCTAssertEqual(copy.array[0].value, 9) XCTAssertEqual(copy.array[1].value, 10) realmB.cancelWrite() } func testCreateObjectWithNestedEmbeddedType() throws { let realm = try Realm() let obj = try realm.write { realm.create(ObjectWithNestedEmbeddedObject.self, value: [1, [2]]) } XCTAssertEqual(obj.value, 1) XCTAssertEqual(obj.inner!.value, 2) } // test null object // test null list // MARK: - Add tests func testAddWithExisingNestedObjects() { let realm = try! Realm() realm.beginWrite() let existingObject = realm.create(SwiftBoolObject.self) try! realm.commitWrite() realm.beginWrite() let object = SwiftObject(value: ["objectCol": existingObject]) realm.add(object) try! realm.commitWrite() XCTAssertNotNil(object.realm) assertEqual(object.objectCol, existingObject) } class EmbeddedObjectFactory { private var value = 0 var objects = [EmbeddedObject]() func create() -> T { let obj = T() obj.value = value value += 1 objects.append(obj) return obj } } func testAddEmbedded() { let objectFactory = EmbeddedObjectFactory() let parent = EmbeddedParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) XCTAssertEqual((object as! EmbeddedTreeObject).value, i) } XCTAssertEqual(parent.object!.value, 0) XCTAssertEqual(parent.object!.child!.value, 1) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 2) XCTAssertEqual(parent.object!.children[1].value, 3) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 4) XCTAssertEqual(parent.array[1].value, 5) realm.cancelWrite() } func testAddAndUpdateWithExisingNestedObjects() throws { let realm = try Realm() let existingObject = try realm.write { realm.create(SwiftPrimaryStringObject.self, value: ["primary", 1]) } let object = try realm.write { () -> SwiftLinkToPrimaryStringObject in let object = SwiftLinkToPrimaryStringObject(value: ["primary", ["primary", 2] as [Any]]) realm.add(object, update: .all) return object } XCTAssertNotNil(object.realm) XCTAssertEqual(object.object!, existingObject) // the existing object should be updated XCTAssertEqual(existingObject.intCol, 2) } func testAddAndUpdateEmbedded() { let objectFactory = EmbeddedObjectFactory() let parent = EmbeddedPrimaryParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let parent2 = EmbeddedPrimaryParentObject() parent2.object = objectFactory.create() parent2.object!.child = objectFactory.create() parent2.object!.children.append(objectFactory.create()) parent2.object!.children.append(objectFactory.create()) parent2.array.append(objectFactory.create()) parent2.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) realm.add(parent2, update: .all) // update all deletes the old embedded objects and creates new ones for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) if i < 6 { XCTAssertTrue(object.isInvalidated) } else { XCTAssertEqual((object as! EmbeddedTreeObject).value, i) } } XCTAssertTrue(parent.isSameObject(as: parent2)) XCTAssertEqual(parent.object!.value, 6) XCTAssertEqual(parent.object!.child!.value, 7) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 8) XCTAssertEqual(parent.object!.children[1].value, 9) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 10) XCTAssertEqual(parent.array[1].value, 11) realm.cancelWrite() } func testAddAndUpdateChangedWithExisingNestedObjects() { let realm = try! Realm() realm.beginWrite() let existingObject = realm.create(SwiftPrimaryStringObject.self, value: ["primary", 1]) try! realm.commitWrite() realm.beginWrite() let object = SwiftLinkToPrimaryStringObject(value: ["primary", ["primary", 2] as [Any]]) realm.add(object, update: .modified) try! realm.commitWrite() XCTAssertNotNil(object.realm) XCTAssertEqual(object.object!, existingObject) // the existing object should be updated XCTAssertEqual(existingObject.intCol, 2) } func testAddAndUpdateChangedEmbedded() { let objectFactory = EmbeddedObjectFactory() let parent = EmbeddedPrimaryParentObject() parent.object = objectFactory.create() parent.object!.child = objectFactory.create() parent.object!.children.append(objectFactory.create()) parent.object!.children.append(objectFactory.create()) parent.array.append(objectFactory.create()) parent.array.append(objectFactory.create()) let parent2 = EmbeddedPrimaryParentObject() parent2.object = objectFactory.create() parent2.object!.child = objectFactory.create() parent2.object!.children.append(objectFactory.create()) parent2.object!.children.append(objectFactory.create()) parent2.array.append(objectFactory.create()) parent2.array.append(objectFactory.create()) let realm = try! Realm() realm.beginWrite() realm.add(parent) realm.add(parent2, update: .modified) // update modified modifies the existing embedded objects for (i, object) in objectFactory.objects.enumerated() { XCTAssertEqual(object.realm, realm) XCTAssertEqual((object as! EmbeddedTreeObject).value, i < 6 ? i + 6 : i) } XCTAssertTrue(parent.isSameObject(as: parent2)) XCTAssertEqual(parent.object!.value, 6) XCTAssertEqual(parent.object!.child!.value, 7) XCTAssertEqual(parent.object!.children.count, 2) XCTAssertEqual(parent.object!.children[0].value, 8) XCTAssertEqual(parent.object!.children[1].value, 9) XCTAssertEqual(parent.array.count, 2) XCTAssertEqual(parent.array[0].value, 10) XCTAssertEqual(parent.array[1].value, 11) realm.cancelWrite() } func testAddObjectCycle() { weak var weakObj1: SwiftCircleObject?, weakObj2: SwiftCircleObject? autoreleasepool { let obj1 = SwiftCircleObject(value: []) let obj2 = SwiftCircleObject(value: [obj1, [obj1]]) obj1.obj = obj2 obj1.array.append(obj2) weakObj1 = obj1 weakObj2 = obj2 let realm = try! Realm() try! realm.write { realm.add(obj1) } XCTAssertEqual(obj1.realm, realm) XCTAssertEqual(obj2.realm, realm) } XCTAssertNil(weakObj1) XCTAssertNil(weakObj2) } func testAddOrUpdateNil() { let realm = try! Realm() realm.beginWrite() // Create with all fields nil let object = SwiftOptionalPrimaryObject() realm.add(object) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) // Try to switch to non-nil let object2 = SwiftOptionalPrimaryObject() object2.optIntCol.value = 1 object2.optInt8Col.value = 1 object2.optInt16Col.value = 1 object2.optInt32Col.value = 1 object2.optInt64Col.value = 1 object2.optFloatCol.value = 1 object2.optDoubleCol.value = 1 object2.optBoolCol.value = true object2.optDateCol = Date() object2.optStringCol = "" object2.optNSStringCol = "" object2.optBinaryCol = Data() object2.optObjectCol = SwiftBoolObject() realm.add(object2, update: .all) XCTAssertNil(object.id.value) XCTAssertNotNil(object.optIntCol.value) XCTAssertNotNil(object.optInt8Col.value) XCTAssertNotNil(object.optInt16Col.value) XCTAssertNotNil(object.optInt32Col.value) XCTAssertNotNil(object.optInt64Col.value) XCTAssertNotNil(object.optBoolCol.value) XCTAssertNotNil(object.optFloatCol.value) XCTAssertNotNil(object.optDoubleCol.value) XCTAssertNotNil(object.optDateCol) XCTAssertNotNil(object.optStringCol) XCTAssertNotNil(object.optNSStringCol) XCTAssertNotNil(object.optBinaryCol) XCTAssertNotNil(object.optObjectCol) // Try to switch back to nil let object3 = SwiftOptionalPrimaryObject() realm.add(object3, update: .all) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) realm.cancelWrite() } func testAddOrUpdateModifiedNil() { let realm = try! Realm() realm.beginWrite() // Create with all fields nil let object = SwiftOptionalPrimaryObject() realm.add(object) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) // Try to switch to non-nil let object2 = SwiftOptionalPrimaryObject() object2.optIntCol.value = 1 object2.optInt8Col.value = 1 object2.optInt16Col.value = 1 object2.optInt32Col.value = 1 object2.optInt64Col.value = 1 object2.optFloatCol.value = 1 object2.optDoubleCol.value = 1 object2.optBoolCol.value = true object2.optDateCol = Date() object2.optStringCol = "" object2.optNSStringCol = "" object2.optBinaryCol = Data() object2.optObjectCol = SwiftBoolObject() realm.add(object2, update: .modified) XCTAssertNil(object.id.value) XCTAssertNotNil(object.optIntCol.value) XCTAssertNotNil(object.optInt8Col.value) XCTAssertNotNil(object.optInt16Col.value) XCTAssertNotNil(object.optInt32Col.value) XCTAssertNotNil(object.optInt64Col.value) XCTAssertNotNil(object.optBoolCol.value) XCTAssertNotNil(object.optFloatCol.value) XCTAssertNotNil(object.optDoubleCol.value) XCTAssertNotNil(object.optDateCol) XCTAssertNotNil(object.optStringCol) XCTAssertNotNil(object.optNSStringCol) XCTAssertNotNil(object.optBinaryCol) XCTAssertNotNil(object.optObjectCol) // Try to switch back to nil let object3 = SwiftOptionalPrimaryObject() realm.add(object3, update: .modified) XCTAssertNil(object.id.value) XCTAssertNil(object.optIntCol.value) XCTAssertNil(object.optInt8Col.value) XCTAssertNil(object.optInt16Col.value) XCTAssertNil(object.optInt32Col.value) XCTAssertNil(object.optInt64Col.value) XCTAssertNil(object.optBoolCol.value) XCTAssertNil(object.optFloatCol.value) XCTAssertNil(object.optDoubleCol.value) XCTAssertNil(object.optDateCol) XCTAssertNil(object.optStringCol) XCTAssertNil(object.optNSStringCol) XCTAssertNil(object.optBinaryCol) XCTAssertNil(object.optObjectCol) realm.cancelWrite() } /// If a Swift class declares generic properties before non-generic ones, the properties /// should be registered in order and creation from an array of values should work. func testProperOrderingOfProperties() { let v: [Any] = [ // Superclass's columns [["intCol": 42], ["intCol": 9001]], [["intCol": 42], ["intCol": 9001]], 100, 200, // Class's columns 1, [["stringCol": "hello"], ["stringCol": "world"]], [["stringCol": "hello"], ["stringCol": "world"]], 2, [["stringCol": "goodbye"], ["stringCol": "cruel"], ["stringCol": "world"]], [["stringCol": "goodbye"], ["stringCol": "cruel"], ["stringCol": "world"]], NSNull(), 3, 300] let object = SwiftGenericPropsOrderingObject(value: v) XCTAssertEqual(object.firstNumber, 1) XCTAssertEqual(object.secondNumber, 2) XCTAssertEqual(object.thirdNumber, 3) XCTAssertTrue(object.firstArray.count == 2) XCTAssertEqual(object.firstArray[0].stringCol, "hello") XCTAssertEqual(object.firstArray[1].stringCol, "world") XCTAssertTrue(object.secondArray.count == 3) XCTAssertEqual(object.secondArray[0].stringCol, "goodbye") XCTAssertEqual(object.secondArray[1].stringCol, "cruel") XCTAssertEqual(object.secondArray[2].stringCol, "world") XCTAssertTrue(object.firstSet.count == 2) assertSetContains(object.firstSet, keyPath: \.stringCol, items: ["hello", "world"]) XCTAssertTrue(object.secondSet.count == 3) assertSetContains(object.secondSet, keyPath: \.stringCol, items: ["goodbye", "cruel", "world"]) XCTAssertEqual(object.firstOptionalNumber.value, nil) XCTAssertEqual(object.secondOptionalNumber.value, 300) XCTAssertTrue(object.parentFirstList.count == 2) XCTAssertEqual(object.parentFirstList[0].intCol, 42) XCTAssertEqual(object.parentFirstList[1].intCol, 9001) XCTAssertEqual(object.parentFirstNumber, 100) XCTAssertEqual(object.parentSecondNumber, 200) XCTAssertTrue(object.firstLinking.count == 0) XCTAssertTrue(object.secondLinking.count == 0) } func testPrivateOptionalNonobjcString() { let realm = try! Realm() try! realm.write { let obj = ObjectWithPrivateOptionals() obj.value = 5 realm.add(obj) XCTAssertEqual(realm.objects(ObjectWithPrivateOptionals.self).first!.value, 5) } } // MARK: - Private utilities private func verifySwiftObjectWithArrayLiteral(_ object: SwiftObject, array: [Any], boolObjectValue: Bool, boolObjectListValues: [Bool]) { XCTAssertEqual(object.boolCol, (array[0] as! Bool)) XCTAssertEqual(object.intCol, (array[1] as! Int)) XCTAssertEqual(object.int8Col, (array[2] as! Int8)) XCTAssertEqual(object.int16Col, (array[3] as! Int16)) XCTAssertEqual(object.int32Col, (array[4] as! Int32)) XCTAssertEqual(object.int64Col, (array[5] as! Int64)) XCTAssertEqual(object.intEnumCol, IntEnum(rawValue: array[6] as! Int)) XCTAssertEqual(object.floatCol, (array[7] as! NSNumber).floatValue) XCTAssertEqual(object.doubleCol, (array[8] as! Double)) XCTAssertEqual(object.stringCol, (array[9] as! String)) XCTAssertEqual(object.binaryCol, (array[10] as! Data)) XCTAssertEqual(object.dateCol, (array[11] as! Date)) XCTAssertEqual(object.decimalCol, Decimal128(value: array[12])) XCTAssertEqual(object.objectIdCol, (array[13] as! ObjectId)) XCTAssertEqual(object.objectCol!.boolCol, boolObjectValue) XCTAssertEqual(object.arrayCol.count, boolObjectListValues.count) XCTAssertEqual(object.setCol.count, boolObjectListValues.count) for i in 0.. [String: Any] { var valueDict = SwiftObject.defaultValues() for (key, value) in replace { valueDict[key] = value } return valueDict } // return an array of valid values that can be used to initialize each type private func validValuesForSwiftObjectType(_ type: PropertyType, _ array: Bool, _ map: Bool) -> [Any] { let realm = try! Realm() realm.beginWrite() let persistedObject = realm.create(SwiftBoolObject.self, value: [true]) try! realm.commitWrite() if map { return [ ["trueVal": ["boolCol": true], "falseVal": ["boolCol": false]], ["trueVal": SwiftBoolObject(value: [true]), "falseVal": SwiftBoolObject(value: [false])], ["trueVal": persistedObject, "falseVal": [false]] as [String: Any] ] } if array { return [ [[true], [false]], [["boolCol": true], ["boolCol": false]], [SwiftBoolObject(value: [true]), SwiftBoolObject(value: [false])], [persistedObject, [false]] as [Any] ] } switch type { case .bool: return [true, NSNumber(value: 0 as Int), NSNumber(value: 1 as Int)] case .int: return [NSNumber(value: 1 as Int)] case .float: return [NSNumber(value: 1 as Int), NSNumber(value: 1.1 as Float), NSNumber(value: 11.1 as Double)] case .double: return [NSNumber(value: 1 as Int), NSNumber(value: 1.1 as Float), NSNumber(value: 11.1 as Double)] case .string: return ["b"] case .data: return ["b".data(using: String.Encoding.utf8, allowLossyConversion: false)!] case .date: return [Date(timeIntervalSince1970: 2)] case .object: return [[true], ["boolCol": true], SwiftBoolObject(value: [true]), persistedObject] case .objectId: return [ObjectId("1234567890ab1234567890ab")] case .decimal128: return [1, "2", Decimal128(number: 3)] case .any: return ["hello"] case .linkingObjects: fatalError("not supported") case .UUID: return [UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")!, UUID(uuidString: "00000000-0000-0000-0000-000000000000")!] default: fatalError() } } private func invalidValuesForSwiftObjectType(_ type: PropertyType, _ array: Bool, _ map: Bool) -> [Any] { let realm = try! Realm() realm.beginWrite() let persistedObject = realm.create(SwiftIntObject.self) try! realm.commitWrite() if map { return [ ["trueVal": ["boolCol": "invalid"] as [String: Any], "falseVal": ["boolCol": false]], ["trueVal": "invalid", "falseVal": SwiftBoolObject(value: [false])] as [String: Any] ] } if array { return ["invalid", [["a"]], [["boolCol": "a"]], [[SwiftIntObject()]], [[persistedObject]]] } switch type { case .bool: return ["invalid", NSNumber(value: 2 as Int), NSNumber(value: 1.1 as Float), NSNumber(value: 11.1 as Double)] case .int: return ["invalid", NSNumber(value: 1.1 as Float), NSNumber(value: 11.1 as Double)] case .float: return ["invalid", true, false] case .double: return ["invalid", true, false] case .string: return [0x197A71D, true, false] case .data: return ["invalid"] case .date: return ["invalid"] case .object: return ["invalid", ["a"], ["boolCol": "a"], SwiftIntObject()] case .objectId: return ["invalid", 123] case .decimal128: return ["invalid"] case .any: return [MutableSet()] case .linkingObjects: fatalError("not supported") case .UUID: return ["invalid"] default: fatalError() } } } ================================================ FILE: RealmSwift/Tests/ObjectCustomPropertiesTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // 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 import Realm @_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 nonisolated(unsafe) var injected_customRealmProperties: [RLMProperty]? static let preMadeRLMProperty = RLMProperty(name: "value", objectType: CustomPropertiesObject.self, valueType: String.self) } ================================================ FILE: RealmSwift/Tests/ObjectIdTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import RealmSwift class ObjectIdTests: TestCase { func testObjectIdInitialization() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) XCTAssertEqual(objectId.stringValue, strValue) XCTAssertEqual(strValue, objectId.stringValue) let now = Date() let objectId2 = ObjectId(timestamp: now, machineId: 10, processId: 20) XCTAssertEqual(Int(now.timeIntervalSince1970), Int(objectId2.timestamp.timeIntervalSince1970)) } func testObjectIdComparision() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) let strValue2 = "000123450000ffbeef91906d" let objectId2 = try! ObjectId(string: strValue2) let strValue3 = "000123450000ffbeef91906c" let objectId3 = try! ObjectId(string: strValue3) XCTAssertTrue(objectId != objectId2) XCTAssertTrue(objectId == objectId3) } func testObjectIdGreaterThan() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) let strValue2 = "000123450000ffbeef91906d" let objectId2 = try! ObjectId(string: strValue2) let strValue3 = "000123450000ffbeef91906c" let objectId3 = try! ObjectId(string: strValue3) XCTAssertTrue(objectId2 > objectId) XCTAssertFalse(objectId > objectId3) } func testObjectIdGreaterThanOrEqualTo() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) let strValue2 = "000123450000ffbeef91906d" let objectId2 = try! ObjectId(string: strValue2) let strValue3 = "000123450000ffbeef91906c" let objectId3 = try! ObjectId(string: strValue3) XCTAssertTrue(objectId2 >= objectId) XCTAssertTrue(objectId >= objectId3) } func testObjectIdLessThan() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) let strValue2 = "000123450000ffbeef91906d" let objectId2 = try! ObjectId(string: strValue2) let strValue3 = "000123450000ffbeef91906c" let objectId3 = try! ObjectId(string: strValue3) XCTAssertTrue(objectId < objectId2) XCTAssertFalse(objectId < objectId3) } func testObjectIdLessThanOrEqualTo() { let strValue = "000123450000ffbeef91906c" let objectId = try! ObjectId(string: strValue) let strValue2 = "000123450000ffbeef91906d" let objectId2 = try! ObjectId(string: strValue2) let strValue3 = "000123450000ffbeef91906c" let objectId3 = try! ObjectId(string: strValue3) XCTAssertTrue(objectId <= objectId2) XCTAssertTrue(objectId <= objectId3) } } ================================================ FILE: RealmSwift/Tests/ObjectSchemaInitializationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import Realm.Private import Realm.Dynamic import Foundation #if DEBUG @testable import RealmSwift #else import RealmSwift #endif @available(*, deprecated) // Silence deprecation warnings for RealmOptional class ObjectSchemaInitializationTests: TestCase { func testAllValidTypes() { let object = SwiftObject() let objectSchema = object.objectSchema let noSuchCol = objectSchema["noSuchCol"] XCTAssertNil(noSuchCol) let boolCol = objectSchema["boolCol"] XCTAssertNotNil(boolCol) XCTAssertEqual(boolCol!.name, "boolCol") XCTAssertEqual(boolCol!.type, PropertyType.bool) XCTAssertFalse(boolCol!.isIndexed) XCTAssertFalse(boolCol!.isOptional) XCTAssertNil(boolCol!.objectClassName) let intCol = objectSchema["intCol"] XCTAssertNotNil(intCol) XCTAssertEqual(intCol!.name, "intCol") XCTAssertEqual(intCol!.type, PropertyType.int) XCTAssertFalse(intCol!.isIndexed) XCTAssertFalse(intCol!.isOptional) XCTAssertNil(intCol!.objectClassName) let floatCol = objectSchema["floatCol"] XCTAssertNotNil(floatCol) XCTAssertEqual(floatCol!.name, "floatCol") XCTAssertEqual(floatCol!.type, PropertyType.float) XCTAssertFalse(floatCol!.isIndexed) XCTAssertFalse(floatCol!.isOptional) XCTAssertNil(floatCol!.objectClassName) let doubleCol = objectSchema["doubleCol"] XCTAssertNotNil(doubleCol) XCTAssertEqual(doubleCol!.name, "doubleCol") XCTAssertEqual(doubleCol!.type, PropertyType.double) XCTAssertFalse(doubleCol!.isIndexed) XCTAssertFalse(doubleCol!.isOptional) XCTAssertNil(doubleCol!.objectClassName) let stringCol = objectSchema["stringCol"] XCTAssertNotNil(stringCol) XCTAssertEqual(stringCol!.name, "stringCol") XCTAssertEqual(stringCol!.type, PropertyType.string) XCTAssertFalse(stringCol!.isIndexed) XCTAssertFalse(stringCol!.isOptional) XCTAssertNil(stringCol!.objectClassName) let binaryCol = objectSchema["binaryCol"] XCTAssertNotNil(binaryCol) XCTAssertEqual(binaryCol!.name, "binaryCol") XCTAssertEqual(binaryCol!.type, PropertyType.data) XCTAssertFalse(binaryCol!.isIndexed) XCTAssertFalse(binaryCol!.isOptional) XCTAssertNil(binaryCol!.objectClassName) let dateCol = objectSchema["dateCol"] XCTAssertNotNil(dateCol) XCTAssertEqual(dateCol!.name, "dateCol") XCTAssertEqual(dateCol!.type, PropertyType.date) XCTAssertFalse(dateCol!.isIndexed) XCTAssertFalse(dateCol!.isOptional) XCTAssertNil(dateCol!.objectClassName) let uuidCol = objectSchema["uuidCol"] XCTAssertNotNil(uuidCol) XCTAssertEqual(uuidCol!.name, "uuidCol") XCTAssertEqual(uuidCol!.type, PropertyType.UUID) XCTAssertFalse(uuidCol!.isIndexed) XCTAssertFalse(uuidCol!.isOptional) XCTAssertNil(uuidCol!.objectClassName) let anyCol = objectSchema["anyCol"] XCTAssertNotNil(anyCol) XCTAssertEqual(anyCol!.name, "anyCol") XCTAssertEqual(anyCol!.type, PropertyType.any) XCTAssertFalse(anyCol!.isIndexed) XCTAssertFalse(anyCol!.isOptional) XCTAssertNil(anyCol!.objectClassName) let objectCol = objectSchema["objectCol"] XCTAssertNotNil(objectCol) XCTAssertEqual(objectCol!.name, "objectCol") XCTAssertEqual(objectCol!.type, PropertyType.object) XCTAssertFalse(objectCol!.isIndexed) XCTAssertTrue(objectCol!.isOptional) XCTAssertEqual(objectCol!.objectClassName!, "SwiftBoolObject") let arrayCol = objectSchema["arrayCol"] XCTAssertNotNil(arrayCol) XCTAssertEqual(arrayCol!.name, "arrayCol") XCTAssertEqual(arrayCol!.type, PropertyType.object) XCTAssertTrue(arrayCol!.isArray) XCTAssertFalse(arrayCol!.isIndexed) XCTAssertFalse(arrayCol!.isOptional) XCTAssertEqual(objectCol!.objectClassName!, "SwiftBoolObject") let setCol = objectSchema["setCol"] XCTAssertNotNil(setCol) XCTAssertEqual(setCol!.name, "setCol") XCTAssertEqual(setCol!.type, PropertyType.object) XCTAssertTrue(setCol!.isSet) XCTAssertFalse(setCol!.isIndexed) XCTAssertFalse(setCol!.isOptional) XCTAssertEqual(setCol!.objectClassName!, "SwiftBoolObject") let dynamicArrayCol = SwiftCompanyObject().objectSchema["employees"] XCTAssertNotNil(dynamicArrayCol) XCTAssertEqual(dynamicArrayCol!.name, "employees") XCTAssertEqual(dynamicArrayCol!.type, PropertyType.object) XCTAssertTrue(dynamicArrayCol!.isArray) XCTAssertFalse(dynamicArrayCol!.isIndexed) XCTAssertFalse(dynamicArrayCol!.isOptional) XCTAssertEqual(dynamicArrayCol!.objectClassName!, "SwiftEmployeeObject") let dynamicSetCol = SwiftCompanyObject().objectSchema["employeeSet"] XCTAssertNotNil(dynamicSetCol) XCTAssertEqual(dynamicSetCol!.name, "employeeSet") XCTAssertEqual(dynamicSetCol!.type, PropertyType.object) XCTAssertTrue(dynamicSetCol!.isSet) XCTAssertFalse(dynamicSetCol!.isIndexed) XCTAssertFalse(dynamicSetCol!.isOptional) XCTAssertEqual(dynamicSetCol!.objectClassName!, "SwiftEmployeeObject") } func testInvalidObjects() { let schema = SwiftFakeObjectSubclass.sharedSchema()! XCTAssertEqual(schema.properties.count, 2) assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithAnyObject.self), reason: "Property SwiftObjectWithAnyObject.anyObject is declared as NSObject") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithStringArray.self), reason: "Property SwiftObjectWithStringArray.stringArray is declared as Array") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithOptionalStringArray.self), reason: "Property SwiftObjectWithOptionalStringArray.stringArray is declared as Optional>") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithBadPropertyName.self), reason: "Property names beginning with 'new' are not supported.") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithManagedLazyProperty.self), reason: "Lazy managed property 'foobar' is not allowed on a Realm Swift object class.") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithDynamicManagedLazyProperty.self), reason: "Lazy managed property 'foobar' is not allowed on a Realm Swift object class.") // Shouldn't throw when not ignoring a property of a type we can't persist if it's not dynamic _ = RLMObjectSchema(forObjectClass: SwiftObjectWithEnum.self) // Shouldn't throw when not ignoring a property of a type we can't persist if it's not dynamic _ = RLMObjectSchema(forObjectClass: SwiftObjectWithStruct.self) assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithDatePrimaryKey.self), reason: "Property 'date' cannot be made the primary key of 'SwiftObjectWithDatePrimaryKey'") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNSURL.self), reason: "Property SwiftObjectWithNSURL.url is declared as NSURL") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNonOptionalLinkProperty.self), reason: "Object property 'objectCol' must be marked as optional.") } func testPrimaryKey() { XCTAssertNil(SwiftObject().objectSchema.primaryKeyProperty, "Object should default to having no primary key property") XCTAssertEqual(SwiftPrimaryStringObject().objectSchema.primaryKeyProperty!.name, "stringCol") } func testIgnoredProperties() { let schema = SwiftIgnoredPropertiesObject().objectSchema XCTAssertNil(schema["runtimeProperty"], "The object schema shouldn't contain ignored properties") XCTAssertNil(schema["runtimeDefaultProperty"], "The object schema shouldn't contain ignored properties") XCTAssertNil(schema["readOnlyProperty"], "The object schema shouldn't contain read-only properties") } func testIndexedProperties() { XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["stringCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["intCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int8Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int16Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int32Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int64Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["boolCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["dateCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["floatCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["doubleCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["dataCol"]!.isIndexed) } func testOptionalProperties() { let schema = RLMObjectSchema(forObjectClass: SwiftOptionalObject.self) for prop in schema.properties { XCTAssertTrue(prop.optional) } let types = Set(schema.properties.map { $0.type }) XCTAssertEqual(types, Set([.string, .string, .data, .date, .object, .int, .float, .double, .bool, .decimal128, .objectId, .UUID])) } func testImplicitlyUnwrappedOptionalsAreParsedAsOptionals() { let schema = SwiftImplicitlyUnwrappedOptionalObject().objectSchema XCTAssertTrue(schema["optObjectCol"]!.isOptional) XCTAssertTrue(schema["optNSStringCol"]!.isOptional) XCTAssertTrue(schema["optStringCol"]!.isOptional) XCTAssertTrue(schema["optBinaryCol"]!.isOptional) XCTAssertTrue(schema["optDateCol"]!.isOptional) XCTAssertTrue(schema["optDecimalCol"]!.isOptional) XCTAssertTrue(schema["optObjectIdCol"]!.isOptional) XCTAssertTrue(schema["optUuidCol"]!.isOptional) } func testNonRealmOptionalTypesDeclaredAsRealmOptional() { assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNonRealmOptionalType.self)) } func testNotExplicitlyIgnoredComputedProperties() { let schema = SwiftComputedPropertyNotIgnoredObject().objectSchema // The two computed properties should not appear on the schema. XCTAssertEqual(schema.properties.count, 1) XCTAssertNotNil(schema["_urlBacking"]) } func testMultiplePrimaryKeys() { assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithMultiplePrimaryKeys.self), reason: "Properties 'pk2' and 'pk1' are both marked as the primary key of 'SwiftObjectWithMultiplePrimaryKeys'") } func testModernIndexableTypes() { let indexed = ModernAllIndexableTypesObject().objectSchema for property in indexed.properties { XCTAssertTrue(property.isIndexed) } let notIndexed = ModernAllIndexableButNotIndexedObject().objectSchema for property in notIndexed.properties { XCTAssertFalse(property.isIndexed) } } func testCustomIndexableTypes() { let indexed = CustomAllIndexableTypesObject().objectSchema for property in indexed.properties { XCTAssertTrue(property.isIndexed) } let notIndexed = CustomAllIndexableButNotIndexedObject().objectSchema for property in notIndexed.properties { XCTAssertFalse(property.isIndexed) } } #if DEBUG // this test depends on @testable import func assertType(_ value: T, _ propertyType: PropertyType, optional: Bool = false, list: Bool = false, set: Bool = false, objectType: String? = nil, hasSelectors: Bool = true, line: UInt = #line) { let prop = RLMProperty(name: "property", value: value) XCTAssertEqual(prop.type, propertyType, line: line) XCTAssertEqual(prop.optional, optional, line: line) XCTAssertEqual(prop.array, list, line: line) XCTAssertEqual(prop.set, set, line: line) XCTAssertEqual(prop.objectClassName, objectType, line: line) if hasSelectors { XCTAssertNotNil(prop.getterSel, line: line) XCTAssertNotNil(prop.setterSel, line: line) } else { XCTAssertNil(prop.getterSel, line: line) XCTAssertNil(prop.setterSel, line: line) } } func testPropertyPopulation() { assertType(Int(), .int) assertType(Int8(), .int) assertType(Int16(), .int) assertType(Int32(), .int) assertType(Int64(), .int) assertType(Bool(), .bool) assertType(Float(), .float) assertType(Double(), .double) assertType(String(), .string) assertType(Data(), .data) assertType(Date(), .date) assertType(UUID(), .UUID) assertType(Decimal128(), .decimal128) assertType(ObjectId(), .objectId) assertType(Optional.none, .int, optional: true) assertType(Optional.none, .int, optional: true) assertType(Optional.none, .int, optional: true) assertType(Optional.none, .int, optional: true) assertType(Optional.none, .int, optional: true) assertType(Optional.none, .bool, optional: true) assertType(Optional.none, .float, optional: true) assertType(Optional.none, .double, optional: true) assertType(Optional.none, .string, optional: true) assertType(Optional.none, .data, optional: true) assertType(Optional.none, .date, optional: true) assertType(Optional.none, .UUID, optional: true) assertType(Optional.none, .decimal128, optional: true) assertType(Optional.none, .objectId, optional: true) assertType(RealmProperty(), .int, optional: true, hasSelectors: false) assertType(RealmProperty(), .int, optional: true, hasSelectors: false) assertType(RealmProperty(), .int, optional: true, hasSelectors: false) assertType(RealmProperty(), .int, optional: true, hasSelectors: false) assertType(RealmProperty(), .int, optional: true, hasSelectors: false) assertType(RealmProperty(), .bool, optional: true, hasSelectors: false) assertType(RealmProperty(), .float, optional: true, hasSelectors: false) assertType(RealmProperty(), .double, optional: true, hasSelectors: false) assertType(List(), .int, list: true, hasSelectors: false) assertType(List(), .int, list: true, hasSelectors: false) assertType(List(), .int, list: true, hasSelectors: false) assertType(List(), .int, list: true, hasSelectors: false) assertType(List(), .int, list: true, hasSelectors: false) assertType(List(), .bool, list: true, hasSelectors: false) assertType(List(), .float, list: true, hasSelectors: false) assertType(List(), .double, list: true, hasSelectors: false) assertType(List(), .string, list: true, hasSelectors: false) assertType(List(), .data, list: true, hasSelectors: false) assertType(List(), .date, list: true, hasSelectors: false) assertType(List(), .UUID, list: true, hasSelectors: false) assertType(List(), .decimal128, list: true, hasSelectors: false) assertType(List(), .objectId, list: true, hasSelectors: false) assertType(List(), .int, optional: true, list: true, hasSelectors: false) assertType(List(), .int, optional: true, list: true, hasSelectors: false) assertType(List(), .int, optional: true, list: true, hasSelectors: false) assertType(List(), .int, optional: true, list: true, hasSelectors: false) assertType(List(), .int, optional: true, list: true, hasSelectors: false) assertType(List(), .bool, optional: true, list: true, hasSelectors: false) assertType(List(), .float, optional: true, list: true, hasSelectors: false) assertType(List(), .double, optional: true, list: true, hasSelectors: false) assertType(List(), .string, optional: true, list: true, hasSelectors: false) assertType(List(), .data, optional: true, list: true, hasSelectors: false) assertType(List(), .date, optional: true, list: true, hasSelectors: false) assertType(List(), .UUID, optional: true, list: true, hasSelectors: false) assertType(List(), .decimal128, optional: true, list: true, hasSelectors: false) assertType(List(), .objectId, optional: true, list: true, hasSelectors: false) assertType(MutableSet(), .int, set: true, hasSelectors: false) assertType(MutableSet(), .int, set: true, hasSelectors: false) assertType(MutableSet(), .int, set: true, hasSelectors: false) assertType(MutableSet(), .int, set: true, hasSelectors: false) assertType(MutableSet(), .int, set: true, hasSelectors: false) assertType(MutableSet(), .bool, set: true, hasSelectors: false) assertType(MutableSet(), .float, set: true, hasSelectors: false) assertType(MutableSet(), .double, set: true, hasSelectors: false) assertType(MutableSet(), .string, set: true, hasSelectors: false) assertType(MutableSet(), .data, set: true, hasSelectors: false) assertType(MutableSet(), .date, set: true, hasSelectors: false) assertType(MutableSet(), .UUID, set: true, hasSelectors: false) assertType(MutableSet(), .decimal128, set: true, hasSelectors: false) assertType(MutableSet(), .objectId, set: true, hasSelectors: false) assertType(MutableSet(), .int, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .int, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .int, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .int, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .int, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .bool, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .float, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .double, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .string, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .data, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .date, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .UUID, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .decimal128, optional: true, set: true, hasSelectors: false) assertType(MutableSet(), .objectId, optional: true, set: true, hasSelectors: false) assertThrows(RLMProperty(name: "name", value: Object()), reason: "Object property 'name' must be marked as optional.") assertThrows(RLMProperty(name: "name", value: List()), reason: "List property 'name' must not be marked as optional.") assertThrows(RLMProperty(name: "name", value: MutableSet()), reason: "MutableSet property 'name' must not be marked as optional.") assertType(Object?.none, .object, optional: true, objectType: "RealmSwiftObject") assertType(List(), .object, list: true, objectType: "RealmSwiftObject", hasSelectors: false) assertType(MutableSet(), .object, set: true, objectType: "RealmSwiftObject", hasSelectors: false) } func assertType(_ type: T.Type, _ propertyType: PropertyType, optional: Bool = false, list: Bool = false, set: Bool = false, map: Bool = false, objectType: String? = nil, line: UInt = #line) { let prop = RLMProperty(name: "_property", value: Persisted()) XCTAssertEqual(prop.name, "property", line: line) XCTAssertEqual(prop.type, propertyType, line: line) XCTAssertEqual(prop.optional, optional, line: line) XCTAssertEqual(prop.array, list, line: line) XCTAssertEqual(prop.set, set, line: line) XCTAssertEqual(prop.dictionary, map, line: line) XCTAssertEqual(prop.objectClassName, objectType, line: line) XCTAssertNil(prop.getterSel, line: line) XCTAssertNil(prop.setterSel, line: line) } func testModernPropertyPopulation() { assertType(Int.self, .int) assertType(Int8.self, .int) assertType(Int16.self, .int) assertType(Int32.self, .int) assertType(Int64.self, .int) assertType(Bool.self, .bool) assertType(Float.self, .float) assertType(Double.self, .double) assertType(String.self, .string) assertType(Data.self, .data) assertType(Date.self, .date) assertType(UUID.self, .UUID) assertType(Decimal128.self, .decimal128) assertType(ObjectId.self, .objectId) assertType(AnyRealmValue.self, .any) assertType(ModernIntEnum.self, .int) assertType(ModernStringEnum.self, .string) assertType(Int?.self, .int, optional: true) assertType(Int8?.self, .int, optional: true) assertType(Int16?.self, .int, optional: true) assertType(Int32?.self, .int, optional: true) assertType(Int64?.self, .int, optional: true) assertType(Bool?.self, .bool, optional: true) assertType(Float?.self, .float, optional: true) assertType(Double?.self, .double, optional: true) assertType(String?.self, .string, optional: true) assertType(Data?.self, .data, optional: true) assertType(Date?.self, .date, optional: true) assertType(UUID?.self, .UUID, optional: true) assertType(Decimal128?.self, .decimal128, optional: true) assertType(ObjectId?.self, .objectId, optional: true) assertType(Object?.self, .object, optional: true, objectType: "RealmSwiftObject") assertType(EmbeddedObject?.self, .object, optional: true, objectType: "RealmSwiftEmbeddedObject") assertType(ModernIntEnum?.self, .int, optional: true) assertType(ModernStringEnum?.self, .string, optional: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .bool, list: true) assertType(List.self, .float, list: true) assertType(List.self, .double, list: true) assertType(List.self, .string, list: true) assertType(List.self, .data, list: true) assertType(List.self, .date, list: true) assertType(List.self, .UUID, list: true) assertType(List.self, .decimal128, list: true) assertType(List.self, .objectId, list: true) assertType(List.self, .any, list: true) assertType(List.self, .object, list: true, objectType: "RealmSwiftObject") assertType(List.self, .object, list: true, objectType: "RealmSwiftEmbeddedObject") assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .bool, optional: true, list: true) assertType(List.self, .float, optional: true, list: true) assertType(List.self, .double, optional: true, list: true) assertType(List.self, .string, optional: true, list: true) assertType(List.self, .data, optional: true, list: true) assertType(List.self, .date, optional: true, list: true) assertType(List.self, .UUID, optional: true, list: true) assertType(List.self, .decimal128, optional: true, list: true) assertType(List.self, .objectId, optional: true, list: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .bool, set: true) assertType(MutableSet.self, .float, set: true) assertType(MutableSet.self, .double, set: true) assertType(MutableSet.self, .string, set: true) assertType(MutableSet.self, .data, set: true) assertType(MutableSet.self, .date, set: true) assertType(MutableSet.self, .UUID, set: true) assertType(MutableSet.self, .decimal128, set: true) assertType(MutableSet.self, .objectId, set: true) assertType(MutableSet.self, .any, set: true) assertType(MutableSet.self, .object, set: true, objectType: "RealmSwiftObject") assertType(MutableSet.self, .object, set: true, objectType: "RealmSwiftEmbeddedObject") assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .bool, optional: true, set: true) assertType(MutableSet.self, .float, optional: true, set: true) assertType(MutableSet.self, .double, optional: true, set: true) assertType(MutableSet.self, .string, optional: true, set: true) assertType(MutableSet.self, .data, optional: true, set: true) assertType(MutableSet.self, .date, optional: true, set: true) assertType(MutableSet.self, .UUID, optional: true, set: true) assertType(MutableSet.self, .decimal128, optional: true, set: true) assertType(MutableSet.self, .objectId, optional: true, set: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .bool, map: true) assertType(Map.self, .float, map: true) assertType(Map.self, .double, map: true) assertType(Map.self, .string, map: true) assertType(Map.self, .data, map: true) assertType(Map.self, .date, map: true) assertType(Map.self, .UUID, map: true) assertType(Map.self, .decimal128, map: true) assertType(Map.self, .objectId, map: true) assertType(Map.self, .any, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .bool, optional: true, map: true) assertType(Map.self, .float, optional: true, map: true) assertType(Map.self, .double, optional: true, map: true) assertType(Map.self, .string, optional: true, map: true) assertType(Map.self, .data, optional: true, map: true) assertType(Map.self, .date, optional: true, map: true) assertType(Map.self, .UUID, optional: true, map: true) assertType(Map.self, .decimal128, optional: true, map: true) assertType(Map.self, .objectId, optional: true, map: true) assertType(Map.self, .object, optional: true, map: true, objectType: "RealmSwiftObject") assertType(Map.self, .object, optional: true, map: true, objectType: "RealmSwiftEmbeddedObject") assertThrows(RLMProperty(name: "_name", value: Persisted()), reason: "Object property 'name' must be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "List property 'name' must not be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "MutableSet property 'name' must not be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "LinkingObjects property 'name' must set the origin property name with @Persisted(originProperty: \"name\").") assertThrows(RLMProperty(name: "_name", value: Persisted()), reason: "Object property 'name' must be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "List property 'name' must not be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "MutableSet property 'name' must not be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "LinkingObjects property 'name' must set the origin property name with @Persisted(originProperty: \"name\").") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "Map property 'name' must be marked as optional.") assertThrows(RLMProperty(name: "_name", value: Persisted>()), reason: "Map property 'name' must be marked as optional.") } func testModernIndexed() { XCTAssertFalse(RLMProperty(name: "_property", value: Persisted()).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1)).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(indexed: false)).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1, indexed: false)).indexed) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(indexed: true)).indexed) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1, indexed: true)).indexed) } func testModernPrimary() { XCTAssertFalse(RLMProperty(name: "_property", value: Persisted()).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1)).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(primaryKey: false)).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1, primaryKey: false)).isPrimary) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(primaryKey: true)).isPrimary) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(wrappedValue: 1, primaryKey: true)).isPrimary) } func testCustomPropertyPopulation() { assertType(IntWrapper.self, .int) assertType(Int8Wrapper.self, .int) assertType(Int16Wrapper.self, .int) assertType(Int32Wrapper.self, .int) assertType(Int64Wrapper.self, .int) assertType(BoolWrapper.self, .bool) assertType(FloatWrapper.self, .float) assertType(DoubleWrapper.self, .double) assertType(StringWrapper.self, .string) assertType(DataWrapper.self, .data) assertType(DateWrapper.self, .date) assertType(UUIDWrapper.self, .UUID) assertType(Decimal128Wrapper.self, .decimal128) assertType(ObjectIdWrapper.self, .objectId) assertType(IntWrapper?.self, .int, optional: true) assertType(Int8Wrapper?.self, .int, optional: true) assertType(Int16Wrapper?.self, .int, optional: true) assertType(Int32Wrapper?.self, .int, optional: true) assertType(Int64Wrapper?.self, .int, optional: true) assertType(BoolWrapper?.self, .bool, optional: true) assertType(FloatWrapper?.self, .float, optional: true) assertType(DoubleWrapper?.self, .double, optional: true) assertType(StringWrapper?.self, .string, optional: true) assertType(DataWrapper?.self, .data, optional: true) assertType(DateWrapper?.self, .date, optional: true) assertType(UUIDWrapper?.self, .UUID, optional: true) assertType(Decimal128Wrapper?.self, .decimal128, optional: true) assertType(ObjectIdWrapper?.self, .objectId, optional: true) assertType(EmbeddedObjectWrapper?.self, .object, optional: true, objectType: "ModernEmbeddedObject") assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .int, list: true) assertType(List.self, .bool, list: true) assertType(List.self, .float, list: true) assertType(List.self, .double, list: true) assertType(List.self, .string, list: true) assertType(List.self, .data, list: true) assertType(List.self, .date, list: true) assertType(List.self, .UUID, list: true) assertType(List.self, .decimal128, list: true) assertType(List.self, .objectId, list: true) assertType(List.self, .object, list: true, objectType: "ModernEmbeddedObject") assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .int, optional: true, list: true) assertType(List.self, .bool, optional: true, list: true) assertType(List.self, .float, optional: true, list: true) assertType(List.self, .double, optional: true, list: true) assertType(List.self, .string, optional: true, list: true) assertType(List.self, .data, optional: true, list: true) assertType(List.self, .date, optional: true, list: true) assertType(List.self, .UUID, optional: true, list: true) assertType(List.self, .decimal128, optional: true, list: true) assertType(List.self, .objectId, optional: true, list: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .int, set: true) assertType(MutableSet.self, .bool, set: true) assertType(MutableSet.self, .float, set: true) assertType(MutableSet.self, .double, set: true) assertType(MutableSet.self, .string, set: true) assertType(MutableSet.self, .data, set: true) assertType(MutableSet.self, .date, set: true) assertType(MutableSet.self, .UUID, set: true) assertType(MutableSet.self, .decimal128, set: true) assertType(MutableSet.self, .objectId, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .int, optional: true, set: true) assertType(MutableSet.self, .bool, optional: true, set: true) assertType(MutableSet.self, .float, optional: true, set: true) assertType(MutableSet.self, .double, optional: true, set: true) assertType(MutableSet.self, .string, optional: true, set: true) assertType(MutableSet.self, .data, optional: true, set: true) assertType(MutableSet.self, .date, optional: true, set: true) assertType(MutableSet.self, .UUID, optional: true, set: true) assertType(MutableSet.self, .decimal128, optional: true, set: true) assertType(MutableSet.self, .objectId, optional: true, set: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .int, map: true) assertType(Map.self, .bool, map: true) assertType(Map.self, .float, map: true) assertType(Map.self, .double, map: true) assertType(Map.self, .string, map: true) assertType(Map.self, .data, map: true) assertType(Map.self, .date, map: true) assertType(Map.self, .UUID, map: true) assertType(Map.self, .decimal128, map: true) assertType(Map.self, .objectId, map: true) assertType(Map.self, .object, optional: true, map: true, objectType: "ModernEmbeddedObject") assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .int, optional: true, map: true) assertType(Map.self, .bool, optional: true, map: true) assertType(Map.self, .float, optional: true, map: true) assertType(Map.self, .double, optional: true, map: true) assertType(Map.self, .string, optional: true, map: true) assertType(Map.self, .data, optional: true, map: true) assertType(Map.self, .date, optional: true, map: true) assertType(Map.self, .UUID, optional: true, map: true) assertType(Map.self, .decimal128, optional: true, map: true) assertType(Map.self, .objectId, optional: true, map: true) assertType(Map.self, .object, optional: true, map: true, objectType: "ModernEmbeddedObject") } func testCustomIndexed() { let v = IntWrapper(persistedValue: 1) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted()).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: v)).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(indexed: false)).indexed) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: v, indexed: false)).indexed) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(indexed: true)).indexed) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(wrappedValue: v, indexed: true)).indexed) } func testCustomPrimary() { let v = IntWrapper(persistedValue: 1) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted()).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: v)).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(primaryKey: false)).isPrimary) XCTAssertFalse(RLMProperty(name: "_property", value: Persisted(wrappedValue: v, primaryKey: false)).isPrimary) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(primaryKey: true)).isPrimary) XCTAssertTrue(RLMProperty(name: "_property", value: Persisted(wrappedValue: v, primaryKey: true)).isPrimary) } #endif // DEBUG } class SwiftFakeObject: Object { override class func _realmIgnoreClass() -> Bool { return true } @objc dynamic var requiredProp: String? } class SwiftObjectWithNSURL: SwiftFakeObject { @objc dynamic var url = NSURL(string: "http://realm.io")! } class SwiftObjectWithAnyObject: SwiftFakeObject { @objc dynamic var anyObject: AnyObject = NSObject() } class SwiftObjectWithStringArray: SwiftFakeObject { @objc dynamic var stringArray = [String]() } class SwiftObjectWithOptionalStringArray: SwiftFakeObject { @objc dynamic var stringArray: [String]? } enum SwiftEnum { case case1 case case2 } class SwiftObjectWithEnum: SwiftFakeObject { var swiftEnum = SwiftEnum.case1 } class SwiftObjectWithStruct: SwiftFakeObject { var swiftStruct = SortDescriptor(keyPath: "prop") } class SwiftObjectWithDatePrimaryKey: SwiftFakeObject { @objc dynamic var date = Date() override class func primaryKey() -> String? { return "date" } } class SwiftFakeObjectSubclass: SwiftFakeObject { @objc dynamic var dateCol = Date() } // swiftlint:disable:next type_name class SwiftObjectWithNonNullableOptionalProperties: SwiftFakeObject { @objc dynamic var optDateCol: Date? } class SwiftObjectWithNonOptionalLinkProperty: SwiftFakeObject { @objc dynamic var objectCol = SwiftBoolObject() } #if compiler(<6) || SWIFT_PACKAGE extension Set: RealmOptionalType { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Set? { fatalError() } public var _rlmObjcValue: Any { fatalError() } } #else extension Set: @retroactive RealmOptionalType { public static func _rlmFromObjc(_ value: Any, insideOptional: Bool) -> Set? { fatalError() } public var _rlmObjcValue: Any { fatalError() } } #endif @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftObjectWithNonRealmOptionalType: SwiftFakeObject { let set = RealmOptional>() } class SwiftObjectWithBadPropertyName: SwiftFakeObject { @objc dynamic var newValue = false } class SwiftObjectWithManagedLazyProperty: SwiftFakeObject { lazy var foobar: String = "foo" } // swiftlint:disable:next type_name class SwiftObjectWithDynamicManagedLazyProperty: SwiftFakeObject { @objc dynamic lazy var foobar: String = "foo" } class SwiftObjectWithMultiplePrimaryKeys: SwiftFakeObject { @Persisted(primaryKey: true) var pk1: Int @Persisted(primaryKey: true) var pk2: Int } ================================================ FILE: RealmSwift/Tests/ObjectSchemaTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift class ObjectSchemaTests: TestCase { var objectSchema: ObjectSchema! var swiftObjectSchema: ObjectSchema { return try! Realm().schema["SwiftObject"]! } func testProperties() { let objectSchema = swiftObjectSchema let propertyNames = objectSchema.properties.map { $0.name } XCTAssertEqual(propertyNames, ["boolCol", "intCol", "int8Col", "int16Col", "int32Col", "int64Col", "intEnumCol", "floatCol", "doubleCol", "stringCol", "binaryCol", "dateCol", "decimalCol", "objectIdCol", "objectCol", "uuidCol", "anyCol", "arrayCol", "setCol", "mapCol"] ) } // Cannot name testClassName() because it interferes with the method on XCTest func testClassNameProperty() { let objectSchema = swiftObjectSchema XCTAssertEqual(objectSchema.className, "SwiftObject") } func testObjectClass() { let objectSchema = swiftObjectSchema XCTAssertTrue(objectSchema.objectClass === SwiftObject.self) } func testPrimaryKeyProperty() { let objectSchema = swiftObjectSchema XCTAssertNil(objectSchema.primaryKeyProperty) XCTAssertEqual(try! Realm().schema["SwiftPrimaryStringObject"]!.primaryKeyProperty!.name, "stringCol") } func testDescription() { let objectSchema = swiftObjectSchema let expected = """ SwiftObject { boolCol { type = bool; columnName = boolCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } intCol { type = int; columnName = intCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } int8Col { type = int; columnName = int8Col; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } int16Col { type = int; columnName = int16Col; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } int32Col { type = int; columnName = int32Col; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } int64Col { type = int; columnName = int64Col; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } intEnumCol { type = int; columnName = intEnumCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } floatCol { type = float; columnName = floatCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } doubleCol { type = double; columnName = doubleCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } stringCol { type = string; columnName = stringCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } binaryCol { type = data; columnName = binaryCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } dateCol { type = date; columnName = dateCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } decimalCol { type = decimal128; columnName = decimalCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } objectIdCol { type = object id; columnName = objectIdCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } objectCol { type = object; objectClassName = SwiftBoolObject; linkOriginPropertyName = (null); columnName = objectCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = YES; } uuidCol { type = uuid; columnName = uuidCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } anyCol { type = mixed; columnName = anyCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = NO; optional = NO; } arrayCol { type = object; objectClassName = SwiftBoolObject; linkOriginPropertyName = (null); columnName = arrayCol; indexed = NO; isPrimary = NO; array = YES; set = NO; dictionary = NO; optional = NO; } setCol { type = object; objectClassName = SwiftBoolObject; linkOriginPropertyName = (null); columnName = setCol; indexed = NO; isPrimary = NO; array = NO; set = YES; dictionary = NO; optional = NO; } mapCol { type = object; objectClassName = SwiftBoolObject; linkOriginPropertyName = (null); columnName = mapCol; indexed = NO; isPrimary = NO; array = NO; set = NO; dictionary = YES; optional = YES; } } """ XCTAssertEqual(objectSchema.description, expected.replacingOccurrences(of: " ", with: "\t")) } func testSubscript() { let objectSchema = swiftObjectSchema XCTAssertNil(objectSchema["noSuchProperty"]) XCTAssertEqual(objectSchema["boolCol"]!.name, "boolCol") } func testEquals() { let objectSchema = swiftObjectSchema XCTAssert(try! objectSchema == Realm().schema["SwiftObject"]!) XCTAssert(try! objectSchema != Realm().schema["SwiftStringObject"]!) } } ================================================ FILE: RealmSwift/Tests/ObjectTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import Realm import RealmSwift import Foundation import os.lock #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif private nonisolated(unsafe) var dynamicDefaultSeed = 0 private func nextDynamicDefaultSeed() -> Int { dynamicDefaultSeed += 1 return dynamicDefaultSeed } class SwiftDynamicDefaultObject: Object { @objc dynamic var intCol = nextDynamicDefaultSeed() @objc dynamic var floatCol = Float(nextDynamicDefaultSeed()) @objc dynamic var doubleCol = Double(nextDynamicDefaultSeed()) @objc dynamic var dateCol = Date(timeIntervalSinceReferenceDate: TimeInterval(nextDynamicDefaultSeed())) @objc dynamic var stringCol = UUID().uuidString @objc dynamic var binaryCol = UUID().uuidString.data(using: .utf8) override static func primaryKey() -> String? { return "intCol" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class ObjectTests: TestCase { // init() Tests are in ObjectCreationTests.swift // init(value:) tests are in ObjectCreationTests.swift func testRealm() { let standalone = SwiftStringObject() XCTAssertNil(standalone.realm) let realm = try! Realm() nonisolated(unsafe) var persisted: SwiftStringObject! try! realm.write { persisted = realm.create(SwiftStringObject.self) XCTAssertNotNil(persisted.realm) XCTAssertEqual(realm, persisted.realm!) } XCTAssertNotNil(persisted.realm) XCTAssertEqual(realm, persisted.realm!) dispatchSyncNewThread { autoreleasepool { XCTAssertNotEqual(try! Realm(), persisted.realm!) } } } func testObjectSchema() { let object = SwiftObject() let schema = object.objectSchema XCTAssert(schema as AnyObject is ObjectSchema) XCTAssert(schema.properties as AnyObject is [Property]) XCTAssertEqual(schema.className, "SwiftObject") XCTAssertEqual(schema.properties.map { $0.name }, ["boolCol", "intCol", "int8Col", "int16Col", "int32Col", "int64Col", "intEnumCol", "floatCol", "doubleCol", "stringCol", "binaryCol", "dateCol", "decimalCol", "objectIdCol", "objectCol", "uuidCol", "anyCol", "arrayCol", "setCol", "mapCol"] ) } func testObjectSchemaForObjectWithConvenienceInitializer() { let object = SwiftConvenienceInitializerObject(stringCol: "abc") let schema = object.objectSchema XCTAssert(schema as AnyObject is ObjectSchema) XCTAssert(schema.properties as AnyObject is [Property]) XCTAssertEqual(schema.className, "SwiftConvenienceInitializerObject") XCTAssertEqual(schema.properties.map { $0.name }, ["stringCol"]) } func testSharedSchemaUnmanaged() { let object = SwiftObject() XCTAssertEqual(type(of: object).sharedSchema(), SwiftObject.sharedSchema()) } func testSharedSchemaManaged() { let object = SwiftObject() XCTAssertEqual(type(of: object).sharedSchema(), SwiftObject.sharedSchema()) } func testBaseClassesDoNotHaveSharedSchema() { XCTAssertNil(ObjectBase.sharedSchema()) XCTAssertNil(Object.sharedSchema()) XCTAssertNil(EmbeddedObject.sharedSchema()) XCTAssertNil(RLMObject.sharedSchema()) XCTAssertNil(RLMEmbeddedObject.sharedSchema()) } func testInvalidated() { let object = SwiftObject() XCTAssertFalse(object.isInvalidated) let realm = try! Realm() try! realm.write { realm.add(object) XCTAssertFalse(object.isInvalidated) } try! realm.write { realm.deleteAll() XCTAssertTrue(object.isInvalidated) } XCTAssertTrue(object.isInvalidated) } func testInvalidatedWithCustomObjectClasses() { var config = Realm.Configuration.defaultConfiguration config.objectTypes = [SwiftObject.self, SwiftBoolObject.self] let realm = try! Realm(configuration: config) let object = SwiftObject() XCTAssertFalse(object.isInvalidated) try! realm.write { realm.add(object) XCTAssertFalse(object.isInvalidated) } try! realm.write { realm.deleteAll() XCTAssertTrue(object.isInvalidated) } XCTAssertTrue(object.isInvalidated) } func testDescription() { let object = SwiftObject() // swiftlint:disable line_length assertMatches(object.description, "SwiftObject \\{\n\tboolCol = 0;\n\tintCol = 123;\n\tint8Col = 123;\n\tint16Col = 123;\n\tint32Col = 123;\n\tint64Col = 123;\n\tintEnumCol = 1;\n\tfloatCol = 1\\.23;\n\tdoubleCol = 12\\.3;\n\tstringCol = a;\n\tbinaryCol = <.*61.*>;\n\tdateCol = 1970-01-01 00:00:01 \\+0000;\n\tdecimalCol = 1.23E6;\n\tobjectIdCol = 1234567890ab1234567890ab;\n\tobjectCol = SwiftBoolObject \\{\n\t\tboolCol = 0;\n\t\\};\n\tuuidCol = 137DECC8-B300-4954-A233-F89909F4FD89;\n\tanyCol = \\(null\\);\n\tarrayCol = List <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\tsetCol = MutableSet <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\tmapCol = Map <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\\}") let recursiveObject = SwiftRecursiveObject() recursiveObject.objects.append(recursiveObject) recursiveObject.objectSet.insert(recursiveObject) assertMatches(recursiveObject.description, "SwiftRecursiveObject \\{\n\tobjects = List <0x[0-9a-f]+> \\(\n\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\tobjects = List <0x[0-9a-f]+> \\(\n\t\t\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\t\t\tobjects = ;\n\t\t\t\t\tobjectSet = ;\n\t\t\t\t\\}\n\t\t\t\\);\n\t\t\tobjectSet = MutableSet <0x[0-9a-f]+> \\(\n\t\t\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\t\t\tobjects = ;\n\t\t\t\t\tobjectSet = ;\n\t\t\t\t\\}\n\t\t\t\\);\n\t\t\\}\n\t\\);\n\tobjectSet = MutableSet <0x[0-9a-f]+> \\(\n\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\tobjects = List <0x[0-9a-f]+> \\(\n\t\t\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\t\t\tobjects = ;\n\t\t\t\t\tobjectSet = ;\n\t\t\t\t\\}\n\t\t\t\\);\n\t\t\tobjectSet = MutableSet <0x[0-9a-f]+> \\(\n\t\t\t\t\\[0\\] SwiftRecursiveObject \\{\n\t\t\t\t\tobjects = ;\n\t\t\t\t\tobjectSet = ;\n\t\t\t\t\\}\n\t\t\t\\);\n\t\t\\}\n\t\\);\n\\}") let renamedObject = LinkToSwiftRenamedProperties1() renamedObject.linkA = SwiftRenamedProperties1() assertMatches(renamedObject.description, "LinkToSwiftRenamedProperties1 \\{\n\tlinkA = SwiftRenamedProperties1 \\{\n\t\tpropA = 0;\n\t\tpropB = ;\n\t\\};\n\tlinkB = \\(null\\);\n\tarray1 = List <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\tset1 = MutableSet <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\\}") assertMatches(renamedObject.linkA!.linking1.description, "LinkingObjects <0x[0-9a-f]+> \\(\n\n\\)") let realm = try! Realm() try! realm.write { realm.add(renamedObject) } assertMatches(renamedObject.description, "LinkToSwiftRenamedProperties1 \\{\n\tlinkA = SwiftRenamedProperties1 \\{\n\t\tpropA = 0;\n\t\tpropB = ;\n\t\\};\n\tlinkB = \\(null\\);\n\tarray1 = List <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\tset1 = MutableSet <0x[0-9a-f]+> \\(\n\t\n\t\\);\n\\}") assertMatches(renamedObject.linkA!.linking1.description, "LinkingObjects <0x[0-9a-f]+> \\(\n\t\\[0\\] LinkToSwiftRenamedProperties1 \\{\n\t\tlinkA = SwiftRenamedProperties1 \\{\n\t\t\tpropA = 0;\n\t\t\tpropB = ;\n\t\t\\};\n\t\tlinkB = \\(null\\);\n\t\tarray1 = List <0x[0-9a-f]+> \\(\n\t\t\n\t\t\\);\n\t\tset1 = MutableSet <0x[0-9a-f]+> \\(\n\t\t\n\t\t\\);\n\t\\}\n\\)") // swiftlint:enable line_length } func testSchemaHasPrimaryKey() { XCTAssertNil(Object.primaryKey(), "primary key should default to nil") XCTAssertNil(SwiftStringObject.primaryKey()) XCTAssertNil(SwiftStringObject().objectSchema.primaryKeyProperty) XCTAssertEqual(SwiftPrimaryStringObject.primaryKey()!, "stringCol") XCTAssertEqual(SwiftPrimaryStringObject().objectSchema.primaryKeyProperty!.name, "stringCol") XCTAssertEqual(SwiftPrimaryUUIDObject().objectSchema.primaryKeyProperty!.name, "uuidCol") XCTAssertEqual(SwiftPrimaryObjectIdObject().objectSchema.primaryKeyProperty!.name, "objectIdCol") } func testCannotUpdatePrimaryKey() { let realm = self.realmWithTestPath() let primaryKeyReason = "Primary key can't be changed .*after an object is inserted." let intObj = SwiftPrimaryIntObject() intObj.intCol = 1 intObj.intCol = 0 // can change primary key unattached XCTAssertEqual(0, intObj.intCol) let optionalIntObj = SwiftPrimaryOptionalIntObject() optionalIntObj.intCol.value = 1 optionalIntObj.intCol.value = 0 // can change primary key unattached XCTAssertEqual(0, optionalIntObj.intCol.value) let stringObj = SwiftPrimaryStringObject() stringObj.stringCol = "a" stringObj.stringCol = "b" // can change primary key unattached XCTAssertEqual("b", stringObj.stringCol) let uuidObj = SwiftPrimaryUUIDObject() uuidObj.uuidCol = UUID(uuidString: "8a12daba-8b23-11eb-8dcd-0242ac130003")! uuidObj.uuidCol = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! XCTAssertEqual(UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!, uuidObj.uuidCol) let objectIdObj = SwiftPrimaryObjectIdObject() objectIdObj.objectIdCol = ObjectId("1234567890ab1234567890aa") objectIdObj.objectIdCol = ObjectId("1234567890ab1234567890ab") XCTAssertEqual(ObjectId("1234567890ab1234567890ab"), objectIdObj.objectIdCol) try! realm.write { realm.add(intObj) assertThrows(intObj.intCol = 2, reasonMatching: primaryKeyReason) assertThrows(intObj["intCol"] = 2, reasonMatching: primaryKeyReason) assertThrows(intObj.setValue(2, forKey: "intCol"), reasonMatching: primaryKeyReason) realm.add(optionalIntObj) assertThrows(optionalIntObj.intCol.value = 2, reasonMatching: "Cannot modify primary key") assertThrows(optionalIntObj["intCol"] = 2, reasonMatching: primaryKeyReason) assertThrows(optionalIntObj.setValue(2, forKey: "intCol"), reasonMatching: "Cannot modify primary key") realm.add(stringObj) assertThrows(stringObj.stringCol = "c", reasonMatching: primaryKeyReason) assertThrows(stringObj["stringCol"] = "c", reasonMatching: primaryKeyReason) assertThrows(stringObj.setValue("c", forKey: "stringCol"), reasonMatching: primaryKeyReason) realm.add(uuidObj) assertThrows(uuidObj.uuidCol = UUID(uuidString: "4ee1fa48-8b23-11eb-8dcd-0242ac130003")!, reasonMatching: primaryKeyReason) assertThrows(uuidObj["uuidCol"] = UUID(uuidString: "4ee1fa48-8b23-11eb-8dcd-0242ac130003")!, reasonMatching: primaryKeyReason) assertThrows(uuidObj.setValue(UUID(uuidString: "4ee1fa48-8b23-11eb-8dcd-0242ac130003")!, forKey: "uuidCol"), reasonMatching: primaryKeyReason) realm.add(objectIdObj) assertThrows(objectIdObj.objectIdCol = ObjectId("1234567890ab1234567890ac"), reasonMatching: primaryKeyReason) assertThrows(objectIdObj["objectIdCol"] = ObjectId("1234567890ab1234567890ac"), reasonMatching: primaryKeyReason) assertThrows(objectIdObj.setValue(ObjectId("1234567890ab1234567890ac"), forKey: "objectIdCol"), reasonMatching: primaryKeyReason) } } func testIgnoredProperties() { XCTAssertEqual(Object.ignoredProperties(), [], "ignored properties should default to []") XCTAssertEqual(SwiftIgnoredPropertiesObject.ignoredProperties().count, 2) XCTAssertNil(SwiftIgnoredPropertiesObject().objectSchema["runtimeProperty"]) } func testIndexedProperties() { XCTAssertEqual(Object.indexedProperties(), [], "indexed properties should default to []") XCTAssertEqual(SwiftIndexedPropertiesObject.indexedProperties().count, 10) let objectSchema = SwiftIndexedPropertiesObject().objectSchema XCTAssertTrue(objectSchema["stringCol"]!.isIndexed) XCTAssertTrue(objectSchema["intCol"]!.isIndexed) XCTAssertTrue(objectSchema["int8Col"]!.isIndexed) XCTAssertTrue(objectSchema["int16Col"]!.isIndexed) XCTAssertTrue(objectSchema["int32Col"]!.isIndexed) XCTAssertTrue(objectSchema["int64Col"]!.isIndexed) XCTAssertTrue(objectSchema["boolCol"]!.isIndexed) XCTAssertTrue(objectSchema["dateCol"]!.isIndexed) XCTAssertTrue(objectSchema["uuidCol"]!.isIndexed) XCTAssertTrue(objectSchema["anyCol"]!.isIndexed) XCTAssertFalse(objectSchema["floatCol"]!.isIndexed) XCTAssertFalse(objectSchema["doubleCol"]!.isIndexed) XCTAssertFalse(objectSchema["dataCol"]!.isIndexed) } func testIndexedOptionalProperties() { XCTAssertEqual(Object.indexedProperties(), [], "indexed properties should default to []") XCTAssertEqual(SwiftIndexedOptionalPropertiesObject.indexedProperties().count, 9) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalStringCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalDateCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalBoolCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalIntCol"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalInt8Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalInt16Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalInt32Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalInt64Col"]!.isIndexed) XCTAssertTrue(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalUUIDCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalDataCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalFloatCol"]!.isIndexed) XCTAssertFalse(SwiftIndexedOptionalPropertiesObject().objectSchema["optionalDoubleCol"]!.isIndexed) } func testDynamicDefaultPropertyValues() { func assertDifferentPropertyValues(_ obj1: SwiftDynamicDefaultObject, _ obj2: SwiftDynamicDefaultObject) { XCTAssertNotEqual(obj1.intCol, obj2.intCol) XCTAssertNotEqual(obj1.floatCol, obj2.floatCol) XCTAssertNotEqual(obj1.doubleCol, obj2.doubleCol) XCTAssertNotEqual(obj1.dateCol.timeIntervalSinceReferenceDate, obj2.dateCol.timeIntervalSinceReferenceDate, accuracy: 0.01) XCTAssertNotEqual(obj1.stringCol, obj2.stringCol) XCTAssertNotEqual(obj1.binaryCol, obj2.binaryCol) } assertDifferentPropertyValues(SwiftDynamicDefaultObject(), SwiftDynamicDefaultObject()) let realm = try! Realm() try! realm.write { assertDifferentPropertyValues(realm.create(SwiftDynamicDefaultObject.self), realm.create(SwiftDynamicDefaultObject.self)) } } func testValueForKey() { let test: (SwiftObject) -> Void = { object in XCTAssertEqual(object.value(forKey: "boolCol") as! Bool?, false) XCTAssertEqual(object.value(forKey: "intCol") as! Int?, 123) XCTAssertEqual(object.value(forKey: "int8Col") as! Int8?, 123) XCTAssertEqual(object.value(forKey: "int16Col") as! Int16?, 123) XCTAssertEqual(object.value(forKey: "int32Col") as! Int32?, 123) XCTAssertEqual(object.value(forKey: "int64Col") as! Int64?, 123) XCTAssertEqual(object.value(forKey: "floatCol") as! Float?, 1.23 as Float) XCTAssertEqual(object.value(forKey: "doubleCol") as! Double?, 12.3) XCTAssertEqual(object.value(forKey: "stringCol") as! String?, "a") XCTAssertEqual(object.value(forKey: "uuidCol") as! UUID?, UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")!) XCTAssertNil(object.value(forKey: "anyCol")) let expected = object.value(forKey: "binaryCol") as! Data let actual = Data("a".utf8) XCTAssertEqual(expected, actual) XCTAssertEqual(object.value(forKey: "dateCol") as! Date?, Date(timeIntervalSince1970: 1)) XCTAssertEqual((object.value(forKey: "objectCol")! as! SwiftBoolObject).boolCol, false) XCTAssert(object.value(forKey: "arrayCol")! is List) XCTAssert(object.value(forKey: "setCol")! is MutableSet) } test(SwiftObject()) let realm = try! Realm() try! realm.write { test(realm.create(SwiftObject.self)) let addedObj = SwiftObject() realm.add(addedObj) test(addedObj) } } func testValueForKeyOptionals() { let test: (SwiftOptionalObject) -> Void = { object in XCTAssertNil(object.value(forKey: "optNSStringCol")) XCTAssertNil(object.value(forKey: "optStringCol")) XCTAssertNil(object.value(forKey: "optBinaryCol")) XCTAssertNil(object.value(forKey: "optDateCol")) XCTAssertNil(object.value(forKey: "optIntCol")) XCTAssertNil(object.value(forKey: "optInt8Col")) XCTAssertNil(object.value(forKey: "optInt16Col")) XCTAssertNil(object.value(forKey: "optInt32Col")) XCTAssertNil(object.value(forKey: "optInt64Col")) XCTAssertNil(object.value(forKey: "optFloatCol")) XCTAssertNil(object.value(forKey: "optDoubleCol")) XCTAssertNil(object.value(forKey: "optBoolCol")) XCTAssertNil(object.value(forKey: "optEnumCol")) XCTAssertNil(object.value(forKey: "optUuidCol")) } test(SwiftOptionalObject()) let realm = try! Realm() try! realm.write { test(realm.create(SwiftOptionalObject.self)) let addedObj = SwiftOptionalObject() realm.add(addedObj) test(addedObj) } } func testValueForKeyList() { let test: (SwiftListObject) -> Void = { object in XCTAssertNil((object.value(forKey: "int") as! List).first) XCTAssertNil((object.value(forKey: "int8") as! List).first) XCTAssertNil((object.value(forKey: "int16") as! List).first) XCTAssertNil((object.value(forKey: "int32") as! List).first) XCTAssertNil((object.value(forKey: "int64") as! List).first) XCTAssertNil((object.value(forKey: "float") as! List).first) XCTAssertNil((object.value(forKey: "double") as! List).first) XCTAssertNil((object.value(forKey: "string") as! List).first) XCTAssertNil((object.value(forKey: "data") as! List).first) XCTAssertNil((object.value(forKey: "date") as! List).first) XCTAssertNil((object.value(forKey: "decimal") as! List).first) XCTAssertNil((object.value(forKey: "objectId") as! List).first) XCTAssertNil((object.value(forKey: "uuid") as! List).first) XCTAssertNil((object.value(forKey: "any") as! List).first) // The `as Any?` casts below are only to silence the warning about it // happening implicitly and are not functionally required XCTAssertNil((object.value(forKey: "intOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "int8Opt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "int16Opt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "int32Opt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "int64Opt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "floatOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "doubleOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "stringOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "dataOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "dateOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "decimalOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "objectIdOpt") as! List).first as Any?) XCTAssertNil((object.value(forKey: "uuidOpt") as! List).first as Any?) } test(SwiftListObject()) let realm = try! Realm() try! realm.write { test(realm.create(SwiftListObject.self)) let addedObj = SwiftListObject() realm.add(addedObj) test(addedObj) } } func testValueForKeyMutableSet() { let test: (SwiftMutableSetObject) -> Void = { object in XCTAssertEqual((object.value(forKey: "int") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int8") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int16") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int32") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int64") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "float") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "double") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "string") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "data") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "date") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "decimal") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "objectId") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "uuid") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "any") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "intOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int8Opt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int16Opt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int32Opt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "int64Opt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "floatOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "doubleOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "stringOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "dataOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "dateOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "decimalOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "objectIdOpt") as! MutableSet).count, 0) XCTAssertEqual((object.value(forKey: "uuidOpt") as! MutableSet).count, 0) } test(SwiftMutableSetObject()) let realm = try! Realm() try! realm.write { test(realm.create(SwiftMutableSetObject.self)) let addedObj = SwiftMutableSetObject() realm.add(addedObj) test(addedObj) } } func testValueForKeyLinkingObjects() { let test: (SwiftDogObject) -> Void = { object in let owners = object.value(forKey: "owners") as! LinkingObjects if object.realm != nil { XCTAssertEqual(owners.first!.name, "owner name") } } let dog = SwiftDogObject() let owner = SwiftOwnerObject(value: ["owner name", dog]) test(dog) let realm = try! Realm() try! realm.write { test(realm.create(SwiftOwnerObject.self, value: owner).dog!) realm.add(owner) test(dog) } } func testSettingUnmanagedObjectValuesWithSwiftDictionary() { let json: [String: Any] = ["name": "foo", "array": [["stringCol": "bar"]], "intArray": [["intCol": 50]]] let object = SwiftArrayPropertyObject() json.keys.forEach { key in object.setValue(json[key], forKey: key) } XCTAssertEqual(object.name, "foo") XCTAssertEqual(object.array[0].stringCol, "bar") XCTAssertEqual(object.intArray[0].intCol, 50) let json2: [String: Any] = ["name": "foo", "set": [["stringCol": "bar"]], "intSet": [["intCol": 50]]] let object2 = SwiftMutableSetPropertyObject() json2.keys.forEach { key in object2.setValue(json2[key], forKey: key) } XCTAssertEqual(object2.name, "foo") XCTAssertEqual(object2.set[0].stringCol, "bar") XCTAssertEqual(object2.intSet[0].intCol, 50) } func testSettingUnmanagedObjectValuesWithBadSwiftDictionary() { let json: [String: Any] = ["name": "foo", "array": [["stringCol": NSObject()]], "intArray": [["intCol": 50]]] let object = SwiftArrayPropertyObject() assertThrows({ json.keys.forEach { key in object.setValue(json[key], forKey: key) } }()) let json2: [String: Any] = ["name": "foo", "set": [["stringCol": NSObject()]], "intSet": [["intCol": 50]]] let object2 = SwiftMutableSetPropertyObject() assertThrows({ json2.keys.forEach { key in object2.setValue(json2[key], forKey: key) } }()) } func setAndTestAllTypes(_ setter: (SwiftObject, Any?, String) -> Void, getter: (SwiftObject, String) -> (Any?), object: SwiftObject) { setter(object, true, "boolCol") XCTAssertEqual(getter(object, "boolCol") as! Bool?, true) setter(object, 321, "intCol") XCTAssertEqual(getter(object, "intCol") as! Int?, 321) setter(object, Int8(1), "int8Col") XCTAssertEqual(getter(object, "int8Col") as! Int8?, 1) setter(object, Int16(321), "int16Col") XCTAssertEqual(getter(object, "int16Col") as! Int16?, 321) setter(object, Int32(321), "int32Col") XCTAssertEqual(getter(object, "int32Col") as! Int32?, 321) setter(object, Int64(321), "int64Col") XCTAssertEqual(getter(object, "int64Col") as! Int64?, 321) setter(object, NSNumber(value: 32.1 as Float), "floatCol") XCTAssertEqual(getter(object, "floatCol") as! Float?, 32.1 as Float) setter(object, 3.21, "doubleCol") XCTAssertEqual(getter(object, "doubleCol") as! Double?, 3.21) setter(object, "z", "stringCol") XCTAssertEqual(getter(object, "stringCol") as! String?, "z") setter(object, Data("z".utf8), "binaryCol") let gotData = getter(object, "binaryCol") as! Data XCTAssertTrue(gotData == Data("z".utf8)) setter(object, Date(timeIntervalSince1970: 333), "dateCol") XCTAssertEqual(getter(object, "dateCol") as! Date?, Date(timeIntervalSince1970: 333)) setter(object, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89"), "uuidCol") XCTAssertEqual(getter(object, "uuidCol") as! UUID?, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")) setter(object, "hello", "anyCol") XCTAssertEqual(getter(object, "anyCol") as! String, "hello") let boolObject = SwiftBoolObject(value: [true]) setter(object, boolObject, "anyCol") assertEqual(getter(object, "anyCol") as? SwiftBoolObject, boolObject) XCTAssertEqual((getter(object, "anyCol") as! SwiftBoolObject).boolCol, true) setter(object, boolObject, "objectCol") assertEqual(getter(object, "objectCol") as? SwiftBoolObject, boolObject) XCTAssertEqual((getter(object, "objectCol") as! SwiftBoolObject).boolCol, true) let list = List() list.append(boolObject) setter(object, list, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 1) assertEqual((getter(object, "arrayCol") as! List).first!, boolObject) list.removeAll() setter(object, list, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 0) setter(object, [boolObject], "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 1) assertEqual((getter(object, "arrayCol") as! List).first!, boolObject) setter(object, nil, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 0) setter(object, [boolObject], "arrayCol") setter(object, NSNull(), "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 0) if object.realm != nil { let listRes = object.realm!.objects(SwiftBoolObject.self).filter("boolCol == true") setter(object, listRes, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, listRes.count) assertEqual((getter(object, "arrayCol") as! List).first!, boolObject) } let set = MutableSet() set.insert(boolObject) setter(object, set, "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 1) assertEqual((getter(object, "setCol") as! MutableSet)[0], boolObject) set.removeAll() setter(object, set, "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 0) setter(object, [boolObject], "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 1) assertEqual((getter(object, "setCol") as! MutableSet)[0], boolObject) setter(object, nil, "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 0) setter(object, [boolObject], "setCol") setter(object, NSNull(), "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 0) } static func dynamicSetAndTestAllTypes(_ setter: (DynamicObject, Any?, String) -> Void, getter: (DynamicObject, String) -> (Any?), object: DynamicObject, boolObject: DynamicObject) { setter(object, true, "boolCol") XCTAssertEqual((getter(object, "boolCol") as! Bool), true) setter(object, 321, "intCol") XCTAssertEqual((getter(object, "intCol") as! Int), 321) setter(object, Int8(1), "int8Col") XCTAssertEqual(getter(object, "int8Col") as! Int8?, 1) setter(object, Int16(321), "int16Col") XCTAssertEqual(getter(object, "int16Col") as! Int16?, 321) setter(object, Int32(321), "int32Col") XCTAssertEqual(getter(object, "int32Col") as! Int32?, 321) setter(object, Int64(321), "int64Col") XCTAssertEqual(getter(object, "int64Col") as! Int64?, 321) setter(object, NSNumber(value: 32.1 as Float), "floatCol") XCTAssertEqual((getter(object, "floatCol") as! Float), 32.1 as Float) setter(object, 3.21, "doubleCol") XCTAssertEqual((getter(object, "doubleCol") as! Double), 3.21) setter(object, "z", "stringCol") XCTAssertEqual((getter(object, "stringCol") as! String), "z") setter(object, Data("z".utf8), "binaryCol") let gotData = getter(object, "binaryCol") as! Data XCTAssertTrue(gotData == Data("z".utf8)) setter(object, Date(timeIntervalSince1970: 333), "dateCol") XCTAssertEqual((getter(object, "dateCol") as! Date), Date(timeIntervalSince1970: 333)) setter(object, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89"), "uuidCol") XCTAssertEqual(getter(object, "uuidCol") as! UUID?, UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")) setter(object, "hello", "anyCol") XCTAssertEqual((getter(object, "anyCol") as! String), "hello") setter(object, boolObject, "anyCol") assertEqual((getter(object, "anyCol") as! DynamicObject), boolObject) XCTAssertEqual(((getter(object, "anyCol") as! DynamicObject)["boolCol"] as! Bool), true) XCTAssertEqual(((getter(object, "anyCol") as! DynamicObject).boolCol as! Bool), true) setter(object, boolObject, "objectCol") assertEqual((getter(object, "objectCol") as! DynamicObject), boolObject) XCTAssertEqual(((getter(object, "objectCol") as! DynamicObject)["boolCol"] as! Bool), true) XCTAssertEqual(((getter(object, "objectCol") as! DynamicObject).boolCol as! Bool), true) setter(object, [boolObject], "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 1) assertEqual((getter(object, "arrayCol") as! List).first!, boolObject) let list = getter(object, "arrayCol") as! List list.removeAll() setter(object, list, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 0) setter(object, [boolObject], "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 1) assertEqual((getter(object, "arrayCol") as! List).first!, boolObject) setter(object, nil, "arrayCol") XCTAssertEqual((getter(object, "arrayCol") as! List).count, 0) setter(object, [boolObject], "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 1) assertEqual((getter(object, "setCol") as! MutableSet)[0], boolObject) let set = getter(object, "setCol") as! MutableSet set.removeAll() setter(object, set, "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 0) setter(object, [boolObject], "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 1) assertEqual((getter(object, "setCol") as! MutableSet)[0], boolObject) setter(object, nil, "setCol") XCTAssertEqual((getter(object, "setCol") as! MutableSet).count, 0) } // Yields a read-write migration `SwiftObject` to the given block private func withMigrationObject(block: @escaping @Sendable (MigrationObject, Migration) -> Void) { autoreleasepool { let realm = self.realmWithTestPath() try! realm.write { _ = realm.create(SwiftObject.self) } } let enumerated = Locked(false) autoreleasepool { let configuration = Realm.Configuration(schemaVersion: 1, migrationBlock: { migration, _ in migration.enumerateObjects(ofType: SwiftObject.className()) { _, newObject in if let newObject = newObject { block(newObject, migration) enumerated.value = true } } }) self.realmWithTestPath(configuration: configuration) XCTAssert(enumerated.value) } } func testSetValueForKey() { @Sendable func setter(_ object: Object, _ value: Any?, _ key: String) { object.setValue(value, forKey: key) } @Sendable func getter(_ object: Object, _ key: String) -> Any? { object.value(forKey: key) } withMigrationObject { migrationObject, migration in let boolObject = migration.create("SwiftBoolObject", value: [true]) Self.dynamicSetAndTestAllTypes(setter, getter: getter, object: migrationObject, boolObject: boolObject) } setAndTestAllTypes(setter, getter: getter, object: SwiftObject()) try! Realm().write { let persistedObject = try! Realm().create(SwiftObject.self) self.setAndTestAllTypes(setter, getter: getter, object: persistedObject) } } func testSubscript() { @Sendable func setter(_ object: Object, _ value: Any?, _ key: String) { object[key] = value } @Sendable func getter(_ object: Object, _ key: String) -> Any? { object[key] } withMigrationObject { migrationObject, migration in let boolObject = migration.create("SwiftBoolObject", value: [true]) Self.dynamicSetAndTestAllTypes(setter, getter: getter, object: migrationObject, boolObject: boolObject) } setAndTestAllTypes(setter, getter: getter, object: SwiftObject()) try! Realm().write { let persistedObject = try! Realm().create(SwiftObject.self) self.setAndTestAllTypes(setter, getter: getter, object: persistedObject) } } func testDynamicMemberSubscript() { withMigrationObject { migrationObject, migration in let boolObject = migration.create("SwiftBoolObject", value: [true]) migrationObject.anyCol = boolObject assertEqual(migrationObject.anyCol as? DynamicObject, boolObject) migrationObject.objectCol = boolObject assertEqual(migrationObject.objectCol as? DynamicObject, boolObject) migrationObject.anyCol = 12345 XCTAssertEqual(migrationObject.anyCol as! Int, 12345) } } func testDynamicList() { let realm = try! Realm() let arrayObject = SwiftArrayPropertyObject() let str1 = SwiftStringObject() let str2 = SwiftStringObject() arrayObject.array.append(objectsIn: [str1, str2]) try! realm.write { realm.add(arrayObject) } let dynamicArray = arrayObject.dynamicList("array") XCTAssertEqual(dynamicArray.count, 2) assertEqual(dynamicArray[0], str1) assertEqual(dynamicArray[1], str2) XCTAssertEqual(arrayObject.dynamicList("intArray").count, 0) assertThrows(arrayObject.dynamicList("noSuchList")) } func testDynamicMutableSet() { let realm = try! Realm() let setObject = SwiftMutableSetPropertyObject() let str1 = SwiftStringObject() let str2 = SwiftStringObject() setObject.set.insert(objectsIn: [str1, str2]) try! realm.write { realm.add(setObject) } let dynamicSet = setObject.dynamicMutableSet("set") XCTAssertEqual(dynamicSet.count, 2) XCTAssertTrue(dynamicSet.map { (o) in o.isSameObject(as: str1) }.contains(true)) XCTAssertTrue(dynamicSet.map { (o) in o.isSameObject(as: str2) }.contains(true)) XCTAssertEqual(setObject.dynamicMutableSet("intSet").count, 0) assertThrows(setObject.dynamicMutableSet("noSuchSet")) } func testObjectiveCTypeProperties() { let realm = try! Realm() var object: SwiftObjectiveCTypesObject! let now = NSDate() let data = Data("fizzbuzz".utf8) as NSData try! realm.write { object = SwiftObjectiveCTypesObject() realm.add(object) object.stringCol = "Hello world!" object.dateCol = now object.dataCol = data } XCTAssertEqual("Hello world!", object.stringCol) XCTAssertEqual(now, object.dateCol) XCTAssertEqual(data, object.dataCol) } // MARK: - Observation tests func testObserveUnmanagedObject() { assertThrows(SwiftIntObject().observe { _ in }, reason: "managed") assertThrows(SwiftIntObject().observe(keyPaths: ["intCol"]) { _ in }, reason: "managed") } @MainActor func testDeleteObservedObject() throws { let realm = try Realm() realm.beginWrite() let object0 = realm.create(SwiftIntObject.self, value: [0]) let object1 = realm.create(SwiftIntObject.self, value: [0]) try realm.commitWrite() let exp0 = expectation(description: "Delete observed object") let token0 = object0.observe { change in guard case .deleted = change else { XCTFail("expected .deleted, got \(change)") return } exp0.fulfill() } let exp1 = expectation(description: "Delete observed object") let token1 = object1.observe(keyPaths: ["intCol"]) { change in guard case .deleted = change else { XCTFail("expected .deleted, got \(change)") return } exp1.fulfill() } realm.beginWrite() realm.delete(object0) realm.delete(object1) try realm.commitWrite() waitForExpectations(timeout: 1) token0.invalidate() token1.invalidate() } func createObject(_ value: Int = 0) throws -> SwiftObject { let realm = try Realm() return try realm.write { realm.create(SwiftObject.self, value: ["intCol": value]) } } func testObserveInvalidKeyPath() throws { let object = try createObject() assertThrows(object.observe(keyPaths: ["notAProperty"], { _ in }), reason: "property 'notAProperty' not found in object of type 'SwiftObject'") assertThrows(object.observe(keyPaths: ["arrayCol.alsoNotAProperty"], { _ in }), reason: "property 'alsoNotAProperty' not found in object of type 'SwiftBoolObject'") } func checkChange(_ name: String, _ old: T?, _ new: U?, _ change: ObjectChange) { if case .change(_, let properties) = change { XCTAssertEqual(properties.count, 1) if let prop = properties.first { XCTAssertEqual(prop.name, name) XCTAssertEqual(prop.oldValue as? T, old) XCTAssertEqual(prop.newValue as? U, new) } } else { XCTFail("expected .change, got \(change)") } } func expectChange(_ name: String, _ old: T?, _ new: U?, _ inverted: Bool = false) -> ((ObjectChange) -> Void) { let exp = expectation(description: "change from \(String(describing: old)) to \(String(describing: new))") exp.isInverted = inverted return { change in self.checkChange(name, old, new, change) exp.fulfill() } } @MainActor func testModifyObservedObjectLocally() throws { let object = try createObject() let token = object.observe(expectChange("intCol", Int?.none, 2)) try object.realm!.write { object.intCol = 2 } waitForExpectations(timeout: 2) token.invalidate() } @MainActor func testModifyObservedKeyPathLocally() throws { let object = try createObject() let token = object.observe(keyPaths: ["intCol"], expectChange("intCol", Int?.none, 2)) try object.realm!.write { object.intCol = 2 } waitForExpectations(timeout: 0.1) token.invalidate() } @MainActor func testModifyUnobservedKeyPathLocally() throws { let object = try createObject() // Expect no notification for "boolCol" keypath when "intCol" is modified let ex = expectation(description: "no change") ex.isInverted = true let token = object.observe(keyPaths: ["boolCol"]) { _ in ex.fulfill() } try object.realm!.write { object.intCol = 3 } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testModifyMultipleObservedPartialKeyPathLocally() throws { let object = try createObject() // Expect notification for "intCol" keyPath when "intCol" is modified var ex = expectation(description: "expect notification") var token = object.observe(keyPaths: [\SwiftObject.intCol, \SwiftObject.stringCol]) { changes in if case .change(_, let properties) = changes { XCTAssertEqual(properties.count, 1) XCTAssertEqual(properties[0].newValue as! Int, 2) ex.fulfill() } } try object.realm!.write { object.intCol = 2 } waitForExpectations(timeout: 0.1) token.invalidate() // Expect notification for "stringCol" keyPath when "stringCol" is modified ex = expectation(description: "expect notification") token = object.observe(keyPaths: [\SwiftObject.intCol, \SwiftObject.stringCol]) { changes in if case .change(_, let properties) = changes { nonisolated(unsafe) let p = properties // this appears to be an autoclosure bug XCTAssertEqual(p.count, 1) XCTAssertEqual(p[0].newValue as! String, "new string") ex.fulfill() } } try object.realm!.write { object.stringCol = "new string" } waitForExpectations(timeout: 0.1) token.invalidate() } @MainActor func testModifyUnobservedPartialKeyPathLocally() throws { let object = try createObject() // Expect no notification for "boolCol" keypath when "intCol" is modified let ex = expectation(description: "no change") ex.isInverted = true let token = object.observe(keyPaths: [\SwiftObject.boolCol, \SwiftObject.stringCol]) { _ in ex.fulfill() } try object.realm!.write { object.intCol = 3 } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testModifyObservedObjectRemotely() throws { let object = try createObject(1) let token = object.observe(expectChange("intCol", 1, 2)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftObject.self).first!.intCol = 2 } } object.realm!.refresh() waitForExpectations(timeout: 0) token.invalidate() } @MainActor func testModifyObservedKeyPathRemotely() throws { let object = try createObject(123) // Expect notification for "intCol" keyPath when "intCol" is modified let token = object.observe(keyPaths: ["intCol"], expectChange("intCol", 123, 2)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftObject.self).first!.intCol = 2 } } object.realm!.refresh() waitForExpectations(timeout: 0.1) token.invalidate() } @MainActor func testModifyUnobservedKeyPathRemotely() throws { let object = try createObject() // Expect no notification for "boolCol" keypath when "intCol" is modified let ex = expectation(description: "no change") ex.isInverted = true let token = object.observe(keyPaths: ["boolCol"]) { _ in ex.fulfill() } dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { let first = realm.objects(SwiftObject.self).first! first.intCol += 1 } } object.realm!.refresh() waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testListPropertyNotifications() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftRecursiveObject.self) try! realm.commitWrite() let token = object.observe(expectChange("objects", Int?.none, Int?.none)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { let obj = realm.objects(SwiftRecursiveObject.self).first! obj.objects.append(obj) } } waitForExpectations(timeout: 2) token.invalidate() } @MainActor func testListPropertyKeyPathNotifications() { let realm = try! Realm() realm.beginWrite() let employee = realm.create(SwiftEmployeeObject.self) let company = realm.create(SwiftCompanyObject.self) company.employees.append(employee) try! realm.commitWrite() // Expect no notification for "employees" when "employee.hired" is changed var ex = expectation(description: "no change notification") ex.isInverted = true var token = company.observe(keyPaths: ["employees"], { _ in ex.fulfill() }) try! realm.write { employee.hired = true } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect a notification for "employees.hired" when "employee.hired" is changed token = company.observe(keyPaths: ["employees.hired"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { XCTAssertTrue(employee.hired) employee.hired = false } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect no notification for "employees.hired" when "employee.age" is changed. ex = expectation(description: "no change notification") ex.isInverted = true token = company.observe(keyPaths: ["employees.hired"], { _ in ex.fulfill() }) try! realm.write { employee.age = 35 } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect notification for "employees.hired" when an employee is deleted. token = company.observe(keyPaths: ["employees.hired"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { realm.delete(employee) } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect notification for "employees.hired" when an employee is added. token = company.observe(keyPaths: ["employees.hired"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { let employee2 = realm.create(SwiftEmployeeObject.self) company.employees.append(employee2) } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect notification for "employees.hired" when an employee is reassigned. token = company.observe(keyPaths: ["employees.hired"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { let employee3 = realm.create(SwiftEmployeeObject.self) company.employees[0] = employee3 } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect notification for "employees" when an employee is added. token = company.observe(keyPaths: ["employees"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { let employee4 = realm.create(SwiftEmployeeObject.self) company.employees.append(employee4) } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect notification for "employees" when an employee is reassigned. token = company.observe(keyPaths: ["employees"], expectChange("employees", Int?.none, Int?.none)) try! realm.write { let employee5 = realm.create(SwiftEmployeeObject.self) company.employees[0] = employee5 } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() // Expect no notification for "employees" when "company.name" is changed ex = expectation(description: "no change notification") ex.isInverted = true token = company.observe(keyPaths: ["employees"], { _ in ex.fulfill() }) try! realm.write { company.name = "changed" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testLinkPropertyKeyPathNotifications1() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect notification for "dog.dogName" when "dog.dogName" is changed let token = person.observe(keyPaths: ["dog.dogName"], expectChange("dog", Int?.none, Int?.none)) try! realm.write { dog.dogName = "rex" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testLinkPropertyKeyPathNotifications2() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect notification for "dog.dogName" when "dog" is reassigned. let token = person.observe(keyPaths: ["dog.dogName"], expectChange("dog", Int?.none, Int?.none)) try! realm.write { let newDog = SwiftDogObject() person.dog = newDog } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testLinkPropertyKeyPathNotifications3() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect no notification for "dog" when "person.name" is changed let ex = expectation(description: "no change notification") ex.isInverted = true let token = person.observe(keyPaths: ["dog"], { _ in ex.fulfill() }) try! realm.write { person.name = "Teddy" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testLinkPropertyKeyPathNotifications4() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect no notification for "dog" when "dog.dogName" is changed let ex = expectation(description: "no change notification") ex.isInverted = true let token = person.observe(keyPaths: ["dog"], {_ in ex.fulfill() }) try! realm.write { dog.dogName = "fido" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testBacklinkPropertyKeyPathNotifications1() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect no notification for "owners" when "dog.dogName" is changed let ex = expectation(description: "no change notification") ex.isInverted = true let token = dog.observe(keyPaths: ["owners"], { _ in ex.fulfill() }) try! realm.write { dog.dogName = "fido" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testBacklinkPropertyKeyPathNotifications2() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect no notification for "owners" when "owner.name" is changed let ex = expectation(description: "no change notification") ex.isInverted = true let token = dog.observe(keyPaths: ["owners"], { _ in ex.fulfill() }) try! realm.write { let owner = dog.owners.first! owner.name = "Tom" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testBacklinkPropertyKeyPathNotifications3() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect notification for "owners.name" when "owner.name" is changed let token = dog.observe(keyPaths: ["owners.name"], expectChange("owners", String?.none, String?.none)) try! realm.write { let owner = dog.owners.first! owner.name = "Abe" } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testBacklinkPropertyKeyPathNotifications4() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect notification for "owners" when a new owner is added. let token = dog.observe(keyPaths: ["owners"], expectChange("owners", Int?.none, Int?.none)) try! realm.write { let newPerson = SwiftOwnerObject() realm.add(newPerson) newPerson.dog = dog } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testBacklinkPropertyKeyPathNotifications5() { let realm = try! Realm() realm.beginWrite() let person = realm.create(SwiftOwnerObject.self) let dog = realm.create(SwiftDogObject.self) person.dog = dog try! realm.commitWrite() // Expect notification for "owners.name" when a new owner is added. let token = dog.observe(keyPaths: ["owners.name"], expectChange("owners", Int?.none, Int?.none)) try! realm.write { let newPerson = SwiftOwnerObject() realm.add(newPerson) newPerson.dog = dog } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testMutableSetPropertyNotifications() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftRecursiveObject.self) try! realm.commitWrite() let token = object.observe(expectChange("objectSet", Int?.none, Int?.none)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { let obj = realm.objects(SwiftRecursiveObject.self).first! obj.objectSet.insert(obj) } } waitForExpectations(timeout: 2) token.invalidate() } @MainActor func testOptionalPropertyNotifications() { let realm = try! Realm() let object = SwiftOptionalDefaultValuesObject() try! realm.write { realm.add(object) } var token = object.observe(expectChange("optIntCol", 1, 2)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = 2 } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() token = object.observe(expectChange("optIntCol", 2, Int?.none)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = nil } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() token = object.observe(expectChange("optIntCol", Int?.none, 3)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = 3 } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() } @MainActor func testOptionalPropertyKeyPathNotifications() { let realm = try! Realm() let object = SwiftOptionalDefaultValuesObject() try! realm.write { realm.add(object) } // Expect notification for change on observed path var token = object.observe(keyPaths: ["optIntCol"], expectChange("optIntCol", 1, 2)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = 2 } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() // Expect no notification for change outside of observed path token = object.observe(keyPaths: ["optStringCol"], expectChange("optIntCol", 2, 3, true)) // Passing true inverts expectation dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = 3 } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() // Expect notification for change from value to nil on observed path token = object.observe(keyPaths: ["optIntCol"], expectChange("optIntCol", 3, Int?.none)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = nil } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() // Expect notification for change from nil to value on observed path token = object.observe(keyPaths: ["optIntCol"], expectChange("optIntCol", Int?.none, 2)) dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.objects(SwiftOptionalDefaultValuesObject.self).first!.optIntCol.value = 2 } } realm.refresh() waitForExpectations(timeout: 0) token.invalidate() } func testObserveOnDifferentQueue() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftIntObject.self, value: [1]) try! realm.commitWrite() let queue = DispatchQueue(label: "label") let sema = DispatchSemaphore(value: 0) let token = object.observe(on: queue) { change in self.checkChange("intCol", 1, 2, change) sema.signal() } // wait for the notification to be registered as otherwise it may not // have the old value queue.sync { } try! realm.write { object.intCol = 2 } sema.wait() token.invalidate() queue.sync { } } func testObserveKeyPathOnDifferentQueue() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftObject.self) object.intCol = 1 try! realm.commitWrite() let queue = DispatchQueue(label: "label") let sema = DispatchSemaphore(value: 0) let token = object.observe(keyPaths: ["intCol"], on: queue) { change in self.checkChange("intCol", 1, 2, change) sema.signal() } // wait for the notification to be registered as otherwise it may not // have the old value queue.sync { } try! realm.write { object.intCol = 2 } sema.wait() token.invalidate() queue.sync { } } func testInvalidateObserverOnDifferentQueueBeforeRegistration() { let realm = try! Realm() realm.beginWrite() let object = realm.create(SwiftIntObject.self, value: [1]) try! realm.commitWrite() let queue = DispatchQueue(label: "label") let sema = DispatchSemaphore(value: 0) // Block the queue for now queue.async { sema.wait() } // Add two observers, invalidating one let token1 = object.observe(on: queue) { _ in XCTFail("notification should not have fired") } let token2 = object.observe(on: queue) { _ in sema.signal() } token1.invalidate() // Now let token2 registration happen sema.signal() queue.sync { } // Perform a write and make sure only token2 notifies try! realm.write { object.intCol = 2 } sema.wait() token2.invalidate() queue.sync { } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func expectChange(_ obj: T, _ ex: XCTestExpectation, newValue: Int) -> @Sendable (isolated CustomGlobalActor, ObjectChange) -> Void { nonisolated(unsafe) let unchecked = obj return { _, change in XCTAssertFalse(Thread.isMainThread) guard case let .change(object, props) = change else { return XCTFail("Expected change event but got \(change))") } XCTAssertNotIdentical(unchecked, object) XCTAssertEqual(RLMObjectBaseGetCombineId(unchecked), RLMObjectBaseGetCombineId(object)) XCTAssertEqual(props.count, 1) XCTAssertEqual(props[0].name, "intCol") XCTAssertEqual(props[0].oldValue! as! Int, 0) XCTAssertEqual(props[0].newValue! as! Int, newValue) ex.fulfill() } } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testObserveOnActor() async throws { let obj1 = try createObject() let obj2 = try createObject() let ex1 = expectation(description: "first object") let ex2 = expectation(description: "second object") let tokens = await [ obj1.observe(keyPaths: ["intCol"], on: CustomGlobalActor.shared, expectChange(obj1, ex1, newValue: 2)), obj2.observe(keyPaths: [\SwiftObject.intCol], on: CustomGlobalActor.shared, expectChange(obj2, ex2, newValue: 3)) ] let realm = obj1.realm! try await realm.asyncWrite { obj1.boolCol = true obj2.boolCol = true } try await realm.asyncWrite { obj1.intCol = 2 obj2.intCol = 3 } await fulfillment(of: [ex1, ex2], timeout: 2.0) tokens.forEach { $0.invalidate() } } #if swift(<6) // FIXME: need async iterator implementation // This test consistently crashes inside the Swift runtime when building // with SPM. @MainActor @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testAsyncSequenceObserve() async throws { let obj = try createObject() let ex = Locked(expectation(description: "got value")) let task = Task { @MainActor in var value = 0 for try await object in valuePublisher(obj).values { XCTAssertIdentical(object, obj) value += 1 XCTAssertEqual(object.intCol, value) ex.wrappedValue.fulfill() } } for i in 1..<10 { try await obj.realm!.asyncWrite { obj.intCol += 1 } await fulfillment(of: [ex.wrappedValue]) if i < 9 { ex.wrappedValue = expectation(description: "got value") } } task.cancel() _ = try await task.value } @MainActor @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testAsyncSequenceObserveCustomActor() async throws { let obj = try createObject() let ex = Locked(expectation(description: "got next")) let task = Task { @CustomGlobalActor in let realm = try await Realm(actor: CustomGlobalActor.shared) let obj = realm.objects(SwiftObject.self).first! var value = 0 for try await change in changesetPublisher(obj).values { guard case let .change(object, props) = change else { return XCTFail("Expected .change, got \(change)") } XCTAssertIdentical(object, obj) value += 1 XCTAssertEqual(object.intCol, value) XCTAssertEqual(props.count, 1) let prop = props[0] XCTAssertEqual(prop.name, "intCol") XCTAssertEqual(prop.oldValue as? Int, value - 1) XCTAssertEqual(prop.newValue as? Int, value) ex.wrappedValue.fulfill() } XCTAssertEqual(value, 9) ex.wrappedValue.fulfill() } // Use a dummy observation as synchronization to reduce the chance of // writing before the notifier is ready let token = await obj.observe(on: CustomGlobalActor.shared) { (_, _) in } for _ in 1..<10 { try await obj.realm!.asyncWrite { obj.intCol += 1 } await fulfillment(of: [ex.wrappedValue]) ex.wrappedValue = expectation(description: "got next") } try await obj.realm!.asyncWrite { obj.realm!.delete(obj) } await fulfillment(of: [ex.wrappedValue]) _ = try await task.value token.invalidate() } #endif @MainActor @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testObserveOnAlreadyCancelledTask() async throws { let obj = try createObject() _ = await Task { @MainActor in withUnsafeCurrentTask { task in task?.cancel() } let token = await obj.observe(on: MainActor.shared) { _, _ in XCTFail("should not have been called") } XCTAssertFalse(token.invalidate()) }.value } @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testCancelTaskWhileWaitingForInitial() async throws { // This can't be tested deterministically as it's trying to hit specific // timing windows, so instead spawn a bunch of tasks and hope that at // least one is in each of the interesting states. Not handling all of // the cancel timing correctly is likely to make this test either crash // or hang (if there's some scenario where we fail to call the completion // handler). _ = try createObject().realm! let waitingForRealm = Locked(0) let active = Locked(0) let completed = Locked(0) await withTaskGroup(of: NotificationToken.self) { group in while active.value < 10 { group.addTask { @Sendable @CustomGlobalActor in waitingForRealm.withLock { $0 += 1 } // can throw due to cancellation guard let realm = try? await openRealm(actor: CustomGlobalActor.shared) else { waitingForRealm.withLock { $0 -= 1 } return NotificationToken() } waitingForRealm.withLock { $0 -= 1 } active.withLock { $0 += 1 } let token = await realm.objects(SwiftObject.self).first!.observe(on: CustomGlobalActor.shared) { _, _ in } completed.withLock { $0 += 1 } return token } // Actor executors aren't fifo, so we can sometimes prevent the // async opens from ever completing by continuously spawning new // tasks while waitingForRealm.value >= 10 { await Task.yield() } } group.cancelAll() await group.waitForAll() XCTAssertEqual(waitingForRealm.value, 0) XCTAssertEqual(active.value, completed.value) } } // MARK: Equality Tests func testEqualityForObjectTypeWithPrimaryKey() { let realm = try! Realm() let pk = "123456" let testObject = SwiftPrimaryStringObject() testObject.stringCol = pk testObject.intCol = 12345 let unmanaged = SwiftPrimaryStringObject() unmanaged.stringCol = pk unmanaged.intCol = 12345 let otherObject = SwiftPrimaryStringObject() otherObject.stringCol = "not" + pk otherObject.intCol = 12345 try! realm.write { realm.add([testObject, otherObject]) } // Should not match an object that's not equal. XCTAssertNotEqual(testObject, otherObject) // Should not match an object whose fields are equal if it's not the same row in the database. XCTAssertNotEqual(testObject, unmanaged) // Should match an object that represents the same row. let retrievedObject = realm.object(ofType: SwiftPrimaryStringObject.self, forPrimaryKey: pk)! XCTAssertEqual(testObject, retrievedObject) XCTAssertEqual(testObject.hash, retrievedObject.hash) XCTAssertTrue(testObject.isSameObject(as: retrievedObject)) } func testEqualityForObjectTypeWithoutPrimaryKey() { let realm = try! Realm() let pk = "123456" XCTAssertNil(SwiftStringObject.primaryKey()) let testObject = SwiftStringObject() testObject.stringCol = pk let alias = testObject try! realm.write { realm.add(testObject) } XCTAssertEqual(testObject, alias) // Should not match an object even if it represents the same row. let retrievedObject = realm.objects(SwiftStringObject.self).first! XCTAssertNotEqual(testObject, retrievedObject) // Should be able to use `isSameObject(as:)` to check if same row in the database. XCTAssertTrue(testObject.isSameObject(as: retrievedObject)) } func testEqualityForFrozenObjectTypeWithoutPrimaryKey() { let realm = try! Realm() let testObject = try! realm.write { realm.create(SwiftStringObject.self) } let frozen = testObject.freeze() let retrievedObject = realm.objects(SwiftStringObject.self).first!.freeze() XCTAssertEqual(frozen, retrievedObject) } func testRetrievingObjectWithRuntimeType() { let realm = try! Realm() let unmanagedStringObject = SwiftPrimaryStringObject() unmanagedStringObject.stringCol = UUID().uuidString let managedStringObject = SwiftPrimaryStringObject() managedStringObject.stringCol = UUID().uuidString // Add the object. try! realm.write { realm.add(managedStringObject) } // Shouldn't throw when using type(of:). XCTAssertNotNil(realm.object(ofType: type(of: unmanagedStringObject), forPrimaryKey: managedStringObject.stringCol)) // Shouldn't throw when using type(of:). XCTAssertNotNil(realm.object(ofType: type(of: managedStringObject), forPrimaryKey: managedStringObject.stringCol)) } func testRetrievingObjectsWithRuntimeType() { let realm = try! Realm() let unmanagedStringObject = SwiftStringObject() unmanagedStringObject.stringCol = "foo" let managedStringObject = SwiftStringObject() managedStringObject.stringCol = "bar" // Add the object. try! realm.write { realm.add(managedStringObject) } // Shouldn't throw when using type(of:). XCTAssertEqual(realm.objects(type(of: unmanagedStringObject)).count, 1) // Shouldn't throw when using type(of:). XCTAssertEqual(realm.objects(type(of: managedStringObject)).count, 1) } // MARK: Frozen Objects Tests func testIsFrozen() { let obj = SwiftStringObject() XCTAssertFalse(obj.isFrozen) let realm = try! Realm() try! realm.write { realm.add(obj) } XCTAssertFalse(obj.isFrozen) let frozen = obj.freeze() XCTAssertFalse(obj.isFrozen) XCTAssertTrue(frozen.isFrozen) } func testFreezeUnmanaged() { assertThrows(SwiftStringObject().freeze(), reason: "Unmanaged objects cannot be frozen.") } func testModifyFrozenObject() { let obj = SwiftStringObject() XCTAssertFalse(obj.isFrozen) let realm = try! Realm() try! realm.write { realm.add(obj) } let frozenObj = obj.freeze() assertThrows(frozenObj.stringCol = "foo", reason: "Attempting to modify a frozen object - call thaw on the Object instance first.") } func testFreezeDynamicObject() { let realm = try! Realm() try! realm.write { realm.create(SwiftObject.self, value: ["arrayCol": [[true]], "setCol": [[true]]]) } let obj = realm.dynamicObjects("SwiftObject").first!.freeze() XCTAssertTrue(obj.isFrozen) XCTAssertTrue(obj.dynamicList("arrayCol").isFrozen) XCTAssertTrue(obj.dynamicList("arrayCol").first!.isFrozen) XCTAssertTrue(obj.dynamicMutableSet("setCol").isFrozen) XCTAssertTrue(obj.dynamicMutableSet("setCol").first!.isFrozen) } func testFreezeAllPropertyTypes() { let realm = try! Realm() let (obj, optObj, listObj) = try! realm.write { return ( realm.create(SwiftObject.self, value: [ "boolCol": true, "intCol": 456, "floatCol": 4.56 as Float, "doubleCol": 45.6, "stringCol": "b", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 2), "objectCol": [true], "uuidCol": UUID(), "anyCol": "hello" ]), realm.create(SwiftOptionalObject.self, value: [ "optNSStringCol": "NSString", "optStringCol": "String", "optBinaryCol": Data(), "optDateCol": Date(), "optIntCol": 1, "optInt8Col": 2, "optInt16Col": 3, "optInt32Col": 4, "optInt64Col": 5, "optFloatCol": 6.1, "optDoubleCol": 7.2, "optBoolCol": true ]), realm.create(SwiftListObject.self, value: [ "int": [1], "int8": [2], "int16": [3], "int32": [4], "int64": [5], "float": [6.6 as Float], "double": [7.7], "string": ["8"], "data": [Data("9".utf8)], "date": [Date(timeIntervalSince1970: 10)], "intOpt": [11, nil], "int8Opt": [12, nil], "int16Opt": [13, nil], "int32Opt": [14, nil], "int64Opt": [15, nil], "floatOpt": [16.16, nil], "doubleOpt": [17.17, nil], "stringOpt": ["18", nil], "dataOpt": [Data("19".utf8), nil], "dateOpt": [Date(timeIntervalSince1970: 20), nil], "uuid": [UUID()], "uuidOpt": [UUID(), nil], "any": ["hello", nil] ]) ) } let frozenObj = obj.freeze() XCTAssertEqual(obj.boolCol, frozenObj.boolCol) XCTAssertEqual(obj.intCol, frozenObj.intCol) XCTAssertEqual(obj.floatCol, frozenObj.floatCol) XCTAssertEqual(obj.doubleCol, frozenObj.doubleCol) XCTAssertEqual(obj.stringCol, frozenObj.stringCol) XCTAssertEqual(obj.binaryCol, frozenObj.binaryCol) XCTAssertEqual(obj.dateCol, frozenObj.dateCol) XCTAssertEqual(obj.objectCol?.boolCol, frozenObj.objectCol?.boolCol) XCTAssertEqual(obj.uuidCol, frozenObj.uuidCol) XCTAssertEqual(obj.anyCol.value, frozenObj.anyCol.value) let frozenOptObj = optObj.freeze() XCTAssertEqual(optObj.optNSStringCol, frozenOptObj.optNSStringCol) XCTAssertEqual(optObj.optStringCol, frozenOptObj.optStringCol) XCTAssertEqual(optObj.optBinaryCol, frozenOptObj.optBinaryCol) XCTAssertEqual(optObj.optDateCol, frozenOptObj.optDateCol) XCTAssertEqual(optObj.optIntCol.value, frozenOptObj.optIntCol.value) XCTAssertEqual(optObj.optInt8Col.value, frozenOptObj.optInt8Col.value) XCTAssertEqual(optObj.optInt16Col.value, frozenOptObj.optInt16Col.value) XCTAssertEqual(optObj.optInt32Col.value, frozenOptObj.optInt32Col.value) XCTAssertEqual(optObj.optInt64Col.value, frozenOptObj.optInt64Col.value) XCTAssertEqual(optObj.optFloatCol.value, frozenOptObj.optFloatCol.value) XCTAssertEqual(optObj.optDoubleCol.value, frozenOptObj.optDoubleCol.value) XCTAssertEqual(optObj.optBoolCol.value, frozenOptObj.optBoolCol.value) XCTAssertEqual(optObj.optEnumCol.value, frozenOptObj.optEnumCol.value) XCTAssertEqual(optObj.optUuidCol, frozenOptObj.optUuidCol) let frozenListObj = listObj.freeze() XCTAssertEqual(Array(listObj.int), Array(frozenListObj.int)) XCTAssertEqual(Array(listObj.int8), Array(frozenListObj.int8)) XCTAssertEqual(Array(listObj.int16), Array(frozenListObj.int16)) XCTAssertEqual(Array(listObj.int32), Array(frozenListObj.int32)) XCTAssertEqual(Array(listObj.int64), Array(frozenListObj.int64)) XCTAssertEqual(Array(listObj.float), Array(frozenListObj.float)) XCTAssertEqual(Array(listObj.double), Array(frozenListObj.double)) XCTAssertEqual(Array(listObj.string), Array(frozenListObj.string)) XCTAssertEqual(Array(listObj.data), Array(frozenListObj.data)) XCTAssertEqual(Array(listObj.date), Array(frozenListObj.date)) XCTAssertEqual(Array(listObj.intOpt), Array(frozenListObj.intOpt)) XCTAssertEqual(Array(listObj.int8Opt), Array(frozenListObj.int8Opt)) XCTAssertEqual(Array(listObj.int16Opt), Array(frozenListObj.int16Opt)) XCTAssertEqual(Array(listObj.int32Opt), Array(frozenListObj.int32Opt)) XCTAssertEqual(Array(listObj.int64Opt), Array(frozenListObj.int64Opt)) XCTAssertEqual(Array(listObj.floatOpt), Array(frozenListObj.floatOpt)) XCTAssertEqual(Array(listObj.doubleOpt), Array(frozenListObj.doubleOpt)) XCTAssertEqual(Array(listObj.stringOpt), Array(frozenListObj.stringOpt)) XCTAssertEqual(Array(listObj.dataOpt), Array(frozenListObj.dataOpt)) XCTAssertEqual(Array(listObj.dateOpt), Array(frozenListObj.dateOpt)) XCTAssertEqual(Array(listObj.uuid), Array(frozenListObj.uuid)) XCTAssertEqual(Array(listObj.uuidOpt), Array(frozenListObj.uuidOpt)) XCTAssertEqual(Array(listObj.any.map { $0 }), Array(frozenListObj.any.map { $0 })) } func testThaw() { let realm = try! Realm() let obj = try! realm.write { realm.create(SwiftBoolObject.self, value: ["boolCol": true]) } let frozenObj = obj.freeze() XCTAssertTrue(frozenObj.isFrozen) assertThrows(try! frozenObj.realm!.write {}, reason: "Can't perform transactions on a frozen Realm") let liveObj = frozenObj.thaw()! XCTAssertFalse(liveObj.isFrozen) XCTAssertEqual(liveObj.boolCol, frozenObj.boolCol) try! liveObj.realm!.write({ liveObj.boolCol = false }) XCTAssertNotEqual(liveObj.boolCol, frozenObj.boolCol) } func testThawUnmanaged() { assertThrows(SwiftBoolObject().thaw(), reason: "Unmanaged objects cannot be thawed.") } func testThawDeleted() { let realm = try! Realm() let obj = try! realm.write { realm.create(SwiftBoolObject.self, value: ["boolCol": true]) } let frozen = obj.freeze() try! realm.write { realm.delete(obj) } XCTAssertNotNil(frozen) let thawed = frozen.thaw() XCTAssertNil(thawed, "Thaw should return nil when object was deleted") } func testThawPreviousVersion() { let realm = try! Realm() let obj = try! realm.write { realm.create(SwiftBoolObject.self, value: ["boolCol": true]) } let frozen = obj.freeze() XCTAssertTrue(frozen.isFrozen) try! obj.realm!.write { obj.boolCol = false } XCTAssert(frozen.boolCol, "Frozen objects shouldn't mutate") let thawed = frozen.thaw()! XCTAssertFalse(thawed.isFrozen) XCTAssertFalse(thawed.boolCol, "Thaw should reflect transactions since the original reference was frozen") } func testThawUpdatedOnDifferentThread() { let obj = try! Realm().write { try! Realm().create(SwiftBoolObject.self, value: ["boolCol": true]) } let frozen = obj.freeze() let thawed = frozen.thaw()! let tsr = ThreadSafeReference(to: thawed) dispatchSyncNewThread { let resolved = try! Realm().resolve(tsr)! try! Realm().write({ resolved.boolCol = false }) } XCTAssert(frozen.thaw()!.boolCol) XCTAssert(thawed.boolCol) try! Realm().refresh() XCTAssertFalse(frozen.thaw()!.boolCol) XCTAssertFalse(thawed.boolCol) } } ================================================ FILE: RealmSwift/Tests/ObjectiveCSupportTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Foundation import Realm import RealmSwift import XCTest class ObjectiveCSupportTests: TestCase { func testSupport() { let realm = try! Realm() try! realm.write { realm.add(SwiftObject()) return } let results = realm.objects(SwiftObject.self) let rlmResults = ObjectiveCSupport.convert(object: results) XCTAssert(rlmResults.isKind(of: RLMResults.self)) XCTAssertEqual(rlmResults.count, 1) XCTAssertEqual(unsafeBitCast(rlmResults.firstObject(), to: SwiftObject.self).intCol, 123) let list = List() list.append(SwiftObject()) let rlmArray = ObjectiveCSupport.convert(object: list) XCTAssert(rlmArray.isKind(of: RLMArray.self)) XCTAssertEqual(unsafeBitCast(rlmArray.firstObject(), to: SwiftObject.self).floatCol, 1.23) XCTAssertEqual(rlmArray.count, 1) let set = MutableSet() set.insert(SwiftObject()) let rlmSet = ObjectiveCSupport.convert(object: set) XCTAssert(rlmSet.isKind(of: RLMSet.self)) XCTAssertEqual(unsafeDowncast(rlmSet.allObjects[0], to: SwiftObject.self).floatCol, 1.23) XCTAssertEqual(rlmSet.count, 1) let map = Map() map["0"] = SwiftObject() let rlmDictionary = ObjectiveCSupport.convert(object: map) XCTAssert(rlmDictionary.isKind(of: RLMDictionary.self)) XCTAssertEqual(unsafeDowncast(rlmDictionary.allValues[0], to: SwiftObject.self).floatCol, 1.23) XCTAssertEqual(rlmDictionary.count, 1) let rlmRealm = ObjectiveCSupport.convert(object: realm) XCTAssert(rlmRealm.isKind(of: RLMRealm.self)) XCTAssertEqual(rlmRealm.allObjects("SwiftObject").count, 1) let sortDescriptor: RealmSwift.SortDescriptor = "property" XCTAssertEqual(sortDescriptor.keyPath, ObjectiveCSupport.convert(object: sortDescriptor).keyPath, "SortDescriptor.keyPath must be equal to RLMSortDescriptor.keyPath") XCTAssertEqual(sortDescriptor.ascending, ObjectiveCSupport.convert(object: sortDescriptor).ascending, "SortDescriptor.ascending must be equal to RLMSortDescriptor.ascending") } func testConfigurationSupport() { let realm = try! Realm() try! realm.write { realm.add(SwiftObject()) } XCTAssertEqual(realm.configuration.fileURL, ObjectiveCSupport.convert(object: realm.configuration).fileURL, "Configuration.fileURL must be equal to RLMConfiguration.fileURL") XCTAssertEqual(realm.configuration.inMemoryIdentifier, ObjectiveCSupport.convert(object: realm.configuration).inMemoryIdentifier, "Configuration.inMemoryIdentifier must be equal to RLMConfiguration.inMemoryIdentifier") XCTAssertEqual(realm.configuration.encryptionKey, ObjectiveCSupport.convert(object: realm.configuration).encryptionKey, "Configuration.encryptionKey must be equal to RLMConfiguration.encryptionKey") XCTAssertEqual(realm.configuration.readOnly, ObjectiveCSupport.convert(object: realm.configuration).readOnly, "Configuration.readOnly must be equal to RLMConfiguration.readOnly") XCTAssertEqual(realm.configuration.schemaVersion, ObjectiveCSupport.convert(object: realm.configuration).schemaVersion, "Configuration.schemaVersion must be equal to RLMConfiguration.schemaVersion") XCTAssertEqual(realm.configuration.deleteRealmIfMigrationNeeded, ObjectiveCSupport.convert(object: realm.configuration).deleteRealmIfMigrationNeeded, "Configuration.deleteRealmIfMigrationNeeded must be equal to RLMConfiguration.deleteRealmIfMigrationNeeded") } func testAnyRealmValueSupport() { let obj = SwiftObject() let expected: [(RLMValue, AnyRealmValue)] = [ (NSNumber(1234), .int(1234)), (NSNumber(value: true), .bool(true)), (NSNumber(value: Float(1234.4567)), .float(1234.4567)), (NSNumber(value: Double(1234.4567)), .double(1234.4567)), (NSString("hello"), .string("hello")), (NSData(data: Data.init(repeating: 0, count: 64)), .data(Data.init(repeating: 0, count: 64))), (NSDate.init(timeIntervalSince1970: 1000000), .date(Date.init(timeIntervalSince1970: 1000000))), (try! RLMObjectId(string: "60425fff91d7a195d5ddac1b"), .objectId(try! ObjectId(string: "60425fff91d7a195d5ddac1b"))), (RLMDecimal128(number: 1234.4567), .decimal128(Decimal128(floatLiteral: 1234.4567))), (NSUUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")!, .uuid(UUID(uuidString: "137DECC8-B300-4954-A233-F89909F4FD89")!)), (obj, .object(obj)) ] func testObjCSupport(_ objCValue: RLMValue, value: AnyRealmValue) { XCTAssertEqual(ObjectiveCSupport.convert(value: objCValue), value) } expected.forEach { testObjCSupport($0.0, value: $0.1) } } } ================================================ FILE: RealmSwift/Tests/PerformanceTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift #if canImport(RealmTestSupport) import RealmTestSupport #endif private func createStringObjects(_ factor: Int) -> Realm { let realm = inMemoryRealm(factor.description) try! realm.write { for _ in 0..<(1000 * factor) { realm.create(SwiftStringObject.self, value: ["a"]) realm.create(SwiftStringObject.self, value: ["b"]) realm.create(SwiftIntObject.self, value: [1]) realm.create(SwiftIntObject.self, value: [2]) } } return realm } class SwiftIntProjection: Projection { @Projected(\SwiftIntObject.intCol) var intCol } private nonisolated(unsafe) var smallRealm: Realm! private nonisolated(unsafe) var mediumRealm: Realm! private nonisolated(unsafe) var largeRealm: Realm! @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPerformanceTests: TestCase { override class var defaultTestSuite: XCTestSuite { #if !DEBUG && os(iOS) && !targetEnvironment(macCatalyst) && !targetEnvironment(simulator) return super.defaultTestSuite #else return XCTestSuite(name: "SwiftPerformanceTests") #endif } override class func setUp() { super.setUp() autoreleasepool { smallRealm = createStringObjects(10) mediumRealm = createStringObjects(50) largeRealm = createStringObjects(500) } } override class func tearDown() { smallRealm = nil mediumRealm = nil largeRealm = nil super.tearDown() } override func resetRealmState() { // Do nothing, as we need to keep our in-memory realms around between tests } func measure(times: Int = 1, _ block: (() -> Void)) { super.measure { for _ in 0.. Void) { super.measureMetrics(metrics, automaticallyStartMeasuring: automaticallyStartMeasuring) { autoreleasepool { block() } } } func inMeasureBlock(block: () -> Void) { measureMetrics(type(of: self).defaultPerformanceMetrics, automaticallyStartMeasuring: false) { _ = block() } } private func copyRealmToTestPath(_ realm: Realm) -> Realm { do { try FileManager.default.removeItem(at: testRealmURL()) } catch let error as NSError { XCTAssertTrue(error.domain == NSCocoaErrorDomain && error.code == 4) } catch { fatalError("Unexpected error: \(error)") } try! realm.writeCopy(toFile: testRealmURL()) return realmWithTestPath() } func testInsertMultiple() { inMeasureBlock { let realm = self.realmWithTestPath() self.startMeasuring() try! realm.write { for _ in 0..<100_000 { let obj = SwiftStringObject() obj.stringCol = "a" realm.add(obj) } } self.stopMeasuring() self.tearDown() } } func testInsertSingleLiteral() { inMeasureBlock { let realm = self.realmWithTestPath() self.startMeasuring() for _ in 0..<5000 { try! realm.write { _ = realm.create(SwiftStringObject.self, value: ["a"]) } } self.stopMeasuring() self.tearDown() } } func testInsertMultipleLiteral() { inMeasureBlock { let realm = self.realmWithTestPath() self.startMeasuring() try! realm.write { for _ in 0..<100_000 { realm.create(SwiftStringObject.self, value: ["a"]) } } self.stopMeasuring() self.tearDown() } } func testCountWhereQuery() { let realm = copyRealmToTestPath(largeRealm) measure(times: 50) { let results = realm.objects(SwiftStringObject.self).filter("stringCol = 'a'") _ = results.count } } func testCountWhereTableView() { let realm = copyRealmToTestPath(largeRealm) measure(times: 50) { let results = realm.objects(SwiftStringObject.self).filter("stringCol = 'a'") _ = results.first _ = results.count } } func testEnumerateAndAccessQuery() { let realm = copyRealmToTestPath(largeRealm) measure { for stringObject in realm.objects(SwiftStringObject.self).filter("stringCol = 'a'") { _ = stringObject.stringCol } } } func testEnumerateAndAccessAll() { let realm = copyRealmToTestPath(largeRealm) measure { for stringObject in realm.objects(SwiftStringObject.self) { _ = stringObject.stringCol } } } func testEnumerateAndAccessAllSlow() { let realm = copyRealmToTestPath(largeRealm) measure { let results = realm.objects(SwiftStringObject.self) for i in 0..(_ type: T.Type, _ create: (T, Int) -> Void) -> Realm { let realm = try! Realm() try! realm.write { for value in 0..<10000 { let obj = T() create(obj, value) realm.add(obj) } } return realm } func createSwiftListObjects() -> Realm { return createObjects(SwiftListOfSwiftObject.self) { (listObject, value) in let object = SwiftObject() object.intCol = value object.stringCol = String(value) listObject.array.append(object) } } func createSwiftMutableSetObjects() -> Realm { return createObjects(SwiftMutableSetOfSwiftObject.self) { (setObject, value) in let object = SwiftObject() object.intCol = value object.stringCol = String(value) setObject.set.insert(object) } } func createIntSwiftObjects() -> Realm { return createObjects(SwiftObject.self) { (object, value) in object.intCol = value } } func createStringSwiftObjects() -> Realm { return createObjects(SwiftObject.self) { (object, value) in object.stringCol = String(value) } } func createOptionalIntObjects() -> Realm { return createObjects(SwiftOptionalObject.self) { (object, value) in object.optIntCol.value = value } } func createOptionalStringObjects() -> Realm { return createObjects(SwiftOptionalObject.self) { (object, value) in object.optStringCol = String(value) } } // MARK: - Legacy value(forKey:) vs. loop func testLegacyListObjectsMap() { let objects = createSwiftListObjects().objects(SwiftListOfSwiftObject.self) measure(times: 100) { _ = Array(objects.map { $0.array }) } } func testLegacyListObjectsValueForKey() { let objects = createSwiftListObjects().objects(SwiftListOfSwiftObject.self) measure(times: 100) { _ = objects.value(forKeyPath: "array") as! [List] } } func testLegacyMutableSetObjectsMap() { let objects = createSwiftMutableSetObjects().objects(SwiftMutableSetOfSwiftObject.self) measure(times: 100) { _ = Array(objects.map { $0.set }) } } func testLegacyMutableSetObjectsValueForKey() { let objects = createSwiftMutableSetObjects().objects(SwiftMutableSetOfSwiftObject.self) measure(times: 100) { _ = objects.value(forKeyPath: "set") as! [MutableSet] } } func testLegacyIntObjectsMap() { let objects = createIntSwiftObjects().objects(SwiftObject.self) measure(times: 100) { _ = Array(objects.map { $0.intCol }) } } func testLegacyIntObjectsValueForKey() { let objects = createIntSwiftObjects().objects(SwiftObject.self) measure(times: 100) { _ = objects.value(forKeyPath: "intCol") as! [Int] } } func testLegacyStringObjectsMap() { let objects = createStringSwiftObjects().objects(SwiftObject.self) measure(times: 100) { _ = Array(objects.map { $0.stringCol }) } } func testLegacyStringObjectsValueForKey() { let objects = createStringSwiftObjects().objects(SwiftObject.self) measure(times: 100) { _ = objects.value(forKeyPath: "stringCol") as! [String] } } func testLegacyOptionalIntObjectsMap() { let objects = createOptionalIntObjects().objects(SwiftOptionalObject.self) measure(times: 10) { _ = Array(objects.map { $0.optIntCol.value }) } } func testLegacyOptionalIntObjectsValueForKey() { let objects = createOptionalIntObjects().objects(SwiftOptionalObject.self) measure(times: 10) { _ = objects.value(forKeyPath: "optIntCol") as! [Int] } } func testLegacyOptionalStringObjectsMap() { let objects = createOptionalStringObjects().objects(SwiftOptionalObject.self) measure(times: 10) { _ = Array(objects.map { $0.optStringCol }) } } func testLegacyOptionalStringObjectsValueForKey() { let objects = createOptionalStringObjects().objects(SwiftOptionalObject.self) measure(times: 10) { _ = objects.value(forKeyPath: "optStringCol") as! [String] } } // MARK: - Modern object creation helpers func createModernCollectionObjects() -> Results { return createObjects(ModernCollectionObject.self) { (listObject, value) in let object = ModernAllTypesObject() object.intCol = value object.stringCol = String(value) listObject.list.append(object) listObject.set.insert(object) listObject.map[""] = object }.objects(ModernCollectionObject.self) } func createModernObjects() -> Results { return createObjects(ModernIntAndStringObject.self) { (object, value) in object.intCol = value object.stringCol = String(value) object.optIntCol = value object.optStringCol = String(value) }.objects(ModernIntAndStringObject.self) } // MARK: - Modern value(forKey:) vs. loop func testModernListObjectsMap() { let objects = createModernCollectionObjects() measure(times: 100) { _ = Array(objects.map { $0.list }) } } func testModernListObjectsValueForKey() { let objects = createModernCollectionObjects() measure(times: 100) { _ = objects.value(forKeyPath: "list") as! [List] } } func testModernMutableSetObjectsMap() { let objects = createModernCollectionObjects() measure(times: 100) { _ = Array(objects.map { $0.set }) } } func testModernMutableSetObjectsValueForKey() { let objects = createModernCollectionObjects() measure(times: 100) { _ = objects.value(forKeyPath: "set") as! [MutableSet] } } func testModernIntObjectsMap() { let objects = createModernObjects() measure(times: 100) { _ = Array(objects.map { $0.intCol }) } } func testModernIntObjectsValueForKey() { let objects = createModernObjects() measure(times: 100) { _ = objects.value(forKeyPath: "intCol") as! [Int] } } func testModernStringObjectsMap() { let objects = createModernObjects() measure(times: 100) { _ = Array(objects.map { $0.stringCol }) } } func testModernStringObjectsValueForKey() { let objects = createModernObjects() measure(times: 100) { _ = objects.value(forKeyPath: "stringCol") as! [String] } } func testModernOptionalIntObjectsMap() { let objects = createModernObjects() measure(times: 10) { _ = Array(objects.map { $0.optIntCol }) } } func testModernOptionalIntObjectsValueForKey() { let objects = createModernObjects() measure(times: 10) { _ = objects.value(forKeyPath: "optIntCol") as! [Int] } } func testModernOptionalStringObjectsMap() { let objects = createModernObjects() measure(times: 10) { _ = Array(objects.map { $0.optStringCol }) } } func testModernOptionalStringObjectsValueForKey() { let objects = createModernObjects() measure(times: 10) { _ = objects.value(forKeyPath: "optStringCol") as! [String] } } // MARK: Test Projections func testCastSingleProjection() { let realm = copyRealmToTestPath(mediumRealm) try! realm.write({ for obj in realm.objects(SwiftStringObject.self) { realm.create(ModernSwiftStringObject.self, value: [obj.stringCol]) } }) let objects = realm.objects(ModernSwiftStringObject.self) measure(times: 10) { for obj in objects { _ = ModernSwiftStringProjection(projecting: obj) } } } func testCastResultsToProjection() { let realm = copyRealmToTestPath(mediumRealm) try! realm.write { for obj in realm.objects(SwiftStringObject.self) { realm.create(ModernSwiftStringObject.self, value: [obj.stringCol]) } } measure(times: 5) { _ = Array(realm.objects(ModernSwiftStringProjection.self)) } } func testAccessProjectionProperty() { let realm = copyRealmToTestPath(mediumRealm) try! realm.write { for obj in realm.objects(SwiftStringObject.self) { realm.create(ModernSwiftStringObject.self, value: [obj.stringCol]) } } let projections = realm.objects(ModernSwiftStringProjection.self) measure(times: 3) { for proj in projections { _ = proj.string } } } } ================================================ FILE: RealmSwift/Tests/PrimitiveListTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 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 RealmSwift import XCTest #if canImport(RealmTestSupport) import RealmTestSupport import RealmSwiftTestSupport #endif class PrimitiveListTestsBase: RLMTestCaseBase { var realm: Realm? var obj: V.ListRoot! var array: List! var values: [V]! override func setUp() { obj = O.get() realm = obj.realm array = obj[keyPath: V.array] values = V.values() } override func tearDown() { realm?.cancelWrite() array = nil obj = nil realm = nil } // These test suites run such a large number of very short tests that the // setup/teardown overhead from TestCase ends up being a significant portion // of the runtime, so bypass that and replicate just the bit we need here. override func invokeTest() { autoreleasepool { super.invokeTest() } } func assertThrows(_ block: @autoclosure () -> T, named: String? = RLMExceptionName, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { RLMAssertThrowsWithName(self, { _ = block() }, named, message, fileName, lineNumber) } func assertThrows(_ block: @autoclosure () -> T, reason: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { RLMAssertThrowsWithReason(self, { _ = block() }, reason, message, fileName, lineNumber) } func assertThrows(_ block: @autoclosure () -> T, reasonMatching regexString: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { RLMAssertThrowsWithReasonMatching(self, { _ = block() }, regexString, message, fileName, lineNumber) } } class PrimitiveListTests: PrimitiveListTestsBase { func testInvalidated() { XCTAssertFalse(array.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(array.isInvalidated) } } func testIndexOf() { guard V._rlmType != .object else { return } XCTAssertNil(array.index(of: values[0])) array.append(values[0]) XCTAssertEqual(0, array.index(of: values[0])) array.append(values[1]) XCTAssertEqual(0, array.index(of: values[0])) XCTAssertEqual(1, array.index(of: values[1])) } // FIXME: Not yet implemented func disabled_testIndexMatching() { XCTAssertNil(array.index(matching: "self = %@", values[0])) array.append(values[0]) XCTAssertEqual(0, array.index(matching: "self = %@", values[0])) array.append(values[1]) XCTAssertEqual(0, array.index(matching: "self = %@", values[0])) XCTAssertEqual(1, array.index(matching: "self = %@", values[1])) } func testSubscript() { array.append(objectsIn: values) for i in 0..: PrimitiveListTestsBase where V.PersistedType: MinMaxType { func testMin() { XCTAssertNil(array.min()) array.append(objectsIn: values.reversed()) XCTAssertEqual(array.min(), V.min()) } func testMax() { XCTAssertNil(array.max()) array.append(objectsIn: values.reversed()) XCTAssertEqual(array.max(), V.max()) } } class AddablePrimitiveListTests: PrimitiveListTestsBase where V: NumericValueFactory, V.PersistedType: AddableType { func testSum() { XCTAssertEqual(array.sum(), .zero) array.append(objectsIn: values) XCTAssertEqual(V.doubleValue(array.sum()), V.sum(), accuracy: 0.01) } func testAverage() { XCTAssertNil(array.average() as V.AverageType?) array.append(objectsIn: values) XCTAssertEqual(V.doubleValue(array.average()!), V.average(), accuracy: 0.01) } } private func rotate(_ values: Array) -> Array { var shuffled = values shuffled.removeFirst() shuffled.append(values.first!) return shuffled } class SortablePrimitiveListTests: PrimitiveListTestsBase where V.PersistedType: SortableType { func testSorted() { array.append(objectsIn: rotate(values!)) assertEqual(Array(array.sorted(ascending: true)), values) assertEqual(Array(array.sorted(ascending: false)), values.reversed()) } func testDistinct() { array.append(objectsIn: values!) array.append(objectsIn: values!) assertEqual(Array(array.distinct()), values!) } } func addTests(_ suite: XCTestSuite, _ type: OF.Type) { // Plain types PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) // Optional plain types PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) // Enum wrappers PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) // Optional Enum wrappers PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) // Custom persistable wrappers PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) // Optional custom persistable wrappers PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedPrimitiveListTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged Primitive Lists") addTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedPrimitiveListTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed Primitive Lists") addTests(suite, ManagedObjectFactory.self) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveListTests.defaultTestSuite.tests.forEach(suite.addTest) return suite } } ================================================ FILE: RealmSwift/Tests/PrimitiveMapTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import Realm import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif // swiftlint:disable cyclomatic_complexity class PrimitiveMapTestsBase: TestCase { var realm: Realm? var obj: V.MapRoot! var obj2: V.MapRoot! var map: Map! var otherMap: Map! var values: [(key: String, value: V)]! override func setUp() { obj = O.get() obj2 = O.get() realm = obj.realm map = obj[keyPath: V.map] otherMap = obj2[keyPath: V.map] values = V.values().enumerated().map { (key: "key\($0)", value: $1) } } override func tearDown() { realm?.cancelWrite() realm = nil map = nil otherMap = nil obj = nil obj2 = nil } } class PrimitiveMapTests: PrimitiveMapTestsBase { func testInvalidated() { XCTAssertFalse(map.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(map.isInvalidated) } } @MainActor func testEnumeration() { XCTAssertEqual(0, map.count) map.merge(values) { $1 } let exp = expectation(description: "did enumerate all keys and values") exp.expectedFulfillmentCount = 3 for element in map where values.filter({ $0.key == element.key }).first!.value == element.value { exp.fulfill() } waitForExpectations(timeout: 1.0, handler: nil) } func testValueForKey() { let key = values[0].key XCTAssertNil(map.value(forKey: key)) map.setValue(values[0].value, forKey: key) let kvc: AnyObject = map.value(forKey: key)! XCTAssertEqual(dynamicBridgeCast(fromObjectiveC: kvc) as V, values[0].value) } func testInsert() { XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value XCTAssertEqual(1, map.count) XCTAssertEqual(1, map.keys.count) XCTAssertEqual(1, map.values.count) XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) XCTAssertEqual(map[values[0].key], values[0].value) map[values[1].key] = values[1].value XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[1].key]).isSubset(of: map.keys)) XCTAssertEqual(map[values[1].key], values[1].value) map[values[2].key] = values[2].value XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) XCTAssertEqual(map[values[2].key], values[2].value) } func testUpdate() { XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value XCTAssertEqual(1, map.count) XCTAssertEqual(1, map.keys.count) XCTAssertEqual(1, map.values.count) XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) XCTAssertEqual(map[values[0].key], values[0].value) map.updateValue(values[1].value, forKey: values[0].key) XCTAssertEqual(1, map.count) XCTAssertEqual(1, map.keys.count) XCTAssertEqual(1, map.values.count) XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) XCTAssertEqual(map[values[0].key], values[1].value) } func testRemove() { XCTAssertEqual(0, map.count) map.merge(values) { $1 } XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) let key = values[0].key map.setValue(nil, forKey: key) XCTAssertNil(map.value(forKey: key)) map.removeAll() XCTAssertEqual(0, map.count) map.merge(values) { $1 } map[values[1].key] = nil XCTAssertNil(map[values[1].key]) map.removeObject(for: values[2].key) // make sure the key was deleted XCTAssertTrue(Set([values[0].key]).isSubset(of: map.keys)) } func testSubscript() { // setter XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value map[values[1].key] = values[1].value map[values[2].key] = values[2].value XCTAssertEqual(3, map.count) XCTAssertEqual(3, map.keys.count) XCTAssertEqual(3, map.values.count) XCTAssertTrue(Set(values.map { $0.key }).isSubset(of: map.keys)) map[values[0].key] = values[0].value map[values[1].key] = nil map[values[2].key] = values[2].value XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[2].key]).isSubset(of: map.keys)) XCTAssertEqual(2, map.count) XCTAssertEqual(2, map.keys.count) XCTAssertEqual(2, map.values.count) XCTAssertTrue(Set([values[0].key, values[2].key]).isSubset(of: map.keys)) // getter map.removeAll() XCTAssertNil(map[values[0].key]) map[values[0].key] = values[0].value XCTAssertEqual(values[0].value, map[values[0].key]) } func testObjectForKey() { XCTAssertEqual(0, map.count) map[values[0].key] = values[0].value XCTAssertEqual(values[0].value, dynamicBridgeCast(fromObjectiveC: map.object(forKey: values[0].key as AnyObject)!) as V) } } class MinMaxPrimitiveMapTests: PrimitiveMapTestsBase where V.PersistedType: MinMaxType { func testMin() { XCTAssertNil(map.min()) map.merge(values) { $1 } map.merge(values) { $1 } XCTAssertEqual(map.min(), V.min()) } func testMax() { XCTAssertNil(map.max()) map.merge(values) { $1 } XCTAssertEqual(map.max(), V.max()) } } class AddablePrimitiveMapTests: PrimitiveMapTestsBase where V: NumericValueFactory, V.PersistedType: AddableType { func testSum() { XCTAssertEqual(map.sum(), .zero) map.merge(values) { $1 } XCTAssertEqual(V.doubleValue(map.sum()), V.sum(), accuracy: 0.01) } func testAverage() { XCTAssertNil(map.average() as V.AverageType?) map.merge(values) { $1 } XCTAssertEqual(V.doubleValue(map.average()!), V.average(), accuracy: 0.1) } } class SortablePrimitiveMapTests: PrimitiveMapTestsBase where V.PersistedType: SortableType { func testSorted() { map.merge(values) { $1 } XCTAssertEqual(map.count, 3) let values2: [V] = values.map { $0.value } assertEqual(values2, Array(map.sorted())) assertEqual(values2, Array(map.sorted(ascending: true))) assertEqual(values2.reversed(), Array(map.sorted(ascending: false))) } } func addPrimitiveMapTests(_ suite: XCTestSuite, _ type: OF.Type) { PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedPrimitiveMapTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged Primitive Maps") addPrimitiveMapTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedPrimitiveMapTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed Primitive Maps") addPrimitiveMapTests(suite, ManagedObjectFactory.self) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMapTests.defaultTestSuite.tests.forEach(suite.addTest) return suite } } ================================================ FILE: RealmSwift/Tests/PrimitiveMutableSetTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif class PrimitiveMutableSetTestsBase: TestCase { var realm: Realm? var obj: V.SetRoot! var obj2: V.SetRoot! var mutableSet: MutableSet! var otherMutableSet: MutableSet! var values: [V]! override func setUp() { obj = O.get() obj2 = O.get() realm = obj.realm mutableSet = obj[keyPath: V.mutableSet] otherMutableSet = obj2[keyPath: V.mutableSet] values = V.values() } override func tearDown() { realm?.cancelWrite() mutableSet = nil otherMutableSet = nil obj = nil obj2 = nil realm = nil } } class PrimitiveMutableSetTests: PrimitiveMutableSetTestsBase { func testInvalidated() { XCTAssertFalse(mutableSet.isInvalidated) if let realm = obj.realm { realm.delete(obj) XCTAssertTrue(mutableSet.isInvalidated) } } func testValueForKey() { XCTAssertEqual(mutableSet.value(forKey: "self").count, 0) mutableSet.insert(objectsIn: values) let valuesSet = Set(values!) let kvoSet = Set(mutableSet.value(forKey: "self").map { dynamicBridgeCast(fromObjectiveC: $0) as V }) XCTAssertEqual(valuesSet, kvoSet) assertThrows(mutableSet.value(forKey: "not self"), named: "NSUnknownKeyException") } func testInsert() { XCTAssertEqual(Int(0), mutableSet.count) mutableSet.insert(values[0]) XCTAssertEqual(Int(1), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) mutableSet.insert(values[1]) XCTAssertEqual(Int(2), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) // Insert duplicate mutableSet.insert(values[2]) XCTAssertEqual(Int(3), mutableSet.count) XCTAssertTrue(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) } func testRemove() { mutableSet.removeAll() XCTAssertEqual(mutableSet.count, 0) mutableSet.insert(objectsIn: values) mutableSet.remove(values[0]) XCTAssertFalse(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) XCTAssertTrue(mutableSet.contains(values[2])) } func testRemoveAll() { mutableSet.removeAll() mutableSet.insert(objectsIn: values) mutableSet.removeAll() XCTAssertEqual(mutableSet.count, 0) } func testIsSubset() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] XCTAssertTrue(otherMutableSet.isSubset(of: mutableSet)) otherMutableSet.remove(values[0]) XCTAssertFalse(mutableSet.isSubset(of: otherMutableSet)) } func testContains() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) XCTAssertEqual(values.count, mutableSet.count) values.forEach { XCTAssertTrue(mutableSet.contains($0)) } } func testIntersects() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] XCTAssertTrue(otherMutableSet.intersects(mutableSet)) otherMutableSet.remove(values[0]) XCTAssertFalse(mutableSet.intersects(otherMutableSet)) } func testFormIntersection() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(objectsIn: values) otherMutableSet.insert(values[0]) // Both sets contain values[0] mutableSet.formIntersection(otherMutableSet) XCTAssertEqual(Int(1), mutableSet.count) assertSetContains(mutableSet, keyPath: \.self, items: [values[0]]) } func testFormUnion() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(values[0]) mutableSet.insert(values[1]) otherMutableSet.insert(values[0]) otherMutableSet.insert(values[2]) mutableSet.formUnion(otherMutableSet) XCTAssertEqual(Int(3), mutableSet.count) assertSetContains(mutableSet, keyPath: \.self, items: [values[0], values[1], values[2]]) } func testSubtract() { XCTAssertEqual(Int(0), mutableSet.count) XCTAssertEqual(Int(0), otherMutableSet.count) mutableSet.insert(values[0]) mutableSet.insert(values[1]) otherMutableSet.insert(values[0]) otherMutableSet.insert(values[2]) mutableSet.subtract(otherMutableSet) XCTAssertEqual(Int(1), mutableSet.count) XCTAssertFalse(mutableSet.contains(values[0])) XCTAssertTrue(mutableSet.contains(values[1])) } func testSubscript() { mutableSet.insert(objectsIn: values) XCTAssertTrue(values.contains(mutableSet[0])) XCTAssertTrue(values.contains(mutableSet[1])) XCTAssertTrue(values.contains(mutableSet[2])) } } class MinMaxPrimitiveMutableSetTests: PrimitiveMutableSetTestsBase where V.PersistedType: MinMaxType { func testMin() { XCTAssertNil(mutableSet.min()) mutableSet.insert(objectsIn: values) XCTAssertEqual(mutableSet.min(), V.min()) } func testMax() { XCTAssertNil(mutableSet.max()) mutableSet.insert(objectsIn: values) XCTAssertEqual(mutableSet.max(), V.max()) } } class AddablePrimitiveMutableSetTests: PrimitiveMutableSetTestsBase where V: NumericValueFactory, V.PersistedType: AddableType { func testSum() { XCTAssertEqual(mutableSet.sum(), .zero) mutableSet.insert(objectsIn: values) XCTAssertEqual(V.doubleValue(mutableSet.sum()), V.sum(), accuracy: 0.01) } func testAverage() { XCTAssertNil(mutableSet.average() as V.AverageType?) mutableSet.insert(objectsIn: values) XCTAssertEqual(V.doubleValue(mutableSet.average()!), V.average(), accuracy: 0.01) } } class SortablePrimitiveMutableSetTests: PrimitiveMutableSetTestsBase where V.PersistedType: SortableType { func testSorted() { var shuffled = values! shuffled.removeFirst() shuffled.append(values!.first!) mutableSet.insert(objectsIn: shuffled) assertEqual(Array(mutableSet.sorted(ascending: true)), values) assertEqual(Array(mutableSet.sorted(ascending: false)), values.reversed()) } } func addMutableSetTests(_ suite: XCTestSuite, _ type: OF.Type) { PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) AddablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) PrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) MinMaxPrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) } class UnmanagedPrimitiveMutableSetTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Unmanaged Primitive Sets") addMutableSetTests(suite, UnmanagedObjectFactory.self) return suite } } class ManagedPrimitiveMutableSetTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Managed Primitive Sets") addMutableSetTests(suite, ManagedObjectFactory.self) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests .defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) SortablePrimitiveMutableSetTests.defaultTestSuite.tests.forEach(suite.addTest) return suite } } ================================================ FILE: RealmSwift/Tests/ProjectedCollectTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import RealmSwift import XCTest class PersistedCollections: Object { @Persisted public var list: List @Persisted public var set: MutableSet } class ProjectedCollections: Projection { @Projected(\PersistedCollections.list.projectTo.firstName) var list: ProjectedCollection @Projected(\PersistedCollections.set.projectTo.firstName) var set: ProjectedCollection } class ProjectedCollectionsTestsTemplate: TestCase { // To test some of methods there should be a collection of projections instead of collection of strings // set value in subclass var collection: ProjectedCollection! override func setUp() { super.setUp() let realm = realmWithTestPath() try! realm.write { let js = realm.create(CommonPerson.self, value: ["firstName": "John", "lastName": "Snow", "birthday": Date(timeIntervalSince1970: 10), "address": ["Winterfell", "Kingdom in the North"], "money": Decimal128("2.22")]) let dt = realm.create(CommonPerson.self, value: ["firstName": "Daenerys", "lastName": "Targaryen", "birthday": Date(timeIntervalSince1970: 0), "address": ["King's Landing", "Westeros"], "money": Decimal128("2.22")]) let tl = realm.create(CommonPerson.self, value: ["firstName": "Tyrion", "lastName": "Lannister", "birthday": Date(timeIntervalSince1970: 20), "address": ["Casterly Rock", "Westeros"], "money": Decimal128("9999.95")]) js.friends.append(dt) js.friends.append(tl) dt.friends.append(js) realm.create(PersistedCollections.self, value: [[js, dt, tl], [js, dt, tl]]) } } override func tearDown() { collection = nil super.tearDown() } override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(ProjectedCollectionsTestsTemplate.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func testCount() { XCTAssertEqual(collection.count, 3) } func testAccess() { XCTAssertEqual(collection[0], collection.first) XCTAssertEqual(collection[2], collection.last) XCTAssertNil(collection.firstIndex(of: "Not tere")) XCTAssertNotNil(collection.firstIndex(of: "Daenerys")) } func testSetValues() { let realm = realmWithTestPath() try! realm.write { collection[0] = "Overwrite" } XCTAssertEqual(collection.first, "Overwrite") let chandedObject = realm.objects(CommonPerson.self).filter("firstName == 'Overwrite'").first XCTAssertNotNil(chandedObject) } func testFreezeThawProjectedCollection() { let realm = realmWithTestPath() let johnSnow = realm.objects(PersonProjection.self).first! let projectedSet = collection.freeze() XCTAssertTrue(projectedSet.isFrozen) XCTAssertFalse(johnSnow.isFrozen) let frosenJohn = johnSnow.freeze() let frozenProjectedSet = frosenJohn.firstFriendsName XCTAssertTrue(frosenJohn.isFrozen) XCTAssertTrue(projectedSet.isFrozen) XCTAssertTrue(frozenProjectedSet.isFrozen) } func testRealm() { guard collection.realm != nil else { XCTAssertNotNil(collection.realm) return } XCTAssertEqual(collection.realm!.configuration.fileURL, realmWithTestPath().configuration.fileURL) } func testDescription() { XCTAssertEqual(collection.description, "ProjectedCollection {\n\t[0] John\n\t[1] Daenerys\n\t[2] Tyrion\n}") } func testFilterFormat() { XCTAssertNotNil(collection.filter { $0 == "Daenerys" }.first!) XCTAssertNil(collection.filter { $0 == "Not There" }.first) } func testSortWithProperty() { XCTAssertEqual("Tyrion", collection.sorted { $0 > $1 }.first!) XCTAssertEqual("Daenerys", collection.sorted { $0 > $1 }.last!) } func testFastEnumeration() { XCTAssertGreaterThan(collection.count, 0) for element in collection { _ = element } } @MainActor func testObserve() { let ex = expectation(description: "initial notification") let token = collection.observe { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case .update: XCTFail("Shouldn't happen") case .error: XCTFail("Shouldn't happen") } ex.fulfill() } waitForExpectations(timeout: 1, handler: nil) // add a second notification and wait for it var ex2 = expectation(description: "second initial notification") let token2 = collection.observe { _ in ex2.fulfill() } waitForExpectations(timeout: 1, handler: nil) // make a write and implicitly verify that only the unskipped // notification is called (the first would error on .update) ex2 = expectation(description: "change notification") let realm = realmWithTestPath() realm.beginWrite() realm.delete(realm.objects(CommonPerson.self)) try! realm.commitWrite(withoutNotifying: [token]) waitForExpectations(timeout: 1, handler: nil) token.invalidate() token2.invalidate() } @MainActor func testObserveKeyPathNoChange() { let ex = expectation(description: "initial notification") let token0 = collection.observe(keyPaths: ["firstName"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case .update: XCTFail("update not expected") case .error: XCTFail("error not expected") } ex.fulfill() } let config = configurationWithTestPath() dispatchSyncNewThread { @Sendable in let realm = try! Realm(configuration: config) realm.beginWrite() let obj = realm.create(CommonPerson.self) obj.firstName = "Name" try! realm.commitWrite() } waitForExpectations(timeout: 2, handler: nil) token0.invalidate() } func observeOnQueue(_ collection: Collection) where Collection.Element: Object { let sema = DispatchSemaphore(value: 0) let token = collection.observe(keyPaths: nil, on: queue) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(let collection, let deletions, _, _): XCTAssertEqual(collection.count, 0) XCTAssertEqual(deletions, [0, 1]) case .error: XCTFail("Shouldn't happen") } sema.signal() } sema.wait() let realm = realmWithTestPath() try! realm.write { realm.delete(collection) } sema.wait() token.invalidate() } func testInvalidate() { XCTAssertFalse(collection.isInvalidated) realmWithTestPath().invalidate() XCTAssertTrue(collection.realm == nil || collection.isInvalidated) } func testIsFrozen() { XCTAssertFalse(collection.isFrozen) XCTAssertTrue(collection.freeze().isFrozen) } func testThaw() { let frozen = collection.freeze() XCTAssertTrue(frozen.isFrozen) let frozenRealm = frozen.realm! assertThrows(try! frozenRealm.write {}, reason: "Can't perform transactions on a frozen Realm") let live = frozen.thaw() XCTAssertFalse(live!.isFrozen) let liveRealm = live!.realm! try! liveRealm.write { liveRealm.delete(liveRealm.objects(PersistedCollections.self)) } XCTAssertTrue(live!.isInvalidated) XCTAssertFalse(frozen.isEmpty) try! liveRealm.write { liveRealm.delete(liveRealm.objects(CommonPerson.self)) } XCTAssertFalse(frozen.isInvalidated) } func testThawFromDifferentThread() { nonisolated(unsafe) let frozen = collection.freeze() XCTAssertTrue(frozen.isFrozen) dispatchSyncNewThread { let live = frozen.thaw() XCTAssertFalse(live!.isFrozen) let liveRealm = live!.realm! try! liveRealm.write { liveRealm.delete(liveRealm.objects(PersistedCollections.self)) } XCTAssertTrue(live!.isInvalidated) XCTAssertFalse(frozen.isEmpty) } } func testFreezeFromWrongThread() { nonisolated(unsafe) let collection = realmWithTestPath().objects(PersonProjection.self).first!.firstFriendsName nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { unsafeSelf.assertThrows(collection.freeze(), reason: "Realm accessed from incorrect thread") } } func testAccessFrozenCollectionFromDifferentThread() { nonisolated(unsafe) let frozen = collection.freeze() dispatchSyncNewThread { XCTAssertTrue(frozen.contains(where: { $0 == "Daenerys" })) XCTAssertTrue(frozen.contains(where: { $0 == "Tyrion" })) } } func testObserveFrozenCollection() { let frozen = collection.freeze() assertThrows(frozen.observe({ _ in }), reason: "Frozen Realms do not change and do not have change notifications.") } func testFilterFrozenCollection() { let frozen = collection.freeze() XCTAssertEqual(frozen.filter({ $0 == "Daenerys" }).count, 1) XCTAssertNil(frozen.filter({ $0 == "Nothing" }).first) } } class ProjectedListTests: ProjectedCollectionsTestsTemplate { override func setUp() { super.setUp() let realm = realmWithTestPath() try! realm.write { let people = realm.objects(CommonPerson.self) realm.create(PersistedCollections.self, value: ["list": people]) } collection = realmWithTestPath().objects(ProjectedCollections.self)[0].list } } class ProjectedSetTests: ProjectedCollectionsTestsTemplate { override func setUp() { super.setUp() let realm = realmWithTestPath() try! realm.write { let people = realm.objects(CommonPerson.self) realm.create(PersistedCollections.self, value: ["set": people]) } collection = realmWithTestPath().objects(ProjectedCollections.self)[0].set } } ================================================ FILE: RealmSwift/Tests/ProjectionTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import Realm import RealmSwift import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif // Keypaths are supposed to be Sendable but that never got implemented #if compiler(<6) extension KeyPath: @unchecked Sendable {} #else extension KeyPath: @retroactive @unchecked Sendable {} #endif // MARK: Test objects definitions enum IntegerEnum: Int, PersistableEnum { case value1 = 1 case value2 = 3 } class AllTypesPrimitiveProjection: Projection { @Projected(\ModernAllTypesObject.pk) var pk @Projected(\ModernAllTypesObject.boolCol) var boolCol @Projected(\ModernAllTypesObject.intCol) var intCol @Projected(\ModernAllTypesObject.int8Col) var int8Col @Projected(\ModernAllTypesObject.int16Col) var int16Col @Projected(\ModernAllTypesObject.int32Col) var int32Col @Projected(\ModernAllTypesObject.int64Col) var int64Col @Projected(\ModernAllTypesObject.floatCol) var floatCol @Projected(\ModernAllTypesObject.doubleCol) var doubleCol @Projected(\ModernAllTypesObject.stringCol) var stringCol @Projected(\ModernAllTypesObject.binaryCol) var binaryCol @Projected(\ModernAllTypesObject.dateCol) var dateCol @Projected(\ModernAllTypesObject.decimalCol) var decimalCol @Projected(\ModernAllTypesObject.objectIdCol) var objectIdCol @Projected(\ModernAllTypesObject.objectCol) var objectCol @Projected(\ModernAllTypesObject.arrayCol) var arrayCol @Projected(\ModernAllTypesObject.setCol) var setCol @Projected(\ModernAllTypesObject.anyCol) var anyCol @Projected(\ModernAllTypesObject.uuidCol) var uuidCol @Projected(\ModernAllTypesObject.intEnumCol) var intEnumCol @Projected(\ModernAllTypesObject.stringEnumCol) var stringEnumCol @Projected(\ModernAllTypesObject.optIntCol) var optIntCol @Projected(\ModernAllTypesObject.optInt8Col) var optInt8Col @Projected(\ModernAllTypesObject.optInt16Col) var optInt16Col @Projected(\ModernAllTypesObject.optInt32Col) var optInt32Col @Projected(\ModernAllTypesObject.optInt64Col) var optInt64Col @Projected(\ModernAllTypesObject.optFloatCol) var optFloatCol @Projected(\ModernAllTypesObject.optDoubleCol) var optDoubleCol @Projected(\ModernAllTypesObject.optBoolCol) var optBoolCol @Projected(\ModernAllTypesObject.optStringCol) var optStringCol @Projected(\ModernAllTypesObject.optBinaryCol) var optBinaryCol @Projected(\ModernAllTypesObject.optDateCol) var optDateCol @Projected(\ModernAllTypesObject.optDecimalCol) var optDecimalCol @Projected(\ModernAllTypesObject.optObjectIdCol) var optObjectIdCol @Projected(\ModernAllTypesObject.optUuidCol) var optUuidCol @Projected(\ModernAllTypesObject.optIntEnumCol) var optIntEnumCol @Projected(\ModernAllTypesObject.optStringEnumCol) var optStringEnumCol @Projected(\ModernAllTypesObject.arrayBool) var arrayBool @Projected(\ModernAllTypesObject.arrayInt) var arrayInt @Projected(\ModernAllTypesObject.arrayInt8) var arrayInt8 @Projected(\ModernAllTypesObject.arrayInt16) var arrayInt16 @Projected(\ModernAllTypesObject.arrayInt32) var arrayInt32 @Projected(\ModernAllTypesObject.arrayInt64) var arrayInt64 @Projected(\ModernAllTypesObject.arrayFloat) var arrayFloat @Projected(\ModernAllTypesObject.arrayDouble) var arrayDouble @Projected(\ModernAllTypesObject.arrayString) var arrayString @Projected(\ModernAllTypesObject.arrayBinary) var arrayBinary @Projected(\ModernAllTypesObject.arrayDate) var arrayDate @Projected(\ModernAllTypesObject.arrayDecimal) var arrayDecimal @Projected(\ModernAllTypesObject.arrayObjectId) var arrayObjectId @Projected(\ModernAllTypesObject.arrayAny) var arrayAny @Projected(\ModernAllTypesObject.arrayUuid) var arrayUuid @Projected(\ModernAllTypesObject.arrayOptBool) var arrayOptBool @Projected(\ModernAllTypesObject.arrayOptInt) var arrayOptInt @Projected(\ModernAllTypesObject.arrayOptInt8) var arrayOptInt8 @Projected(\ModernAllTypesObject.arrayOptInt16) var arrayOptInt16 @Projected(\ModernAllTypesObject.arrayOptInt32) var arrayOptInt32 @Projected(\ModernAllTypesObject.arrayOptInt64) var arrayOptInt64 @Projected(\ModernAllTypesObject.arrayOptFloat) var arrayOptFloat @Projected(\ModernAllTypesObject.arrayOptDouble) var arrayOptDouble @Projected(\ModernAllTypesObject.arrayOptString) var arrayOptString @Projected(\ModernAllTypesObject.arrayOptBinary) var arrayOptBinary @Projected(\ModernAllTypesObject.arrayOptDate) var arrayOptDate @Projected(\ModernAllTypesObject.arrayOptDecimal) var arrayOptDecimal @Projected(\ModernAllTypesObject.arrayOptObjectId) var arrayOptObjectId @Projected(\ModernAllTypesObject.arrayOptUuid) var arrayOptUuid @Projected(\ModernAllTypesObject.setBool) var setBool @Projected(\ModernAllTypesObject.setInt) var setInt @Projected(\ModernAllTypesObject.setInt8) var setInt8 @Projected(\ModernAllTypesObject.setInt16) var setInt16 @Projected(\ModernAllTypesObject.setInt32) var setInt32 @Projected(\ModernAllTypesObject.setInt64) var setInt64 @Projected(\ModernAllTypesObject.setFloat) var setFloat @Projected(\ModernAllTypesObject.setDouble) var setDouble @Projected(\ModernAllTypesObject.setString) var setString @Projected(\ModernAllTypesObject.setBinary) var setBinary @Projected(\ModernAllTypesObject.setDate) var setDate @Projected(\ModernAllTypesObject.setDecimal) var setDecimal @Projected(\ModernAllTypesObject.setObjectId) var setObjectId @Projected(\ModernAllTypesObject.setAny) var setAny @Projected(\ModernAllTypesObject.setUuid) var setUuid @Projected(\ModernAllTypesObject.setOptBool) var setOptBool @Projected(\ModernAllTypesObject.setOptInt) var setOptInt @Projected(\ModernAllTypesObject.setOptInt8) var setOptInt8 @Projected(\ModernAllTypesObject.setOptInt16) var setOptInt16 @Projected(\ModernAllTypesObject.setOptInt32) var setOptInt32 @Projected(\ModernAllTypesObject.setOptInt64) var setOptInt64 @Projected(\ModernAllTypesObject.setOptFloat) var setOptFloat @Projected(\ModernAllTypesObject.setOptDouble) var setOptDouble @Projected(\ModernAllTypesObject.setOptString) var setOptString @Projected(\ModernAllTypesObject.setOptBinary) var setOptBinary @Projected(\ModernAllTypesObject.setOptDate) var setOptDate @Projected(\ModernAllTypesObject.setOptDecimal) var setOptDecimal @Projected(\ModernAllTypesObject.setOptObjectId) var setOptObjectId @Projected(\ModernAllTypesObject.setOptUuid) var setOptUuid @Projected(\ModernAllTypesObject.mapBool) var mapBool @Projected(\ModernAllTypesObject.mapInt) var mapInt @Projected(\ModernAllTypesObject.mapInt8) var mapInt8 @Projected(\ModernAllTypesObject.mapInt16) var mapInt16 @Projected(\ModernAllTypesObject.mapInt32) var mapInt32 @Projected(\ModernAllTypesObject.mapInt64) var mapInt64 @Projected(\ModernAllTypesObject.mapFloat) var mapFloat @Projected(\ModernAllTypesObject.mapDouble) var mapDouble @Projected(\ModernAllTypesObject.mapString) var mapString @Projected(\ModernAllTypesObject.mapBinary) var mapBinary @Projected(\ModernAllTypesObject.mapDate) var mapDate @Projected(\ModernAllTypesObject.mapDecimal) var mapDecimal @Projected(\ModernAllTypesObject.mapObjectId) var mapObjectId @Projected(\ModernAllTypesObject.mapAny) var mapAny @Projected(\ModernAllTypesObject.mapUuid) var mapUuid @Projected(\ModernAllTypesObject.mapOptBool) var mapOptBool @Projected(\ModernAllTypesObject.mapOptInt) var mapOptInt @Projected(\ModernAllTypesObject.mapOptInt8) var mapOptInt8 @Projected(\ModernAllTypesObject.mapOptInt16) var mapOptInt16 @Projected(\ModernAllTypesObject.mapOptInt32) var mapOptInt32 @Projected(\ModernAllTypesObject.mapOptInt64) var mapOptInt64 @Projected(\ModernAllTypesObject.mapOptFloat) var mapOptFloat @Projected(\ModernAllTypesObject.mapOptDouble) var mapOptDouble @Projected(\ModernAllTypesObject.mapOptString) var mapOptString @Projected(\ModernAllTypesObject.mapOptBinary) var mapOptBinary @Projected(\ModernAllTypesObject.mapOptDate) var mapOptDate @Projected(\ModernAllTypesObject.mapOptDecimal) var mapOptDecimal @Projected(\ModernAllTypesObject.mapOptObjectId) var mapOptObjectId @Projected(\ModernAllTypesObject.mapOptUuid) var mapOptUuid @Projected(\ModernAllTypesObject.linkingObjects) var linkingObjects } class AdvancedObject: Object { @Persisted(primaryKey: true) var pk: ObjectId @Persisted var commonArray: List @Persisted var objectsArray: List @Persisted var commonSet: MutableSet @Persisted var objectsSet: MutableSet } extension SimpleObject { var stringify: String { "\(int) - \(bool)" } } class AdvancedProjection: Projection { @Projected(\AdvancedObject.commonArray.count) var arrayLen @Projected(\AdvancedObject.commonArray) var renamedArray @Projected(\AdvancedObject.objectsArray.projectTo.stringify) var projectedArray: ProjectedCollection @Projected(\AdvancedObject.commonSet.first) var firstElement @Projected(\AdvancedObject.objectsSet.projectTo.bool) var projectedSet: ProjectedCollection } class FailedProjection: Projection { @Projected(\ModernAllTypesObject.ignored) var ignored } public class AddressSwift: EmbeddedObject { @Persisted var city: String = "" @Persisted var country = "" } public class ExtraInfo: Object { @Persisted var phone: PhoneInfo? @Persisted var email: String? } public class PhoneInfo: Object { @Persisted var mobile: Mobile? } public class Mobile: EmbeddedObject { @Persisted var number: String = "" } public class CommonPerson: Object { @Persisted var firstName: String @Persisted var lastName = "" @Persisted var birthday: Date @Persisted var address: AddressSwift? @Persisted var extras: ExtraInfo? @Persisted public var friends: List @Persisted var reviews: List @Persisted var money: Decimal128 } public final class PersonProjection: Projection { @Projected(\CommonPerson.firstName) var firstName @Projected(\CommonPerson.lastName.localizedUppercase) var lastNameCaps @Projected(\CommonPerson.birthday.timeIntervalSince1970) var birthdayAsEpochtime @Projected(\CommonPerson.address?.city) var homeCity @Projected(\CommonPerson.extras?.email) var email @Projected(\CommonPerson.extras?.phone?.mobile?.number) var mobile @Projected(\CommonPerson.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection } public class SimpleObject: Object { @Persisted var int: Int @Persisted var bool: Bool } public final class SimpleProjection: Projection { @Projected(\SimpleObject.int) var int } public final class MultipleProjectionsFromOneProperty: Projection { @Projected(\SimpleObject.int) var int1 @Projected(\SimpleObject.int) var int2 @Projected(\SimpleObject.int) var int3 } // MARK: Tests @available(iOS 13.0, *) class ProjectionTests: TestCase { func assertSetEquals(_ set: MutableSet, _ expected: Array) { XCTAssertEqual(set.count, Set(expected).count) XCTAssertEqual(Set(set), Set(expected)) } func assertEquivalent(_ actual: AnyRealmCollection, _ expected: Array, expectedShouldBeCopy: Bool) { XCTAssertEqual(actual.count, expected.count) for obj in expected { if expectedShouldBeCopy { XCTAssertTrue(actual.contains { $0.pk == obj.pk }) } else { XCTAssertTrue(actual.contains(obj)) } } } func assertMapEquals(_ actual: Map, _ expected: Dictionary) { XCTAssertEqual(actual.count, expected.count) for (key, value) in expected { XCTAssertEqual(actual[key], value) } } var allTypeValues: [String: Any] { return [ "boolCol": true, "intCol": 10, "int8Col": 11 as Int8, "int16Col": 12 as Int16, "int32Col": 13 as Int32, "int64Col": 14 as Int64, "floatCol": 15 as Float, "doubleCol": 16 as Double, "stringCol": "a", "binaryCol": Data("b".utf8), "dateCol": Date(timeIntervalSince1970: 17), "decimalCol": 18 as Decimal128, "objectIdCol": ObjectId("6058f12b957ba06156586a7c"), "objectCol": ModernAllTypesObject(value: ["intCol": 1]), "arrayCol": [ ModernAllTypesObject(value: ["pk": ObjectId("6058f12682b2fbb1f334ef1d"), "intCol": 2]), ModernAllTypesObject(value: ["pk": ObjectId("6058f12d42e5a393e67538d0"), "intCol": 3]) ], "setCol": [ ModernAllTypesObject(value: ["pk": ObjectId("6058f12d42e5a393e67538d1"), "intCol": 4]), ModernAllTypesObject(value: ["pk": ObjectId("6058f12682b2fbb1f334ef1f"), "intCol": 5]), ModernAllTypesObject(value: ["pk": ObjectId("507f1f77bcf86cd799439011"), "intCol": 6]) ], "anyCol": AnyRealmValue.int(20), "uuidCol": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, "intEnumCol": ModernIntEnum.value2, "stringEnumCol": ModernStringEnum.value3, "optBoolCol": false, "optIntCol": 30, "optInt8Col": 31 as Int8, "optInt16Col": 32 as Int16, "optInt32Col": 33 as Int32, "optInt64Col": 34 as Int64, "optFloatCol": 35 as Float, "optDoubleCol": 36 as Double, "optStringCol": "c", "optBinaryCol": Data("d".utf8), "optDateCol": Date(timeIntervalSince1970: 37), "optDecimalCol": 38 as Decimal128, "optObjectIdCol": ObjectId("6058f12b957ba06156586a7c"), "optUuidCol": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, "optIntEnumCol": ModernIntEnum.value1, "optStringEnumCol": ModernStringEnum.value1, "arrayBool": [true, false] as [Bool], "arrayInt": [1, 1, 2, 3] as [Int], "arrayInt8": [1, 2, 3, 1] as [Int8], "arrayInt16": [1, 2, 3, 1] as [Int16], "arrayInt32": [1, 2, 3, 1] as [Int32], "arrayInt64": [1, 2, 3, 1] as [Int64], "arrayFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float], "arrayDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double], "arrayString": ["a", "b", "c"] as [String], "arrayBinary": [Data("a".utf8)] as [Data], "arrayDate": [Date(timeIntervalSince1970: 0), Date(timeIntervalSince1970: 1)] as [Date], "arrayDecimal": [1 as Decimal128, 2 as Decimal128], "arrayObjectId": [ObjectId("6058f12b957ba06156586a7c"), ObjectId("6058f12682b2fbb1f334ef1d")], "arrayAny": [.none, .int(1), .string("a"), .none] as [AnyRealmValue], "arrayUuid": [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!], "arrayOptBool": [true, false, nil] as [Bool?], "arrayOptInt": [1, 1, 2, 3, nil] as [Int?], "arrayOptInt8": [1, 2, 3, 1, nil] as [Int8?], "arrayOptInt16": [1, 2, 3, 1, nil] as [Int16?], "arrayOptInt32": [1, 2, 3, 1, nil] as [Int32?], "arrayOptInt64": [1, 2, 3, 1, nil] as [Int64?], "arrayOptFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float, nil], "arrayOptDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double, nil], "arrayOptString": ["a", "b", "c", nil], "arrayOptBinary": [Data("a".utf8), nil], "arrayOptDate": [Date(timeIntervalSince1970: 0), Date(timeIntervalSince1970: 1), nil], "arrayOptDecimal": [1 as Decimal128, 2 as Decimal128, nil], "arrayOptObjectId": [ObjectId("6058f12b957ba06156586a7c"), ObjectId("6058f12682b2fbb1f334ef1d"), nil], "arrayOptUuid": [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, nil], "setBool": [true] as [Bool], "setInt": [1, 1, 2, 3] as [Int], "setInt8": [1, 2, 3, 1] as [Int8], "setInt16": [1, 2, 3, 1] as [Int16], "setInt32": [1, 2, 3, 1] as [Int32], "setInt64": [1, 2, 3, 1] as [Int64], "setFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float], "setDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double], "setString": ["a", "b", "c"] as [String], "setBinary": [Data("a".utf8)] as [Data], "setDate": [Date(timeIntervalSince1970: 1), Date(timeIntervalSince1970: 2)] as [Date], "setDecimal": [1 as Decimal128, 2 as Decimal128], "setObjectId": [ObjectId("6058f12b957ba06156586a7c"), ObjectId("6058f12682b2fbb1f334ef1d")], "setAny": [.none, .int(1), .string("a"), .none] as [AnyRealmValue], "setUuid": [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!], "setOptBool": [true, false, nil] as [Bool?], "setOptInt": [1, 1, 2, 3, nil] as [Int?], "setOptInt8": [1, 2, 3, 1, nil] as [Int8?], "setOptInt16": [1, 2, 3, 1, nil] as [Int16?], "setOptInt32": [1, 2, 3, 1, nil] as [Int32?], "setOptInt64": [1, 2, 3, 1, nil] as [Int64?], "setOptFloat": [1 as Float, 2 as Float, 3 as Float, 1 as Float, nil], "setOptDouble": [1 as Double, 2 as Double, 3 as Double, 1 as Double, nil], "setOptString": ["a", "b", "c", nil], "setOptBinary": [Data("a".utf8), nil], "setOptDate": [Date(timeIntervalSince1970: 1), Date(timeIntervalSince1970: 2), nil], "setOptDecimal": [1 as Decimal128, 2 as Decimal128, nil], "setOptObjectId": [ObjectId("6058f12b957ba06156586a7c"), ObjectId("6058f12682b2fbb1f334ef1d"), nil], "setOptUuid": [UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!, nil], "mapBool": ["1": true, "2": false] as [String: Bool], "mapInt": ["1": 1, "2": 1, "3": 2, "4": 3] as [String: Int], "mapInt8": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int8], "mapInt16": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int16], "mapInt32": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int32], "mapInt64": ["1": 1, "2": 2, "3": 3, "4": 1] as [String: Int64], "mapFloat": ["1": 1 as Float, "2": 2 as Float, "3": 3 as Float, "4": 1 as Float], "mapDouble": ["1": 1 as Double, "2": 2 as Double, "3": 3 as Double, "4": 1 as Double], "mapString": ["1": "a", "2": "b", "3": "c"] as [String: String], "mapBinary": ["1": Data("a".utf8)] as [String: Data], "mapDate": ["1": Date(timeIntervalSince1970: 1), "2": Date(timeIntervalSince1970: 2)] as [String: Date], "mapDecimal": ["1": 1 as Decimal128, "2": 2 as Decimal128], "mapObjectId": ["1": ObjectId("6058f12b957ba06156586a7c"), "2": ObjectId("6058f12682b2fbb1f334ef1d")], "mapAny": ["1": .none, "2": .int(1), "3": .string("a"), "4": .none] as [String: AnyRealmValue], "mapUuid": ["1": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, "2": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, "3": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!], "mapOptBool": ["1": true, "2": false, "3": nil] as [String: Bool?], "mapOptInt": ["1": 1, "2": 1, "3": 2, "4": 3, "5": nil] as [String: Int?], "mapOptInt8": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int8?], "mapOptInt16": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int16?], "mapOptInt32": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int32?], "mapOptInt64": ["1": 1, "2": 2, "3": 3, "4": 1, "5": nil] as [String: Int64?], "mapOptFloat": ["1": 1 as Float, "2": 2 as Float, "3": 3 as Float, "4": 1 as Float, "5": nil], "mapOptDouble": ["1": 1 as Double, "2": 2 as Double, "3": 3 as Double, "4": 1 as Double, "5": nil], "mapOptString": ["1": "a", "2": "b", "3": "c", "4": nil], "mapOptBinary": ["1": Data("a".utf8), "2": nil], "mapOptDate": ["1": Date(timeIntervalSince1970: 1), "2": Date(timeIntervalSince1970: 2), "3": nil], "mapOptDecimal": ["1": 1 as Decimal128, "2": 2 as Decimal128, "3": nil], "mapOptObjectId": ["1": ObjectId("6058f12b957ba06156586a7c"), "2": ObjectId("6058f12682b2fbb1f334ef1d"), "3": nil], "mapOptUuid": ["1": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fd")!, "2": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90fe")!, "3": UUID(uuidString: "6b28ec45-b29a-4b0a-bd6a-343c7f6d90ff")!, "4": nil], ] as [String: Any] } func populatedRealm() -> Realm { let realm = realmWithTestPath() try! realm.write { let js = realm.create(CommonPerson.self, value: ["firstName": "John", "lastName": "Snow", "birthday": Date(timeIntervalSince1970: 10), "address": [ "city": "Winterfell", "country": "Kingdom in the North"], "extras": ["phone": ["mobile": ["number": "555-555-555"]], "email": "john@doe.com"], "money": Decimal128("2.22")]) let dt = realm.create(CommonPerson.self, value: ["firstName": "Daenerys", "lastName": "Targaryen", "birthday": Date(timeIntervalSince1970: 0), "address": ["King's Landing", "Westeros"], "money": Decimal128("2.22")]) js.friends.append(dt) dt.friends.append(js) realm.create(ModernAllTypesObject.self, value: allTypeValues) realm.create(AdvancedObject.self, value: ["pk": ObjectId.generate(), "commonArray": [1, 2, 3], "objectsArray": [[1, true] as [Any], [2, false]], "commonSet": [1, 2, 3], "objectsSet": [[1, true] as [Any], [2, false]]]) } return realm } func testProjectionManualInit() { let realm = populatedRealm() let johnSnow = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! // this step will happen under the hood let pp = PersonProjection(projecting: johnSnow) XCTAssertEqual(pp.homeCity, "Winterfell") XCTAssertEqual(pp.birthdayAsEpochtime, Date(timeIntervalSince1970: 10).timeIntervalSince1970) XCTAssertEqual(pp.firstFriendsName.first!, "Daenerys") } func testProjectionFromResult() { let realm = populatedRealm() let johnSnow: PersonProjection = realm.objects(PersonProjection.self).first! XCTAssertEqual(johnSnow.homeCity, "Winterfell") XCTAssertEqual(johnSnow.birthdayAsEpochtime, Date(timeIntervalSince1970: 10).timeIntervalSince1970) XCTAssertEqual(johnSnow.firstFriendsName.first!, "Daenerys") } func testProjectionFromResultFiltered() { let realm = populatedRealm() let johnSnow: PersonProjection = realm.objects(PersonProjection.self).filter("lastName == 'Snow'").first! XCTAssertEqual(johnSnow.homeCity, "Winterfell") XCTAssertEqual(johnSnow.birthdayAsEpochtime, Date(timeIntervalSince1970: 10).timeIntervalSince1970) XCTAssertEqual(johnSnow.firstFriendsName.first!, "Daenerys") } func testProjectionFromResultSorted() { let realm = populatedRealm() let dany: PersonProjection = realm.objects(PersonProjection.self).sorted(byKeyPath: "firstName").first! XCTAssertEqual(dany.homeCity, "King's Landing") XCTAssertEqual(dany.birthdayAsEpochtime, Date(timeIntervalSince1970: 0).timeIntervalSince1970) XCTAssertEqual(dany.firstFriendsName.first!, "John") } func testProjectionEnumeration() { let realm = populatedRealm() XCTAssertGreaterThan(realm.objects(PersonProjection.self).count, 0) for proj in realm.objects(PersonProjection.self) { _ = proj } } func testProjectionEquality() { let realm = populatedRealm() let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! let johnDefaultInit = PersonProjection(projecting: johnObject) let johnMapped = realm.objects(PersonProjection.self).filter("lastName == 'Snow'").first! let notJohn = realm.objects(PersonProjection.self).filter("lastName != 'Snow'").first! XCTAssertEqual(johnMapped, johnDefaultInit) XCTAssertNotEqual(johnMapped, notJohn) } func testDescription() { let actual = populatedRealm().objects(PersonProjection.self).filter("lastName == 'Snow'").first!.description let expected = "PersonProjection <0x[0-9a-f]+> \\{\n\t\tfirstName\\(\\\\.firstName\\) = John;\n\tlastNameCaps\\(\\\\.lastName\\) = SNOW;\n\tbirthdayAsEpochtime\\(\\\\.birthday\\) = 10.0;\n\thomeCity\\(\\\\.address.city\\) = Optional\\(\"Winterfell\"\\);\n\temail\\(\\\\.extras.email\\) = Optional\\(\"john@doe.com\"\\);\n\tmobile\\(\\\\.extras.phone.mobile.number\\) = Optional\\(\"555-555-555\"\\);\n\tfirstFriendsName\\(\\\\.friends\\) = ProjectedCollection \\{\n\t\\[0\\] Daenerys\n\\};\n\\}" assertMatches(actual, expected) } func testProjectionsRealmShouldNotBeNil() { XCTAssertNotNil(populatedRealm().objects(PersonProjection.self).first!.realm) } func testProjectionFromResultSortedBirthday() { let realm = populatedRealm() let dany: PersonProjection = realm.objects(PersonProjection.self).sorted(byKeyPath: "birthday").first! XCTAssertEqual(dany.homeCity, "King's Landing") XCTAssertEqual(dany.birthdayAsEpochtime, Date(timeIntervalSince1970: 0).timeIntervalSince1970) XCTAssertEqual(dany.firstFriendsName.first!, "John") } func testProjectionForAllRealmTypes() { let allTypesModel = populatedRealm().objects(AllTypesPrimitiveProjection.self).first! XCTAssertEqual(allTypesModel.boolCol, allTypeValues["boolCol"] as! Bool) XCTAssertEqual(allTypesModel.intCol, allTypeValues["intCol"] as! Int) XCTAssertEqual(allTypesModel.int8Col, allTypeValues["int8Col"] as! Int8) XCTAssertEqual(allTypesModel.int16Col, allTypeValues["int16Col"] as! Int16) XCTAssertEqual(allTypesModel.int32Col, allTypeValues["int32Col"] as! Int32) XCTAssertEqual(allTypesModel.int64Col, allTypeValues["int64Col"] as! Int64) XCTAssertEqual(allTypesModel.floatCol, allTypeValues["floatCol"] as! Float) XCTAssertEqual(allTypesModel.doubleCol, allTypeValues["doubleCol"] as! Double) XCTAssertEqual(allTypesModel.stringCol, allTypeValues["stringCol"] as! String) XCTAssertEqual(allTypesModel.binaryCol, allTypeValues["binaryCol"] as! Data) XCTAssertEqual(allTypesModel.dateCol, allTypeValues["dateCol"] as! Date) XCTAssertEqual(allTypesModel.decimalCol, allTypeValues["decimalCol"] as! Decimal128) assertEquivalent(AnyRealmCollection(allTypesModel.arrayCol), allTypeValues["arrayCol"] as! [ModernAllTypesObject], expectedShouldBeCopy: true) assertEquivalent(AnyRealmCollection(allTypesModel.setCol), allTypeValues["setCol"] as! [ModernAllTypesObject], expectedShouldBeCopy: true) XCTAssertEqual(allTypesModel.anyCol, allTypeValues["anyCol"] as! AnyRealmValue) XCTAssertEqual(allTypesModel.uuidCol, allTypeValues["uuidCol"] as! UUID) XCTAssertEqual(allTypesModel.intEnumCol, allTypeValues["intEnumCol"] as! ModernIntEnum) XCTAssertEqual(allTypesModel.stringEnumCol, allTypeValues["stringEnumCol"] as! ModernStringEnum) XCTAssertEqual(allTypesModel.optBoolCol, allTypeValues["optBoolCol"] as! Bool?) XCTAssertEqual(allTypesModel.optIntCol, allTypeValues["optIntCol"] as! Int?) XCTAssertEqual(allTypesModel.optInt8Col, allTypeValues["optInt8Col"] as! Int8?) XCTAssertEqual(allTypesModel.optInt16Col, allTypeValues["optInt16Col"] as! Int16?) XCTAssertEqual(allTypesModel.optInt32Col, allTypeValues["optInt32Col"] as! Int32?) XCTAssertEqual(allTypesModel.optInt64Col, allTypeValues["optInt64Col"] as! Int64?) XCTAssertEqual(allTypesModel.optFloatCol, allTypeValues["optFloatCol"] as! Float?) XCTAssertEqual(allTypesModel.optDoubleCol, allTypeValues["optDoubleCol"] as! Double?) XCTAssertEqual(allTypesModel.optStringCol, allTypeValues["optStringCol"] as! String?) XCTAssertEqual(allTypesModel.optBinaryCol, allTypeValues["optBinaryCol"] as! Data?) XCTAssertEqual(allTypesModel.optDateCol, allTypeValues["optDateCol"] as! Date?) XCTAssertEqual(allTypesModel.optDecimalCol, allTypeValues["optDecimalCol"] as! Decimal128?) XCTAssertEqual(allTypesModel.optObjectIdCol, allTypeValues["optObjectIdCol"] as! ObjectId?) XCTAssertEqual(allTypesModel.optUuidCol, allTypeValues["optUuidCol"] as! UUID?) XCTAssertEqual(allTypesModel.optIntEnumCol, allTypeValues["optIntEnumCol"] as! ModernIntEnum?) XCTAssertEqual(allTypesModel.optStringEnumCol, allTypeValues["optStringEnumCol"] as! ModernStringEnum?) XCTAssertEqual(Array(allTypesModel.arrayBool), allTypeValues["arrayBool"] as! [Bool]) XCTAssertEqual(Array(allTypesModel.arrayInt), allTypeValues["arrayInt"] as! [Int]) XCTAssertEqual(Array(allTypesModel.arrayInt8), allTypeValues["arrayInt8"] as! [Int8]) XCTAssertEqual(Array(allTypesModel.arrayInt16), allTypeValues["arrayInt16"] as! [Int16]) XCTAssertEqual(Array(allTypesModel.arrayInt32), allTypeValues["arrayInt32"] as! [Int32]) XCTAssertEqual(Array(allTypesModel.arrayInt64), allTypeValues["arrayInt64"] as! [Int64]) XCTAssertEqual(Array(allTypesModel.arrayFloat), allTypeValues["arrayFloat"] as! [Float]) XCTAssertEqual(Array(allTypesModel.arrayDouble), allTypeValues["arrayDouble"] as! [Double]) XCTAssertEqual(Array(allTypesModel.arrayString), allTypeValues["arrayString"] as! [String]) XCTAssertEqual(Array(allTypesModel.arrayBinary), allTypeValues["arrayBinary"] as! [Data]) XCTAssertEqual(Array(allTypesModel.arrayDate), allTypeValues["arrayDate"] as! [Date]) XCTAssertEqual(Array(allTypesModel.arrayDecimal), allTypeValues["arrayDecimal"] as! [Decimal128]) XCTAssertEqual(Array(allTypesModel.arrayObjectId), allTypeValues["arrayObjectId"] as! [ObjectId]) XCTAssertEqual(Array(allTypesModel.arrayAny), allTypeValues["arrayAny"] as! [AnyRealmValue]) XCTAssertEqual(Array(allTypesModel.arrayUuid), allTypeValues["arrayUuid"] as! [UUID]) XCTAssertEqual(Array(allTypesModel.arrayOptBool), allTypeValues["arrayOptBool"] as! [Bool?]) XCTAssertEqual(Array(allTypesModel.arrayOptInt), allTypeValues["arrayOptInt"] as! [Int?]) XCTAssertEqual(Array(allTypesModel.arrayOptInt8), allTypeValues["arrayOptInt8"] as! [Int8?]) XCTAssertEqual(Array(allTypesModel.arrayOptInt16), allTypeValues["arrayOptInt16"] as! [Int16?]) XCTAssertEqual(Array(allTypesModel.arrayOptInt32), allTypeValues["arrayOptInt32"] as! [Int32?]) XCTAssertEqual(Array(allTypesModel.arrayOptInt64), allTypeValues["arrayOptInt64"] as! [Int64?]) XCTAssertEqual(Array(allTypesModel.arrayOptFloat), allTypeValues["arrayOptFloat"] as! [Float?]) XCTAssertEqual(Array(allTypesModel.arrayOptDouble), allTypeValues["arrayOptDouble"] as! [Double?]) XCTAssertEqual(Array(allTypesModel.arrayOptString), allTypeValues["arrayOptString"] as! [String?]) XCTAssertEqual(Array(allTypesModel.arrayOptBinary), allTypeValues["arrayOptBinary"] as! [Data?]) XCTAssertEqual(Array(allTypesModel.arrayOptDate), allTypeValues["arrayOptDate"] as! [Date?]) XCTAssertEqual(Array(allTypesModel.arrayOptDecimal), allTypeValues["arrayOptDecimal"] as! [Decimal128?]) XCTAssertEqual(Array(allTypesModel.arrayOptObjectId), allTypeValues["arrayOptObjectId"] as! [ObjectId?]) XCTAssertEqual(Array(allTypesModel.arrayOptUuid), allTypeValues["arrayOptUuid"] as! [UUID?]) assertSetEquals(allTypesModel.setBool, allTypeValues["setBool"] as! [Bool]) assertSetEquals(allTypesModel.setInt, allTypeValues["setInt"] as! [Int]) assertSetEquals(allTypesModel.setInt8, allTypeValues["setInt8"] as! [Int8]) assertSetEquals(allTypesModel.setInt16, allTypeValues["setInt16"] as! [Int16]) assertSetEquals(allTypesModel.setInt32, allTypeValues["setInt32"] as! [Int32]) assertSetEquals(allTypesModel.setInt64, allTypeValues["setInt64"] as! [Int64]) assertSetEquals(allTypesModel.setFloat, allTypeValues["setFloat"] as! [Float]) assertSetEquals(allTypesModel.setDouble, allTypeValues["setDouble"] as! [Double]) assertSetEquals(allTypesModel.setString, allTypeValues["setString"] as! [String]) assertSetEquals(allTypesModel.setBinary, allTypeValues["setBinary"] as! [Data]) assertSetEquals(allTypesModel.setDate, allTypeValues["setDate"] as! [Date]) assertSetEquals(allTypesModel.setDecimal, allTypeValues["setDecimal"] as! [Decimal128]) assertSetEquals(allTypesModel.setObjectId, allTypeValues["setObjectId"] as! [ObjectId]) assertSetEquals(allTypesModel.setAny, allTypeValues["setAny"] as! [AnyRealmValue]) assertSetEquals(allTypesModel.setUuid, allTypeValues["setUuid"] as! [UUID]) assertSetEquals(allTypesModel.setOptBool, allTypeValues["setOptBool"] as! [Bool?]) assertSetEquals(allTypesModel.setOptInt, allTypeValues["setOptInt"] as! [Int?]) assertSetEquals(allTypesModel.setOptInt8, allTypeValues["setOptInt8"] as! [Int8?]) assertSetEquals(allTypesModel.setOptInt16, allTypeValues["setOptInt16"] as! [Int16?]) assertSetEquals(allTypesModel.setOptInt32, allTypeValues["setOptInt32"] as! [Int32?]) assertSetEquals(allTypesModel.setOptInt64, allTypeValues["setOptInt64"] as! [Int64?]) assertSetEquals(allTypesModel.setOptFloat, allTypeValues["setOptFloat"] as! [Float?]) assertSetEquals(allTypesModel.setOptDouble, allTypeValues["setOptDouble"] as! [Double?]) assertSetEquals(allTypesModel.setOptString, allTypeValues["setOptString"] as! [String?]) assertSetEquals(allTypesModel.setOptBinary, allTypeValues["setOptBinary"] as! [Data?]) assertSetEquals(allTypesModel.setOptDate, allTypeValues["setOptDate"] as! [Date?]) assertSetEquals(allTypesModel.setOptDecimal, allTypeValues["setOptDecimal"] as! [Decimal128?]) assertSetEquals(allTypesModel.setOptObjectId, allTypeValues["setOptObjectId"] as! [ObjectId?]) assertSetEquals(allTypesModel.setOptUuid, allTypeValues["setOptUuid"] as! [UUID?]) assertMapEquals(allTypesModel.mapBool, allTypeValues["mapBool"] as! [String: Bool]) assertMapEquals(allTypesModel.mapInt, allTypeValues["mapInt"] as! [String: Int]) assertMapEquals(allTypesModel.mapInt8, allTypeValues["mapInt8"] as! [String: Int8]) assertMapEquals(allTypesModel.mapInt16, allTypeValues["mapInt16"] as! [String: Int16]) assertMapEquals(allTypesModel.mapInt32, allTypeValues["mapInt32"] as! [String: Int32]) assertMapEquals(allTypesModel.mapInt64, allTypeValues["mapInt64"] as! [String: Int64]) assertMapEquals(allTypesModel.mapFloat, allTypeValues["mapFloat"] as! [String: Float]) assertMapEquals(allTypesModel.mapDouble, allTypeValues["mapDouble"] as! [String: Double]) assertMapEquals(allTypesModel.mapString, allTypeValues["mapString"] as! [String: String]) assertMapEquals(allTypesModel.mapBinary, allTypeValues["mapBinary"] as! [String: Data]) assertMapEquals(allTypesModel.mapDate, allTypeValues["mapDate"] as! [String: Date]) assertMapEquals(allTypesModel.mapDecimal, allTypeValues["mapDecimal"] as! [String: Decimal128]) assertMapEquals(allTypesModel.mapObjectId, allTypeValues["mapObjectId"] as! [String: ObjectId]) assertMapEquals(allTypesModel.mapAny, allTypeValues["mapAny"] as! [String: AnyRealmValue]) assertMapEquals(allTypesModel.mapUuid, allTypeValues["mapUuid"] as! [String: UUID]) assertMapEquals(allTypesModel.mapOptBool, allTypeValues["mapOptBool"] as! [String: Bool?]) assertMapEquals(allTypesModel.mapOptInt, allTypeValues["mapOptInt"] as! [String: Int?]) assertMapEquals(allTypesModel.mapOptInt8, allTypeValues["mapOptInt8"] as! [String: Int8?]) assertMapEquals(allTypesModel.mapOptInt16, allTypeValues["mapOptInt16"] as! [String: Int16?]) assertMapEquals(allTypesModel.mapOptInt32, allTypeValues["mapOptInt32"] as! [String: Int32?]) assertMapEquals(allTypesModel.mapOptInt64, allTypeValues["mapOptInt64"] as! [String: Int64?]) assertMapEquals(allTypesModel.mapOptFloat, allTypeValues["mapOptFloat"] as! [String: Float?]) assertMapEquals(allTypesModel.mapOptDouble, allTypeValues["mapOptDouble"] as! [String: Double?]) assertMapEquals(allTypesModel.mapOptString, allTypeValues["mapOptString"] as! [String: String?]) assertMapEquals(allTypesModel.mapOptBinary, allTypeValues["mapOptBinary"] as! [String: Data?]) assertMapEquals(allTypesModel.mapOptDate, allTypeValues["mapOptDate"] as! [String: Date?]) assertMapEquals(allTypesModel.mapOptDecimal, allTypeValues["mapOptDecimal"] as! [String: Decimal128?]) assertMapEquals(allTypesModel.mapOptObjectId, allTypeValues["mapOptObjectId"] as! [String: ObjectId?]) assertMapEquals(allTypesModel.mapOptUuid, allTypeValues["mapOptUuid"] as! [String: UUID?]) } func expectPropertyChange(_ obj: AllTypesPrimitiveProjection, _ keyPath: KeyPath, _ expectedName: String, _ callback: @escaping (AllTypesPrimitiveProjection, Any?, Any?) -> Void ) -> (XCTestExpectation, NotificationToken) { let ex = expectation(description: "observeKeyPathChange") let token = obj.observe(keyPaths: [keyPath]) { changes in ex.fulfill() guard case let .change(object, properties) = changes else { return XCTFail("Expected .change but got \(changes)") } guard properties.count == 1 else { return XCTFail("Expected one property change but got \(properties)") } let prop = properties[0] XCTAssertEqual(prop.name, expectedName) callback(object, prop.oldValue, prop.newValue) } return (ex, token) } func observeKeyPathChange( _ obj: AllTypesPrimitiveProjection, _ keyPath: ReferenceWritableKeyPath, _ name: String, _ new: E, fileName: StaticString = #filePath, lineNumber: UInt = #line ) { let old = obj[keyPath: keyPath] let (ex, token) = expectPropertyChange(obj, keyPath, name) { _, oldValue, newValue in let actualOld = oldValue as? E let actualNew = newValue as? E if E.self != Optional.self { XCTAssertNotEqual(actualOld, actualNew, file: fileName, line: lineNumber) XCTAssertEqual(new, actualNew, file: fileName, line: lineNumber) XCTAssertEqual(old, actualOld, file: fileName, line: lineNumber) } } // Write on a background thread so that oldValue is present let tsr = ThreadSafeReference(to: obj) nonisolated(unsafe) let newValue = new nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realmWithTestPath() let obj = realm.resolve(tsr)! try! realm.write { obj.int8Col = 5 // Write to another property to verify keypath filtering works obj[keyPath: keyPath] = newValue } } wait(for: [ex], timeout: 2.0) token.invalidate() } func observeKeyPathChange( _ obj: AllTypesPrimitiveProjection, _ keyPath: KeyPath>, _ name: String, _ new: E, fileName: StaticString = #file, lineNumber: UInt = #line ) { let old = Array(obj[keyPath: keyPath]) let (ex, token) = expectPropertyChange(obj, keyPath, name) { object, oldValue, newValue in // We wrote on the same thread, so oldValue is nil. oldValue doesn't // really work for collections so it's not worth testing. XCTAssertNil(oldValue) let observedNew = newValue as? List XCTAssertIdentical(observedNew, object[keyPath: keyPath]) if let observedNew = observedNew { XCTAssertEqual(Array(observedNew), old + [new]) } } try! obj.realm!.write { obj.int8Col = 5 // Write to another property to verify keypath filtering works obj[keyPath: keyPath].append(new) } wait(for: [ex], timeout: 2.0) token.invalidate() } func observeKeyPathChange( _ obj: AllTypesPrimitiveProjection, _ keyPath: KeyPath>, _ name: String, _ new: E, fileName: StaticString = #file, lineNumber: UInt = #line ) { let old = Array(obj[keyPath: keyPath]) let (ex, token) = expectPropertyChange(obj, keyPath, name) { object, oldValue, newValue in // We wrote on the same thread, so oldValue is nil. oldValue doesn't // really work for collections so it's not worth testing. XCTAssertNil(oldValue) let observedNew = newValue as? MutableSet XCTAssertIdentical(observedNew, object[keyPath: keyPath]) if let observedNew = observedNew { self.assertSetEquals(observedNew, old + [new]) } } try! obj.realm!.write { obj.int8Col = 5 // Write to another property to verify keypath filtering works obj[keyPath: keyPath].insert(new) } wait(for: [ex], timeout: 2.0) token.invalidate() } func observeKeyPathChange( _ obj: AllTypesPrimitiveProjection, _ keyPath: KeyPath>, _ name: String, _ new: E, fileName: StaticString = #file, lineNumber: UInt = #line ) { let old = Dictionary(uniqueKeysWithValues: obj[keyPath: keyPath].map { ($0.key, $0.value) }) let (ex, token) = expectPropertyChange(obj, keyPath, name) { object, oldValue, newValue in // We wrote on the same thread, so oldValue is nil. oldValue doesn't // really work for collections so it's not worth testing. XCTAssertNil(oldValue) let observedNew = newValue as? Map XCTAssertIdentical(observedNew, object[keyPath: keyPath]) if let observedNew = observedNew { var updated = old updated["1"] = new self.assertMapEquals(observedNew, updated) } } try! obj.realm!.write { obj.int8Col = 5 // Write to another property to verify keypath filtering works obj[keyPath: keyPath]["1"] = new } wait(for: [ex], timeout: 2.0) token.invalidate() } func testAllPropertyTypesNotifications() { let realm = populatedRealm() let obj = realm.objects(ModernAllTypesObject.self).first! let obs = realm.objects(AllTypesPrimitiveProjection.self).first! let data = Data("c".utf8) let date = Date(timeIntervalSince1970: 7) let decimal = Decimal128(number: 3) let objectId = ObjectId.generate() let uuid = UUID() let object = ModernAllTypesObject(value: ["intCol": 2]) let anyValue = AnyRealmValue.int(22) observeKeyPathChange(obs, \.boolCol, "boolCol", false) observeKeyPathChange(obs, \.intCol, "intCol", 2) observeKeyPathChange(obs, \.int8Col, "int8Col", 2) observeKeyPathChange(obs, \.int16Col, "int16Col", 2) observeKeyPathChange(obs, \.int32Col, "int32Col", 2) observeKeyPathChange(obs, \.int64Col, "int64Col", 2) observeKeyPathChange(obs, \.floatCol, "floatCol", 2.0) observeKeyPathChange(obs, \.doubleCol, "doubleCol", 2.0) observeKeyPathChange(obs, \.stringCol, "stringCol", "def") observeKeyPathChange(obs, \.binaryCol, "binaryCol", data) observeKeyPathChange(obs, \.dateCol, "dateCol", date) observeKeyPathChange(obs, \.decimalCol, "decimalCol", decimal) observeKeyPathChange(obs, \.objectIdCol, "objectIdCol", objectId) observeKeyPathChange(obs, \.objectCol, "objectCol", object) observeKeyPathChange(obs, \.anyCol, "anyCol", anyValue) observeKeyPathChange(obs, \.uuidCol, "uuidCol", uuid) observeKeyPathChange(obs, \.intEnumCol, "intEnumCol", .value3) observeKeyPathChange(obs, \.stringEnumCol, "stringEnumCol", .value2) observeKeyPathChange(obs, \.optIntCol, "optIntCol", 2) observeKeyPathChange(obs, \.optInt8Col, "optInt8Col", 2) observeKeyPathChange(obs, \.optInt16Col, "optInt16Col", 2) observeKeyPathChange(obs, \.optInt32Col, "optInt32Col", 2) observeKeyPathChange(obs, \.optInt64Col, "optInt64Col", 2) observeKeyPathChange(obs, \.optFloatCol, "optFloatCol", 2.0) observeKeyPathChange(obs, \.optDoubleCol, "optDoubleCol", 2.0) observeKeyPathChange(obs, \.optBoolCol, "optBoolCol", true) observeKeyPathChange(obs, \.optStringCol, "optStringCol", "def") observeKeyPathChange(obs, \.optBinaryCol, "optBinaryCol", data) observeKeyPathChange(obs, \.optDateCol, "optDateCol", date) observeKeyPathChange(obs, \.optDecimalCol, "optDecimalCol", decimal) observeKeyPathChange(obs, \.optObjectIdCol, "optObjectIdCol", objectId) observeKeyPathChange(obs, \.optUuidCol, "optUuidCol", uuid) observeKeyPathChange(obs, \.optIntEnumCol, "optIntEnumCol", .value2) observeKeyPathChange(obs, \.optStringEnumCol, "optStringEnumCol", .value2) observeKeyPathChange(obs, \.arrayBool, "arrayBool", false) observeKeyPathChange(obs, \.arrayInt, "arrayInt", 4) observeKeyPathChange(obs, \.arrayInt8, "arrayInt8", 4) observeKeyPathChange(obs, \.arrayInt16, "arrayInt16", 4) observeKeyPathChange(obs, \.arrayInt32, "arrayInt32", 4) observeKeyPathChange(obs, \.arrayInt64, "arrayInt64", 4) observeKeyPathChange(obs, \.arrayFloat, "arrayFloat", 4) observeKeyPathChange(obs, \.arrayDouble, "arrayDouble", 4) observeKeyPathChange(obs, \.arrayString, "arrayString", "d") observeKeyPathChange(obs, \.arrayBinary, "arrayBinary", data) observeKeyPathChange(obs, \.arrayDate, "arrayDate", date) observeKeyPathChange(obs, \.arrayDecimal, "arrayDecimal", decimal) observeKeyPathChange(obs, \.arrayObjectId, "arrayObjectId", objectId) observeKeyPathChange(obs, \.arrayAny, "arrayAny", anyValue) observeKeyPathChange(obs, \.arrayUuid, "arrayUuid", uuid) observeKeyPathChange(obs, \.arrayOptBool, "arrayOptBool", true) observeKeyPathChange(obs, \.arrayOptInt, "arrayOptInt", 4) observeKeyPathChange(obs, \.arrayOptInt8, "arrayOptInt8", 4) observeKeyPathChange(obs, \.arrayOptInt16, "arrayOptInt16", 4) observeKeyPathChange(obs, \.arrayOptInt32, "arrayOptInt32", 4) observeKeyPathChange(obs, \.arrayOptInt64, "arrayOptInt64", 4) observeKeyPathChange(obs, \.arrayOptFloat, "arrayOptFloat", 4) observeKeyPathChange(obs, \.arrayOptDouble, "arrayOptDouble", 4) observeKeyPathChange(obs, \.arrayOptString, "arrayOptString", "d") observeKeyPathChange(obs, \.arrayOptBinary, "arrayOptBinary", data) observeKeyPathChange(obs, \.arrayOptDate, "arrayOptDate", date) observeKeyPathChange(obs, \.arrayOptDecimal, "arrayOptDecimal", decimal) observeKeyPathChange(obs, \.arrayOptObjectId, "arrayOptObjectId", objectId) observeKeyPathChange(obs, \.arrayOptUuid, "arrayOptUuid", uuid) try! realmWithTestPath().write { obj.setBool.removeAll() obj.setBool.insert(objectsIn: [true]) obj.setOptBool.removeAll() obj.setOptBool.insert(objectsIn: [true, nil]) } observeKeyPathChange(obs, \.setBool, "setBool", false) observeKeyPathChange(obs, \.setInt, "setInt", 4) observeKeyPathChange(obs, \.setInt8, "setInt8", 4) observeKeyPathChange(obs, \.setInt16, "setInt16", 4) observeKeyPathChange(obs, \.setInt32, "setInt32", 4) observeKeyPathChange(obs, \.setInt64, "setInt64", 4) observeKeyPathChange(obs, \.setFloat, "setFloat", 4) observeKeyPathChange(obs, \.setDouble, "setDouble", 4) observeKeyPathChange(obs, \.setString, "setString", "d") observeKeyPathChange(obs, \.setBinary, "setBinary", data) observeKeyPathChange(obs, \.setDate, "setDate", date) observeKeyPathChange(obs, \.setDecimal, "setDecimal", decimal) observeKeyPathChange(obs, \.setObjectId, "setObjectId", objectId) observeKeyPathChange(obs, \.setAny, "setAny", anyValue) observeKeyPathChange(obs, \.setUuid, "setUuid", uuid) observeKeyPathChange(obs, \.setOptBool, "setOptBool", false) observeKeyPathChange(obs, \.setOptInt, "setOptInt", 4) observeKeyPathChange(obs, \.setOptInt8, "setOptInt8", 4) observeKeyPathChange(obs, \.setOptInt16, "setOptInt16", 4) observeKeyPathChange(obs, \.setOptInt32, "setOptInt32", 4) observeKeyPathChange(obs, \.setOptInt64, "setOptInt64", 4) observeKeyPathChange(obs, \.setOptFloat, "setOptFloat", 4) observeKeyPathChange(obs, \.setOptDouble, "setOptDouble", 4) observeKeyPathChange(obs, \.setOptString, "setOptString", "d") observeKeyPathChange(obs, \.setOptBinary, "setOptBinary", data) observeKeyPathChange(obs, \.setOptDate, "setOptDate", date) observeKeyPathChange(obs, \.setOptDecimal, "setOptDecimal", decimal) observeKeyPathChange(obs, \.setOptObjectId, "setOptObjectId", objectId) observeKeyPathChange(obs, \.setOptUuid, "setOptUuid", uuid) observeKeyPathChange(obs, \.mapBool, "mapBool", false) observeKeyPathChange(obs, \.mapInt, "mapInt", 4) observeKeyPathChange(obs, \.mapInt8, "mapInt8", 4) observeKeyPathChange(obs, \.mapInt16, "mapInt16", 4) observeKeyPathChange(obs, \.mapInt32, "mapInt32", 4) observeKeyPathChange(obs, \.mapInt64, "mapInt64", 4) observeKeyPathChange(obs, \.mapFloat, "mapFloat", 4) observeKeyPathChange(obs, \.mapDouble, "mapDouble", 4) observeKeyPathChange(obs, \.mapString, "mapString", "d") observeKeyPathChange(obs, \.mapBinary, "mapBinary", data) observeKeyPathChange(obs, \.mapDate, "mapDate", date) observeKeyPathChange(obs, \.mapDecimal, "mapDecimal", decimal) observeKeyPathChange(obs, \.mapObjectId, "mapObjectId", objectId) observeKeyPathChange(obs, \.mapAny, "mapAny", anyValue) observeKeyPathChange(obs, \.mapUuid, "mapUuid", uuid) observeKeyPathChange(obs, \.mapOptBool, "mapOptBool", false) observeKeyPathChange(obs, \.mapOptInt, "mapOptInt", 4) observeKeyPathChange(obs, \.mapOptInt8, "mapOptInt8", 4) observeKeyPathChange(obs, \.mapOptInt16, "mapOptInt16", 4) observeKeyPathChange(obs, \.mapOptInt32, "mapOptInt32", 4) observeKeyPathChange(obs, \.mapOptInt64, "mapOptInt64", 4) observeKeyPathChange(obs, \.mapOptFloat, "mapOptFloat", 4) observeKeyPathChange(obs, \.mapOptDouble, "mapOptDouble", 4) observeKeyPathChange(obs, \.mapOptString, "mapOptString", "d") observeKeyPathChange(obs, \.mapOptBinary, "mapOptBinary", data) observeKeyPathChange(obs, \.mapOptDate, "mapOptDate", date) observeKeyPathChange(obs, \.mapOptDecimal, "mapOptDecimal", decimal) observeKeyPathChange(obs, \.mapOptObjectId, "mapOptObjectId", objectId) observeKeyPathChange(obs, \.mapOptUuid, "mapOptUuid", uuid) } @MainActor func testObserveKeyPath() { let realm = populatedRealm() let johnProjection = realm.objects(PersonProjection.self).filter("lastName == 'Snow'").first! let ex = expectation(description: "testProjectionNotification") let token = johnProjection.observe(keyPaths: ["lastName"], on: nil) { _ in ex.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { @Sendable in let realm = unsafeSelf.realmWithTestPath() try! realm.write { let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! johnObject.lastName = "Targaryen" } } waitForExpectations(timeout: 1, handler: nil) token.invalidate() } @MainActor func testObserveNestedProjection() { let realm = populatedRealm() let johnProjection = realm.objects(PersonProjection.self).first! var ex = expectation(description: "testProjectionNotificationNestedWithKeyPath") let token = johnProjection.observe(keyPaths: [\PersonProjection.mobile]) { changes in if case .change(_, let propertyChange) = changes { XCTAssertEqual(propertyChange[0].name, "mobile") XCTAssertEqual((propertyChange[0].newValue as? String), "529-345-678") ex.fulfill() } else { XCTFail("expected .change, got \(changes)") } } nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { @Sendable in let realm = unsafeSelf.realmWithTestPath() try! realm.write { let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! johnObject.extras?.phone?.mobile?.number = "529-345-678" } } waitForExpectations(timeout: 2.0, handler: nil) token.invalidate() ex = expectation(description: "testProjectionNotificationNested") let token2 = johnProjection.observe { changes in if case .change(_, let propertyChange) = changes { XCTAssertEqual(propertyChange[0].name, "email") XCTAssertEqual(propertyChange[0].newValue as? String, "joe@realm.com") ex.fulfill() } else { XCTFail("expected .change, got \(changes)") } } dispatchSyncNewThread { @Sendable in let realm = unsafeSelf.realmWithTestPath() try! realm.write { let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! johnObject.extras?.email = "joe@realm.com" } } waitForExpectations(timeout: 2.0, handler: nil) token2.invalidate() ex = expectation(description: "testProjectionNotificationEmbeddedNested") let token3 = johnProjection.observe { changes in if case .change(_, let propertyChange) = changes { // this appears to be required due to an autoclosure bug nonisolated(unsafe) let change = propertyChange[0] XCTAssertEqual(change.name, "homeCity") XCTAssertEqual(change.newValue as? String, "Barranquilla") ex.fulfill() } else { XCTFail("expected .change, got \(changes)") } } dispatchSyncNewThread { @Sendable in let realm = unsafeSelf.realmWithTestPath() try! realm.write { let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! johnObject.address?.city = "Barranquilla" } } waitForExpectations(timeout: 2.0, handler: nil) token3.invalidate() } var changeDictionary: [NSKeyValueChangeKey: Any]? override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { changeDictionary = change } func observeChange(_ obj: AllTypesPrimitiveProjection, _ key: String, _ block: () -> Void) -> [NSKeyValueChangeKey: Any]? { obj.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil) try! obj.realm!.write(block) obj.removeObserver(self, forKeyPath: key) let change = changeDictionary changeDictionary = nil return change } func observeChange(_ obj: AllTypesPrimitiveProjection, _ key: String, _ old: T?, _ new: T?, fileName: StaticString = #filePath, lineNumber: UInt = #line, _ block: () -> Void) { guard let change = observeChange(obj, key, block) else { return XCTFail("did not get a notification", file: fileName, line: lineNumber) } XCTAssertEqual(old, change[.oldKey] as? T, file: fileName, line: lineNumber) XCTAssertEqual(new, change[.newKey] as? T, file: fileName, line: lineNumber) } func observeListChange(_ obj: AllTypesPrimitiveProjection, _ key: String, _ kind: NSKeyValueChange, _ indexes: NSIndexSet = NSIndexSet(index: 0), fileName: StaticString = #filePath, lineNumber: UInt = #line, _ block: () -> Void) { guard let change = observeChange(obj, key, block) else { return XCTFail("did not get a notification", file: fileName, line: lineNumber) } let actualKind = NSKeyValueChange(rawValue: change[.kindKey] as! UInt) let actualIndexes = change[.indexesKey]! as! NSIndexSet XCTAssertEqual(actualKind, kind, file: fileName, line: lineNumber) XCTAssertEqual(actualIndexes, indexes, file: fileName, line: lineNumber) } func newObjects(_ defaultValues: [String: Any] = [:]) -> (ModernAllTypesObject, AllTypesPrimitiveProjection) { let realm = realmWithTestPath() realm.beginWrite() realm.delete(realm.objects(ModernAllTypesObject.self)) let obj = realm.create(ModernAllTypesObject.self, value: defaultValues) let obs = AllTypesPrimitiveProjection(projecting: obj) try! realm.commitWrite() return (obj, obs) } func observeSetChange(_ obj: AllTypesPrimitiveProjection, _ key: String, fileName: StaticString = #filePath, lineNumber: UInt = #line, _ block: () -> Void) { guard let change = observeChange(obj, key, block) else { return XCTFail("did not get a notification", file: fileName, line: lineNumber) } let actualKind = NSKeyValueChange(rawValue: change[.kindKey]! as! UInt) XCTAssertEqual(actualKind, .setting, file: fileName, line: lineNumber) } func testAllPropertyTypes() { var (obj, obs) = newObjects(allTypeValues) let data = Data("b".utf8) let date = Date(timeIntervalSince1970: 2) let decimal = Decimal128(number: 3) let objectId = ObjectId() let uuid = UUID() observeChange(obs, "boolCol", true, false) { obj.boolCol = false } observeChange(obs, "int8Col", 11 as Int8, 10) { obj.int8Col = 10 } observeChange(obs, "int16Col", 12 as Int16, 10) { obj.int16Col = 10 } observeChange(obs, "int32Col", 13 as Int32, 10) { obj.int32Col = 10 } observeChange(obs, "int64Col", 14 as Int64, 10) { obj.int64Col = 10 } observeChange(obs, "floatCol", 15 as Float, 10) { obj.floatCol = 10 } observeChange(obs, "doubleCol", 16 as Double, 10) { obj.doubleCol = 10 } observeChange(obs, "stringCol", "a", "abc") { obj.stringCol = "abc" } observeChange(obs, "objectCol", obj.objectCol, obj) { obj.objectCol = obj } observeChange(obs, "binaryCol", obj.binaryCol, data) { obj.binaryCol = data } observeChange(obs, "dateCol", obj.dateCol, date) { obj.dateCol = date } observeChange(obs, "decimalCol", obj.decimalCol, decimal) { obj.decimalCol = decimal } observeChange(obs, "objectIdCol", obj.objectIdCol, objectId) { obj.objectIdCol = objectId } observeChange(obs, "uuidCol", obj.uuidCol, uuid) { obj.uuidCol = uuid } observeChange(obs, "anyCol", 20, 1) { obj.anyCol = .int(1) } (obj, obs) = newObjects() observeListChange(obs, "arrayCol", .insertion) { obj.arrayCol.append(obj) } observeListChange(obs, "arrayCol", .removal) { obj.arrayCol.removeAll() } observeSetChange(obs, "setCol") { obj.setCol.insert(obj) } observeSetChange(obs, "setCol") { obj.setCol.remove(obj) } observeChange(obs, "optIntCol", nil, 10) { obj.optIntCol = 10 } observeChange(obs, "optFloatCol", nil, 10.0) { obj.optFloatCol = 10 } observeChange(obs, "optDoubleCol", nil, 10.0) { obj.optDoubleCol = 10 } observeChange(obs, "optBoolCol", nil, true) { obj.optBoolCol = true } observeChange(obs, "optStringCol", nil, "abc") { obj.optStringCol = "abc" } observeChange(obs, "optBinaryCol", nil, data) { obj.optBinaryCol = data } observeChange(obs, "optDateCol", nil, date) { obj.optDateCol = date } observeChange(obs, "optDecimalCol", nil, decimal) { obj.optDecimalCol = decimal } observeChange(obs, "optObjectIdCol", nil, objectId) { obj.optObjectIdCol = objectId } observeChange(obs, "optUuidCol", nil, uuid) { obj.optUuidCol = uuid } observeChange(obs, "optIntCol", 10, nil) { obj.optIntCol = nil } observeChange(obs, "optFloatCol", 10.0, nil) { obj.optFloatCol = nil } observeChange(obs, "optDoubleCol", 10.0, nil) { obj.optDoubleCol = nil } observeChange(obs, "optBoolCol", true, nil) { obj.optBoolCol = nil } observeChange(obs, "optStringCol", "abc", nil) { obj.optStringCol = nil } observeChange(obs, "optBinaryCol", data, nil) { obj.optBinaryCol = nil } observeChange(obs, "optDateCol", date, nil) { obj.optDateCol = nil } observeChange(obs, "optDecimalCol", decimal, nil) { obj.optDecimalCol = nil } observeChange(obs, "optObjectIdCol", objectId, nil) { obj.optObjectIdCol = nil } observeChange(obs, "optUuidCol", uuid, nil) { obj.optUuidCol = nil } // .insertion append observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.append(true) } observeListChange(obs, "arrayInt", .insertion) { obj.arrayInt.append(10) } observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.append(10) } observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.append(10) } observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.append(10) } observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.append(10) } observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.append(10.0) } observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.append(10.0) } observeListChange(obs, "arrayString", .insertion) { obj.arrayString.append("10") } observeListChange(obs, "arrayBinary", .insertion) { obj.arrayBinary.append(data) } observeListChange(obs, "arrayDate", .insertion) { obj.arrayDate.append(date) } observeListChange(obs, "arrayDecimal", .insertion) { obj.arrayDecimal.append(decimal) } observeListChange(obs, "arrayObjectId", .insertion) { obj.arrayObjectId.append(objectId) } observeListChange(obs, "arrayAny", .insertion) { obj.arrayAny.append(.int(10)) } observeListChange(obs, "arrayUuid", .insertion) { obj.arrayUuid.append(uuid) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(true) } observeListChange(obs, "arrayOptInt", .insertion) { obj.arrayOptInt.append(10) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(10) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(10) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(10) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(10) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(10.0) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(10.0) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append("10") } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(data) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(date) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.append(decimal) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.append(objectId) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.append(uuid) } (obj, obs) = newObjects() observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(nil) } observeListChange(obs, "arrayOptInt", .insertion) { obj.arrayOptInt.append(nil) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(nil) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(nil) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(nil) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(nil) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(nil) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(nil) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append(nil) } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(nil) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(nil) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.append(nil) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.append(nil) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.append(nil) } // .insertion insert at observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.insert(true, at: 0) } observeListChange(obs, "arrayInt", .insertion) { obj.arrayInt.append(10) } observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.insert(10, at: 0) } observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.insert(10, at: 0) } observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.insert(10, at: 0) } observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.insert(10, at: 0) } observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.insert(10, at: 0) } observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.insert(10, at: 0) } observeListChange(obs, "arrayString", .insertion) { obj.arrayString.insert("abc", at: 0) } observeListChange(obs, "arrayBinary", .insertion) { obj.arrayBinary.append(data) } observeListChange(obs, "arrayDate", .insertion) { obj.arrayDate.append(date) } observeListChange(obs, "arrayDecimal", .insertion) { obj.arrayDecimal.insert(decimal, at: 0) } observeListChange(obs, "arrayObjectId", .insertion) { obj.arrayObjectId.insert(objectId, at: 0) } observeListChange(obs, "arrayUuid", .insertion) { obj.arrayUuid.insert(uuid, at: 0) } observeListChange(obs, "arrayAny", .insertion) { obj.arrayAny.insert(.string("a"), at: 0) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(true, at: 0) } observeListChange(obs, "arrayOptInt", .insertion) { obj.arrayOptInt.insert(10, at: 0) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(10, at: 0) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(10, at: 0) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(10, at: 0) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(10, at: 0) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(10, at: 0) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(10, at: 0) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert("abc", at: 0) } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(data, at: 0) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(date, at: 0) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.insert(decimal, at: 0) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.insert(objectId, at: 0) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.insert(uuid, at: 0) } observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt", .insertion) { obj.arrayOptInt.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(nil, at: 0) } observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(nil, at: 0) } observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(nil, at: 0) } observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(nil, at: 0) } observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert(nil, at: 0) } observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(nil, at: 0) } observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(nil, at: 0) } observeListChange(obs, "arrayOptDecimal", .insertion) { obj.arrayOptDecimal.insert(nil, at: 0) } observeListChange(obs, "arrayOptObjectId", .insertion) { obj.arrayOptObjectId.insert(nil, at: 0) } observeListChange(obs, "arrayOptUuid", .insertion) { obj.arrayOptUuid.insert(nil, at: 0) } // .replacement observeListChange(obs, "arrayBool", .replacement) { obj.arrayBool[0] = true } observeListChange(obs, "arrayInt", .replacement) { obj.arrayInt[0] = 10 } observeListChange(obs, "arrayInt8", .replacement) { obj.arrayInt8[0] = 10 } observeListChange(obs, "arrayInt16", .replacement) { obj.arrayInt16[0] = 10 } observeListChange(obs, "arrayInt32", .replacement) { obj.arrayInt32[0] = 10 } observeListChange(obs, "arrayInt64", .replacement) { obj.arrayInt64[0] = 10 } observeListChange(obs, "arrayFloat", .replacement) { obj.arrayFloat[0] = 10 } observeListChange(obs, "arrayDouble", .replacement) { obj.arrayDouble[0] = 10 } observeListChange(obs, "arrayString", .replacement) { obj.arrayString[0] = "abc" } observeListChange(obs, "arrayBinary", .replacement) { obj.arrayBinary[0] = data } observeListChange(obs, "arrayDate", .replacement) { obj.arrayDate[0] = date } observeListChange(obs, "arrayDecimal", .replacement) { obj.arrayDecimal[0] = decimal } observeListChange(obs, "arrayObjectId", .replacement) { obj.arrayObjectId[0] = objectId } observeListChange(obs, "arrayUuid", .replacement) { obj.arrayUuid[0] = uuid } observeListChange(obs, "arrayAny", .replacement) { obj.arrayAny[0] = .string("a") } observeListChange(obs, "arrayOptBool", .replacement) { obj.arrayOptBool[0] = true } observeListChange(obs, "arrayOptInt", .replacement) { obj.arrayOptInt[0] = 10 } observeListChange(obs, "arrayOptInt8", .replacement) { obj.arrayOptInt8[0] = 10 } observeListChange(obs, "arrayOptInt16", .replacement) { obj.arrayOptInt16[0] = 10 } observeListChange(obs, "arrayOptInt32", .replacement) { obj.arrayOptInt32[0] = 10 } observeListChange(obs, "arrayOptInt64", .replacement) { obj.arrayOptInt64[0] = 10 } observeListChange(obs, "arrayOptFloat", .replacement) { obj.arrayOptFloat[0] = 10 } observeListChange(obs, "arrayOptDouble", .replacement) { obj.arrayOptDouble[0] = 10 } observeListChange(obs, "arrayOptString", .replacement) { obj.arrayOptString[0] = "abc" } observeListChange(obs, "arrayOptBinary", .replacement) { obj.arrayOptBinary[0] = data } observeListChange(obs, "arrayOptDate", .replacement) { obj.arrayOptDate[0] = date } observeListChange(obs, "arrayOptBinary", .replacement) { obj.arrayOptBinary[0] = data } observeListChange(obs, "arrayOptDate", .replacement) { obj.arrayOptDate[0] = date } observeListChange(obs, "arrayOptDecimal", .replacement) { obj.arrayOptDecimal[0] = decimal } observeListChange(obs, "arrayOptObjectId", .replacement) { obj.arrayOptObjectId[0] = objectId } observeListChange(obs, "arrayOptUuid", .replacement) { obj.arrayOptUuid[0] = uuid } observeListChange(obs, "arrayOptBool", .replacement) { obj.arrayOptBool[0] = nil } observeListChange(obs, "arrayOptInt", .replacement) { obj.arrayOptInt[0] = nil } observeListChange(obs, "arrayOptInt8", .replacement) { obj.arrayOptInt8[0] = nil } observeListChange(obs, "arrayOptInt16", .replacement) { obj.arrayOptInt16[0] = nil } observeListChange(obs, "arrayOptInt32", .replacement) { obj.arrayOptInt32[0] = nil } observeListChange(obs, "arrayOptInt64", .replacement) { obj.arrayOptInt64[0] = nil } observeListChange(obs, "arrayOptFloat", .replacement) { obj.arrayOptFloat[0] = nil } observeListChange(obs, "arrayOptDouble", .replacement) { obj.arrayOptDouble[0] = nil } observeListChange(obs, "arrayOptString", .replacement) { obj.arrayOptString[0] = nil } observeListChange(obs, "arrayOptBinary", .replacement) { obj.arrayOptBinary[0] = nil } observeListChange(obs, "arrayOptDate", .replacement) { obj.arrayOptDate[0] = nil } observeListChange(obs, "arrayOptDate", .replacement) { obj.arrayOptDate[0] = nil } observeListChange(obs, "arrayOptBinary", .replacement) { obj.arrayOptBinary[0] = nil } observeListChange(obs, "arrayOptDecimal", .replacement) { obj.arrayOptDecimal[0] = nil } observeListChange(obs, "arrayOptObjectId", .replacement) { obj.arrayOptObjectId[0] = nil } observeListChange(obs, "arrayOptUuid", .replacement) { obj.arrayOptUuid[0] = nil } // .removal removeAll observeListChange(obs, "arrayBool", .removal) { obj.arrayBool.removeAll() } observeListChange(obs, "arrayInt", .removal) { obj.arrayInt.removeAll() } observeListChange(obs, "arrayInt8", .removal) { obj.arrayInt8.removeAll() } observeListChange(obs, "arrayInt16", .removal) { obj.arrayInt16.removeAll() } observeListChange(obs, "arrayInt32", .removal) { obj.arrayInt32.removeAll() } observeListChange(obs, "arrayInt64", .removal) { obj.arrayInt64.removeAll() } observeListChange(obs, "arrayFloat", .removal) { obj.arrayFloat.removeAll() } observeListChange(obs, "arrayDouble", .removal) { obj.arrayDouble.removeAll() } observeListChange(obs, "arrayString", .removal) { obj.arrayString.removeAll() } observeListChange(obs, "arrayBinary", .removal) { obj.arrayBinary.removeAll() } observeListChange(obs, "arrayDate", .removal) { obj.arrayDate.removeAll() } observeListChange(obs, "arrayDecimal", .removal) { obj.arrayDecimal.removeAll() } observeListChange(obs, "arrayObjectId", .removal) { obj.arrayObjectId.removeAll() } observeListChange(obs, "arrayUuid", .removal) { obj.arrayUuid.removeAll() } observeListChange(obs, "arrayAny", .removal) { obj.arrayAny.removeAll() } let indices = NSIndexSet(indexesIn: NSRange(location: 0, length: 3)) observeListChange(obs, "arrayOptBool", .removal, indices) { obj.arrayOptBool.removeAll() } observeListChange(obs, "arrayOptInt", .removal, indices) { obj.arrayOptInt.removeAll() } observeListChange(obs, "arrayOptInt8", .removal, indices) { obj.arrayOptInt8.removeAll() } observeListChange(obs, "arrayOptInt16", .removal, indices) { obj.arrayOptInt16.removeAll() } observeListChange(obs, "arrayOptInt32", .removal, indices) { obj.arrayOptInt32.removeAll() } observeListChange(obs, "arrayOptInt64", .removal, indices) { obj.arrayOptInt64.removeAll() } observeListChange(obs, "arrayOptFloat", .removal, indices) { obj.arrayOptFloat.removeAll() } observeListChange(obs, "arrayOptDouble", .removal, indices) { obj.arrayOptDouble.removeAll() } observeListChange(obs, "arrayOptString", .removal, indices) { obj.arrayOptString.removeAll() } observeListChange(obs, "arrayOptBinary", .removal, indices) { obj.arrayOptBinary.removeAll() } observeListChange(obs, "arrayOptDate", .removal, indices) { obj.arrayOptDate.removeAll() } observeListChange(obs, "arrayOptDecimal", .removal, indices) { obj.arrayOptDecimal.removeAll() } observeListChange(obs, "arrayOptObjectId", .removal, indices) { obj.arrayOptObjectId.removeAll() } observeListChange(obs, "arrayOptUuid", .removal, indices) { obj.arrayOptUuid.removeAll() } // .removal remove at (obj, obs) = newObjects(allTypeValues) observeListChange(obs, "arrayBool", .removal) { obj.arrayBool.remove(at: 0) } observeListChange(obs, "arrayInt", .removal) { obj.arrayInt.remove(at: 0) } observeListChange(obs, "arrayInt8", .removal) { obj.arrayInt8.remove(at: 0) } observeListChange(obs, "arrayInt16", .removal) { obj.arrayInt16.remove(at: 0) } observeListChange(obs, "arrayInt32", .removal) { obj.arrayInt32.remove(at: 0) } observeListChange(obs, "arrayInt64", .removal) { obj.arrayInt64.remove(at: 0) } observeListChange(obs, "arrayFloat", .removal) { obj.arrayFloat.remove(at: 0) } observeListChange(obs, "arrayDouble", .removal) { obj.arrayDouble.remove(at: 0) } observeListChange(obs, "arrayString", .removal) { obj.arrayString.remove(at: 0) } observeListChange(obs, "arrayBinary", .removal) { obj.arrayBinary.remove(at: 0) } observeListChange(obs, "arrayDate", .removal) { obj.arrayDate.remove(at: 0) } observeListChange(obs, "arrayDecimal", .removal) { obj.arrayDecimal.remove(at: 0) } observeListChange(obs, "arrayObjectId", .removal) { obj.arrayObjectId.remove(at: 0) } observeListChange(obs, "arrayUuid", .removal) { obj.arrayUuid.remove(at: 0) } observeListChange(obs, "arrayAny", .removal) { obj.arrayAny.remove(at: 0) } observeListChange(obs, "arrayOptBool", .removal) { obj.arrayOptBool.remove(at: 0) } observeListChange(obs, "arrayOptInt", .removal) { obj.arrayOptInt.remove(at: 0) } observeListChange(obs, "arrayOptInt8", .removal) { obj.arrayOptInt8.remove(at: 0) } observeListChange(obs, "arrayOptInt16", .removal) { obj.arrayOptInt16.remove(at: 0) } observeListChange(obs, "arrayOptInt32", .removal) { obj.arrayOptInt32.remove(at: 0) } observeListChange(obs, "arrayOptInt64", .removal) { obj.arrayOptInt64.remove(at: 0) } observeListChange(obs, "arrayOptFloat", .removal) { obj.arrayOptFloat.remove(at: 0) } observeListChange(obs, "arrayOptDouble", .removal) { obj.arrayOptDouble.remove(at: 0) } observeListChange(obs, "arrayOptString", .removal) { obj.arrayOptString.remove(at: 0) } observeListChange(obs, "arrayOptBinary", .removal) { obj.arrayOptBinary.remove(at: 0) } observeListChange(obs, "arrayOptDate", .removal) { obj.arrayOptDate.remove(at: 0) } observeListChange(obs, "arrayOptDecimal", .removal) { obj.arrayOptDecimal.remove(at: 0) } observeListChange(obs, "arrayOptObjectId", .removal) { obj.arrayOptObjectId.remove(at: 0) } observeListChange(obs, "arrayOptUuid", .removal) { obj.arrayOptUuid.remove(at: 0) } // insert observeSetChange(obs, "setBool") { obj.setBool.insert(true) } observeSetChange(obs, "setInt") { obj.setInt.insert(10) } observeSetChange(obs, "setInt8") { obj.setInt8.insert(10) } observeSetChange(obs, "setInt16") { obj.setInt16.insert(10) } observeSetChange(obs, "setInt32") { obj.setInt32.insert(10) } observeSetChange(obs, "setInt64") { obj.setInt64.insert(10) } observeSetChange(obs, "setFloat") { obj.setFloat.insert(10.0) } observeSetChange(obs, "setDouble") { obj.setDouble.insert(10.0) } observeSetChange(obs, "setString") { obj.setString.insert("10") } observeSetChange(obs, "setBinary") { obj.setBinary.insert(data) } observeSetChange(obs, "setDate") { obj.setDate.insert(date) } observeSetChange(obs, "setDecimal") { obj.setDecimal.insert(decimal) } observeSetChange(obs, "setObjectId") { obj.setObjectId.insert(objectId) } observeSetChange(obs, "setAny") { obj.setAny.insert(.string("a")) } observeSetChange(obs, "setUuid") { obj.setUuid.insert(uuid) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(true) } observeSetChange(obs, "setOptInt") { obj.setOptInt.insert(10) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(10) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(10) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(10) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(10) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(10.0) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(10.0) } observeSetChange(obs, "setOptString") { obj.setOptString.insert("10") } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(data) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(date) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(decimal) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(objectId) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.insert(uuid) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(nil) } observeSetChange(obs, "setOptInt") { obj.setOptInt.insert(nil) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(nil) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(nil) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(nil) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(nil) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(nil) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(nil) } observeSetChange(obs, "setOptString") { obj.setOptString.insert(nil) } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(nil) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(nil) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(nil) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(nil) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.insert(nil) } // insert objectsIn observeSetChange(obs, "setBool") { obj.setBool.insert(objectsIn: [true]) } observeSetChange(obs, "setInt") { obj.setInt.insert(objectsIn: [10]) } observeSetChange(obs, "setInt8") { obj.setInt8.insert(objectsIn: [10]) } observeSetChange(obs, "setInt16") { obj.setInt16.insert(objectsIn: [10]) } observeSetChange(obs, "setInt32") { obj.setInt32.insert(objectsIn: [10]) } observeSetChange(obs, "setInt64") { obj.setInt64.insert(objectsIn: [10]) } observeSetChange(obs, "setFloat") { obj.setFloat.insert(objectsIn: [10.0]) } observeSetChange(obs, "setDouble") { obj.setDouble.insert(objectsIn: [10.0]) } observeSetChange(obs, "setString") { obj.setString.insert(objectsIn: ["10"]) } observeSetChange(obs, "setBinary") { obj.setBinary.insert(objectsIn: [data]) } observeSetChange(obs, "setDate") { obj.setDate.insert(objectsIn: [date]) } observeSetChange(obs, "setDecimal") { obj.setDecimal.insert(objectsIn: [decimal]) } observeSetChange(obs, "setObjectId") { obj.setObjectId.insert(objectsIn: [objectId]) } observeSetChange(obs, "setAny") { obj.setAny.insert(objectsIn: [.string("a")]) } observeSetChange(obs, "setUuid") { obj.setUuid.insert(objectsIn: [uuid]) } observeSetChange(obs, "setOptBool") { obj.setOptBool.insert(objectsIn: [true, nil]) } observeSetChange(obs, "setOptInt") { obj.setOptInt.insert(objectsIn: [10, nil]) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.insert(objectsIn: [10, nil]) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.insert(objectsIn: [10, nil]) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.insert(objectsIn: [10, nil]) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.insert(objectsIn: [10, nil]) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.insert(objectsIn: [10.0, nil]) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.insert(objectsIn: [10.0, nil]) } observeSetChange(obs, "setOptString") { obj.setOptString.insert(objectsIn: ["10", nil]) } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.insert(objectsIn: [data, nil]) } observeSetChange(obs, "setOptDate") { obj.setOptDate.insert(objectsIn: [date, nil]) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.insert(objectsIn: [decimal, nil]) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.insert(objectsIn: [objectId, nil]) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.insert(objectsIn: [uuid, nil]) } // delete observeSetChange(obs, "setBool") { obj.setBool.remove(true) } observeSetChange(obs, "setInt") { obj.setInt.remove(10) } observeSetChange(obs, "setInt8") { obj.setInt8.remove(10) } observeSetChange(obs, "setInt16") { obj.setInt16.remove(10) } observeSetChange(obs, "setInt32") { obj.setInt32.remove(10) } observeSetChange(obs, "setInt64") { obj.setInt64.remove(10) } observeSetChange(obs, "setFloat") { obj.setFloat.remove(10.0) } observeSetChange(obs, "setDouble") { obj.setDouble.remove(10.0) } observeSetChange(obs, "setString") { obj.setString.remove("10") } observeSetChange(obs, "setBinary") { obj.setBinary.remove(data) } observeSetChange(obs, "setDate") { obj.setDate.remove(date) } observeSetChange(obs, "setDecimal") { obj.setDecimal.remove(decimal) } observeSetChange(obs, "setObjectId") { obj.setObjectId.remove(objectId) } observeSetChange(obs, "setAny") { obj.setAny.remove(.string("a")) } observeSetChange(obs, "setUuid") { obj.setUuid.remove(uuid) } observeSetChange(obs, "setOptBool") { obj.setOptBool.remove(true) } observeSetChange(obs, "setOptInt") { obj.setOptInt.remove(10) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.remove(10) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.remove(10) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.remove(10) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.remove(10) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.remove(10.0) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.remove(10.0) } observeSetChange(obs, "setOptString") { obj.setOptString.remove("10") } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.remove(data) } observeSetChange(obs, "setOptDate") { obj.setOptDate.remove(date) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.remove(decimal) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.remove(objectId) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.remove(uuid) } observeSetChange(obs, "setOptBool") { obj.setOptBool.remove(nil) } observeSetChange(obs, "setOptInt") { obj.setOptInt.remove(nil) } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.remove(nil) } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.remove(nil) } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.remove(nil) } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.remove(nil) } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.remove(nil) } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.remove(nil) } observeSetChange(obs, "setOptString") { obj.setOptString.remove(nil) } observeSetChange(obs, "setOptDate") { obj.setOptDate.remove(nil) } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.remove(nil) } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.remove(nil) } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.remove(nil) } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.remove(nil) } // delete all observeSetChange(obs, "setBool") { obj.setBool.removeAll() } observeSetChange(obs, "setInt") { obj.setInt.removeAll() } observeSetChange(obs, "setInt8") { obj.setInt8.removeAll() } observeSetChange(obs, "setInt16") { obj.setInt16.removeAll() } observeSetChange(obs, "setInt32") { obj.setInt32.removeAll() } observeSetChange(obs, "setInt64") { obj.setInt64.removeAll() } observeSetChange(obs, "setFloat") { obj.setFloat.removeAll() } observeSetChange(obs, "setDouble") { obj.setDouble.removeAll() } observeSetChange(obs, "setString") { obj.setString.removeAll() } observeSetChange(obs, "setBinary") { obj.setBinary.removeAll() } observeSetChange(obs, "setDate") { obj.setDate.removeAll() } observeSetChange(obs, "setDecimal") { obj.setDecimal.removeAll() } observeSetChange(obs, "setObjectId") { obj.setObjectId.removeAll() } observeSetChange(obs, "setAny") { obj.setAny.removeAll() } observeSetChange(obs, "setUuid") { obj.setUuid.removeAll() } observeSetChange(obs, "setOptBool") { obj.setOptBool.removeAll() } observeSetChange(obs, "setOptInt") { obj.setOptInt.removeAll() } observeSetChange(obs, "setOptInt8") { obj.setOptInt8.removeAll() } observeSetChange(obs, "setOptInt16") { obj.setOptInt16.removeAll() } observeSetChange(obs, "setOptInt32") { obj.setOptInt32.removeAll() } observeSetChange(obs, "setOptInt64") { obj.setOptInt64.removeAll() } observeSetChange(obs, "setOptFloat") { obj.setOptFloat.removeAll() } observeSetChange(obs, "setOptDouble") { obj.setOptDouble.removeAll() } observeSetChange(obs, "setOptString") { obj.setOptString.removeAll() } observeSetChange(obs, "setOptBinary") { obj.setOptBinary.removeAll() } observeSetChange(obs, "setOptDate") { obj.setOptDate.removeAll() } observeSetChange(obs, "setOptDecimal") { obj.setOptDecimal.removeAll() } observeSetChange(obs, "setOptObjectId") { obj.setOptObjectId.removeAll() } observeSetChange(obs, "setOptUuid") { obj.setOptUuid.removeAll() } observeSetChange(obs, "mapBool") { obj.mapBool["key"] = true } observeSetChange(obs, "mapInt") { obj.mapInt["key"] = 10 } observeSetChange(obs, "mapInt8") { obj.mapInt8["key"] = 10 } observeSetChange(obs, "mapInt16") { obj.mapInt16["key"] = 10 } observeSetChange(obs, "mapInt32") { obj.mapInt32["key"] = 10 } observeSetChange(obs, "mapInt64") { obj.mapInt64["key"] = 10 } observeSetChange(obs, "mapFloat") { obj.mapFloat["key"] = 10.0 } observeSetChange(obs, "mapDouble") { obj.mapDouble["key"] = 10.0 } observeSetChange(obs, "mapString") { obj.mapString["key"] = "10" } observeSetChange(obs, "mapBinary") { obj.mapBinary["key"] = data } observeSetChange(obs, "mapDate") { obj.mapDate["key"] = date } observeSetChange(obs, "mapDecimal") { obj.mapDecimal["key"] = decimal } observeSetChange(obs, "mapObjectId") { obj.mapObjectId["key"] = objectId } observeSetChange(obs, "mapAny") { obj.mapAny["key"] = .string("a") } observeSetChange(obs, "mapUuid") { obj.mapUuid["key"] = uuid } observeSetChange(obs, "mapOptBool") { obj.mapOptBool["key"] = true } observeSetChange(obs, "mapOptInt") { obj.mapOptInt["key"] = 10 } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8["key"] = 10 } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16["key"] = 10 } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32["key"] = 10 } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64["key"] = 10 } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat["key"] = 10.0 } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble["key"] = 10.0 } observeSetChange(obs, "mapOptString") { obj.mapOptString["key"] = "10" } observeSetChange(obs, "mapOptBinary") { obj.mapOptBinary["key"] = data } observeSetChange(obs, "mapOptDate") { obj.mapOptDate["key"] = date } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal["key"] = decimal } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId["key"] = objectId } observeSetChange(obs, "mapOptUuid") { obj.mapOptUuid["key"] = uuid } observeSetChange(obs, "mapBool") { obj.mapBool["key"] = nil } observeSetChange(obs, "mapInt") { obj.mapInt["key"] = nil } observeSetChange(obs, "mapInt8") { obj.mapInt8["key"] = nil } observeSetChange(obs, "mapInt16") { obj.mapInt16["key"] = nil } observeSetChange(obs, "mapInt32") { obj.mapInt32["key"] = nil } observeSetChange(obs, "mapInt64") { obj.mapInt64["key"] = nil } observeSetChange(obs, "mapFloat") { obj.mapFloat["key"] = nil } observeSetChange(obs, "mapDouble") { obj.mapDouble["key"] = nil } observeSetChange(obs, "mapString") { obj.mapString["key"] = nil } observeSetChange(obs, "mapBinary") { obj.mapBinary["key"] = nil } observeSetChange(obs, "mapDate") { obj.mapDate["key"] = nil } observeSetChange(obs, "mapDecimal") { obj.mapDecimal["key"] = nil } observeSetChange(obs, "mapObjectId") { obj.mapObjectId["key"] = nil } observeSetChange(obs, "mapAny") { obj.mapAny["key"] = nil } observeSetChange(obs, "mapUuid") { obj.mapUuid["key"] = nil } observeSetChange(obs, "mapOptBool") { obj.mapOptBool["key"] = nil } observeSetChange(obs, "mapOptInt") { obj.mapOptInt["key"] = nil } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8["key"] = nil } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16["key"] = nil } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32["key"] = nil } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64["key"] = nil } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat["key"] = nil } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble["key"] = nil } observeSetChange(obs, "mapOptString") { obj.mapOptString["key"] = nil } observeSetChange(obs, "mapOptBinary") { obj.mapOptBinary["key"] = nil } observeSetChange(obs, "mapOptDate") { obj.mapOptDate["key"] = nil } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal["key"] = nil } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId["key"] = nil } observeSetChange(obs, "mapOptUuid") { obj.mapOptUuid["key"] = nil } observeSetChange(obs, "mapOptBool") { obj.mapOptBool.removeObject(for: "key") } observeSetChange(obs, "mapOptInt") { obj.mapOptInt.removeObject(for: "key") } observeSetChange(obs, "mapOptInt8") { obj.mapOptInt8.removeObject(for: "key") } observeSetChange(obs, "mapOptInt16") { obj.mapOptInt16.removeObject(for: "key") } observeSetChange(obs, "mapOptInt32") { obj.mapOptInt32.removeObject(for: "key") } observeSetChange(obs, "mapOptInt64") { obj.mapOptInt64.removeObject(for: "key") } observeSetChange(obs, "mapOptFloat") { obj.mapOptFloat.removeObject(for: "key") } observeSetChange(obs, "mapOptDouble") { obj.mapOptDouble.removeObject(for: "key") } observeSetChange(obs, "mapOptString") { obj.mapOptString.removeObject(for: "key") } observeSetChange(obs, "mapOptBinary") { obj.mapOptBinary.removeObject(for: "key") } observeSetChange(obs, "mapOptDate") { obj.mapOptDate.removeObject(for: "key") } observeSetChange(obs, "mapOptDecimal") { obj.mapOptDecimal.removeObject(for: "key") } observeSetChange(obs, "mapOptObjectId") { obj.mapOptObjectId.removeObject(for: "key") } observeSetChange(obs, "mapOptUuid") { obj.mapOptUuid.removeObject(for: "key") } } @MainActor func testObserveOnActor() async throws { let projection = simpleProjection() let ex = expectation(description: "got change") ex.expectedFulfillmentCount = 2 let block = { @Sendable (_: isolated CustomGlobalActor, change: ObjectChange) in guard case let .change(_, properties) = change else { return XCTFail("expected .change but got \(change)") } guard properties.count == 1 else { return XCTFail("expected one property but got \(properties)") } let prop = properties[0] XCTAssertEqual(prop.name, "int") XCTAssertEqual(prop.oldValue as? Int, 0) XCTAssertEqual(prop.newValue as? Int, 1) ex.fulfill() } let tokens = await [ projection.observe(keyPaths: ["int"], on: CustomGlobalActor.shared, block), projection.observe(keyPaths: [\.int], on: CustomGlobalActor.shared, block) ] // should not produce notification try projection.realm!.write { projection.rootObject.bool = true } try projection.realm!.write { projection.int = 1 } await fulfillment(of: [ex]) tokens.forEach { $0.invalidate() } } // MARK: Frozen Objects func simpleProjection() -> SimpleProjection { let realm = realmWithTestPath() var obj: SimpleObject! try! realm.write { obj = realm.create(SimpleObject.self) } return SimpleProjection(projecting: obj) } func testIsFrozen() { let projection = simpleProjection() let frozen = projection.freeze() XCTAssertFalse(projection.isFrozen) XCTAssertTrue(frozen.isFrozen) } func testFreezingFrozenObjectReturnsSelf() { let projection = simpleProjection() let frozen = projection.freeze() XCTAssertNotEqual(projection, frozen) XCTAssertFalse(projection.freeze() === frozen) XCTAssertEqual(frozen, frozen.freeze()) } func testFreezingDeletedObject() { let projection = simpleProjection() let object = projection.rootObject try! projection.realm!.write({ projection.realm!.delete(object) }) assertThrows(projection.freeze(), "Object has been deleted or invalidated.") } func testFreezeFromWrongThread() { nonisolated(unsafe) let projection = simpleProjection() nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { unsafeSelf.assertThrows(projection.freeze(), "Realm accessed from incorrect thread") } } func testAccessFrozenObjectFromDifferentThread() { let projection = simpleProjection() nonisolated(unsafe) let frozen = projection.freeze() dispatchSyncNewThread { XCTAssertEqual(frozen.int, 0) } } func testMutateFrozenObject() { let projection = simpleProjection() let frozen = projection.freeze() XCTAssertTrue(frozen.isFrozen) assertThrows(try! frozen.realm!.write { }, "Can't perform transactions on a frozen Realm") } func testObserveFrozenObject() { let frozen = simpleProjection().freeze() assertThrows(frozen.observe { _ in }, "Frozen Realms do not change and do not have change notifications.") } func testFrozenObjectEquality() { let projectionA = simpleProjection() let frozenA1 = projectionA.freeze() let frozenA2 = projectionA.freeze() XCTAssertEqual(frozenA1, frozenA2) let projectionB = simpleProjection() let frozenB = projectionB.freeze() XCTAssertNotEqual(frozenA1, frozenB) } func testFreezeInsideWriteTransaction() { let realm = realmWithTestPath() var object: SimpleObject! var projection: SimpleProjection! try! realm.write { object = realm.create(SimpleObject.self) projection = SimpleProjection(projecting: object) self.assertThrows(projection.freeze(), "Cannot freeze an object in the same write transaction as it was created in.") } try! realm.write { object.int = 2 // Frozen objects have the value of the object at the start of the transaction XCTAssertEqual(projection.freeze().int, 0) } } func testThaw() { let frozen = simpleProjection().freeze() XCTAssertTrue(frozen.isFrozen) let live = frozen.thaw()! XCTAssertFalse(live.isFrozen) try! live.realm!.write { live.int = 2 } XCTAssertNotEqual(live.int, frozen.int) } func testThawDeleted() { let projection = simpleProjection() let frozen = projection.freeze() let realm = realmWithTestPath() XCTAssertTrue(frozen.isFrozen) try! realm.write { realm.deleteAll() } let thawed = frozen.thaw() XCTAssertNil(thawed, "Thaw should return nil when object was deleted") } func testThawPreviousVersion() { let projection = simpleProjection() let frozen = projection.freeze() XCTAssertTrue(frozen.isFrozen) XCTAssertEqual(projection.int, frozen.int) try! projection.realm!.write { projection.int = 1 } XCTAssertNotEqual(projection.int, frozen.int, "Frozen object shouldn't mutate") let thawed = frozen.thaw()! XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.int, projection.int, "Thawed object should reflect transactions since the original reference was frozen.") } func testThawUpdatedOnDifferentThread() { let realm = realmWithTestPath() let projection = simpleProjection() let tsr = ThreadSafeReference(to: projection) nonisolated(unsafe) var frozen: SimpleProjection! nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realmWithTestPath() let resolvedProjection: SimpleProjection = realm.resolve(tsr)! try! realm.write { resolvedProjection.int = 1 } frozen = resolvedProjection.freeze() } let thawed = frozen.thaw()! XCTAssertEqual(thawed.int, 0, "Thaw shouldn't reflect background transactions until main thread realm is refreshed") realm.refresh() XCTAssertEqual(thawed.int, 1) } func testThawCreatedOnDifferentThread() { let realm = realmWithTestPath() XCTAssertEqual(realm.objects(SimpleProjection.self).count, 0) nonisolated(unsafe) var frozen: SimpleProjection! nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let projection = unsafeSelf.simpleProjection() frozen = projection.freeze() } XCTAssertNil(frozen.thaw()) XCTAssertEqual(realm.objects(SimpleProjection.self).count, 0) realm.refresh() XCTAssertEqual(realm.objects(SimpleProjection.self).count, 1) } @MainActor func testObserveComputedChange() throws { let realm = populatedRealm() let johnProjection = realm.objects(PersonProjection.self).first! XCTAssertEqual(johnProjection.lastNameCaps, "SNOW") let ex = expectation(description: "values will be observed") let token = johnProjection.observe(keyPaths: [\PersonProjection.lastNameCaps]) { chg in if case let .change(_, change) = chg { ex.fulfill() guard let value = change.first else { XCTFail("Change should contain PropertyChange") return } XCTAssertEqual(value.name, "lastNameCaps") XCTAssertEqual(value.oldValue as? String, "SNOW") XCTAssertEqual(value.newValue as? String, "ALI") } } // Wait for the notifier to be registered before we do the write realm.refresh() nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { @Sendable in let realm = unsafeSelf.realmWithTestPath() let johnObject = realm.objects(CommonPerson.self).filter("lastName == 'Snow'").first! try! realm.write { johnObject.lastName = "Ali" } } waitForExpectations(timeout: 2) token.invalidate() } func testObserveMultipleProjectionsFromOneProperty() { let realm = realmWithTestPath() try! realm.write { realm.create(SimpleObject.self, value: [1, false]) } let projection = realm.objects(MultipleProjectionsFromOneProperty.self).first! let ex = expectation(description: "values will be observed") let token = projection.observe { c in if case let .change(_, change) = c { ex.fulfill() XCTAssertEqual(change.count, 3) for (i, prop) in change.enumerated() { XCTAssertEqual(prop.name, "int\(i + 1)") XCTAssertEqual(prop.oldValue as? Int, 1) XCTAssertEqual(prop.newValue as? Int, 2) } } } try! realm.write {} nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realmWithTestPath() try! realm.write { realm.objects(SimpleObject.self).first!.int = 2 } } wait(for: [ex], timeout: 2.0) token.invalidate() } func testFailedProjection() { let realm = populatedRealm() XCTAssertGreaterThan(realm.objects(FailedProjection.self).count, 0) assertThrows(realm.objects(FailedProjection.self).first, reason: "@Projected property") } func testAdvancedProjection() throws { let realm = populatedRealm() let proj = realm.objects(AdvancedProjection.self).first! XCTAssertEqual(proj.arrayLen, 3) XCTAssertTrue(proj.projectedArray.elementsEqual(["1 - true", "2 - false"]), "'\(proj.projectedArray)' should be equal to '[\"1 - true\", \"2 - false\"]'") XCTAssertTrue(proj.renamedArray.elementsEqual([1, 2, 3])) XCTAssertEqual(proj.firstElement, 1) XCTAssertTrue(proj.projectedSet.elementsEqual([true, false]), "'\(proj.projectedArray)' should be equal to '[true, false]'") } } ================================================ FILE: RealmSwift/Tests/PropertyTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift class PropertyTests: TestCase { var primitiveProperty: Property! var linkProperty: Property! var primaryProperty: Property! var optionalProperty: Property! override func setUp() { super.setUp() autoreleasepool { let schema = try! Realm().schema self.primitiveProperty = schema["SwiftObject"]!["intCol"]! self.linkProperty = schema["SwiftOptionalObject"]!["optObjectCol"]! self.primaryProperty = schema["SwiftPrimaryStringObject"]!["stringCol"]! self.optionalProperty = schema["SwiftOptionalObject"]!["optObjectCol"]! } } func testName() { XCTAssertEqual(primitiveProperty.name, "intCol") XCTAssertEqual(linkProperty.name, "optObjectCol") XCTAssertEqual(primaryProperty.name, "stringCol") } func testType() { XCTAssertEqual(primitiveProperty.type, PropertyType.int) XCTAssertEqual(linkProperty.type, PropertyType.object) XCTAssertEqual(primaryProperty.type, PropertyType.string) } func testIndexed() { XCTAssertFalse(primitiveProperty.isIndexed) XCTAssertFalse(linkProperty.isIndexed) XCTAssertTrue(primaryProperty.isIndexed) } func testOptional() { XCTAssertFalse(primitiveProperty.isOptional) XCTAssertTrue(optionalProperty.isOptional) } func testObjectClassName() { XCTAssertNil(primitiveProperty.objectClassName) XCTAssertEqual(linkProperty.objectClassName!, "SwiftBoolObject") XCTAssertNil(primaryProperty.objectClassName) } func testEquals() { XCTAssert(try! primitiveProperty == Realm().schema["SwiftObject"]!["intCol"]!) XCTAssert(primitiveProperty != linkProperty) } } ================================================ FILE: RealmSwift/Tests/QueryTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift // This file is generated from a template. Do not edit directly. // swiftlint:disable large_tuple vertical_parameter_alignment class QueryTests: TestCase { private var realm: Realm! // MARK: Test data population private func objects() -> Results { realm.objects(ModernAllTypesObject.self) } private func getOrCreate(_ type: T.Type) -> T { if let object = realm.objects(T.self).first { return object } let object = T() try! realm.write { realm.add(object) } return object } private func collectionObject() -> ModernCollectionObject { return getOrCreate(ModernCollectionObject.self) } private func setAnyRealmValueCol(with value: AnyRealmValue, object: ModernAllTypesObject) { try! realm.write { object.anyCol = value } } private var circleObject: ModernCircleObject { return getOrCreate(ModernCircleObject.self) } override func setUp() { realm = inMemoryRealm("QueryTests") try! realm.write { let objCustomPersistableCollections = CustomPersistableCollections() let objModernCollectionsOfEnums = ModernCollectionsOfEnums() let objAllCustomPersistableTypes = AllCustomPersistableTypes() let objModernAllTypesObject = ModernAllTypesObject() objModernAllTypesObject.boolCol = false objModernAllTypesObject.intCol = 3 objModernAllTypesObject.int8Col = Int8(9) objModernAllTypesObject.int16Col = Int16(17) objModernAllTypesObject.int32Col = Int32(33) objModernAllTypesObject.int64Col = Int64(65) objModernAllTypesObject.floatCol = Float(6.55444333) objModernAllTypesObject.doubleCol = 234.567 objModernAllTypesObject.stringCol = "Foó" objModernAllTypesObject.binaryCol = Data(count: 128) objModernAllTypesObject.dateCol = Date(timeIntervalSince1970: 2000000) objModernAllTypesObject.decimalCol = Decimal128(234.567) objModernAllTypesObject.objectIdCol = ObjectId("61184062c1d8f096a3695045") objModernAllTypesObject.uuidCol = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! objModernAllTypesObject.intEnumCol = .value2 objModernAllTypesObject.stringEnumCol = .value2 objAllCustomPersistableTypes.bool = BoolWrapper(persistedValue: false) objAllCustomPersistableTypes.int = IntWrapper(persistedValue: 3) objAllCustomPersistableTypes.int8 = Int8Wrapper(persistedValue: Int8(9)) objAllCustomPersistableTypes.int16 = Int16Wrapper(persistedValue: Int16(17)) objAllCustomPersistableTypes.int32 = Int32Wrapper(persistedValue: Int32(33)) objAllCustomPersistableTypes.int64 = Int64Wrapper(persistedValue: Int64(65)) objAllCustomPersistableTypes.float = FloatWrapper(persistedValue: Float(6.55444333)) objAllCustomPersistableTypes.double = DoubleWrapper(persistedValue: 234.567) objAllCustomPersistableTypes.string = StringWrapper(persistedValue: "Foó") objAllCustomPersistableTypes.binary = DataWrapper(persistedValue: Data(count: 128)) objAllCustomPersistableTypes.date = DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) objAllCustomPersistableTypes.decimal = Decimal128Wrapper(persistedValue: Decimal128(234.567)) objAllCustomPersistableTypes.objectId = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) objAllCustomPersistableTypes.uuid = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) objModernAllTypesObject.optBoolCol = false objModernAllTypesObject.optIntCol = 3 objModernAllTypesObject.optInt8Col = Int8(9) objModernAllTypesObject.optInt16Col = Int16(17) objModernAllTypesObject.optInt32Col = Int32(33) objModernAllTypesObject.optInt64Col = Int64(65) objModernAllTypesObject.optFloatCol = Float(6.55444333) objModernAllTypesObject.optDoubleCol = 234.567 objModernAllTypesObject.optStringCol = "Foó" objModernAllTypesObject.optBinaryCol = Data(count: 128) objModernAllTypesObject.optDateCol = Date(timeIntervalSince1970: 2000000) objModernAllTypesObject.optDecimalCol = Decimal128(234.567) objModernAllTypesObject.optObjectIdCol = ObjectId("61184062c1d8f096a3695045") objModernAllTypesObject.optUuidCol = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! objModernAllTypesObject.optIntEnumCol = .value2 objModernAllTypesObject.optStringEnumCol = .value2 objAllCustomPersistableTypes.optBool = BoolWrapper(persistedValue: false) objAllCustomPersistableTypes.optInt = IntWrapper(persistedValue: 3) objAllCustomPersistableTypes.optInt8 = Int8Wrapper(persistedValue: Int8(9)) objAllCustomPersistableTypes.optInt16 = Int16Wrapper(persistedValue: Int16(17)) objAllCustomPersistableTypes.optInt32 = Int32Wrapper(persistedValue: Int32(33)) objAllCustomPersistableTypes.optInt64 = Int64Wrapper(persistedValue: Int64(65)) objAllCustomPersistableTypes.optFloat = FloatWrapper(persistedValue: Float(6.55444333)) objAllCustomPersistableTypes.optDouble = DoubleWrapper(persistedValue: 234.567) objAllCustomPersistableTypes.optString = StringWrapper(persistedValue: "Foó") objAllCustomPersistableTypes.optBinary = DataWrapper(persistedValue: Data(count: 128)) objAllCustomPersistableTypes.optDate = DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) objAllCustomPersistableTypes.optDecimal = Decimal128Wrapper(persistedValue: Decimal128(234.567)) objAllCustomPersistableTypes.optObjectId = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) objAllCustomPersistableTypes.optUuid = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) objModernAllTypesObject.arrayBool.append(objectsIn: [true, true]) objModernAllTypesObject.arrayInt.append(objectsIn: [1, 3]) objModernAllTypesObject.arrayInt8.append(objectsIn: [Int8(8), Int8(9)]) objModernAllTypesObject.arrayInt16.append(objectsIn: [Int16(16), Int16(17)]) objModernAllTypesObject.arrayInt32.append(objectsIn: [Int32(32), Int32(33)]) objModernAllTypesObject.arrayInt64.append(objectsIn: [Int64(64), Int64(65)]) objModernAllTypesObject.arrayFloat.append(objectsIn: [Float(5.55444333), Float(6.55444333)]) objModernAllTypesObject.arrayDouble.append(objectsIn: [123.456, 234.567]) objModernAllTypesObject.arrayString.append(objectsIn: ["Foo", "Foó"]) objModernAllTypesObject.arrayBinary.append(objectsIn: [Data(count: 64), Data(count: 128)]) objModernAllTypesObject.arrayDate.append(objectsIn: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) objModernAllTypesObject.arrayDecimal.append(objectsIn: [Decimal128(123.456), Decimal128(234.567)]) objModernAllTypesObject.arrayObjectId.append(objectsIn: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) objModernAllTypesObject.arrayUuid.append(objectsIn: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) objModernAllTypesObject.arrayAny.append(objectsIn: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")]) objModernCollectionsOfEnums.listInt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt8.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt16.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt32.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt64.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listFloat.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listDouble.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listString.append(objectsIn: [.value1, .value2]) objCustomPersistableCollections.listBool.append(objectsIn: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) objCustomPersistableCollections.listInt.append(objectsIn: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) objCustomPersistableCollections.listInt8.append(objectsIn: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) objCustomPersistableCollections.listInt16.append(objectsIn: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) objCustomPersistableCollections.listInt32.append(objectsIn: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) objCustomPersistableCollections.listInt64.append(objectsIn: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) objCustomPersistableCollections.listFloat.append(objectsIn: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) objCustomPersistableCollections.listDouble.append(objectsIn: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) objCustomPersistableCollections.listString.append(objectsIn: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) objCustomPersistableCollections.listBinary.append(objectsIn: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) objCustomPersistableCollections.listDate.append(objectsIn: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) objCustomPersistableCollections.listDecimal.append(objectsIn: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) objCustomPersistableCollections.listObjectId.append(objectsIn: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) objCustomPersistableCollections.listUuid.append(objectsIn: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) objModernAllTypesObject.arrayOptBool.append(objectsIn: [true, true]) objModernAllTypesObject.arrayOptInt.append(objectsIn: [1, 3]) objModernAllTypesObject.arrayOptInt8.append(objectsIn: [Int8(8), Int8(9)]) objModernAllTypesObject.arrayOptInt16.append(objectsIn: [Int16(16), Int16(17)]) objModernAllTypesObject.arrayOptInt32.append(objectsIn: [Int32(32), Int32(33)]) objModernAllTypesObject.arrayOptInt64.append(objectsIn: [Int64(64), Int64(65)]) objModernAllTypesObject.arrayOptFloat.append(objectsIn: [Float(5.55444333), Float(6.55444333)]) objModernAllTypesObject.arrayOptDouble.append(objectsIn: [123.456, 234.567]) objModernAllTypesObject.arrayOptString.append(objectsIn: ["Foo", "Foó"]) objModernAllTypesObject.arrayOptBinary.append(objectsIn: [Data(count: 64), Data(count: 128)]) objModernAllTypesObject.arrayOptDate.append(objectsIn: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) objModernAllTypesObject.arrayOptDecimal.append(objectsIn: [Decimal128(123.456), Decimal128(234.567)]) objModernAllTypesObject.arrayOptObjectId.append(objectsIn: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) objModernAllTypesObject.arrayOptUuid.append(objectsIn: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) objModernCollectionsOfEnums.listIntOpt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt8Opt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt16Opt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt32Opt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listInt64Opt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listFloatOpt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listDoubleOpt.append(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.listStringOpt.append(objectsIn: [.value1, .value2]) objCustomPersistableCollections.listOptBool.append(objectsIn: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) objCustomPersistableCollections.listOptInt.append(objectsIn: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) objCustomPersistableCollections.listOptInt8.append(objectsIn: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) objCustomPersistableCollections.listOptInt16.append(objectsIn: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) objCustomPersistableCollections.listOptInt32.append(objectsIn: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) objCustomPersistableCollections.listOptInt64.append(objectsIn: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) objCustomPersistableCollections.listOptFloat.append(objectsIn: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) objCustomPersistableCollections.listOptDouble.append(objectsIn: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) objCustomPersistableCollections.listOptString.append(objectsIn: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) objCustomPersistableCollections.listOptBinary.append(objectsIn: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) objCustomPersistableCollections.listOptDate.append(objectsIn: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) objCustomPersistableCollections.listOptDecimal.append(objectsIn: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) objCustomPersistableCollections.listOptObjectId.append(objectsIn: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) objCustomPersistableCollections.listOptUuid.append(objectsIn: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) objModernAllTypesObject.setBool.insert(objectsIn: [true, true]) objModernAllTypesObject.setInt.insert(objectsIn: [1, 3]) objModernAllTypesObject.setInt8.insert(objectsIn: [Int8(8), Int8(9)]) objModernAllTypesObject.setInt16.insert(objectsIn: [Int16(16), Int16(17)]) objModernAllTypesObject.setInt32.insert(objectsIn: [Int32(32), Int32(33)]) objModernAllTypesObject.setInt64.insert(objectsIn: [Int64(64), Int64(65)]) objModernAllTypesObject.setFloat.insert(objectsIn: [Float(5.55444333), Float(6.55444333)]) objModernAllTypesObject.setDouble.insert(objectsIn: [123.456, 234.567]) objModernAllTypesObject.setString.insert(objectsIn: ["Foo", "Foó"]) objModernAllTypesObject.setBinary.insert(objectsIn: [Data(count: 64), Data(count: 128)]) objModernAllTypesObject.setDate.insert(objectsIn: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) objModernAllTypesObject.setDecimal.insert(objectsIn: [Decimal128(123.456), Decimal128(234.567)]) objModernAllTypesObject.setObjectId.insert(objectsIn: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) objModernAllTypesObject.setUuid.insert(objectsIn: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) objModernAllTypesObject.setAny.insert(objectsIn: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")]) objModernCollectionsOfEnums.setInt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt8.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt16.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt32.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt64.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setFloat.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setDouble.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setString.insert(objectsIn: [.value1, .value2]) objCustomPersistableCollections.setBool.insert(objectsIn: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) objCustomPersistableCollections.setInt.insert(objectsIn: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) objCustomPersistableCollections.setInt8.insert(objectsIn: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) objCustomPersistableCollections.setInt16.insert(objectsIn: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) objCustomPersistableCollections.setInt32.insert(objectsIn: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) objCustomPersistableCollections.setInt64.insert(objectsIn: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) objCustomPersistableCollections.setFloat.insert(objectsIn: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) objCustomPersistableCollections.setDouble.insert(objectsIn: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) objCustomPersistableCollections.setString.insert(objectsIn: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) objCustomPersistableCollections.setBinary.insert(objectsIn: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) objCustomPersistableCollections.setDate.insert(objectsIn: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) objCustomPersistableCollections.setDecimal.insert(objectsIn: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) objCustomPersistableCollections.setObjectId.insert(objectsIn: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) objCustomPersistableCollections.setUuid.insert(objectsIn: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) objModernAllTypesObject.setOptBool.insert(objectsIn: [true, true]) objModernAllTypesObject.setOptInt.insert(objectsIn: [1, 3]) objModernAllTypesObject.setOptInt8.insert(objectsIn: [Int8(8), Int8(9)]) objModernAllTypesObject.setOptInt16.insert(objectsIn: [Int16(16), Int16(17)]) objModernAllTypesObject.setOptInt32.insert(objectsIn: [Int32(32), Int32(33)]) objModernAllTypesObject.setOptInt64.insert(objectsIn: [Int64(64), Int64(65)]) objModernAllTypesObject.setOptFloat.insert(objectsIn: [Float(5.55444333), Float(6.55444333)]) objModernAllTypesObject.setOptDouble.insert(objectsIn: [123.456, 234.567]) objModernAllTypesObject.setOptString.insert(objectsIn: ["Foo", "Foó"]) objModernAllTypesObject.setOptBinary.insert(objectsIn: [Data(count: 64), Data(count: 128)]) objModernAllTypesObject.setOptDate.insert(objectsIn: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) objModernAllTypesObject.setOptDecimal.insert(objectsIn: [Decimal128(123.456), Decimal128(234.567)]) objModernAllTypesObject.setOptObjectId.insert(objectsIn: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) objModernAllTypesObject.setOptUuid.insert(objectsIn: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) objModernCollectionsOfEnums.setIntOpt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt8Opt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt16Opt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt32Opt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setInt64Opt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setFloatOpt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setDoubleOpt.insert(objectsIn: [.value1, .value2]) objModernCollectionsOfEnums.setStringOpt.insert(objectsIn: [.value1, .value2]) objCustomPersistableCollections.setOptBool.insert(objectsIn: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) objCustomPersistableCollections.setOptInt.insert(objectsIn: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) objCustomPersistableCollections.setOptInt8.insert(objectsIn: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) objCustomPersistableCollections.setOptInt16.insert(objectsIn: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) objCustomPersistableCollections.setOptInt32.insert(objectsIn: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) objCustomPersistableCollections.setOptInt64.insert(objectsIn: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) objCustomPersistableCollections.setOptFloat.insert(objectsIn: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) objCustomPersistableCollections.setOptDouble.insert(objectsIn: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) objCustomPersistableCollections.setOptString.insert(objectsIn: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) objCustomPersistableCollections.setOptBinary.insert(objectsIn: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) objCustomPersistableCollections.setOptDate.insert(objectsIn: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) objCustomPersistableCollections.setOptDecimal.insert(objectsIn: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) objCustomPersistableCollections.setOptObjectId.insert(objectsIn: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) objCustomPersistableCollections.setOptUuid.insert(objectsIn: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) objModernAllTypesObject.mapBool["foo"] = true objModernAllTypesObject.mapBool["bar"] = true objModernAllTypesObject.mapInt["foo"] = 1 objModernAllTypesObject.mapInt["bar"] = 3 objModernAllTypesObject.mapInt8["foo"] = Int8(8) objModernAllTypesObject.mapInt8["bar"] = Int8(9) objModernAllTypesObject.mapInt16["foo"] = Int16(16) objModernAllTypesObject.mapInt16["bar"] = Int16(17) objModernAllTypesObject.mapInt32["foo"] = Int32(32) objModernAllTypesObject.mapInt32["bar"] = Int32(33) objModernAllTypesObject.mapInt64["foo"] = Int64(64) objModernAllTypesObject.mapInt64["bar"] = Int64(65) objModernAllTypesObject.mapFloat["foo"] = Float(5.55444333) objModernAllTypesObject.mapFloat["bar"] = Float(6.55444333) objModernAllTypesObject.mapDouble["foo"] = 123.456 objModernAllTypesObject.mapDouble["bar"] = 234.567 objModernAllTypesObject.mapString["foo"] = "Foo" objModernAllTypesObject.mapString["bar"] = "Foó" objModernAllTypesObject.mapBinary["foo"] = Data(count: 64) objModernAllTypesObject.mapBinary["bar"] = Data(count: 128) objModernAllTypesObject.mapDate["foo"] = Date(timeIntervalSince1970: 1000000) objModernAllTypesObject.mapDate["bar"] = Date(timeIntervalSince1970: 2000000) objModernAllTypesObject.mapDecimal["foo"] = Decimal128(123.456) objModernAllTypesObject.mapDecimal["bar"] = Decimal128(234.567) objModernAllTypesObject.mapObjectId["foo"] = ObjectId("61184062c1d8f096a3695046") objModernAllTypesObject.mapObjectId["bar"] = ObjectId("61184062c1d8f096a3695045") objModernAllTypesObject.mapUuid["foo"] = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! objModernAllTypesObject.mapUuid["bar"] = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! objModernAllTypesObject.mapAny["foo"] = AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")) objModernAllTypesObject.mapAny["bar"] = AnyRealmValue.string("Hello") objModernCollectionsOfEnums.mapInt["foo"] = .value1 objModernCollectionsOfEnums.mapInt["bar"] = .value2 objModernCollectionsOfEnums.mapInt8["foo"] = .value1 objModernCollectionsOfEnums.mapInt8["bar"] = .value2 objModernCollectionsOfEnums.mapInt16["foo"] = .value1 objModernCollectionsOfEnums.mapInt16["bar"] = .value2 objModernCollectionsOfEnums.mapInt32["foo"] = .value1 objModernCollectionsOfEnums.mapInt32["bar"] = .value2 objModernCollectionsOfEnums.mapInt64["foo"] = .value1 objModernCollectionsOfEnums.mapInt64["bar"] = .value2 objModernCollectionsOfEnums.mapFloat["foo"] = .value1 objModernCollectionsOfEnums.mapFloat["bar"] = .value2 objModernCollectionsOfEnums.mapDouble["foo"] = .value1 objModernCollectionsOfEnums.mapDouble["bar"] = .value2 objModernCollectionsOfEnums.mapString["foo"] = .value1 objModernCollectionsOfEnums.mapString["bar"] = .value2 objCustomPersistableCollections.mapBool["foo"] = BoolWrapper(persistedValue: true) objCustomPersistableCollections.mapBool["bar"] = BoolWrapper(persistedValue: true) objCustomPersistableCollections.mapInt["foo"] = IntWrapper(persistedValue: 1) objCustomPersistableCollections.mapInt["bar"] = IntWrapper(persistedValue: 3) objCustomPersistableCollections.mapInt8["foo"] = Int8Wrapper(persistedValue: Int8(8)) objCustomPersistableCollections.mapInt8["bar"] = Int8Wrapper(persistedValue: Int8(9)) objCustomPersistableCollections.mapInt16["foo"] = Int16Wrapper(persistedValue: Int16(16)) objCustomPersistableCollections.mapInt16["bar"] = Int16Wrapper(persistedValue: Int16(17)) objCustomPersistableCollections.mapInt32["foo"] = Int32Wrapper(persistedValue: Int32(32)) objCustomPersistableCollections.mapInt32["bar"] = Int32Wrapper(persistedValue: Int32(33)) objCustomPersistableCollections.mapInt64["foo"] = Int64Wrapper(persistedValue: Int64(64)) objCustomPersistableCollections.mapInt64["bar"] = Int64Wrapper(persistedValue: Int64(65)) objCustomPersistableCollections.mapFloat["foo"] = FloatWrapper(persistedValue: Float(5.55444333)) objCustomPersistableCollections.mapFloat["bar"] = FloatWrapper(persistedValue: Float(6.55444333)) objCustomPersistableCollections.mapDouble["foo"] = DoubleWrapper(persistedValue: 123.456) objCustomPersistableCollections.mapDouble["bar"] = DoubleWrapper(persistedValue: 234.567) objCustomPersistableCollections.mapString["foo"] = StringWrapper(persistedValue: "Foo") objCustomPersistableCollections.mapString["bar"] = StringWrapper(persistedValue: "Foó") objCustomPersistableCollections.mapBinary["foo"] = DataWrapper(persistedValue: Data(count: 64)) objCustomPersistableCollections.mapBinary["bar"] = DataWrapper(persistedValue: Data(count: 128)) objCustomPersistableCollections.mapDate["foo"] = DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) objCustomPersistableCollections.mapDate["bar"] = DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) objCustomPersistableCollections.mapDecimal["foo"] = Decimal128Wrapper(persistedValue: Decimal128(123.456)) objCustomPersistableCollections.mapDecimal["bar"] = Decimal128Wrapper(persistedValue: Decimal128(234.567)) objCustomPersistableCollections.mapObjectId["foo"] = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) objCustomPersistableCollections.mapObjectId["bar"] = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) objCustomPersistableCollections.mapUuid["foo"] = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) objCustomPersistableCollections.mapUuid["bar"] = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) objModernAllTypesObject.mapOptBool["foo"] = true objModernAllTypesObject.mapOptBool["bar"] = true objModernAllTypesObject.mapOptInt["foo"] = 1 objModernAllTypesObject.mapOptInt["bar"] = 3 objModernAllTypesObject.mapOptInt8["foo"] = Int8(8) objModernAllTypesObject.mapOptInt8["bar"] = Int8(9) objModernAllTypesObject.mapOptInt16["foo"] = Int16(16) objModernAllTypesObject.mapOptInt16["bar"] = Int16(17) objModernAllTypesObject.mapOptInt32["foo"] = Int32(32) objModernAllTypesObject.mapOptInt32["bar"] = Int32(33) objModernAllTypesObject.mapOptInt64["foo"] = Int64(64) objModernAllTypesObject.mapOptInt64["bar"] = Int64(65) objModernAllTypesObject.mapOptFloat["foo"] = Float(5.55444333) objModernAllTypesObject.mapOptFloat["bar"] = Float(6.55444333) objModernAllTypesObject.mapOptDouble["foo"] = 123.456 objModernAllTypesObject.mapOptDouble["bar"] = 234.567 objModernAllTypesObject.mapOptString["foo"] = "Foo" objModernAllTypesObject.mapOptString["bar"] = "Foó" objModernAllTypesObject.mapOptBinary["foo"] = Data(count: 64) objModernAllTypesObject.mapOptBinary["bar"] = Data(count: 128) objModernAllTypesObject.mapOptDate["foo"] = Date(timeIntervalSince1970: 1000000) objModernAllTypesObject.mapOptDate["bar"] = Date(timeIntervalSince1970: 2000000) objModernAllTypesObject.mapOptDecimal["foo"] = Decimal128(123.456) objModernAllTypesObject.mapOptDecimal["bar"] = Decimal128(234.567) objModernAllTypesObject.mapOptObjectId["foo"] = ObjectId("61184062c1d8f096a3695046") objModernAllTypesObject.mapOptObjectId["bar"] = ObjectId("61184062c1d8f096a3695045") objModernAllTypesObject.mapOptUuid["foo"] = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! objModernAllTypesObject.mapOptUuid["bar"] = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! objModernCollectionsOfEnums.mapIntOpt["foo"] = .value1 objModernCollectionsOfEnums.mapIntOpt["bar"] = .value2 objModernCollectionsOfEnums.mapInt8Opt["foo"] = .value1 objModernCollectionsOfEnums.mapInt8Opt["bar"] = .value2 objModernCollectionsOfEnums.mapInt16Opt["foo"] = .value1 objModernCollectionsOfEnums.mapInt16Opt["bar"] = .value2 objModernCollectionsOfEnums.mapInt32Opt["foo"] = .value1 objModernCollectionsOfEnums.mapInt32Opt["bar"] = .value2 objModernCollectionsOfEnums.mapInt64Opt["foo"] = .value1 objModernCollectionsOfEnums.mapInt64Opt["bar"] = .value2 objModernCollectionsOfEnums.mapFloatOpt["foo"] = .value1 objModernCollectionsOfEnums.mapFloatOpt["bar"] = .value2 objModernCollectionsOfEnums.mapDoubleOpt["foo"] = .value1 objModernCollectionsOfEnums.mapDoubleOpt["bar"] = .value2 objModernCollectionsOfEnums.mapStringOpt["foo"] = .value1 objModernCollectionsOfEnums.mapStringOpt["bar"] = .value2 objCustomPersistableCollections.mapOptBool["foo"] = BoolWrapper(persistedValue: true) objCustomPersistableCollections.mapOptBool["bar"] = BoolWrapper(persistedValue: true) objCustomPersistableCollections.mapOptInt["foo"] = IntWrapper(persistedValue: 1) objCustomPersistableCollections.mapOptInt["bar"] = IntWrapper(persistedValue: 3) objCustomPersistableCollections.mapOptInt8["foo"] = Int8Wrapper(persistedValue: Int8(8)) objCustomPersistableCollections.mapOptInt8["bar"] = Int8Wrapper(persistedValue: Int8(9)) objCustomPersistableCollections.mapOptInt16["foo"] = Int16Wrapper(persistedValue: Int16(16)) objCustomPersistableCollections.mapOptInt16["bar"] = Int16Wrapper(persistedValue: Int16(17)) objCustomPersistableCollections.mapOptInt32["foo"] = Int32Wrapper(persistedValue: Int32(32)) objCustomPersistableCollections.mapOptInt32["bar"] = Int32Wrapper(persistedValue: Int32(33)) objCustomPersistableCollections.mapOptInt64["foo"] = Int64Wrapper(persistedValue: Int64(64)) objCustomPersistableCollections.mapOptInt64["bar"] = Int64Wrapper(persistedValue: Int64(65)) objCustomPersistableCollections.mapOptFloat["foo"] = FloatWrapper(persistedValue: Float(5.55444333)) objCustomPersistableCollections.mapOptFloat["bar"] = FloatWrapper(persistedValue: Float(6.55444333)) objCustomPersistableCollections.mapOptDouble["foo"] = DoubleWrapper(persistedValue: 123.456) objCustomPersistableCollections.mapOptDouble["bar"] = DoubleWrapper(persistedValue: 234.567) objCustomPersistableCollections.mapOptString["foo"] = StringWrapper(persistedValue: "Foo") objCustomPersistableCollections.mapOptString["bar"] = StringWrapper(persistedValue: "Foó") objCustomPersistableCollections.mapOptBinary["foo"] = DataWrapper(persistedValue: Data(count: 64)) objCustomPersistableCollections.mapOptBinary["bar"] = DataWrapper(persistedValue: Data(count: 128)) objCustomPersistableCollections.mapOptDate["foo"] = DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) objCustomPersistableCollections.mapOptDate["bar"] = DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) objCustomPersistableCollections.mapOptDecimal["foo"] = Decimal128Wrapper(persistedValue: Decimal128(123.456)) objCustomPersistableCollections.mapOptDecimal["bar"] = Decimal128Wrapper(persistedValue: Decimal128(234.567)) objCustomPersistableCollections.mapOptObjectId["foo"] = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) objCustomPersistableCollections.mapOptObjectId["bar"] = ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) objCustomPersistableCollections.mapOptUuid["foo"] = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) objCustomPersistableCollections.mapOptUuid["bar"] = UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) realm.add(objAllCustomPersistableTypes) realm.add(objModernCollectionsOfEnums) realm.add(objCustomPersistableCollections) realm.add(objModernAllTypesObject) } } override func tearDown() { realm = nil } private func createKeypathCollectionAggregatesObject() { realm.beginWrite() realm.deleteAll() let parentLinkToCustomPersistableCollections = realm.create(LinkToCustomPersistableCollections.self) let childrenCustomPersistableCollections = [CustomPersistableCollections(), CustomPersistableCollections(), CustomPersistableCollections()] parentLinkToCustomPersistableCollections.list.append(objectsIn: childrenCustomPersistableCollections) let parentLinkToModernCollectionsOfEnums = realm.create(LinkToModernCollectionsOfEnums.self) let childrenModernCollectionsOfEnums = [ModernCollectionsOfEnums(), ModernCollectionsOfEnums(), ModernCollectionsOfEnums()] parentLinkToModernCollectionsOfEnums.list.append(objectsIn: childrenModernCollectionsOfEnums) let parentLinkToAllCustomPersistableTypes = realm.create(LinkToAllCustomPersistableTypes.self) let childrenAllCustomPersistableTypes = [AllCustomPersistableTypes(), AllCustomPersistableTypes(), AllCustomPersistableTypes()] parentLinkToAllCustomPersistableTypes.list.append(objectsIn: childrenAllCustomPersistableTypes) let parentLinkToModernAllTypesObject = realm.create(LinkToModernAllTypesObject.self) let childrenModernAllTypesObject = [ModernAllTypesObject(), ModernAllTypesObject(), ModernAllTypesObject()] parentLinkToModernAllTypesObject.list.append(objectsIn: childrenModernAllTypesObject) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.intCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.int8Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.int16Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.int32Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.int64Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.floatCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.doubleCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.dateCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.decimalCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.intEnumCol) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.int) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.int8) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.int16) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.int32) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.int64) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.float) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.double) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.date) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.decimal) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optIntCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optInt8Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optInt16Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optInt32Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optInt64Col) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optFloatCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optDoubleCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optDateCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optDecimalCol) initForKeypathCollectionAggregates(childrenModernAllTypesObject, \.optIntEnumCol) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optInt) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optInt8) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optInt16) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optInt32) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optInt64) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optFloat) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optDouble) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optDate) initForKeypathCollectionAggregates(childrenAllCustomPersistableTypes, \.optDecimal) try! realm.commitWrite() } private func initForKeypathCollectionAggregates( _ objects: [O], _ keyPath: ReferenceWritableKeyPath) { for (obj, value) in zip(objects, T.queryValues()) { obj[keyPath: keyPath] = value } } private func initLinkedCollectionAggregatesObject() { realm.beginWrite() realm.deleteAll() let parentLinkToCustomPersistableCollections = realm.create(LinkToCustomPersistableCollections.self) let objCustomPersistableCollections = CustomPersistableCollections() parentLinkToCustomPersistableCollections["object"] = objCustomPersistableCollections let parentLinkToModernCollectionsOfEnums = realm.create(LinkToModernCollectionsOfEnums.self) let objModernCollectionsOfEnums = ModernCollectionsOfEnums() parentLinkToModernCollectionsOfEnums["object"] = objModernCollectionsOfEnums let parentLinkToModernAllTypesObject = realm.create(LinkToModernAllTypesObject.self) let objModernAllTypesObject = ModernAllTypesObject() parentLinkToModernAllTypesObject["object"] = objModernAllTypesObject objModernAllTypesObject["arrayBool"] = Bool.queryValues() objModernAllTypesObject["arrayInt"] = Int.queryValues() objModernAllTypesObject["arrayInt8"] = Int8.queryValues() objModernAllTypesObject["arrayInt16"] = Int16.queryValues() objModernAllTypesObject["arrayInt32"] = Int32.queryValues() objModernAllTypesObject["arrayInt64"] = Int64.queryValues() objModernAllTypesObject["arrayFloat"] = Float.queryValues() objModernAllTypesObject["arrayDouble"] = Double.queryValues() objModernAllTypesObject["arrayString"] = String.queryValues() objModernAllTypesObject["arrayBinary"] = Data.queryValues() objModernAllTypesObject["arrayDate"] = Date.queryValues() objModernAllTypesObject["arrayDecimal"] = Decimal128.queryValues() objModernAllTypesObject["arrayObjectId"] = ObjectId.queryValues() objModernAllTypesObject["arrayUuid"] = UUID.queryValues() objModernAllTypesObject["arrayAny"] = AnyRealmValue.queryValues() objModernCollectionsOfEnums["listInt"] = EnumInt.queryValues() objModernCollectionsOfEnums["listInt8"] = EnumInt8.queryValues() objModernCollectionsOfEnums["listInt16"] = EnumInt16.queryValues() objModernCollectionsOfEnums["listInt32"] = EnumInt32.queryValues() objModernCollectionsOfEnums["listInt64"] = EnumInt64.queryValues() objModernCollectionsOfEnums["listFloat"] = EnumFloat.queryValues() objModernCollectionsOfEnums["listDouble"] = EnumDouble.queryValues() objModernCollectionsOfEnums["listString"] = EnumString.queryValues() objCustomPersistableCollections["listBool"] = BoolWrapper.queryValues() objCustomPersistableCollections["listInt"] = IntWrapper.queryValues() objCustomPersistableCollections["listInt8"] = Int8Wrapper.queryValues() objCustomPersistableCollections["listInt16"] = Int16Wrapper.queryValues() objCustomPersistableCollections["listInt32"] = Int32Wrapper.queryValues() objCustomPersistableCollections["listInt64"] = Int64Wrapper.queryValues() objCustomPersistableCollections["listFloat"] = FloatWrapper.queryValues() objCustomPersistableCollections["listDouble"] = DoubleWrapper.queryValues() objCustomPersistableCollections["listString"] = StringWrapper.queryValues() objCustomPersistableCollections["listBinary"] = DataWrapper.queryValues() objCustomPersistableCollections["listDate"] = DateWrapper.queryValues() objCustomPersistableCollections["listDecimal"] = Decimal128Wrapper.queryValues() objCustomPersistableCollections["listObjectId"] = ObjectIdWrapper.queryValues() objCustomPersistableCollections["listUuid"] = UUIDWrapper.queryValues() objModernAllTypesObject["arrayOptBool"] = Bool?.queryValues() objModernAllTypesObject["arrayOptInt"] = Int?.queryValues() objModernAllTypesObject["arrayOptInt8"] = Int8?.queryValues() objModernAllTypesObject["arrayOptInt16"] = Int16?.queryValues() objModernAllTypesObject["arrayOptInt32"] = Int32?.queryValues() objModernAllTypesObject["arrayOptInt64"] = Int64?.queryValues() objModernAllTypesObject["arrayOptFloat"] = Float?.queryValues() objModernAllTypesObject["arrayOptDouble"] = Double?.queryValues() objModernAllTypesObject["arrayOptString"] = String?.queryValues() objModernAllTypesObject["arrayOptBinary"] = Data?.queryValues() objModernAllTypesObject["arrayOptDate"] = Date?.queryValues() objModernAllTypesObject["arrayOptDecimal"] = Decimal128?.queryValues() objModernAllTypesObject["arrayOptObjectId"] = ObjectId?.queryValues() objModernAllTypesObject["arrayOptUuid"] = UUID?.queryValues() objModernCollectionsOfEnums["listIntOpt"] = EnumInt?.queryValues() objModernCollectionsOfEnums["listInt8Opt"] = EnumInt8?.queryValues() objModernCollectionsOfEnums["listInt16Opt"] = EnumInt16?.queryValues() objModernCollectionsOfEnums["listInt32Opt"] = EnumInt32?.queryValues() objModernCollectionsOfEnums["listInt64Opt"] = EnumInt64?.queryValues() objModernCollectionsOfEnums["listFloatOpt"] = EnumFloat?.queryValues() objModernCollectionsOfEnums["listDoubleOpt"] = EnumDouble?.queryValues() objModernCollectionsOfEnums["listStringOpt"] = EnumString?.queryValues() objCustomPersistableCollections["listOptBool"] = BoolWrapper?.queryValues() objCustomPersistableCollections["listOptInt"] = IntWrapper?.queryValues() objCustomPersistableCollections["listOptInt8"] = Int8Wrapper?.queryValues() objCustomPersistableCollections["listOptInt16"] = Int16Wrapper?.queryValues() objCustomPersistableCollections["listOptInt32"] = Int32Wrapper?.queryValues() objCustomPersistableCollections["listOptInt64"] = Int64Wrapper?.queryValues() objCustomPersistableCollections["listOptFloat"] = FloatWrapper?.queryValues() objCustomPersistableCollections["listOptDouble"] = DoubleWrapper?.queryValues() objCustomPersistableCollections["listOptString"] = StringWrapper?.queryValues() objCustomPersistableCollections["listOptBinary"] = DataWrapper?.queryValues() objCustomPersistableCollections["listOptDate"] = DateWrapper?.queryValues() objCustomPersistableCollections["listOptDecimal"] = Decimal128Wrapper?.queryValues() objCustomPersistableCollections["listOptObjectId"] = ObjectIdWrapper?.queryValues() objCustomPersistableCollections["listOptUuid"] = UUIDWrapper?.queryValues() objModernAllTypesObject["setBool"] = Bool.queryValues() objModernAllTypesObject["setInt"] = Int.queryValues() objModernAllTypesObject["setInt8"] = Int8.queryValues() objModernAllTypesObject["setInt16"] = Int16.queryValues() objModernAllTypesObject["setInt32"] = Int32.queryValues() objModernAllTypesObject["setInt64"] = Int64.queryValues() objModernAllTypesObject["setFloat"] = Float.queryValues() objModernAllTypesObject["setDouble"] = Double.queryValues() objModernAllTypesObject["setString"] = String.queryValues() objModernAllTypesObject["setBinary"] = Data.queryValues() objModernAllTypesObject["setDate"] = Date.queryValues() objModernAllTypesObject["setDecimal"] = Decimal128.queryValues() objModernAllTypesObject["setObjectId"] = ObjectId.queryValues() objModernAllTypesObject["setUuid"] = UUID.queryValues() objModernAllTypesObject["setAny"] = AnyRealmValue.queryValues() objModernCollectionsOfEnums["setInt"] = EnumInt.queryValues() objModernCollectionsOfEnums["setInt8"] = EnumInt8.queryValues() objModernCollectionsOfEnums["setInt16"] = EnumInt16.queryValues() objModernCollectionsOfEnums["setInt32"] = EnumInt32.queryValues() objModernCollectionsOfEnums["setInt64"] = EnumInt64.queryValues() objModernCollectionsOfEnums["setFloat"] = EnumFloat.queryValues() objModernCollectionsOfEnums["setDouble"] = EnumDouble.queryValues() objModernCollectionsOfEnums["setString"] = EnumString.queryValues() objCustomPersistableCollections["setBool"] = BoolWrapper.queryValues() objCustomPersistableCollections["setInt"] = IntWrapper.queryValues() objCustomPersistableCollections["setInt8"] = Int8Wrapper.queryValues() objCustomPersistableCollections["setInt16"] = Int16Wrapper.queryValues() objCustomPersistableCollections["setInt32"] = Int32Wrapper.queryValues() objCustomPersistableCollections["setInt64"] = Int64Wrapper.queryValues() objCustomPersistableCollections["setFloat"] = FloatWrapper.queryValues() objCustomPersistableCollections["setDouble"] = DoubleWrapper.queryValues() objCustomPersistableCollections["setString"] = StringWrapper.queryValues() objCustomPersistableCollections["setBinary"] = DataWrapper.queryValues() objCustomPersistableCollections["setDate"] = DateWrapper.queryValues() objCustomPersistableCollections["setDecimal"] = Decimal128Wrapper.queryValues() objCustomPersistableCollections["setObjectId"] = ObjectIdWrapper.queryValues() objCustomPersistableCollections["setUuid"] = UUIDWrapper.queryValues() objModernAllTypesObject["setOptBool"] = Bool?.queryValues() objModernAllTypesObject["setOptInt"] = Int?.queryValues() objModernAllTypesObject["setOptInt8"] = Int8?.queryValues() objModernAllTypesObject["setOptInt16"] = Int16?.queryValues() objModernAllTypesObject["setOptInt32"] = Int32?.queryValues() objModernAllTypesObject["setOptInt64"] = Int64?.queryValues() objModernAllTypesObject["setOptFloat"] = Float?.queryValues() objModernAllTypesObject["setOptDouble"] = Double?.queryValues() objModernAllTypesObject["setOptString"] = String?.queryValues() objModernAllTypesObject["setOptBinary"] = Data?.queryValues() objModernAllTypesObject["setOptDate"] = Date?.queryValues() objModernAllTypesObject["setOptDecimal"] = Decimal128?.queryValues() objModernAllTypesObject["setOptObjectId"] = ObjectId?.queryValues() objModernAllTypesObject["setOptUuid"] = UUID?.queryValues() objModernCollectionsOfEnums["setIntOpt"] = EnumInt?.queryValues() objModernCollectionsOfEnums["setInt8Opt"] = EnumInt8?.queryValues() objModernCollectionsOfEnums["setInt16Opt"] = EnumInt16?.queryValues() objModernCollectionsOfEnums["setInt32Opt"] = EnumInt32?.queryValues() objModernCollectionsOfEnums["setInt64Opt"] = EnumInt64?.queryValues() objModernCollectionsOfEnums["setFloatOpt"] = EnumFloat?.queryValues() objModernCollectionsOfEnums["setDoubleOpt"] = EnumDouble?.queryValues() objModernCollectionsOfEnums["setStringOpt"] = EnumString?.queryValues() objCustomPersistableCollections["setOptBool"] = BoolWrapper?.queryValues() objCustomPersistableCollections["setOptInt"] = IntWrapper?.queryValues() objCustomPersistableCollections["setOptInt8"] = Int8Wrapper?.queryValues() objCustomPersistableCollections["setOptInt16"] = Int16Wrapper?.queryValues() objCustomPersistableCollections["setOptInt32"] = Int32Wrapper?.queryValues() objCustomPersistableCollections["setOptInt64"] = Int64Wrapper?.queryValues() objCustomPersistableCollections["setOptFloat"] = FloatWrapper?.queryValues() objCustomPersistableCollections["setOptDouble"] = DoubleWrapper?.queryValues() objCustomPersistableCollections["setOptString"] = StringWrapper?.queryValues() objCustomPersistableCollections["setOptBinary"] = DataWrapper?.queryValues() objCustomPersistableCollections["setOptDate"] = DateWrapper?.queryValues() objCustomPersistableCollections["setOptDecimal"] = Decimal128Wrapper?.queryValues() objCustomPersistableCollections["setOptObjectId"] = ObjectIdWrapper?.queryValues() objCustomPersistableCollections["setOptUuid"] = UUIDWrapper?.queryValues() populateMap(objModernAllTypesObject.mapBool) populateMap(objModernAllTypesObject.mapInt) populateMap(objModernAllTypesObject.mapInt8) populateMap(objModernAllTypesObject.mapInt16) populateMap(objModernAllTypesObject.mapInt32) populateMap(objModernAllTypesObject.mapInt64) populateMap(objModernAllTypesObject.mapFloat) populateMap(objModernAllTypesObject.mapDouble) populateMap(objModernAllTypesObject.mapString) populateMap(objModernAllTypesObject.mapBinary) populateMap(objModernAllTypesObject.mapDate) populateMap(objModernAllTypesObject.mapDecimal) populateMap(objModernAllTypesObject.mapObjectId) populateMap(objModernAllTypesObject.mapUuid) populateMap(objModernAllTypesObject.mapAny) populateMap(objModernCollectionsOfEnums.mapInt) populateMap(objModernCollectionsOfEnums.mapInt8) populateMap(objModernCollectionsOfEnums.mapInt16) populateMap(objModernCollectionsOfEnums.mapInt32) populateMap(objModernCollectionsOfEnums.mapInt64) populateMap(objModernCollectionsOfEnums.mapFloat) populateMap(objModernCollectionsOfEnums.mapDouble) populateMap(objModernCollectionsOfEnums.mapString) populateMap(objCustomPersistableCollections.mapBool) populateMap(objCustomPersistableCollections.mapInt) populateMap(objCustomPersistableCollections.mapInt8) populateMap(objCustomPersistableCollections.mapInt16) populateMap(objCustomPersistableCollections.mapInt32) populateMap(objCustomPersistableCollections.mapInt64) populateMap(objCustomPersistableCollections.mapFloat) populateMap(objCustomPersistableCollections.mapDouble) populateMap(objCustomPersistableCollections.mapString) populateMap(objCustomPersistableCollections.mapBinary) populateMap(objCustomPersistableCollections.mapDate) populateMap(objCustomPersistableCollections.mapDecimal) populateMap(objCustomPersistableCollections.mapObjectId) populateMap(objCustomPersistableCollections.mapUuid) populateMap(objModernAllTypesObject.mapOptBool) populateMap(objModernAllTypesObject.mapOptInt) populateMap(objModernAllTypesObject.mapOptInt8) populateMap(objModernAllTypesObject.mapOptInt16) populateMap(objModernAllTypesObject.mapOptInt32) populateMap(objModernAllTypesObject.mapOptInt64) populateMap(objModernAllTypesObject.mapOptFloat) populateMap(objModernAllTypesObject.mapOptDouble) populateMap(objModernAllTypesObject.mapOptString) populateMap(objModernAllTypesObject.mapOptBinary) populateMap(objModernAllTypesObject.mapOptDate) populateMap(objModernAllTypesObject.mapOptDecimal) populateMap(objModernAllTypesObject.mapOptObjectId) populateMap(objModernAllTypesObject.mapOptUuid) populateMap(objModernCollectionsOfEnums.mapIntOpt) populateMap(objModernCollectionsOfEnums.mapInt8Opt) populateMap(objModernCollectionsOfEnums.mapInt16Opt) populateMap(objModernCollectionsOfEnums.mapInt32Opt) populateMap(objModernCollectionsOfEnums.mapInt64Opt) populateMap(objModernCollectionsOfEnums.mapFloatOpt) populateMap(objModernCollectionsOfEnums.mapDoubleOpt) populateMap(objModernCollectionsOfEnums.mapStringOpt) populateMap(objCustomPersistableCollections.mapOptBool) populateMap(objCustomPersistableCollections.mapOptInt) populateMap(objCustomPersistableCollections.mapOptInt8) populateMap(objCustomPersistableCollections.mapOptInt16) populateMap(objCustomPersistableCollections.mapOptInt32) populateMap(objCustomPersistableCollections.mapOptInt64) populateMap(objCustomPersistableCollections.mapOptFloat) populateMap(objCustomPersistableCollections.mapOptDouble) populateMap(objCustomPersistableCollections.mapOptString) populateMap(objCustomPersistableCollections.mapOptBinary) populateMap(objCustomPersistableCollections.mapOptDate) populateMap(objCustomPersistableCollections.mapOptDecimal) populateMap(objCustomPersistableCollections.mapOptObjectId) populateMap(objCustomPersistableCollections.mapOptUuid) try! realm.commitWrite() } private func populateMap(_ map: Map) { let values = T.queryValues() map["foo"] = values[2] map["bar"] = values[1] map["baz"] = values[0] } // MARK: - Assertion Helpers private func assertCount(_ expectedCount: Int, line: UInt = #line, _ query: ((Query) -> Query)) { let results = realm.objects(T.self).where(query) XCTAssertEqual(results.count, expectedCount, line: line) } private func assertPredicate( _ predicate: String, _ values: [Any], _ query: ((Query) -> Query)) { let (queryStr, constructedValues) = query(Query._constructForTesting())._constructPredicate() XCTAssertEqual(queryStr, predicate) XCTAssertEqual(constructedValues.count, values.count) XCTAssertEqual(NSPredicate(format: queryStr, argumentArray: constructedValues), NSPredicate(format: predicate, argumentArray: values)) } private func assertQuery(_ predicate: String, _ value: Any, count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, [value], query) } private func assertQuery(_ type: T.Type, _ predicate: String, _ value: Any, count expectedCount: Int, line: UInt = #line, _ query: ((Query) -> Query)) { assertCount(expectedCount, line: line, query) assertPredicate(predicate, [value], query) } private func assertQuery(_ predicate: String, values: [Any] = [], count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, values, query) } private func assertQuery(_ type: T.Type, _ predicate: String, values: [Any] = [], count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, values, query) } // MARK: - Basic Comparison func validateEquals( _ name: String, _ lhs: (Query) -> Query, _ value: T, equalCount: Int = 1, notEqualCount: Int = 0) { assertQuery(Root.self, "(\(name) == %@)", value, count: equalCount) { lhs($0) == value } assertQuery(Root.self, "(\(name) != %@)", value, count: notEqualCount) { lhs($0) != value } } func validateEqualsNil( _ name: String, _ lhs: (Query) -> Query) { assertQuery(Root.self, "(\(name) == %@)", NSNull(), count: 0) { lhs($0) == nil } assertQuery(Root.self, "(\(name) != %@)", NSNull(), count: 1) { lhs($0) != nil } } func testEquals() { validateEquals("boolCol", \Query.boolCol, false) validateEquals("intCol", \Query.intCol, 3) validateEquals("int8Col", \Query.int8Col, Int8(9)) validateEquals("int16Col", \Query.int16Col, Int16(17)) validateEquals("int32Col", \Query.int32Col, Int32(33)) validateEquals("int64Col", \Query.int64Col, Int64(65)) validateEquals("floatCol", \Query.floatCol, Float(6.55444333)) validateEquals("doubleCol", \Query.doubleCol, 234.567) validateEquals("stringCol", \Query.stringCol, "Foó") validateEquals("binaryCol", \Query.binaryCol, Data(count: 128)) validateEquals("dateCol", \Query.dateCol, Date(timeIntervalSince1970: 2000000)) validateEquals("decimalCol", \Query.decimalCol, Decimal128(234.567)) validateEquals("objectIdCol", \Query.objectIdCol, ObjectId("61184062c1d8f096a3695045")) validateEquals("uuidCol", \Query.uuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) validateEquals("intEnumCol", \Query.intEnumCol, ModernIntEnum.value2) validateEquals("stringEnumCol", \Query.stringEnumCol, ModernStringEnum.value2) validateEquals("bool", \Query.bool, BoolWrapper(persistedValue: false)) validateEquals("int", \Query.int, IntWrapper(persistedValue: 3)) validateEquals("int8", \Query.int8, Int8Wrapper(persistedValue: Int8(9))) validateEquals("int16", \Query.int16, Int16Wrapper(persistedValue: Int16(17))) validateEquals("int32", \Query.int32, Int32Wrapper(persistedValue: Int32(33))) validateEquals("int64", \Query.int64, Int64Wrapper(persistedValue: Int64(65))) validateEquals("float", \Query.float, FloatWrapper(persistedValue: Float(6.55444333))) validateEquals("double", \Query.double, DoubleWrapper(persistedValue: 234.567)) validateEquals("string", \Query.string, StringWrapper(persistedValue: "Foó")) validateEquals("binary", \Query.binary, DataWrapper(persistedValue: Data(count: 128))) validateEquals("date", \Query.date, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateEquals("decimal", \Query.decimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateEquals("objectId", \Query.objectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) validateEquals("uuid", \Query.uuid, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) validateEquals("optBoolCol", \Query.optBoolCol, false) validateEquals("optIntCol", \Query.optIntCol, 3) validateEquals("optInt8Col", \Query.optInt8Col, Int8(9)) validateEquals("optInt16Col", \Query.optInt16Col, Int16(17)) validateEquals("optInt32Col", \Query.optInt32Col, Int32(33)) validateEquals("optInt64Col", \Query.optInt64Col, Int64(65)) validateEquals("optFloatCol", \Query.optFloatCol, Float(6.55444333)) validateEquals("optDoubleCol", \Query.optDoubleCol, 234.567) validateEquals("optStringCol", \Query.optStringCol, "Foó") validateEquals("optBinaryCol", \Query.optBinaryCol, Data(count: 128)) validateEquals("optDateCol", \Query.optDateCol, Date(timeIntervalSince1970: 2000000)) validateEquals("optDecimalCol", \Query.optDecimalCol, Decimal128(234.567)) validateEquals("optObjectIdCol", \Query.optObjectIdCol, ObjectId("61184062c1d8f096a3695045")) validateEquals("optUuidCol", \Query.optUuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) validateEquals("optIntEnumCol", \Query.optIntEnumCol, ModernIntEnum.value2) validateEquals("optStringEnumCol", \Query.optStringEnumCol, ModernStringEnum.value2) validateEquals("optBool", \Query.optBool, BoolWrapper(persistedValue: false)) validateEquals("optInt", \Query.optInt, IntWrapper(persistedValue: 3)) validateEquals("optInt8", \Query.optInt8, Int8Wrapper(persistedValue: Int8(9))) validateEquals("optInt16", \Query.optInt16, Int16Wrapper(persistedValue: Int16(17))) validateEquals("optInt32", \Query.optInt32, Int32Wrapper(persistedValue: Int32(33))) validateEquals("optInt64", \Query.optInt64, Int64Wrapper(persistedValue: Int64(65))) validateEquals("optFloat", \Query.optFloat, FloatWrapper(persistedValue: Float(6.55444333))) validateEquals("optDouble", \Query.optDouble, DoubleWrapper(persistedValue: 234.567)) validateEquals("optString", \Query.optString, StringWrapper(persistedValue: "Foó")) validateEquals("optBinary", \Query.optBinary, DataWrapper(persistedValue: Data(count: 128))) validateEquals("optDate", \Query.optDate, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateEquals("optDecimal", \Query.optDecimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateEquals("optObjectId", \Query.optObjectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) validateEquals("optUuid", \Query.optUuid, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) validateEqualsNil("optBoolCol", \Query.optBoolCol) validateEqualsNil("optIntCol", \Query.optIntCol) validateEqualsNil("optInt8Col", \Query.optInt8Col) validateEqualsNil("optInt16Col", \Query.optInt16Col) validateEqualsNil("optInt32Col", \Query.optInt32Col) validateEqualsNil("optInt64Col", \Query.optInt64Col) validateEqualsNil("optFloatCol", \Query.optFloatCol) validateEqualsNil("optDoubleCol", \Query.optDoubleCol) validateEqualsNil("optStringCol", \Query.optStringCol) validateEqualsNil("optBinaryCol", \Query.optBinaryCol) validateEqualsNil("optDateCol", \Query.optDateCol) validateEqualsNil("optDecimalCol", \Query.optDecimalCol) validateEqualsNil("optObjectIdCol", \Query.optObjectIdCol) validateEqualsNil("optUuidCol", \Query.optUuidCol) validateEqualsNil("optIntEnumCol", \Query.optIntEnumCol) validateEqualsNil("optStringEnumCol", \Query.optStringEnumCol) validateEqualsNil("optBool", \Query.optBool) validateEqualsNil("optInt", \Query.optInt) validateEqualsNil("optInt8", \Query.optInt8) validateEqualsNil("optInt16", \Query.optInt16) validateEqualsNil("optInt32", \Query.optInt32) validateEqualsNil("optInt64", \Query.optInt64) validateEqualsNil("optFloat", \Query.optFloat) validateEqualsNil("optDouble", \Query.optDouble) validateEqualsNil("optString", \Query.optString) validateEqualsNil("optBinary", \Query.optBinary) validateEqualsNil("optDate", \Query.optDate) validateEqualsNil("optDecimal", \Query.optDecimal) validateEqualsNil("optObjectId", \Query.optObjectId) validateEqualsNil("optUuid", \Query.optUuid) } func testImplicitBooleanOperation() { assertQuery(ModernAllTypesObject.self, "boolCol == true", count: 0, { $0.boolCol }) assertQuery(ModernAllTypesObject.self, "boolCol == false", count: 1, { !$0.boolCol }) initLinkedCollectionAggregatesObject() assertQuery(LinkToModernAllTypesObject.self, "object.boolCol == true", count: 0, { $0.object.boolCol }) assertQuery(LinkToModernAllTypesObject.self, "object.boolCol == false", count: 1, { !$0.object.boolCol }) let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() object.object = nestedObject try! realm.write { realm.add(object) } assertQuery(ModernEmbeddedParentObject.self, "object.bool == true", count: 0, { $0.object.bool }) assertQuery(ModernEmbeddedParentObject.self, "object.bool == false", count: 1, { !$0.object.bool }) assertQuery(ModernAllTypesObject.self, "((intCol == %@) && boolCol == true)", 0, count: 0, { $0.intCol == 0 && $0.boolCol }) assertQuery(ModernAllTypesObject.self, "(boolCol == true && (intCol == %@))", 0, count: 0, { $0.boolCol && $0.intCol == 0 }) assertQuery(ModernAllTypesObject.self, "((intCol == %@) || boolCol == false)", 0, count: 1, { $0.intCol == 0 || !$0.boolCol }) assertQuery(ModernAllTypesObject.self, "(boolCol == false || (intCol == %@))", 0, count: 1, { !$0.boolCol || $0.intCol == 0 }) } func testEqualAnyRealmValue() { let circleObject = self.circleObject let object = objects()[0] setAnyRealmValueCol(with: .none, object: object) assertQuery("(anyCol == %@)", AnyRealmValue.none, count: 1) { $0.anyCol == .none } setAnyRealmValueCol(with: .int(123), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.int(123), count: 1) { $0.anyCol == .int(123) } setAnyRealmValueCol(with: .bool(true), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.bool(true), count: 1) { $0.anyCol == .bool(true) } setAnyRealmValueCol(with: .float(123.456), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.float(123.456), count: 1) { $0.anyCol == .float(123.456) } setAnyRealmValueCol(with: .double(123.456), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.double(123.456), count: 1) { $0.anyCol == .double(123.456) } setAnyRealmValueCol(with: .string("FooBar"), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.string("FooBar"), count: 1) { $0.anyCol == .string("FooBar") } setAnyRealmValueCol(with: .data(Data(count: 64)), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.data(Data(count: 64)), count: 1) { $0.anyCol == .data(Data(count: 64)) } setAnyRealmValueCol(with: .date(Date(timeIntervalSince1970: 1000000)), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 1) { $0.anyCol == .date(Date(timeIntervalSince1970: 1000000)) } setAnyRealmValueCol(with: .object(circleObject), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.object(circleObject), count: 1) { $0.anyCol == .object(circleObject) } setAnyRealmValueCol(with: .objectId(ObjectId("61184062c1d8f096a3695046")), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.anyCol == .objectId(ObjectId("61184062c1d8f096a3695046")) } setAnyRealmValueCol(with: .decimal128(123.456), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.decimal128(123.456), count: 1) { $0.anyCol == .decimal128(123.456) } setAnyRealmValueCol(with: .uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 1) { $0.anyCol == .uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } } func testEqualAnyRealmList() { let circleObject = self.circleObject let object = objects()[0] let list = List() list.removeAll() list.append(.none) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.none) return $0.anyCol == .list(list) } list.removeAll() list.append(.int(123)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.int(123)) return $0.anyCol == .list(list) } list.removeAll() list.append(.bool(true)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.bool(true)) return $0.anyCol == .list(list) } list.removeAll() list.append(.float(123.456)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.float(123.456)) return $0.anyCol == .list(list) } list.removeAll() list.append(.double(123.456)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.double(123.456)) return $0.anyCol == .list(list) } list.removeAll() list.append(.string("FooBar")) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.string("FooBar")) return $0.anyCol == .list(list) } list.removeAll() list.append(.data(Data(count: 64))) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.data(Data(count: 64))) return $0.anyCol == .list(list) } list.removeAll() list.append(.date(Date(timeIntervalSince1970: 1000000))) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.date(Date(timeIntervalSince1970: 1000000))) return $0.anyCol == .list(list) } list.removeAll() list.append(.object(circleObject)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.object(circleObject)) return $0.anyCol == .list(list) } list.removeAll() list.append(.objectId(ObjectId("61184062c1d8f096a3695046"))) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.objectId(ObjectId("61184062c1d8f096a3695046"))) return $0.anyCol == .list(list) } list.removeAll() list.append(.decimal128(123.456)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.decimal128(123.456)) return $0.anyCol == .list(list) } list.removeAll() list.append(.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!)) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!)) return $0.anyCol == .list(list) } } func testEqualAnyRealmDictionary() { let circleObject = self.circleObject let object = objects()[0] let dictionary = Map() dictionary.removeAll() dictionary["key"] = AnyRealmValue.none setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.none return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.int(123) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.int(123) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.bool(true) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.bool(true) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.float(123.456) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.float(123.456) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.double(123.456) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.double(123.456) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.string("FooBar") setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.string("FooBar") return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.data(Data(count: 64)) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.data(Data(count: 64)) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.object(circleObject) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.object(circleObject) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.decimal128(123.456) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.decimal128(123.456) return $0.anyCol == .dictionary(dictionary) } dictionary.removeAll() dictionary["key"] = AnyRealmValue.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) return $0.anyCol == .dictionary(dictionary) } } func testNestedAnyRealmList() { let object = objects()[0] let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .string("john"), .bool(false) ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, .double(76.54) ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subArray3, .int(99)]) let array: Array = [ subArray4, .float(20.34) ] setAnyRealmValueCol(with: AnyRealmValue.fromArray(array), object: object) assertQuery("(anyCol[%@] == %@)", values: [1, AnyRealmValue.float(20.34)], count: 1) { $0.anyCol[1] == .float(20.34) } assertQuery("(anyCol[%K] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol.any == .float(20.34) } assertQuery("(anyCol[%@][%@] == %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] == .int(99) } assertQuery("(anyCol[%@][%@][%@] == %@)", values: [0, 0, 1, AnyRealmValue.double(76.54)], count: 1) { $0.anyCol[0][0][1] == .double(76.54) } assertQuery("(anyCol[%@][%@][%K] == %@)", values: [0, 0, "#any", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol[0][0].any == .double(76.54) } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: [0, 0, 0, 0, AnyRealmValue.string("john")], count: 1) { $0.anyCol[0][0][0][0] == .string("john") } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: [0, 0, 0, 1, AnyRealmValue.bool(false)], count: 1) { $0.anyCol[0][0][0][1] == .bool(false) } assertQuery("(anyCol[%@][%@][%@][%K] == %@)", values: [0, 0, 0, "#any", AnyRealmValue.string("john")], count: 1) { $0.anyCol[0][0][0].any == .string("john") } assertQuery("(anyCol[%@][%@][%@][%K] == %@)", values: [0, 0, 0, "#any", AnyRealmValue.bool(false)], count: 1) { $0.anyCol[0][0][0].any == .bool(false) } assertQuery("(anyCol[%@][%@] >= %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] >= .int(99) } assertQuery("(anyCol[%@][%@] <= %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] <= .int(99) } assertQuery("(anyCol[%@][%@] != %@)", values: [0, 1, AnyRealmValue.int(99)], count: 0) { $0.anyCol[0][1] != .int(99) } assertQuery("(anyCol[%@] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 0) { $0.anyCol["#any"] == .float(20.34) } } func testNestedAnyRealmDictionary() { let object = objects()[0] let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary(["key6": .string("john"), "key7": .bool(false)]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary(["key4": subDict2, "key5": .double(76.54)]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary(["key2": subDict3, "key3": .int(99)]) let dict: Dictionary = [ "key0": subDict4, "key1": .float(20.34) ] setAnyRealmValueCol(with: AnyRealmValue.fromDictionary(dict), object: object) assertQuery("(anyCol[%@] == %@)", values: ["key1", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol["key1"] == .float(20.34) } assertQuery("(anyCol[%K] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol.any == .float(20.34) } assertQuery("(anyCol[%@][%@] == %@)", values: ["key0", "key3", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"]["key3"] == .int(99) } assertQuery("(anyCol[%@][%K] == %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any == .int(99) } assertQuery("(anyCol[%@][%@][%@] == %@)", values: ["key0", "key2", "key5", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol["key0"]["key2"]["key5"] == .double(76.54) } assertQuery("(anyCol[%@][%@][%K] == %@)", values: ["key0", "key2", "#any", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol["key0"]["key2"].any == .double(76.54) } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: ["key0", "key2", "key4", "key6", AnyRealmValue.string("john")], count: 1) { $0.anyCol["key0"]["key2"]["key4"]["key6"] == .string("john") } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: ["key0", "key2", "key4", "key7", AnyRealmValue.bool(false)], count: 1) { $0.anyCol["key0"]["key2"]["key4"]["key7"] == .bool(false) } assertQuery("(anyCol[%@][%K] >= %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any >= .int(99) } assertQuery("(anyCol[%@][%K] <= %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any <= .int(99) } assertQuery("(anyCol[%@][%@] != %@)", values: ["key0", "key3", AnyRealmValue.int(99)], count: 0) { $0.anyCol["key0"]["key3"] != .int(99) } assertQuery("(anyCol[%@][%@] == %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 0) { $0.anyCol["key0"]["#any"] == .int(99) } } func testEqualObject() { let nestedObject = ModernAllTypesObject() let object = objects().first! try! realm.write { object.objectCol = nestedObject } assertQuery("(objectCol == %@)", nestedObject, count: 1) { $0.objectCol == nestedObject } } func testEqualEmbeddedObject() { let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() nestedObject.value = 123 object.object = nestedObject try! realm.write { realm.add(object) } let result1 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object == nestedObject } XCTAssertEqual(result1.count, 1) let nestedObject2 = ModernEmbeddedTreeObject1() nestedObject2.value = 123 let result2 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object == nestedObject2 } XCTAssertEqual(result2.count, 0) } private func createLinksToMappedEmbeddedObject() { try! realm.write { let obj = realm.objects(AllCustomPersistableTypes.self).first! obj.object = EmbeddedObjectWrapper(value: 2) _ = realm.create(LinkToAllCustomPersistableTypes.self, value: [obj, [obj], [obj], ["1": obj]]) } } func testEqualMappedToEmbeddedObject() { createLinksToMappedEmbeddedObject() assertQuery(AllCustomPersistableTypes.self, "(object.value == %@)", 2, count: 1) { $0.object.persistableValue.value == 2 } assertQuery(AllCustomPersistableTypes.self, "(object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.object == EmbeddedObjectWrapper(value: 2) } assertQuery(AllCustomPersistableTypes.self, "(object.value == %@)", 3, count: 0) { $0.object.persistableValue.value == 3 } assertQuery(AllCustomPersistableTypes.self, "(object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.object == EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object.value == %@)", 2, count: 1) { $0.object.object.persistableValue.value == 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.object.object == EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object.value == %@)", 3, count: 0) { $0.object.object.persistableValue.value == 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.object.object == EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object.value == %@)", 2, count: 1) { $0.list.object.persistableValue.value == 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.list.object == EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object.value == %@)", 3, count: 0) { $0.list.object.persistableValue.value == 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.list.object == EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object.value == %@)", 2, count: 1) { $0.set.object.persistableValue.value == 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.set.object == EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object.value == %@)", 3, count: 0) { $0.set.object.persistableValue.value == 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.set.object == EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object.value == %@)", 2, count: 1) { $0.map.values.object.persistableValue.value == 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.map.values.object == EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object.value == %@)", 3, count: 0) { $0.map.values.object.persistableValue.value == 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.map.values.object == EmbeddedObjectWrapper(value: 3) } } func testMemberwiseEquality() { realm.beginWrite() let obj1 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["a", "b"])) let obj2 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["a", "c"])) let obj3 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["b", "b"])) let linkObj1 = realm.create(LinkToAddressSwiftWrapper.self, value: [obj1, obj1]) let linkObj2 = realm.create(LinkToAddressSwiftWrapper.self, value: [obj2, obj2]) _ = realm.create(LinkToAddressSwiftWrapper.self, value: [obj3, obj3]) // Test basic equality assertQuery(LinkToAddressSwiftWrapper.self, "(object == %@)", obj1, count: 1) { $0.object == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(object != %@)", obj1, count: 2) { $0.object != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(optObject == %@)", obj1, count: 1) { $0.optObject == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(optObject != %@)", obj1, count: 2) { $0.optObject != obj1 } // Verify that the expanded comparison nested groups correctly. If it doesn't // start/end a subgroup, it'd end up as ((x or y) and z) instead of (x or (y and z)). assertQuery(LinkToAddressSwiftWrapper.self, "((object.city != %@) || (object == %@))", values: ["c", obj1], count: 3) { $0.object.persistableValue.city != "c" || $0.object == obj1 } // Check for ((x and y) or Z) rather than (x and (y or z)) assertQuery(LinkToAddressSwiftWrapper.self, "((object == %@) || (object.city != %@))", values: [obj1, "c"], count: 3) { $0.object == obj1 || $0.object.persistableValue.city != "c" } // Basic equality in collections linkObj1.list.append(obj1) linkObj1.map["foo"] = obj1 linkObj1.optMap["foo"] = obj1 assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list == %@)", obj1, count: 1) { $0.list == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list != %@)", obj1, count: 0) { $0.list != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY map.@allValues == %@)", obj1, count: 1) { $0.map.values == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY map.@allValues != %@)", obj1, count: 0) { $0.map.values != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY optMap.@allValues != %@)", obj1, count: 0) { $0.optMap.values != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY optMap.@allValues == %@)", obj1, count: 1) { $0.optMap.values == obj1 } // Verify that collections use a subquery. If they didn't, this object would // now match as it has objects which match each property separately linkObj2.list.append(obj2) linkObj2.list.append(obj3) assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list == %@)", obj1, count: 1) { $0.list == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list != %@)", obj1, count: 1) { $0.list != obj1 } realm.cancelWrite() } func testInvalidMemberwiseEquality() { assertThrows(assertQuery(LinkToWrapperForTypeWithObjectLink.self, "", count: 0) { $0.link == WrapperForTypeWithObjectLink() }, reason: "Unsupported property 'TypeWithObjectLink.value' for memberwise equality query: object links are not implemented.") assertThrows(assertQuery(LinkToWrapperForTypeWithCollection.self, "", count: 0) { $0.link == WrapperForTypeWithCollection() }, reason: "Unsupported property 'TypeWithCollection.list' for memberwise equality query: equality on collections is not implemented.") } func testNotEqualAnyRealmValue() { let circleObject = self.circleObject let object = objects()[0] setAnyRealmValueCol(with: .none, object: object) assertQuery("(anyCol != %@)", AnyRealmValue.none, count: 0) { $0.anyCol != .none } setAnyRealmValueCol(with: .int(123), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.int(123), count: 0) { $0.anyCol != .int(123) } setAnyRealmValueCol(with: .bool(true), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.bool(true), count: 0) { $0.anyCol != .bool(true) } setAnyRealmValueCol(with: .float(123.456), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.float(123.456), count: 0) { $0.anyCol != .float(123.456) } setAnyRealmValueCol(with: .double(123.456), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.double(123.456), count: 0) { $0.anyCol != .double(123.456) } setAnyRealmValueCol(with: .string("FooBar"), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.string("FooBar"), count: 0) { $0.anyCol != .string("FooBar") } setAnyRealmValueCol(with: .data(Data(count: 64)), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.data(Data(count: 64)), count: 0) { $0.anyCol != .data(Data(count: 64)) } setAnyRealmValueCol(with: .date(Date(timeIntervalSince1970: 1000000)), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 0) { $0.anyCol != .date(Date(timeIntervalSince1970: 1000000)) } setAnyRealmValueCol(with: .object(circleObject), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.object(circleObject), count: 0) { $0.anyCol != .object(circleObject) } setAnyRealmValueCol(with: .objectId(ObjectId("61184062c1d8f096a3695046")), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), count: 0) { $0.anyCol != .objectId(ObjectId("61184062c1d8f096a3695046")) } setAnyRealmValueCol(with: .decimal128(123.456), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.decimal128(123.456), count: 0) { $0.anyCol != .decimal128(123.456) } setAnyRealmValueCol(with: .uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), object: object) assertQuery("(anyCol != %@)", AnyRealmValue.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 0) { $0.anyCol != .uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } } func testNotEqualObject() { let nestedObject = ModernAllTypesObject() let object = objects().first! try! realm.write { object.objectCol = nestedObject } // Count will be one because nestedObject.objectCol will be nil assertQuery("(objectCol != %@)", nestedObject, count: 1) { $0.objectCol != nestedObject } } func testNotEqualEmbeddedObject() { let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() nestedObject.value = 123 object.object = nestedObject try! realm.write { realm.add(object) } let result1 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object != nestedObject } XCTAssertEqual(result1.count, 0) let nestedObject2 = ModernEmbeddedTreeObject1() nestedObject2.value = 123 let result2 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object != nestedObject2 } XCTAssertEqual(result2.count, 1) } func testNotEqualMappedToEmbeddedObject() { createLinksToMappedEmbeddedObject() assertQuery(AllCustomPersistableTypes.self, "(object.value != %@)", 2, count: 0) { $0.object.persistableValue.value != 2 } assertQuery(AllCustomPersistableTypes.self, "(object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.object != EmbeddedObjectWrapper(value: 2) } assertQuery(AllCustomPersistableTypes.self, "(object.value != %@)", 3, count: 1) { $0.object.persistableValue.value != 3 } assertQuery(AllCustomPersistableTypes.self, "(object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.object != EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object.value != %@)", 2, count: 0) { $0.object.object.persistableValue.value != 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.object.object != EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object.value != %@)", 3, count: 1) { $0.object.object.persistableValue.value != 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(object.object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.object.object != EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object.value != %@)", 2, count: 0) { $0.list.object.persistableValue.value != 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.list.object != EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object.value != %@)", 3, count: 1) { $0.list.object.persistableValue.value != 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY list.object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.list.object != EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object.value != %@)", 2, count: 0) { $0.set.object.persistableValue.value != 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.set.object != EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object.value != %@)", 3, count: 1) { $0.set.object.persistableValue.value != 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY set.object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.set.object != EmbeddedObjectWrapper(value: 3) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object.value != %@)", 2, count: 0) { $0.map.values.object.persistableValue.value != 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.map.values.object != EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object.value != %@)", 3, count: 1) { $0.map.values.object.persistableValue.value != 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(ANY map.@allValues.object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.map.values.object != EmbeddedObjectWrapper(value: 3) } } func validateNumericComparisons( _ name: String, _ lhs: (Query) -> Query, _ value: T, count: Int = 1, ltCount: Int = 0, gtCount: Int = 0) where T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(\(name) > %@)", value, count: gtCount) { lhs($0) > value } assertQuery(Root.self, "(\(name) >= %@)", value, count: count) { lhs($0) >= value } assertQuery(Root.self, "(\(name) < %@)", value, count: ltCount) { lhs($0) < value } assertQuery(Root.self, "(\(name) <= %@)", value, count: count) { lhs($0) <= value } } func testNumericComparisons() { validateNumericComparisons("intCol", \Query.intCol, 3) validateNumericComparisons("int8Col", \Query.int8Col, Int8(9)) validateNumericComparisons("int16Col", \Query.int16Col, Int16(17)) validateNumericComparisons("int32Col", \Query.int32Col, Int32(33)) validateNumericComparisons("int64Col", \Query.int64Col, Int64(65)) validateNumericComparisons("floatCol", \Query.floatCol, Float(6.55444333)) validateNumericComparisons("doubleCol", \Query.doubleCol, 234.567) validateNumericComparisons("dateCol", \Query.dateCol, Date(timeIntervalSince1970: 2000000)) validateNumericComparisons("decimalCol", \Query.decimalCol, Decimal128(234.567)) validateNumericComparisons("intEnumCol", \Query.intEnumCol, .value2) validateNumericComparisons("int", \Query.int, IntWrapper(persistedValue: 3)) validateNumericComparisons("int8", \Query.int8, Int8Wrapper(persistedValue: Int8(9))) validateNumericComparisons("int16", \Query.int16, Int16Wrapper(persistedValue: Int16(17))) validateNumericComparisons("int32", \Query.int32, Int32Wrapper(persistedValue: Int32(33))) validateNumericComparisons("int64", \Query.int64, Int64Wrapper(persistedValue: Int64(65))) validateNumericComparisons("float", \Query.float, FloatWrapper(persistedValue: Float(6.55444333))) validateNumericComparisons("double", \Query.double, DoubleWrapper(persistedValue: 234.567)) validateNumericComparisons("date", \Query.date, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateNumericComparisons("decimal", \Query.decimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateNumericComparisons("optIntCol", \Query.optIntCol, 3) validateNumericComparisons("optInt8Col", \Query.optInt8Col, Int8(9)) validateNumericComparisons("optInt16Col", \Query.optInt16Col, Int16(17)) validateNumericComparisons("optInt32Col", \Query.optInt32Col, Int32(33)) validateNumericComparisons("optInt64Col", \Query.optInt64Col, Int64(65)) validateNumericComparisons("optFloatCol", \Query.optFloatCol, Float(6.55444333)) validateNumericComparisons("optDoubleCol", \Query.optDoubleCol, 234.567) validateNumericComparisons("optDateCol", \Query.optDateCol, Date(timeIntervalSince1970: 2000000)) validateNumericComparisons("optDecimalCol", \Query.optDecimalCol, Decimal128(234.567)) validateNumericComparisons("optIntEnumCol", \Query.optIntEnumCol, .value2) validateNumericComparisons("optInt", \Query.optInt, IntWrapper(persistedValue: 3)) validateNumericComparisons("optInt8", \Query.optInt8, Int8Wrapper(persistedValue: Int8(9))) validateNumericComparisons("optInt16", \Query.optInt16, Int16Wrapper(persistedValue: Int16(17))) validateNumericComparisons("optInt32", \Query.optInt32, Int32Wrapper(persistedValue: Int32(33))) validateNumericComparisons("optInt64", \Query.optInt64, Int64Wrapper(persistedValue: Int64(65))) validateNumericComparisons("optFloat", \Query.optFloat, FloatWrapper(persistedValue: Float(6.55444333))) validateNumericComparisons("optDouble", \Query.optDouble, DoubleWrapper(persistedValue: 234.567)) validateNumericComparisons("optDate", \Query.optDate, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateNumericComparisons("optDecimal", \Query.optDecimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateNumericComparisons("optIntCol", \Query.optIntCol, nil, count: 0) validateNumericComparisons("optInt8Col", \Query.optInt8Col, nil, count: 0) validateNumericComparisons("optInt16Col", \Query.optInt16Col, nil, count: 0) validateNumericComparisons("optInt32Col", \Query.optInt32Col, nil, count: 0) validateNumericComparisons("optInt64Col", \Query.optInt64Col, nil, count: 0) validateNumericComparisons("optFloatCol", \Query.optFloatCol, nil, count: 0) validateNumericComparisons("optDoubleCol", \Query.optDoubleCol, nil, count: 0) validateNumericComparisons("optDateCol", \Query.optDateCol, nil, count: 0) validateNumericComparisons("optDecimalCol", \Query.optDecimalCol, nil, count: 0) validateNumericComparisons("optIntEnumCol", \Query.optIntEnumCol, nil, count: 0) validateNumericComparisons("optInt", \Query.optInt, nil, count: 0) validateNumericComparisons("optInt8", \Query.optInt8, nil, count: 0) validateNumericComparisons("optInt16", \Query.optInt16, nil, count: 0) validateNumericComparisons("optInt32", \Query.optInt32, nil, count: 0) validateNumericComparisons("optInt64", \Query.optInt64, nil, count: 0) validateNumericComparisons("optFloat", \Query.optFloat, nil, count: 0) validateNumericComparisons("optDouble", \Query.optDouble, nil, count: 0) validateNumericComparisons("optDate", \Query.optDate, nil, count: 0) validateNumericComparisons("optDecimal", \Query.optDecimal, nil, count: 0) } func validateStringComparisons( _ name: String, _ lhs: (Query) -> Query, _ value: T, letCount: Int = 1, getCount: Int = 1, ltCount: Int = 0, gtCount: Int = 0) where T.PersistedType: _QueryString { assertQuery(Root.self, "(\(name) > %@)", value, count: gtCount) { lhs($0) > value } assertQuery(Root.self, "(\(name) >= %@)", value, count: getCount) { lhs($0) >= value } assertQuery(Root.self, "(\(name) < %@)", value, count: ltCount) { lhs($0) < value } assertQuery(Root.self, "(\(name) <= %@)", value, count: letCount) { lhs($0) <= value } } func testStringComparisons() { validateStringComparisons("stringCol", \Query.stringCol, "Foó") validateStringComparisons("stringEnumCol", \Query.stringEnumCol, .value2) validateStringComparisons("string", \Query.string, StringWrapper(persistedValue: "Foó")) validateStringComparisons("optStringCol", \Query.optStringCol, "Foó") validateStringComparisons("optStringEnumCol", \Query.optStringEnumCol, .value2) validateStringComparisons("optString", \Query.optString, StringWrapper(persistedValue: "Foó")) validateStringComparisons("optStringCol", \Query.optStringCol, nil, letCount: 0, gtCount: 1) validateStringComparisons("optStringEnumCol", \Query.optStringEnumCol, nil, letCount: 0, gtCount: 1) validateStringComparisons("optString", \Query.optString, nil, letCount: 0, gtCount: 1) } func testGreaterThanNumericAnyRealmValue() { let object = objects()[0] setAnyRealmValueCol(with: .int(123), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.int(123), count: 0) { $0.anyCol > .int(123) } assertQuery("(anyCol >= %@)", AnyRealmValue.int(123), count: 1) { $0.anyCol >= .int(123) } setAnyRealmValueCol(with: .float(123.456), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.float(123.456), count: 0) { $0.anyCol > .float(123.456) } assertQuery("(anyCol >= %@)", AnyRealmValue.float(123.456), count: 1) { $0.anyCol >= .float(123.456) } setAnyRealmValueCol(with: .double(123.456), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.double(123.456), count: 0) { $0.anyCol > .double(123.456) } assertQuery("(anyCol >= %@)", AnyRealmValue.double(123.456), count: 1) { $0.anyCol >= .double(123.456) } setAnyRealmValueCol(with: .date(Date(timeIntervalSince1970: 1000000)), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 0) { $0.anyCol > .date(Date(timeIntervalSince1970: 1000000)) } assertQuery("(anyCol >= %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 1) { $0.anyCol >= .date(Date(timeIntervalSince1970: 1000000)) } setAnyRealmValueCol(with: .decimal128(123.456), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.decimal128(123.456), count: 0) { $0.anyCol > .decimal128(123.456) } assertQuery("(anyCol >= %@)", AnyRealmValue.decimal128(123.456), count: 1) { $0.anyCol >= .decimal128(123.456) } } func testGreaterThanStringAnyRealmValue() { let object = objects()[0] setAnyRealmValueCol(with: .string("FooBar"), object: object) assertQuery("(anyCol > %@)", AnyRealmValue.string("FooBar"), count: 0) { $0.anyCol > .string("FooBar") } assertQuery("(anyCol >= %@)", AnyRealmValue.string("FooBar"), count: 1) { $0.anyCol >= .string("FooBar") } } func testLessThanNumericAnyRealmValue() { let object = objects()[0] setAnyRealmValueCol(with: .int(123), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.int(123), count: 0) { $0.anyCol < .int(123) } assertQuery("(anyCol <= %@)", AnyRealmValue.int(123), count: 1) { $0.anyCol <= .int(123) } setAnyRealmValueCol(with: .float(123.456), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.float(123.456), count: 0) { $0.anyCol < .float(123.456) } assertQuery("(anyCol <= %@)", AnyRealmValue.float(123.456), count: 1) { $0.anyCol <= .float(123.456) } setAnyRealmValueCol(with: .double(123.456), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.double(123.456), count: 0) { $0.anyCol < .double(123.456) } assertQuery("(anyCol <= %@)", AnyRealmValue.double(123.456), count: 1) { $0.anyCol <= .double(123.456) } setAnyRealmValueCol(with: .date(Date(timeIntervalSince1970: 1000000)), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 0) { $0.anyCol < .date(Date(timeIntervalSince1970: 1000000)) } assertQuery("(anyCol <= %@)", AnyRealmValue.date(Date(timeIntervalSince1970: 1000000)), count: 1) { $0.anyCol <= .date(Date(timeIntervalSince1970: 1000000)) } setAnyRealmValueCol(with: .decimal128(123.456), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.decimal128(123.456), count: 0) { $0.anyCol < .decimal128(123.456) } assertQuery("(anyCol <= %@)", AnyRealmValue.decimal128(123.456), count: 1) { $0.anyCol <= .decimal128(123.456) } } func testLessThanStringAnyRealmValue() { let object = objects()[0] setAnyRealmValueCol(with: .string("FooBar"), object: object) assertQuery("(anyCol < %@)", AnyRealmValue.string("FooBar"), count: 0) { $0.anyCol < .string("FooBar") } assertQuery("(anyCol <= %@)", AnyRealmValue.string("FooBar"), count: 1) { $0.anyCol <= .string("FooBar") } } private func validateNumericContains( _ name: String, _ lhs: (Query) -> Query) { let values = T.queryValues() assertQuery(Root.self, "((\(name) >= %@) && (\(name) < %@))", values: [values[0], values[2]], count: 1) { lhs($0).contains(values[0]..= %@) && (\(name) < %@))", values: [values[0], values[1]], count: 0) { lhs($0).contains(values[0]..( _ name: String, _ lhs: (Query) -> Query) where T.Wrapped: Comparable & QueryValue { let values = T.Wrapped.queryValues() assertQuery(Root.self, "((\(name) >= %@) && (\(name) < %@))", values: [values[0], values[2]], count: 1) { lhs($0).contains(values[0]..= %@) && (\(name) < %@))", values: [values[0], values[1]], count: 0) { lhs($0).contains(values[0]...intCol) validateNumericContains("int8Col", \Query.int8Col) validateNumericContains("int16Col", \Query.int16Col) validateNumericContains("int32Col", \Query.int32Col) validateNumericContains("int64Col", \Query.int64Col) validateNumericContains("floatCol", \Query.floatCol) validateNumericContains("doubleCol", \Query.doubleCol) validateNumericContains("dateCol", \Query.dateCol) validateNumericContains("decimalCol", \Query.decimalCol) validateNumericContains("intEnumCol", \Query.intEnumCol.rawValue) validateNumericContains("int", \Query.int.persistableValue) validateNumericContains("int8", \Query.int8.persistableValue) validateNumericContains("int16", \Query.int16.persistableValue) validateNumericContains("int32", \Query.int32.persistableValue) validateNumericContains("int64", \Query.int64.persistableValue) validateNumericContains("float", \Query.float.persistableValue) validateNumericContains("double", \Query.double.persistableValue) validateNumericContains("date", \Query.date.persistableValue) validateNumericContains("decimal", \Query.decimal.persistableValue) validateNumericContains("optIntCol", \Query.optIntCol) validateNumericContains("optInt8Col", \Query.optInt8Col) validateNumericContains("optInt16Col", \Query.optInt16Col) validateNumericContains("optInt32Col", \Query.optInt32Col) validateNumericContains("optInt64Col", \Query.optInt64Col) validateNumericContains("optFloatCol", \Query.optFloatCol) validateNumericContains("optDoubleCol", \Query.optDoubleCol) validateNumericContains("optDateCol", \Query.optDateCol) validateNumericContains("optDecimalCol", \Query.optDecimalCol) validateNumericContains("optIntEnumCol", \Query.optIntEnumCol.rawValue) validateNumericContains("optInt", \Query.optInt.persistableValue) validateNumericContains("optInt8", \Query.optInt8.persistableValue) validateNumericContains("optInt16", \Query.optInt16.persistableValue) validateNumericContains("optInt32", \Query.optInt32.persistableValue) validateNumericContains("optInt64", \Query.optInt64.persistableValue) validateNumericContains("optFloat", \Query.optFloat.persistableValue) validateNumericContains("optDouble", \Query.optDouble.persistableValue) validateNumericContains("optDate", \Query.optDate.persistableValue) validateNumericContains("optDecimal", \Query.optDecimal.persistableValue) } // MARK: - Strings let stringModifiers: [(String, StringOptions)] = [ ("", []), ("[c]", [.caseInsensitive]), ("[d]", [.diacriticInsensitive]), ("[cd]", [.caseInsensitive, .diacriticInsensitive]), ] private func validateStringOperations( _ name: String, _ lhs: (Query) -> Query, _ values: (T, T, T), count: (Bool, StringOptions) -> Int) where T.PersistedType: _QueryString { let (full, prefix, suffix) = values for (modifier, options) in stringModifiers { let matchingCount = count(true, options) let notMatchingCount = count(false, options) assertQuery(Root.self, "(\(name) ==\(modifier) %@)", full, count: matchingCount) { lhs($0).equals(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) ==\(modifier) %@)", full, count: 1 - matchingCount) { !lhs($0).equals(full, options: [options]) } assertQuery(Root.self, "(\(name) !=\(modifier) %@)", full, count: notMatchingCount) { lhs($0).notEquals(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) !=\(modifier) %@)", full, count: 1 - notMatchingCount) { !lhs($0).notEquals(full, options: [options]) } assertQuery(Root.self, "(\(name) CONTAINS\(modifier) %@)", full, count: matchingCount) { lhs($0).contains(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) CONTAINS\(modifier) %@)", full, count: 1 - matchingCount) { !lhs($0).contains(full, options: [options]) } assertQuery(Root.self, "(\(name) BEGINSWITH\(modifier) %@)", prefix, count: matchingCount) { lhs($0).starts(with: prefix, options: [options]) } assertQuery(Root.self, "(NOT \(name) BEGINSWITH\(modifier) %@)", prefix, count: 1 - matchingCount) { !lhs($0).starts(with: prefix, options: [options]) } assertQuery(Root.self, "(\(name) ENDSWITH\(modifier) %@)", suffix, count: matchingCount) { lhs($0).ends(with: suffix, options: [options]) } assertQuery(Root.self, "(NOT \(name) ENDSWITH\(modifier) %@)", suffix, count: 1 - matchingCount) { !lhs($0).ends(with: suffix, options: [options]) } } } func testStringOperations() { validateStringOperations("stringCol", \Query.stringCol, ("Foó", "Fo", "oó"), count: { (equals, _) in equals ? 1 : 0 }) validateStringOperations("stringEnumCol", \Query.stringEnumCol.rawValue, ("Foó", "Fo", "oó"), count: { (equals, _) in equals ? 0 : 1 }) validateStringOperations("string", \Query.string, (StringWrapper(persistedValue: "Foó"), StringWrapper(persistedValue: "Fo"), StringWrapper(persistedValue: "oó")), count: { (equals, _) in equals ? 1 : 0 }) validateStringOperations("optStringCol", \Query.optStringCol, (String?.some("Foó"), String?.some("Fo"), String?.some("oó")), count: { (equals, _) in equals ? 1 : 0 }) validateStringOperations("optStringEnumCol", \Query.optStringEnumCol.rawValue, (String?.some("Foó"), String?.some("Fo"), String?.some("oó")), count: { (equals, _) in equals ? 0 : 1 }) validateStringOperations("optString", \Query.optString, (StringWrapper(persistedValue: "Foó"), StringWrapper(persistedValue: "Fo"), StringWrapper(persistedValue: "oó")), count: { (equals, _) in equals ? 1 : 0 }) } private func validateStringLike( _ name: String, _ lhs: (Query) -> Query, _ strings: [(T, Int, Int)], canMatch: Bool) where T.PersistedType: _QueryString { for (str, sensitiveCount, insensitiveCount) in strings { assertQuery(Root.self, "(\(name) LIKE %@)", str, count: canMatch ? sensitiveCount : 0) { lhs($0).like(str) } assertQuery(Root.self, "(\(name) LIKE[c] %@)", str, count: canMatch ? insensitiveCount : 0) { lhs($0).like(str, caseInsensitive: true) } } } func testStringLike() { let likeStrings: [(String, Int, Int)] = [ ("Foó", 1, 1), ("f*", 0, 1), ("*ó", 1, 1), ("f?ó", 0, 1), ("f*ó", 0, 1), ("f??ó", 0, 0), ("*o*", 1, 1), ("*O*", 0, 1), ("?o?", 1, 1), ("?O?", 0, 1) ] validateStringLike("stringCol", \Query.stringCol, likeStrings.map { ($0.0, $0.1, $0.2) }, canMatch: true) validateStringLike("stringEnumCol", \Query.stringEnumCol.rawValue, likeStrings.map { ($0.0, $0.1, $0.2) }, canMatch: false) validateStringLike("string", \Query.string, likeStrings.map { (StringWrapper(persistedValue: $0.0), $0.1, $0.2) }, canMatch: true) validateStringLike("optStringCol", \Query.optStringCol, likeStrings.map { (String?.some($0.0), $0.1, $0.2) }, canMatch: true) validateStringLike("optStringEnumCol", \Query.optStringEnumCol.rawValue, likeStrings.map { (String?.some($0.0), $0.1, $0.2) }, canMatch: false) validateStringLike("optString", \Query.optString, likeStrings.map { (StringWrapper(persistedValue: $0.0), $0.1, $0.2) }, canMatch: true) } private func validateStringLexicographicalComparison( _ name: String, _ lhs: (Query) -> Query, _ strings: [(T, (Int, Int, Int, Int))]) where T.PersistedType: _QueryString { for (str, (gtCount, getCount, ltCount, letCount)) in strings { assertQuery(Root.self, "(\(name) > %@)", str, count: gtCount) { lhs($0) > str } assertQuery(Root.self, "(\(name) >= %@)", str, count: getCount) { lhs($0) >= str } assertQuery(Root.self, "(\(name) < %@)", str, count: ltCount) { lhs($0) < str } assertQuery(Root.self, "(\(name) <= %@)", str, count: letCount) { lhs($0) <= str } } } func testLexicographicalComparison() throws { let obj = objects().first! try realm.write { obj.stringEnumCol = .value4 obj.optStringEnumCol = .value4 } let likeStrings: [(String, (Int, Int, Int, Int))] = [ ("Foó", (0, 1, 0, 1)), ("Foo", (1, 1, 0, 0)), ("f*", (0, 0, 1, 1)), ("*ó", (1, 1, 0, 0)), ("f?ó", (0, 0, 1, 1)), ("f*ó", (0, 0, 1, 1)), ("f??ó", (0, 0, 1, 1)), ("*o*", (1, 1, 0, 0)), ("*O*", (1, 1, 0, 0)), ("?o?", (1, 1, 0, 0)), ("?O?", (1, 1, 0, 0)), ("Foô", (0, 0, 1, 1)), ("Fpó", (0, 0, 1, 1)), ("Goó", (0, 0, 1, 1)), ("Foò", (1, 1, 0, 0)), ("Fnó", (1, 1, 0, 0)), ("Eoó", (1, 1, 0, 0)), ] validateStringLexicographicalComparison("stringCol", \Query.stringCol, likeStrings) validateStringLexicographicalComparison("stringEnumCol", \Query.stringEnumCol.rawValue, likeStrings) validateStringLexicographicalComparison("string", \Query.string, likeStrings.map { (StringWrapper(persistedValue: $0.0), $0.1) }) validateStringLexicographicalComparison("optStringCol", \Query.optStringCol, likeStrings.map { (String?.some($0.0), $0.1) }) validateStringLexicographicalComparison("optStringEnumCol", \Query.optStringEnumCol.rawValue, likeStrings.map { (String?.some($0.0), $0.1) }) validateStringLexicographicalComparison("optString", \Query.optString, likeStrings.map { (StringWrapper(persistedValue: $0.0), $0.1) }) } // MARK: - Data func validateData(_ name: String, _ lhs: (Query) -> Query, zeroData: T, oneData: T) where T.PersistedType: _QueryBinary { assertQuery(Root.self, "(\(name) BEGINSWITH %@)", zeroData, count: 1) { lhs($0).starts(with: zeroData) } assertQuery(Root.self, "(NOT \(name) BEGINSWITH %@)", zeroData, count: 0) { !lhs($0).starts(with: zeroData) } assertQuery(Root.self, "(\(name) ENDSWITH %@)", zeroData, count: 1) { lhs($0).ends(with: zeroData) } assertQuery(Root.self, "(NOT \(name) ENDSWITH %@)", zeroData, count: 0) { !lhs($0).ends(with: zeroData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", zeroData, count: 1) { lhs($0).contains(zeroData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", zeroData, count: 0) { !lhs($0).contains(zeroData) } assertQuery(Root.self, "(\(name) == %@)", zeroData, count: 0) { lhs($0).equals(zeroData) } assertQuery(Root.self, "(NOT \(name) == %@)", zeroData, count: 1) { !lhs($0).equals(zeroData) } assertQuery(Root.self, "(\(name) != %@)", zeroData, count: 1) { lhs($0).notEquals(zeroData) } assertQuery(Root.self, "(NOT \(name) != %@)", zeroData, count: 0) { !lhs($0).notEquals(zeroData) } assertQuery(Root.self, "(\(name) BEGINSWITH %@)", oneData, count: 0) { lhs($0).starts(with: oneData) } assertQuery(Root.self, "(\(name) ENDSWITH %@)", oneData, count: 0) { lhs($0).ends(with: oneData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", oneData, count: 0) { lhs($0).contains(oneData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", oneData, count: 1) { !lhs($0).contains(oneData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", oneData, count: 0) { lhs($0).contains(oneData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", oneData, count: 1) { !lhs($0).contains(oneData) } assertQuery(Root.self, "(\(name) == %@)", oneData, count: 0) { lhs($0).equals(oneData) } assertQuery(Root.self, "(NOT \(name) == %@)", oneData, count: 1) { !lhs($0).equals(oneData) } assertQuery(Root.self, "(\(name) != %@)", oneData, count: 1) { lhs($0).notEquals(oneData) } assertQuery(Root.self, "(NOT \(name) != %@)", oneData, count: 0) { !lhs($0).notEquals(oneData) } } func testBinarySearchQueries() { validateData("binaryCol", \Query.binaryCol, zeroData: Data(count: 28), oneData: Data(repeating: 1, count: 28)) validateData("binary", \Query.binary, zeroData: DataWrapper(persistedValue: Data(count: 28)), oneData: DataWrapper(persistedValue: Data(repeating: 1, count: 28))) validateData("optBinaryCol", \Query.optBinaryCol, zeroData: Data?.some(Data(count: 28)), oneData: Data?.some(Data(repeating: 1, count: 28))) validateData("optBinary", \Query.optBinary, zeroData: DataWrapper(persistedValue: Data(count: 28)), oneData: DataWrapper(persistedValue: Data(repeating: 1, count: 28))) } // MARK: - Array/Set private func validateCollectionContains(_ name: String, _ lhs: (Query) -> Query) where T.Element: QueryValue { let values = T.Element.queryValues() assertQuery(Root.self, "(%@ IN \(name))", values[0], count: 1) { lhs($0).contains(values[0]) } assertQuery(Root.self, "(%@ IN \(name))", values[2], count: 0) { lhs($0).contains(values[2]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[0], count: 0) { !lhs($0).contains(values[0]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[2], count: 1) { !lhs($0).contains(values[2]) } } private func validateCollectionContainsNil(_ name: String, _ lhs: (Query) -> Query) where T.Element: QueryValue & ExpressibleByNilLiteral { assertQuery(Root.self, "(%@ IN \(name))", NSNull(), count: 0) { lhs($0).contains(nil) } assertQuery(Root.self, "(NOT %@ IN \(name))", NSNull(), count: 1) { !lhs($0).contains(nil) } } private func validateCollectionContains(_ name: String, _ lhs: (Query) -> Query) where T.Value: QueryValue { let values = T.Value.queryValues() assertQuery(Root.self, "(%@ IN \(name))", values[0], count: 1) { lhs($0).contains(values[0]) } assertQuery(Root.self, "(%@ IN \(name))", values[2], count: 0) { lhs($0).contains(values[2]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[0], count: 0) { !lhs($0).contains(values[0]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[2], count: 1) { !lhs($0).contains(values[2]) } } private func validateCollectionContainsNil(_ name: String, _ lhs: (Query) -> Query) where T.Value: QueryValue & ExpressibleByNilLiteral { assertQuery(Root.self, "(%@ IN \(name))", NSNull(), count: 0) { lhs($0).contains(nil) } assertQuery(Root.self, "(NOT %@ IN \(name))", NSNull(), count: 1) { !lhs($0).contains(nil) } } func testCollectionContainsElement() { validateCollectionContains("arrayBool", \Query.arrayBool) validateCollectionContains("arrayInt", \Query.arrayInt) validateCollectionContains("arrayInt8", \Query.arrayInt8) validateCollectionContains("arrayInt16", \Query.arrayInt16) validateCollectionContains("arrayInt32", \Query.arrayInt32) validateCollectionContains("arrayInt64", \Query.arrayInt64) validateCollectionContains("arrayFloat", \Query.arrayFloat) validateCollectionContains("arrayDouble", \Query.arrayDouble) validateCollectionContains("arrayString", \Query.arrayString) validateCollectionContains("arrayBinary", \Query.arrayBinary) validateCollectionContains("arrayDate", \Query.arrayDate) validateCollectionContains("arrayDecimal", \Query.arrayDecimal) validateCollectionContains("arrayObjectId", \Query.arrayObjectId) validateCollectionContains("arrayUuid", \Query.arrayUuid) validateCollectionContains("arrayAny", \Query.arrayAny) validateCollectionContains("listInt", \Query.listInt) validateCollectionContains("listInt8", \Query.listInt8) validateCollectionContains("listInt16", \Query.listInt16) validateCollectionContains("listInt32", \Query.listInt32) validateCollectionContains("listInt64", \Query.listInt64) validateCollectionContains("listFloat", \Query.listFloat) validateCollectionContains("listDouble", \Query.listDouble) validateCollectionContains("listString", \Query.listString) validateCollectionContains("listBool", \Query.listBool) validateCollectionContains("listInt", \Query.listInt) validateCollectionContains("listInt8", \Query.listInt8) validateCollectionContains("listInt16", \Query.listInt16) validateCollectionContains("listInt32", \Query.listInt32) validateCollectionContains("listInt64", \Query.listInt64) validateCollectionContains("listFloat", \Query.listFloat) validateCollectionContains("listDouble", \Query.listDouble) validateCollectionContains("listString", \Query.listString) validateCollectionContains("listBinary", \Query.listBinary) validateCollectionContains("listDate", \Query.listDate) validateCollectionContains("listDecimal", \Query.listDecimal) validateCollectionContains("listObjectId", \Query.listObjectId) validateCollectionContains("listUuid", \Query.listUuid) validateCollectionContains("arrayOptBool", \Query.arrayOptBool) validateCollectionContains("arrayOptInt", \Query.arrayOptInt) validateCollectionContains("arrayOptInt8", \Query.arrayOptInt8) validateCollectionContains("arrayOptInt16", \Query.arrayOptInt16) validateCollectionContains("arrayOptInt32", \Query.arrayOptInt32) validateCollectionContains("arrayOptInt64", \Query.arrayOptInt64) validateCollectionContains("arrayOptFloat", \Query.arrayOptFloat) validateCollectionContains("arrayOptDouble", \Query.arrayOptDouble) validateCollectionContains("arrayOptString", \Query.arrayOptString) validateCollectionContains("arrayOptBinary", \Query.arrayOptBinary) validateCollectionContains("arrayOptDate", \Query.arrayOptDate) validateCollectionContains("arrayOptDecimal", \Query.arrayOptDecimal) validateCollectionContains("arrayOptObjectId", \Query.arrayOptObjectId) validateCollectionContains("arrayOptUuid", \Query.arrayOptUuid) validateCollectionContains("listIntOpt", \Query.listIntOpt) validateCollectionContains("listInt8Opt", \Query.listInt8Opt) validateCollectionContains("listInt16Opt", \Query.listInt16Opt) validateCollectionContains("listInt32Opt", \Query.listInt32Opt) validateCollectionContains("listInt64Opt", \Query.listInt64Opt) validateCollectionContains("listFloatOpt", \Query.listFloatOpt) validateCollectionContains("listDoubleOpt", \Query.listDoubleOpt) validateCollectionContains("listStringOpt", \Query.listStringOpt) validateCollectionContains("listOptBool", \Query.listOptBool) validateCollectionContains("listOptInt", \Query.listOptInt) validateCollectionContains("listOptInt8", \Query.listOptInt8) validateCollectionContains("listOptInt16", \Query.listOptInt16) validateCollectionContains("listOptInt32", \Query.listOptInt32) validateCollectionContains("listOptInt64", \Query.listOptInt64) validateCollectionContains("listOptFloat", \Query.listOptFloat) validateCollectionContains("listOptDouble", \Query.listOptDouble) validateCollectionContains("listOptString", \Query.listOptString) validateCollectionContains("listOptBinary", \Query.listOptBinary) validateCollectionContains("listOptDate", \Query.listOptDate) validateCollectionContains("listOptDecimal", \Query.listOptDecimal) validateCollectionContains("listOptObjectId", \Query.listOptObjectId) validateCollectionContains("listOptUuid", \Query.listOptUuid) validateCollectionContains("setBool", \Query.setBool) validateCollectionContains("setInt", \Query.setInt) validateCollectionContains("setInt8", \Query.setInt8) validateCollectionContains("setInt16", \Query.setInt16) validateCollectionContains("setInt32", \Query.setInt32) validateCollectionContains("setInt64", \Query.setInt64) validateCollectionContains("setFloat", \Query.setFloat) validateCollectionContains("setDouble", \Query.setDouble) validateCollectionContains("setString", \Query.setString) validateCollectionContains("setBinary", \Query.setBinary) validateCollectionContains("setDate", \Query.setDate) validateCollectionContains("setDecimal", \Query.setDecimal) validateCollectionContains("setObjectId", \Query.setObjectId) validateCollectionContains("setUuid", \Query.setUuid) validateCollectionContains("setAny", \Query.setAny) validateCollectionContains("setInt", \Query.setInt) validateCollectionContains("setInt8", \Query.setInt8) validateCollectionContains("setInt16", \Query.setInt16) validateCollectionContains("setInt32", \Query.setInt32) validateCollectionContains("setInt64", \Query.setInt64) validateCollectionContains("setFloat", \Query.setFloat) validateCollectionContains("setDouble", \Query.setDouble) validateCollectionContains("setString", \Query.setString) validateCollectionContains("setBool", \Query.setBool) validateCollectionContains("setInt", \Query.setInt) validateCollectionContains("setInt8", \Query.setInt8) validateCollectionContains("setInt16", \Query.setInt16) validateCollectionContains("setInt32", \Query.setInt32) validateCollectionContains("setInt64", \Query.setInt64) validateCollectionContains("setFloat", \Query.setFloat) validateCollectionContains("setDouble", \Query.setDouble) validateCollectionContains("setString", \Query.setString) validateCollectionContains("setBinary", \Query.setBinary) validateCollectionContains("setDate", \Query.setDate) validateCollectionContains("setDecimal", \Query.setDecimal) validateCollectionContains("setObjectId", \Query.setObjectId) validateCollectionContains("setUuid", \Query.setUuid) validateCollectionContains("setOptBool", \Query.setOptBool) validateCollectionContains("setOptInt", \Query.setOptInt) validateCollectionContains("setOptInt8", \Query.setOptInt8) validateCollectionContains("setOptInt16", \Query.setOptInt16) validateCollectionContains("setOptInt32", \Query.setOptInt32) validateCollectionContains("setOptInt64", \Query.setOptInt64) validateCollectionContains("setOptFloat", \Query.setOptFloat) validateCollectionContains("setOptDouble", \Query.setOptDouble) validateCollectionContains("setOptString", \Query.setOptString) validateCollectionContains("setOptBinary", \Query.setOptBinary) validateCollectionContains("setOptDate", \Query.setOptDate) validateCollectionContains("setOptDecimal", \Query.setOptDecimal) validateCollectionContains("setOptObjectId", \Query.setOptObjectId) validateCollectionContains("setOptUuid", \Query.setOptUuid) validateCollectionContains("setIntOpt", \Query.setIntOpt) validateCollectionContains("setInt8Opt", \Query.setInt8Opt) validateCollectionContains("setInt16Opt", \Query.setInt16Opt) validateCollectionContains("setInt32Opt", \Query.setInt32Opt) validateCollectionContains("setInt64Opt", \Query.setInt64Opt) validateCollectionContains("setFloatOpt", \Query.setFloatOpt) validateCollectionContains("setDoubleOpt", \Query.setDoubleOpt) validateCollectionContains("setStringOpt", \Query.setStringOpt) validateCollectionContains("setOptBool", \Query.setOptBool) validateCollectionContains("setOptInt", \Query.setOptInt) validateCollectionContains("setOptInt8", \Query.setOptInt8) validateCollectionContains("setOptInt16", \Query.setOptInt16) validateCollectionContains("setOptInt32", \Query.setOptInt32) validateCollectionContains("setOptInt64", \Query.setOptInt64) validateCollectionContains("setOptFloat", \Query.setOptFloat) validateCollectionContains("setOptDouble", \Query.setOptDouble) validateCollectionContains("setOptString", \Query.setOptString) validateCollectionContains("setOptBinary", \Query.setOptBinary) validateCollectionContains("setOptDate", \Query.setOptDate) validateCollectionContains("setOptDecimal", \Query.setOptDecimal) validateCollectionContains("setOptObjectId", \Query.setOptObjectId) validateCollectionContains("setOptUuid", \Query.setOptUuid) validateCollectionContains("mapBool", \Query.mapBool) validateCollectionContains("mapInt", \Query.mapInt) validateCollectionContains("mapInt8", \Query.mapInt8) validateCollectionContains("mapInt16", \Query.mapInt16) validateCollectionContains("mapInt32", \Query.mapInt32) validateCollectionContains("mapInt64", \Query.mapInt64) validateCollectionContains("mapFloat", \Query.mapFloat) validateCollectionContains("mapDouble", \Query.mapDouble) validateCollectionContains("mapString", \Query.mapString) validateCollectionContains("mapBinary", \Query.mapBinary) validateCollectionContains("mapDate", \Query.mapDate) validateCollectionContains("mapDecimal", \Query.mapDecimal) validateCollectionContains("mapObjectId", \Query.mapObjectId) validateCollectionContains("mapUuid", \Query.mapUuid) validateCollectionContains("mapAny", \Query.mapAny) validateCollectionContains("mapInt", \Query.mapInt) validateCollectionContains("mapInt8", \Query.mapInt8) validateCollectionContains("mapInt16", \Query.mapInt16) validateCollectionContains("mapInt32", \Query.mapInt32) validateCollectionContains("mapInt64", \Query.mapInt64) validateCollectionContains("mapFloat", \Query.mapFloat) validateCollectionContains("mapDouble", \Query.mapDouble) validateCollectionContains("mapString", \Query.mapString) validateCollectionContains("mapBool", \Query.mapBool) validateCollectionContains("mapInt", \Query.mapInt) validateCollectionContains("mapInt8", \Query.mapInt8) validateCollectionContains("mapInt16", \Query.mapInt16) validateCollectionContains("mapInt32", \Query.mapInt32) validateCollectionContains("mapInt64", \Query.mapInt64) validateCollectionContains("mapFloat", \Query.mapFloat) validateCollectionContains("mapDouble", \Query.mapDouble) validateCollectionContains("mapString", \Query.mapString) validateCollectionContains("mapBinary", \Query.mapBinary) validateCollectionContains("mapDate", \Query.mapDate) validateCollectionContains("mapDecimal", \Query.mapDecimal) validateCollectionContains("mapObjectId", \Query.mapObjectId) validateCollectionContains("mapUuid", \Query.mapUuid) validateCollectionContains("mapOptBool", \Query.mapOptBool) validateCollectionContains("mapOptInt", \Query.mapOptInt) validateCollectionContains("mapOptInt8", \Query.mapOptInt8) validateCollectionContains("mapOptInt16", \Query.mapOptInt16) validateCollectionContains("mapOptInt32", \Query.mapOptInt32) validateCollectionContains("mapOptInt64", \Query.mapOptInt64) validateCollectionContains("mapOptFloat", \Query.mapOptFloat) validateCollectionContains("mapOptDouble", \Query.mapOptDouble) validateCollectionContains("mapOptString", \Query.mapOptString) validateCollectionContains("mapOptBinary", \Query.mapOptBinary) validateCollectionContains("mapOptDate", \Query.mapOptDate) validateCollectionContains("mapOptDecimal", \Query.mapOptDecimal) validateCollectionContains("mapOptObjectId", \Query.mapOptObjectId) validateCollectionContains("mapOptUuid", \Query.mapOptUuid) validateCollectionContains("mapIntOpt", \Query.mapIntOpt) validateCollectionContains("mapInt8Opt", \Query.mapInt8Opt) validateCollectionContains("mapInt16Opt", \Query.mapInt16Opt) validateCollectionContains("mapInt32Opt", \Query.mapInt32Opt) validateCollectionContains("mapInt64Opt", \Query.mapInt64Opt) validateCollectionContains("mapFloatOpt", \Query.mapFloatOpt) validateCollectionContains("mapDoubleOpt", \Query.mapDoubleOpt) validateCollectionContains("mapStringOpt", \Query.mapStringOpt) validateCollectionContains("mapOptBool", \Query.mapOptBool) validateCollectionContains("mapOptInt", \Query.mapOptInt) validateCollectionContains("mapOptInt8", \Query.mapOptInt8) validateCollectionContains("mapOptInt16", \Query.mapOptInt16) validateCollectionContains("mapOptInt32", \Query.mapOptInt32) validateCollectionContains("mapOptInt64", \Query.mapOptInt64) validateCollectionContains("mapOptFloat", \Query.mapOptFloat) validateCollectionContains("mapOptDouble", \Query.mapOptDouble) validateCollectionContains("mapOptString", \Query.mapOptString) validateCollectionContains("mapOptBinary", \Query.mapOptBinary) validateCollectionContains("mapOptDate", \Query.mapOptDate) validateCollectionContains("mapOptDecimal", \Query.mapOptDecimal) validateCollectionContains("mapOptObjectId", \Query.mapOptObjectId) validateCollectionContains("mapOptUuid", \Query.mapOptUuid) validateCollectionContainsNil("arrayOptBool", \Query.arrayOptBool) validateCollectionContainsNil("arrayOptInt", \Query.arrayOptInt) validateCollectionContainsNil("arrayOptInt8", \Query.arrayOptInt8) validateCollectionContainsNil("arrayOptInt16", \Query.arrayOptInt16) validateCollectionContainsNil("arrayOptInt32", \Query.arrayOptInt32) validateCollectionContainsNil("arrayOptInt64", \Query.arrayOptInt64) validateCollectionContainsNil("arrayOptFloat", \Query.arrayOptFloat) validateCollectionContainsNil("arrayOptDouble", \Query.arrayOptDouble) validateCollectionContainsNil("arrayOptString", \Query.arrayOptString) validateCollectionContainsNil("arrayOptBinary", \Query.arrayOptBinary) validateCollectionContainsNil("arrayOptDate", \Query.arrayOptDate) validateCollectionContainsNil("arrayOptDecimal", \Query.arrayOptDecimal) validateCollectionContainsNil("arrayOptObjectId", \Query.arrayOptObjectId) validateCollectionContainsNil("arrayOptUuid", \Query.arrayOptUuid) validateCollectionContainsNil("listIntOpt", \Query.listIntOpt) validateCollectionContainsNil("listInt8Opt", \Query.listInt8Opt) validateCollectionContainsNil("listInt16Opt", \Query.listInt16Opt) validateCollectionContainsNil("listInt32Opt", \Query.listInt32Opt) validateCollectionContainsNil("listInt64Opt", \Query.listInt64Opt) validateCollectionContainsNil("listFloatOpt", \Query.listFloatOpt) validateCollectionContainsNil("listDoubleOpt", \Query.listDoubleOpt) validateCollectionContainsNil("listStringOpt", \Query.listStringOpt) validateCollectionContainsNil("listOptBool", \Query.listOptBool) validateCollectionContainsNil("listOptInt", \Query.listOptInt) validateCollectionContainsNil("listOptInt8", \Query.listOptInt8) validateCollectionContainsNil("listOptInt16", \Query.listOptInt16) validateCollectionContainsNil("listOptInt32", \Query.listOptInt32) validateCollectionContainsNil("listOptInt64", \Query.listOptInt64) validateCollectionContainsNil("listOptFloat", \Query.listOptFloat) validateCollectionContainsNil("listOptDouble", \Query.listOptDouble) validateCollectionContainsNil("listOptString", \Query.listOptString) validateCollectionContainsNil("listOptBinary", \Query.listOptBinary) validateCollectionContainsNil("listOptDate", \Query.listOptDate) validateCollectionContainsNil("listOptDecimal", \Query.listOptDecimal) validateCollectionContainsNil("listOptObjectId", \Query.listOptObjectId) validateCollectionContainsNil("listOptUuid", \Query.listOptUuid) validateCollectionContainsNil("setOptBool", \Query.setOptBool) validateCollectionContainsNil("setOptInt", \Query.setOptInt) validateCollectionContainsNil("setOptInt8", \Query.setOptInt8) validateCollectionContainsNil("setOptInt16", \Query.setOptInt16) validateCollectionContainsNil("setOptInt32", \Query.setOptInt32) validateCollectionContainsNil("setOptInt64", \Query.setOptInt64) validateCollectionContainsNil("setOptFloat", \Query.setOptFloat) validateCollectionContainsNil("setOptDouble", \Query.setOptDouble) validateCollectionContainsNil("setOptString", \Query.setOptString) validateCollectionContainsNil("setOptBinary", \Query.setOptBinary) validateCollectionContainsNil("setOptDate", \Query.setOptDate) validateCollectionContainsNil("setOptDecimal", \Query.setOptDecimal) validateCollectionContainsNil("setOptObjectId", \Query.setOptObjectId) validateCollectionContainsNil("setOptUuid", \Query.setOptUuid) validateCollectionContainsNil("setIntOpt", \Query.setIntOpt) validateCollectionContainsNil("setInt8Opt", \Query.setInt8Opt) validateCollectionContainsNil("setInt16Opt", \Query.setInt16Opt) validateCollectionContainsNil("setInt32Opt", \Query.setInt32Opt) validateCollectionContainsNil("setInt64Opt", \Query.setInt64Opt) validateCollectionContainsNil("setFloatOpt", \Query.setFloatOpt) validateCollectionContainsNil("setDoubleOpt", \Query.setDoubleOpt) validateCollectionContainsNil("setStringOpt", \Query.setStringOpt) validateCollectionContainsNil("setOptBool", \Query.setOptBool) validateCollectionContainsNil("setOptInt", \Query.setOptInt) validateCollectionContainsNil("setOptInt8", \Query.setOptInt8) validateCollectionContainsNil("setOptInt16", \Query.setOptInt16) validateCollectionContainsNil("setOptInt32", \Query.setOptInt32) validateCollectionContainsNil("setOptInt64", \Query.setOptInt64) validateCollectionContainsNil("setOptFloat", \Query.setOptFloat) validateCollectionContainsNil("setOptDouble", \Query.setOptDouble) validateCollectionContainsNil("setOptString", \Query.setOptString) validateCollectionContainsNil("setOptBinary", \Query.setOptBinary) validateCollectionContainsNil("setOptDate", \Query.setOptDate) validateCollectionContainsNil("setOptDecimal", \Query.setOptDecimal) validateCollectionContainsNil("setOptObjectId", \Query.setOptObjectId) validateCollectionContainsNil("setOptUuid", \Query.setOptUuid) validateCollectionContainsNil("mapOptBool", \Query.mapOptBool) validateCollectionContainsNil("mapOptInt", \Query.mapOptInt) validateCollectionContainsNil("mapOptInt8", \Query.mapOptInt8) validateCollectionContainsNil("mapOptInt16", \Query.mapOptInt16) validateCollectionContainsNil("mapOptInt32", \Query.mapOptInt32) validateCollectionContainsNil("mapOptInt64", \Query.mapOptInt64) validateCollectionContainsNil("mapOptFloat", \Query.mapOptFloat) validateCollectionContainsNil("mapOptDouble", \Query.mapOptDouble) validateCollectionContainsNil("mapOptString", \Query.mapOptString) validateCollectionContainsNil("mapOptBinary", \Query.mapOptBinary) validateCollectionContainsNil("mapOptDate", \Query.mapOptDate) validateCollectionContainsNil("mapOptDecimal", \Query.mapOptDecimal) validateCollectionContainsNil("mapOptObjectId", \Query.mapOptObjectId) validateCollectionContainsNil("mapOptUuid", \Query.mapOptUuid) validateCollectionContainsNil("mapIntOpt", \Query.mapIntOpt) validateCollectionContainsNil("mapInt8Opt", \Query.mapInt8Opt) validateCollectionContainsNil("mapInt16Opt", \Query.mapInt16Opt) validateCollectionContainsNil("mapInt32Opt", \Query.mapInt32Opt) validateCollectionContainsNil("mapInt64Opt", \Query.mapInt64Opt) validateCollectionContainsNil("mapFloatOpt", \Query.mapFloatOpt) validateCollectionContainsNil("mapDoubleOpt", \Query.mapDoubleOpt) validateCollectionContainsNil("mapStringOpt", \Query.mapStringOpt) validateCollectionContainsNil("mapOptBool", \Query.mapOptBool) validateCollectionContainsNil("mapOptInt", \Query.mapOptInt) validateCollectionContainsNil("mapOptInt8", \Query.mapOptInt8) validateCollectionContainsNil("mapOptInt16", \Query.mapOptInt16) validateCollectionContainsNil("mapOptInt32", \Query.mapOptInt32) validateCollectionContainsNil("mapOptInt64", \Query.mapOptInt64) validateCollectionContainsNil("mapOptFloat", \Query.mapOptFloat) validateCollectionContainsNil("mapOptDouble", \Query.mapOptDouble) validateCollectionContainsNil("mapOptString", \Query.mapOptString) validateCollectionContainsNil("mapOptBinary", \Query.mapOptBinary) validateCollectionContainsNil("mapOptDate", \Query.mapOptDate) validateCollectionContainsNil("mapOptDecimal", \Query.mapOptDecimal) validateCollectionContainsNil("mapOptObjectId", \Query.mapOptObjectId) validateCollectionContainsNil("mapOptUuid", \Query.mapOptUuid) } func testListContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.list.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.list.append(obj) } XCTAssertEqual(result.count, 1) } func testCollectionContainsRange() { assertQuery(ModernAllTypesObject.self, "((arrayInt.@min >= %@) && (arrayInt.@max <= %@))", values: [1, 3], count: 1) { $0.arrayInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((arrayInt.@min >= %@) && (arrayInt.@max < %@))", values: [1, 3], count: 0) { $0.arrayInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((arrayInt8.@min >= %@) && (arrayInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.arrayInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((arrayInt8.@min >= %@) && (arrayInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.arrayInt8.contains(Int8(8)..= %@) && (arrayInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.arrayInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((arrayInt16.@min >= %@) && (arrayInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.arrayInt16.contains(Int16(16)..= %@) && (arrayInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.arrayInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((arrayInt32.@min >= %@) && (arrayInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.arrayInt32.contains(Int32(32)..= %@) && (arrayInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.arrayInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((arrayInt64.@min >= %@) && (arrayInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.arrayInt64.contains(Int64(64)..= %@) && (arrayFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.arrayFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((arrayFloat.@min >= %@) && (arrayFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.arrayFloat.contains(Float(5.55444333)..= %@) && (arrayDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.arrayDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((arrayDouble.@min >= %@) && (arrayDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.arrayDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((arrayDate.@min >= %@) && (arrayDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.arrayDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((arrayDate.@min >= %@) && (arrayDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.arrayDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (arrayDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.arrayDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((arrayDecimal.@min >= %@) && (arrayDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.arrayDecimal.contains(Decimal128(123.456)..= %@) && (listInt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.listInt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt.@min >= %@) && (listInt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.listInt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (listInt8.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.listInt8.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt8.@min >= %@) && (listInt8.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.listInt8.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (listInt16.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.listInt16.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt16.@min >= %@) && (listInt16.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.listInt16.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (listInt32.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.listInt32.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt32.@min >= %@) && (listInt32.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.listInt32.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (listInt64.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.listInt64.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt64.@min >= %@) && (listInt64.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.listInt64.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (listFloat.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.listFloat.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listFloat.@min >= %@) && (listFloat.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.listFloat.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (listDouble.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.listDouble.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listDouble.@min >= %@) && (listDouble.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.listDouble.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (listInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.listInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listInt.@min >= %@) && (listInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.listInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (listInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.listInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listInt8.@min >= %@) && (listInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.listInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (listInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.listInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listInt16.@min >= %@) && (listInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.listInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (listInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.listInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listInt32.@min >= %@) && (listInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.listInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (listInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.listInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listInt64.@min >= %@) && (listInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.listInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (listFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.listFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listFloat.@min >= %@) && (listFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.listFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (listDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.listDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listDouble.@min >= %@) && (listDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.listDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (listDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.listDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listDate.@min >= %@) && (listDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.listDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (listDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.listDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listDecimal.@min >= %@) && (listDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.listDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..= %@) && (arrayOptInt.@max <= %@))", values: [1, 3], count: 1) { $0.arrayOptInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt.@min >= %@) && (arrayOptInt.@max < %@))", values: [1, 3], count: 0) { $0.arrayOptInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt8.@min >= %@) && (arrayOptInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.arrayOptInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt8.@min >= %@) && (arrayOptInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.arrayOptInt8.contains(Int8(8)..= %@) && (arrayOptInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.arrayOptInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt16.@min >= %@) && (arrayOptInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.arrayOptInt16.contains(Int16(16)..= %@) && (arrayOptInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.arrayOptInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt32.@min >= %@) && (arrayOptInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.arrayOptInt32.contains(Int32(32)..= %@) && (arrayOptInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.arrayOptInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((arrayOptInt64.@min >= %@) && (arrayOptInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.arrayOptInt64.contains(Int64(64)..= %@) && (arrayOptFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.arrayOptFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((arrayOptFloat.@min >= %@) && (arrayOptFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.arrayOptFloat.contains(Float(5.55444333)..= %@) && (arrayOptDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.arrayOptDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((arrayOptDouble.@min >= %@) && (arrayOptDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.arrayOptDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((arrayOptDate.@min >= %@) && (arrayOptDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.arrayOptDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((arrayOptDate.@min >= %@) && (arrayOptDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.arrayOptDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (arrayOptDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.arrayOptDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((arrayOptDecimal.@min >= %@) && (arrayOptDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.arrayOptDecimal.contains(Decimal128(123.456)..= %@) && (listIntOpt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.listIntOpt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listIntOpt.@min >= %@) && (listIntOpt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.listIntOpt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (listInt8Opt.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.listInt8Opt.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt8Opt.@min >= %@) && (listInt8Opt.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.listInt8Opt.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (listInt16Opt.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.listInt16Opt.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt16Opt.@min >= %@) && (listInt16Opt.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.listInt16Opt.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (listInt32Opt.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.listInt32Opt.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt32Opt.@min >= %@) && (listInt32Opt.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.listInt32Opt.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (listInt64Opt.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.listInt64Opt.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listInt64Opt.@min >= %@) && (listInt64Opt.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.listInt64Opt.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (listFloatOpt.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.listFloatOpt.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listFloatOpt.@min >= %@) && (listFloatOpt.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.listFloatOpt.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (listDoubleOpt.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.listDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((listDoubleOpt.@min >= %@) && (listDoubleOpt.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.listDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (listOptInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.listOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptInt.@min >= %@) && (listOptInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.listOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (listOptInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.listOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptInt8.@min >= %@) && (listOptInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.listOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (listOptInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.listOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptInt16.@min >= %@) && (listOptInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.listOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (listOptInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.listOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptInt32.@min >= %@) && (listOptInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.listOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (listOptInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.listOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptInt64.@min >= %@) && (listOptInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.listOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (listOptFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.listOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptFloat.@min >= %@) && (listOptFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.listOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (listOptDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.listOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptDouble.@min >= %@) && (listOptDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.listOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (listOptDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.listOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptDate.@min >= %@) && (listOptDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.listOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (listOptDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.listOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((listOptDecimal.@min >= %@) && (listOptDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.listOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..= %@) && (setInt.@max <= %@))", values: [1, 3], count: 1) { $0.setInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((setInt.@min >= %@) && (setInt.@max < %@))", values: [1, 3], count: 0) { $0.setInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((setInt8.@min >= %@) && (setInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.setInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((setInt8.@min >= %@) && (setInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.setInt8.contains(Int8(8)..= %@) && (setInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.setInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((setInt16.@min >= %@) && (setInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.setInt16.contains(Int16(16)..= %@) && (setInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.setInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((setInt32.@min >= %@) && (setInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.setInt32.contains(Int32(32)..= %@) && (setInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.setInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((setInt64.@min >= %@) && (setInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.setInt64.contains(Int64(64)..= %@) && (setFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.setFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((setFloat.@min >= %@) && (setFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.setFloat.contains(Float(5.55444333)..= %@) && (setDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.setDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((setDouble.@min >= %@) && (setDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.setDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((setDate.@min >= %@) && (setDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.setDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((setDate.@min >= %@) && (setDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.setDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (setDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.setDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((setDecimal.@min >= %@) && (setDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.setDecimal.contains(Decimal128(123.456)..= %@) && (setInt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.setInt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt.@min >= %@) && (setInt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.setInt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (setInt8.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.setInt8.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt8.@min >= %@) && (setInt8.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.setInt8.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (setInt16.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.setInt16.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt16.@min >= %@) && (setInt16.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.setInt16.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (setInt32.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.setInt32.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt32.@min >= %@) && (setInt32.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.setInt32.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (setInt64.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.setInt64.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt64.@min >= %@) && (setInt64.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.setInt64.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (setFloat.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.setFloat.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setFloat.@min >= %@) && (setFloat.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.setFloat.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (setDouble.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.setDouble.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setDouble.@min >= %@) && (setDouble.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.setDouble.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (setInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.setInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setInt.@min >= %@) && (setInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.setInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (setInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.setInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setInt8.@min >= %@) && (setInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.setInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (setInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.setInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setInt16.@min >= %@) && (setInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.setInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (setInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.setInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setInt32.@min >= %@) && (setInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.setInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (setInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.setInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setInt64.@min >= %@) && (setInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.setInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (setFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.setFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setFloat.@min >= %@) && (setFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.setFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (setDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.setDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setDouble.@min >= %@) && (setDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.setDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (setDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.setDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setDate.@min >= %@) && (setDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.setDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (setDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.setDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setDecimal.@min >= %@) && (setDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.setDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..= %@) && (setOptInt.@max <= %@))", values: [1, 3], count: 1) { $0.setOptInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((setOptInt.@min >= %@) && (setOptInt.@max < %@))", values: [1, 3], count: 0) { $0.setOptInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((setOptInt8.@min >= %@) && (setOptInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.setOptInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((setOptInt8.@min >= %@) && (setOptInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.setOptInt8.contains(Int8(8)..= %@) && (setOptInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.setOptInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((setOptInt16.@min >= %@) && (setOptInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.setOptInt16.contains(Int16(16)..= %@) && (setOptInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.setOptInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((setOptInt32.@min >= %@) && (setOptInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.setOptInt32.contains(Int32(32)..= %@) && (setOptInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.setOptInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((setOptInt64.@min >= %@) && (setOptInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.setOptInt64.contains(Int64(64)..= %@) && (setOptFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.setOptFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((setOptFloat.@min >= %@) && (setOptFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.setOptFloat.contains(Float(5.55444333)..= %@) && (setOptDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.setOptDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((setOptDouble.@min >= %@) && (setOptDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.setOptDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((setOptDate.@min >= %@) && (setOptDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.setOptDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((setOptDate.@min >= %@) && (setOptDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.setOptDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (setOptDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.setOptDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((setOptDecimal.@min >= %@) && (setOptDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.setOptDecimal.contains(Decimal128(123.456)..= %@) && (setIntOpt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.setIntOpt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setIntOpt.@min >= %@) && (setIntOpt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.setIntOpt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (setInt8Opt.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.setInt8Opt.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt8Opt.@min >= %@) && (setInt8Opt.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.setInt8Opt.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (setInt16Opt.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.setInt16Opt.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt16Opt.@min >= %@) && (setInt16Opt.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.setInt16Opt.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (setInt32Opt.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.setInt32Opt.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt32Opt.@min >= %@) && (setInt32Opt.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.setInt32Opt.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (setInt64Opt.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.setInt64Opt.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setInt64Opt.@min >= %@) && (setInt64Opt.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.setInt64Opt.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (setFloatOpt.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.setFloatOpt.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setFloatOpt.@min >= %@) && (setFloatOpt.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.setFloatOpt.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (setDoubleOpt.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.setDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((setDoubleOpt.@min >= %@) && (setDoubleOpt.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.setDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (setOptInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.setOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptInt.@min >= %@) && (setOptInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.setOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (setOptInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.setOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptInt8.@min >= %@) && (setOptInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.setOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (setOptInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.setOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptInt16.@min >= %@) && (setOptInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.setOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (setOptInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.setOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptInt32.@min >= %@) && (setOptInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.setOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (setOptInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.setOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptInt64.@min >= %@) && (setOptInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.setOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (setOptFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.setOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptFloat.@min >= %@) && (setOptFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.setOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (setOptDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.setOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptDouble.@min >= %@) && (setOptDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.setOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (setOptDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.setOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptDate.@min >= %@) && (setOptDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.setOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (setOptDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.setOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((setOptDecimal.@min >= %@) && (setOptDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.setOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..= %@) && (mapInt.@max <= %@))", values: [1, 3], count: 1) { $0.mapInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((mapInt.@min >= %@) && (mapInt.@max < %@))", values: [1, 3], count: 0) { $0.mapInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((mapInt8.@min >= %@) && (mapInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.mapInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((mapInt8.@min >= %@) && (mapInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.mapInt8.contains(Int8(8)..= %@) && (mapInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.mapInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((mapInt16.@min >= %@) && (mapInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.mapInt16.contains(Int16(16)..= %@) && (mapInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.mapInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((mapInt32.@min >= %@) && (mapInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.mapInt32.contains(Int32(32)..= %@) && (mapInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.mapInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((mapInt64.@min >= %@) && (mapInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.mapInt64.contains(Int64(64)..= %@) && (mapFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.mapFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((mapFloat.@min >= %@) && (mapFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.mapFloat.contains(Float(5.55444333)..= %@) && (mapDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.mapDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((mapDouble.@min >= %@) && (mapDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.mapDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((mapDate.@min >= %@) && (mapDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.mapDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((mapDate.@min >= %@) && (mapDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.mapDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (mapDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.mapDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((mapDecimal.@min >= %@) && (mapDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.mapDecimal.contains(Decimal128(123.456)..= %@) && (mapInt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.mapInt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt.@min >= %@) && (mapInt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.mapInt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (mapInt8.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.mapInt8.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt8.@min >= %@) && (mapInt8.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.mapInt8.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (mapInt16.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.mapInt16.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt16.@min >= %@) && (mapInt16.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.mapInt16.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (mapInt32.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.mapInt32.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt32.@min >= %@) && (mapInt32.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.mapInt32.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (mapInt64.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.mapInt64.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt64.@min >= %@) && (mapInt64.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.mapInt64.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (mapFloat.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.mapFloat.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapFloat.@min >= %@) && (mapFloat.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.mapFloat.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (mapDouble.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.mapDouble.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapDouble.@min >= %@) && (mapDouble.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.mapDouble.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (mapInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.mapInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapInt.@min >= %@) && (mapInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.mapInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (mapInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.mapInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapInt8.@min >= %@) && (mapInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.mapInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (mapInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.mapInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapInt16.@min >= %@) && (mapInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.mapInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (mapInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.mapInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapInt32.@min >= %@) && (mapInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.mapInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (mapInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.mapInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapInt64.@min >= %@) && (mapInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.mapInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (mapFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.mapFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapFloat.@min >= %@) && (mapFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.mapFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (mapDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.mapDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapDouble.@min >= %@) && (mapDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.mapDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (mapDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.mapDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapDate.@min >= %@) && (mapDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.mapDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (mapDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.mapDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapDecimal.@min >= %@) && (mapDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.mapDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..= %@) && (mapOptInt.@max <= %@))", values: [1, 3], count: 1) { $0.mapOptInt.contains(1...3) } assertQuery(ModernAllTypesObject.self, "((mapOptInt.@min >= %@) && (mapOptInt.@max < %@))", values: [1, 3], count: 0) { $0.mapOptInt.contains(1..<3) } assertQuery(ModernAllTypesObject.self, "((mapOptInt8.@min >= %@) && (mapOptInt8.@max <= %@))", values: [Int8(8), Int8(9)], count: 1) { $0.mapOptInt8.contains(Int8(8)...Int8(9)) } assertQuery(ModernAllTypesObject.self, "((mapOptInt8.@min >= %@) && (mapOptInt8.@max < %@))", values: [Int8(8), Int8(9)], count: 0) { $0.mapOptInt8.contains(Int8(8)..= %@) && (mapOptInt16.@max <= %@))", values: [Int16(16), Int16(17)], count: 1) { $0.mapOptInt16.contains(Int16(16)...Int16(17)) } assertQuery(ModernAllTypesObject.self, "((mapOptInt16.@min >= %@) && (mapOptInt16.@max < %@))", values: [Int16(16), Int16(17)], count: 0) { $0.mapOptInt16.contains(Int16(16)..= %@) && (mapOptInt32.@max <= %@))", values: [Int32(32), Int32(33)], count: 1) { $0.mapOptInt32.contains(Int32(32)...Int32(33)) } assertQuery(ModernAllTypesObject.self, "((mapOptInt32.@min >= %@) && (mapOptInt32.@max < %@))", values: [Int32(32), Int32(33)], count: 0) { $0.mapOptInt32.contains(Int32(32)..= %@) && (mapOptInt64.@max <= %@))", values: [Int64(64), Int64(65)], count: 1) { $0.mapOptInt64.contains(Int64(64)...Int64(65)) } assertQuery(ModernAllTypesObject.self, "((mapOptInt64.@min >= %@) && (mapOptInt64.@max < %@))", values: [Int64(64), Int64(65)], count: 0) { $0.mapOptInt64.contains(Int64(64)..= %@) && (mapOptFloat.@max <= %@))", values: [Float(5.55444333), Float(6.55444333)], count: 1) { $0.mapOptFloat.contains(Float(5.55444333)...Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((mapOptFloat.@min >= %@) && (mapOptFloat.@max < %@))", values: [Float(5.55444333), Float(6.55444333)], count: 0) { $0.mapOptFloat.contains(Float(5.55444333)..= %@) && (mapOptDouble.@max <= %@))", values: [123.456, 234.567], count: 1) { $0.mapOptDouble.contains(123.456...234.567) } assertQuery(ModernAllTypesObject.self, "((mapOptDouble.@min >= %@) && (mapOptDouble.@max < %@))", values: [123.456, 234.567], count: 0) { $0.mapOptDouble.contains(123.456..<234.567) } assertQuery(ModernAllTypesObject.self, "((mapOptDate.@min >= %@) && (mapOptDate.@max <= %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.mapOptDate.contains(Date(timeIntervalSince1970: 1000000)...Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((mapOptDate.@min >= %@) && (mapOptDate.@max < %@))", values: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)], count: 0) { $0.mapOptDate.contains(Date(timeIntervalSince1970: 1000000)..= %@) && (mapOptDecimal.@max <= %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 1) { $0.mapOptDecimal.contains(Decimal128(123.456)...Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((mapOptDecimal.@min >= %@) && (mapOptDecimal.@max < %@))", values: [Decimal128(123.456), Decimal128(234.567)], count: 0) { $0.mapOptDecimal.contains(Decimal128(123.456)..= %@) && (mapIntOpt.@max <= %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 1) { $0.mapIntOpt.rawValue.contains(EnumInt.value1.rawValue...EnumInt.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapIntOpt.@min >= %@) && (mapIntOpt.@max < %@))", values: [EnumInt.value1.rawValue, EnumInt.value2.rawValue], count: 0) { $0.mapIntOpt.rawValue.contains(EnumInt.value1.rawValue..= %@) && (mapInt8Opt.@max <= %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 1) { $0.mapInt8Opt.rawValue.contains(EnumInt8.value1.rawValue...EnumInt8.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt8Opt.@min >= %@) && (mapInt8Opt.@max < %@))", values: [EnumInt8.value1.rawValue, EnumInt8.value2.rawValue], count: 0) { $0.mapInt8Opt.rawValue.contains(EnumInt8.value1.rawValue..= %@) && (mapInt16Opt.@max <= %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 1) { $0.mapInt16Opt.rawValue.contains(EnumInt16.value1.rawValue...EnumInt16.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt16Opt.@min >= %@) && (mapInt16Opt.@max < %@))", values: [EnumInt16.value1.rawValue, EnumInt16.value2.rawValue], count: 0) { $0.mapInt16Opt.rawValue.contains(EnumInt16.value1.rawValue..= %@) && (mapInt32Opt.@max <= %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 1) { $0.mapInt32Opt.rawValue.contains(EnumInt32.value1.rawValue...EnumInt32.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt32Opt.@min >= %@) && (mapInt32Opt.@max < %@))", values: [EnumInt32.value1.rawValue, EnumInt32.value2.rawValue], count: 0) { $0.mapInt32Opt.rawValue.contains(EnumInt32.value1.rawValue..= %@) && (mapInt64Opt.@max <= %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 1) { $0.mapInt64Opt.rawValue.contains(EnumInt64.value1.rawValue...EnumInt64.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapInt64Opt.@min >= %@) && (mapInt64Opt.@max < %@))", values: [EnumInt64.value1.rawValue, EnumInt64.value2.rawValue], count: 0) { $0.mapInt64Opt.rawValue.contains(EnumInt64.value1.rawValue..= %@) && (mapFloatOpt.@max <= %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 1) { $0.mapFloatOpt.rawValue.contains(EnumFloat.value1.rawValue...EnumFloat.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapFloatOpt.@min >= %@) && (mapFloatOpt.@max < %@))", values: [EnumFloat.value1.rawValue, EnumFloat.value2.rawValue], count: 0) { $0.mapFloatOpt.rawValue.contains(EnumFloat.value1.rawValue..= %@) && (mapDoubleOpt.@max <= %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 1) { $0.mapDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue...EnumDouble.value2.rawValue) } assertQuery(ModernCollectionsOfEnums.self, "((mapDoubleOpt.@min >= %@) && (mapDoubleOpt.@max < %@))", values: [EnumDouble.value1.rawValue, EnumDouble.value2.rawValue], count: 0) { $0.mapDoubleOpt.rawValue.contains(EnumDouble.value1.rawValue..= %@) && (mapOptInt.@max <= %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 1) { $0.mapOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue...IntWrapper(persistedValue: 3).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptInt.@min >= %@) && (mapOptInt.@max < %@))", values: [IntWrapper(persistedValue: 1).persistableValue, IntWrapper(persistedValue: 3).persistableValue], count: 0) { $0.mapOptInt.persistableValue.contains(IntWrapper(persistedValue: 1).persistableValue..= %@) && (mapOptInt8.@max <= %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 1) { $0.mapOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue...Int8Wrapper(persistedValue: Int8(9)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptInt8.@min >= %@) && (mapOptInt8.@max < %@))", values: [Int8Wrapper(persistedValue: Int8(8)).persistableValue, Int8Wrapper(persistedValue: Int8(9)).persistableValue], count: 0) { $0.mapOptInt8.persistableValue.contains(Int8Wrapper(persistedValue: Int8(8)).persistableValue..= %@) && (mapOptInt16.@max <= %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 1) { $0.mapOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue...Int16Wrapper(persistedValue: Int16(17)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptInt16.@min >= %@) && (mapOptInt16.@max < %@))", values: [Int16Wrapper(persistedValue: Int16(16)).persistableValue, Int16Wrapper(persistedValue: Int16(17)).persistableValue], count: 0) { $0.mapOptInt16.persistableValue.contains(Int16Wrapper(persistedValue: Int16(16)).persistableValue..= %@) && (mapOptInt32.@max <= %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 1) { $0.mapOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue...Int32Wrapper(persistedValue: Int32(33)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptInt32.@min >= %@) && (mapOptInt32.@max < %@))", values: [Int32Wrapper(persistedValue: Int32(32)).persistableValue, Int32Wrapper(persistedValue: Int32(33)).persistableValue], count: 0) { $0.mapOptInt32.persistableValue.contains(Int32Wrapper(persistedValue: Int32(32)).persistableValue..= %@) && (mapOptInt64.@max <= %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 1) { $0.mapOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue...Int64Wrapper(persistedValue: Int64(65)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptInt64.@min >= %@) && (mapOptInt64.@max < %@))", values: [Int64Wrapper(persistedValue: Int64(64)).persistableValue, Int64Wrapper(persistedValue: Int64(65)).persistableValue], count: 0) { $0.mapOptInt64.persistableValue.contains(Int64Wrapper(persistedValue: Int64(64)).persistableValue..= %@) && (mapOptFloat.@max <= %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 1) { $0.mapOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue...FloatWrapper(persistedValue: Float(6.55444333)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptFloat.@min >= %@) && (mapOptFloat.@max < %@))", values: [FloatWrapper(persistedValue: Float(5.55444333)).persistableValue, FloatWrapper(persistedValue: Float(6.55444333)).persistableValue], count: 0) { $0.mapOptFloat.persistableValue.contains(FloatWrapper(persistedValue: Float(5.55444333)).persistableValue..= %@) && (mapOptDouble.@max <= %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 1) { $0.mapOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue...DoubleWrapper(persistedValue: 234.567).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptDouble.@min >= %@) && (mapOptDouble.@max < %@))", values: [DoubleWrapper(persistedValue: 123.456).persistableValue, DoubleWrapper(persistedValue: 234.567).persistableValue], count: 0) { $0.mapOptDouble.persistableValue.contains(DoubleWrapper(persistedValue: 123.456).persistableValue..= %@) && (mapOptDate.@max <= %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 1) { $0.mapOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue...DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptDate.@min >= %@) && (mapOptDate.@max < %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)).persistableValue], count: 0) { $0.mapOptDate.persistableValue.contains(DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)).persistableValue..= %@) && (mapOptDecimal.@max <= %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 1) { $0.mapOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue...Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue) } assertQuery(CustomPersistableCollections.self, "((mapOptDecimal.@min >= %@) && (mapOptDecimal.@max < %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue, Decimal128Wrapper(persistedValue: Decimal128(234.567)).persistableValue], count: 0) { $0.mapOptDecimal.persistableValue.contains(Decimal128Wrapper(persistedValue: Decimal128(123.456)).persistableValue..( _ type: Root.Type, _ predicate: String, _ value: Any, _ q1: ((Query) -> Query), _ q2: ((Query) -> Query)) { assertPredicate(predicate, [value], q1) assertPredicate(predicate, [value], q2) let obj = realm.objects(Root.self).first! XCTAssertEqual(obj.list.where(q1).count, 1) XCTAssertEqual(obj.set.where(q1).count, 1) XCTAssertEqual(obj.map.where(q2).count, 1) } test(LinkToModernAllTypesObject.self, "(boolCol == %@)", false, { $0.boolCol == false }, { $0.boolCol == false }) test(LinkToModernAllTypesObject.self, "(intCol == %@)", 3, { $0.intCol == 3 }, { $0.intCol == 3 }) test(LinkToModernAllTypesObject.self, "(int8Col == %@)", Int8(9), { $0.int8Col == Int8(9) }, { $0.int8Col == Int8(9) }) test(LinkToModernAllTypesObject.self, "(int16Col == %@)", Int16(17), { $0.int16Col == Int16(17) }, { $0.int16Col == Int16(17) }) test(LinkToModernAllTypesObject.self, "(int32Col == %@)", Int32(33), { $0.int32Col == Int32(33) }, { $0.int32Col == Int32(33) }) test(LinkToModernAllTypesObject.self, "(int64Col == %@)", Int64(65), { $0.int64Col == Int64(65) }, { $0.int64Col == Int64(65) }) test(LinkToModernAllTypesObject.self, "(floatCol == %@)", Float(6.55444333), { $0.floatCol == Float(6.55444333) }, { $0.floatCol == Float(6.55444333) }) test(LinkToModernAllTypesObject.self, "(doubleCol == %@)", 234.567, { $0.doubleCol == 234.567 }, { $0.doubleCol == 234.567 }) test(LinkToModernAllTypesObject.self, "(stringCol == %@)", "Foó", { $0.stringCol == "Foó" }, { $0.stringCol == "Foó" }) test(LinkToModernAllTypesObject.self, "(binaryCol == %@)", Data(count: 128), { $0.binaryCol == Data(count: 128) }, { $0.binaryCol == Data(count: 128) }) test(LinkToModernAllTypesObject.self, "(dateCol == %@)", Date(timeIntervalSince1970: 2000000), { $0.dateCol == Date(timeIntervalSince1970: 2000000) }, { $0.dateCol == Date(timeIntervalSince1970: 2000000) }) test(LinkToModernAllTypesObject.self, "(decimalCol == %@)", Decimal128(234.567), { $0.decimalCol == Decimal128(234.567) }, { $0.decimalCol == Decimal128(234.567) }) test(LinkToModernAllTypesObject.self, "(objectIdCol == %@)", ObjectId("61184062c1d8f096a3695045"), { $0.objectIdCol == ObjectId("61184062c1d8f096a3695045") }, { $0.objectIdCol == ObjectId("61184062c1d8f096a3695045") }) test(LinkToModernAllTypesObject.self, "(uuidCol == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, { $0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! }, { $0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! }) test(LinkToModernAllTypesObject.self, "(intEnumCol == %@)", ModernIntEnum.value2, { $0.intEnumCol == .value2 }, { $0.intEnumCol == .value2 }) test(LinkToModernAllTypesObject.self, "(stringEnumCol == %@)", ModernStringEnum.value2, { $0.stringEnumCol == .value2 }, { $0.stringEnumCol == .value2 }) test(LinkToAllCustomPersistableTypes.self, "(bool == %@)", BoolWrapper(persistedValue: false), { $0.bool == BoolWrapper(persistedValue: false) }, { $0.bool == BoolWrapper(persistedValue: false) }) test(LinkToAllCustomPersistableTypes.self, "(int == %@)", IntWrapper(persistedValue: 3), { $0.int == IntWrapper(persistedValue: 3) }, { $0.int == IntWrapper(persistedValue: 3) }) test(LinkToAllCustomPersistableTypes.self, "(int8 == %@)", Int8Wrapper(persistedValue: Int8(9)), { $0.int8 == Int8Wrapper(persistedValue: Int8(9)) }, { $0.int8 == Int8Wrapper(persistedValue: Int8(9)) }) test(LinkToAllCustomPersistableTypes.self, "(int16 == %@)", Int16Wrapper(persistedValue: Int16(17)), { $0.int16 == Int16Wrapper(persistedValue: Int16(17)) }, { $0.int16 == Int16Wrapper(persistedValue: Int16(17)) }) test(LinkToAllCustomPersistableTypes.self, "(int32 == %@)", Int32Wrapper(persistedValue: Int32(33)), { $0.int32 == Int32Wrapper(persistedValue: Int32(33)) }, { $0.int32 == Int32Wrapper(persistedValue: Int32(33)) }) test(LinkToAllCustomPersistableTypes.self, "(int64 == %@)", Int64Wrapper(persistedValue: Int64(65)), { $0.int64 == Int64Wrapper(persistedValue: Int64(65)) }, { $0.int64 == Int64Wrapper(persistedValue: Int64(65)) }) test(LinkToAllCustomPersistableTypes.self, "(float == %@)", FloatWrapper(persistedValue: Float(6.55444333)), { $0.float == FloatWrapper(persistedValue: Float(6.55444333)) }, { $0.float == FloatWrapper(persistedValue: Float(6.55444333)) }) test(LinkToAllCustomPersistableTypes.self, "(double == %@)", DoubleWrapper(persistedValue: 234.567), { $0.double == DoubleWrapper(persistedValue: 234.567) }, { $0.double == DoubleWrapper(persistedValue: 234.567) }) test(LinkToAllCustomPersistableTypes.self, "(string == %@)", StringWrapper(persistedValue: "Foó"), { $0.string == StringWrapper(persistedValue: "Foó") }, { $0.string == StringWrapper(persistedValue: "Foó") }) test(LinkToAllCustomPersistableTypes.self, "(binary == %@)", DataWrapper(persistedValue: Data(count: 128)), { $0.binary == DataWrapper(persistedValue: Data(count: 128)) }, { $0.binary == DataWrapper(persistedValue: Data(count: 128)) }) test(LinkToAllCustomPersistableTypes.self, "(date == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), { $0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) }, { $0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) }) test(LinkToAllCustomPersistableTypes.self, "(decimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(234.567)), { $0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) }, { $0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) }) test(LinkToAllCustomPersistableTypes.self, "(objectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), { $0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) }, { $0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) }) test(LinkToAllCustomPersistableTypes.self, "(uuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!), { $0.uuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) }, { $0.uuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) }) test(LinkToModernAllTypesObject.self, "(optBoolCol == %@)", false, { $0.optBoolCol == false }, { $0.optBoolCol == false }) test(LinkToModernAllTypesObject.self, "(optIntCol == %@)", 3, { $0.optIntCol == 3 }, { $0.optIntCol == 3 }) test(LinkToModernAllTypesObject.self, "(optInt8Col == %@)", Int8(9), { $0.optInt8Col == Int8(9) }, { $0.optInt8Col == Int8(9) }) test(LinkToModernAllTypesObject.self, "(optInt16Col == %@)", Int16(17), { $0.optInt16Col == Int16(17) }, { $0.optInt16Col == Int16(17) }) test(LinkToModernAllTypesObject.self, "(optInt32Col == %@)", Int32(33), { $0.optInt32Col == Int32(33) }, { $0.optInt32Col == Int32(33) }) test(LinkToModernAllTypesObject.self, "(optInt64Col == %@)", Int64(65), { $0.optInt64Col == Int64(65) }, { $0.optInt64Col == Int64(65) }) test(LinkToModernAllTypesObject.self, "(optFloatCol == %@)", Float(6.55444333), { $0.optFloatCol == Float(6.55444333) }, { $0.optFloatCol == Float(6.55444333) }) test(LinkToModernAllTypesObject.self, "(optDoubleCol == %@)", 234.567, { $0.optDoubleCol == 234.567 }, { $0.optDoubleCol == 234.567 }) test(LinkToModernAllTypesObject.self, "(optStringCol == %@)", "Foó", { $0.optStringCol == "Foó" }, { $0.optStringCol == "Foó" }) test(LinkToModernAllTypesObject.self, "(optBinaryCol == %@)", Data(count: 128), { $0.optBinaryCol == Data(count: 128) }, { $0.optBinaryCol == Data(count: 128) }) test(LinkToModernAllTypesObject.self, "(optDateCol == %@)", Date(timeIntervalSince1970: 2000000), { $0.optDateCol == Date(timeIntervalSince1970: 2000000) }, { $0.optDateCol == Date(timeIntervalSince1970: 2000000) }) test(LinkToModernAllTypesObject.self, "(optDecimalCol == %@)", Decimal128(234.567), { $0.optDecimalCol == Decimal128(234.567) }, { $0.optDecimalCol == Decimal128(234.567) }) test(LinkToModernAllTypesObject.self, "(optObjectIdCol == %@)", ObjectId("61184062c1d8f096a3695045"), { $0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045") }, { $0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045") }) test(LinkToModernAllTypesObject.self, "(optUuidCol == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, { $0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! }, { $0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! }) test(LinkToModernAllTypesObject.self, "(optIntEnumCol == %@)", ModernIntEnum.value2, { $0.optIntEnumCol == .value2 }, { $0.optIntEnumCol == .value2 }) test(LinkToModernAllTypesObject.self, "(optStringEnumCol == %@)", ModernStringEnum.value2, { $0.optStringEnumCol == .value2 }, { $0.optStringEnumCol == .value2 }) test(LinkToAllCustomPersistableTypes.self, "(optBool == %@)", BoolWrapper(persistedValue: false), { $0.optBool == BoolWrapper(persistedValue: false) }, { $0.optBool == BoolWrapper(persistedValue: false) }) test(LinkToAllCustomPersistableTypes.self, "(optInt == %@)", IntWrapper(persistedValue: 3), { $0.optInt == IntWrapper(persistedValue: 3) }, { $0.optInt == IntWrapper(persistedValue: 3) }) test(LinkToAllCustomPersistableTypes.self, "(optInt8 == %@)", Int8Wrapper(persistedValue: Int8(9)), { $0.optInt8 == Int8Wrapper(persistedValue: Int8(9)) }, { $0.optInt8 == Int8Wrapper(persistedValue: Int8(9)) }) test(LinkToAllCustomPersistableTypes.self, "(optInt16 == %@)", Int16Wrapper(persistedValue: Int16(17)), { $0.optInt16 == Int16Wrapper(persistedValue: Int16(17)) }, { $0.optInt16 == Int16Wrapper(persistedValue: Int16(17)) }) test(LinkToAllCustomPersistableTypes.self, "(optInt32 == %@)", Int32Wrapper(persistedValue: Int32(33)), { $0.optInt32 == Int32Wrapper(persistedValue: Int32(33)) }, { $0.optInt32 == Int32Wrapper(persistedValue: Int32(33)) }) test(LinkToAllCustomPersistableTypes.self, "(optInt64 == %@)", Int64Wrapper(persistedValue: Int64(65)), { $0.optInt64 == Int64Wrapper(persistedValue: Int64(65)) }, { $0.optInt64 == Int64Wrapper(persistedValue: Int64(65)) }) test(LinkToAllCustomPersistableTypes.self, "(optFloat == %@)", FloatWrapper(persistedValue: Float(6.55444333)), { $0.optFloat == FloatWrapper(persistedValue: Float(6.55444333)) }, { $0.optFloat == FloatWrapper(persistedValue: Float(6.55444333)) }) test(LinkToAllCustomPersistableTypes.self, "(optDouble == %@)", DoubleWrapper(persistedValue: 234.567), { $0.optDouble == DoubleWrapper(persistedValue: 234.567) }, { $0.optDouble == DoubleWrapper(persistedValue: 234.567) }) test(LinkToAllCustomPersistableTypes.self, "(optString == %@)", StringWrapper(persistedValue: "Foó"), { $0.optString == StringWrapper(persistedValue: "Foó") }, { $0.optString == StringWrapper(persistedValue: "Foó") }) test(LinkToAllCustomPersistableTypes.self, "(optBinary == %@)", DataWrapper(persistedValue: Data(count: 128)), { $0.optBinary == DataWrapper(persistedValue: Data(count: 128)) }, { $0.optBinary == DataWrapper(persistedValue: Data(count: 128)) }) test(LinkToAllCustomPersistableTypes.self, "(optDate == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), { $0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) }, { $0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) }) test(LinkToAllCustomPersistableTypes.self, "(optDecimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(234.567)), { $0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) }, { $0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) }) test(LinkToAllCustomPersistableTypes.self, "(optObjectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), { $0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) }, { $0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) }) test(LinkToAllCustomPersistableTypes.self, "(optUuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!), { $0.optUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) }, { $0.optUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) }) } func testSetContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.set.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.set.insert(obj) } XCTAssertEqual(result.count, 1) } func testSetContainsAnyInObject() { assertQuery(ModernAllTypesObject.self, "(ANY setBool IN %@)", values: [NSArray(array: [true, true])], count: 1) { $0.setBool.containsAny(in: [true, true]) } assertQuery(ModernAllTypesObject.self, "(ANY setInt IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.setInt.containsAny(in: [1, 3]) } assertQuery(ModernAllTypesObject.self, "(ANY setInt8 IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.setInt8.containsAny(in: [Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(ANY setInt16 IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.setInt16.containsAny(in: [Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(ANY setInt32 IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.setInt32.containsAny(in: [Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(ANY setInt64 IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.setInt64.containsAny(in: [Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(ANY setFloat IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.setFloat.containsAny(in: [Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(ANY setDouble IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.setDouble.containsAny(in: [123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(ANY setString IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.setString.containsAny(in: ["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(ANY setBinary IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.setBinary.containsAny(in: [Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(ANY setDate IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.setDate.containsAny(in: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(ANY setDecimal IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.setDecimal.containsAny(in: [Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(ANY setObjectId IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.setObjectId.containsAny(in: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(ANY setUuid IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.setUuid.containsAny(in: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(ANY setAny IN %@)", values: [NSArray(array: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")])], count: 1) { $0.setAny.containsAny(in: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt IN %@)", values: [NSArray(array: [EnumInt.value1, EnumInt.value2])], count: 1) { $0.setInt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt8 IN %@)", values: [NSArray(array: [EnumInt8.value1, EnumInt8.value2])], count: 1) { $0.setInt8.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt16 IN %@)", values: [NSArray(array: [EnumInt16.value1, EnumInt16.value2])], count: 1) { $0.setInt16.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt32 IN %@)", values: [NSArray(array: [EnumInt32.value1, EnumInt32.value2])], count: 1) { $0.setInt32.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt64 IN %@)", values: [NSArray(array: [EnumInt64.value1, EnumInt64.value2])], count: 1) { $0.setInt64.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setFloat IN %@)", values: [NSArray(array: [EnumFloat.value1, EnumFloat.value2])], count: 1) { $0.setFloat.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setDouble IN %@)", values: [NSArray(array: [EnumDouble.value1, EnumDouble.value2])], count: 1) { $0.setDouble.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setString IN %@)", values: [NSArray(array: [EnumString.value1, EnumString.value2])], count: 1) { $0.setString.containsAny(in: [.value1, .value2]) } assertQuery(CustomPersistableCollections.self, "(ANY setBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)])], count: 1) { $0.setBool.containsAny(in: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) } assertQuery(CustomPersistableCollections.self, "(ANY setInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.setInt.containsAny(in: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(CustomPersistableCollections.self, "(ANY setInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.setInt8.containsAny(in: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(CustomPersistableCollections.self, "(ANY setInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.setInt16.containsAny(in: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(CustomPersistableCollections.self, "(ANY setInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.setInt32.containsAny(in: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(CustomPersistableCollections.self, "(ANY setInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.setInt64.containsAny(in: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(CustomPersistableCollections.self, "(ANY setFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.setFloat.containsAny(in: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(CustomPersistableCollections.self, "(ANY setDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.setDouble.containsAny(in: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(CustomPersistableCollections.self, "(ANY setString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.setString.containsAny(in: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(CustomPersistableCollections.self, "(ANY setBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.setBinary.containsAny(in: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(CustomPersistableCollections.self, "(ANY setDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.setDate.containsAny(in: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(CustomPersistableCollections.self, "(ANY setDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.setDecimal.containsAny(in: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(CustomPersistableCollections.self, "(ANY setObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.setObjectId.containsAny(in: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(CustomPersistableCollections.self, "(ANY setUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.setUuid.containsAny(in: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptBool IN %@)", values: [NSArray(array: [true, true])], count: 1) { $0.setOptBool.containsAny(in: [true, true]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.setOptInt.containsAny(in: [1, 3]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt8 IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.setOptInt8.containsAny(in: [Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt16 IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.setOptInt16.containsAny(in: [Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt32 IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.setOptInt32.containsAny(in: [Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt64 IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.setOptInt64.containsAny(in: [Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptFloat IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.setOptFloat.containsAny(in: [Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDouble IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.setOptDouble.containsAny(in: [123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptString IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.setOptString.containsAny(in: ["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptBinary IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.setOptBinary.containsAny(in: [Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDate IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.setOptDate.containsAny(in: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDecimal IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.setOptDecimal.containsAny(in: [Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptObjectId IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.setOptObjectId.containsAny(in: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(ANY setOptUuid IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.setOptUuid.containsAny(in: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setIntOpt IN %@)", values: [NSArray(array: [EnumInt.value1, EnumInt.value2])], count: 1) { $0.setIntOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt8Opt IN %@)", values: [NSArray(array: [EnumInt8.value1, EnumInt8.value2])], count: 1) { $0.setInt8Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt16Opt IN %@)", values: [NSArray(array: [EnumInt16.value1, EnumInt16.value2])], count: 1) { $0.setInt16Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt32Opt IN %@)", values: [NSArray(array: [EnumInt32.value1, EnumInt32.value2])], count: 1) { $0.setInt32Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt64Opt IN %@)", values: [NSArray(array: [EnumInt64.value1, EnumInt64.value2])], count: 1) { $0.setInt64Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setFloatOpt IN %@)", values: [NSArray(array: [EnumFloat.value1, EnumFloat.value2])], count: 1) { $0.setFloatOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setDoubleOpt IN %@)", values: [NSArray(array: [EnumDouble.value1, EnumDouble.value2])], count: 1) { $0.setDoubleOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setStringOpt IN %@)", values: [NSArray(array: [EnumString.value1, EnumString.value2])], count: 1) { $0.setStringOpt.containsAny(in: [.value1, .value2]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)])], count: 1) { $0.setOptBool.containsAny(in: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.setOptInt.containsAny(in: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.setOptInt8.containsAny(in: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.setOptInt16.containsAny(in: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.setOptInt32.containsAny(in: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.setOptInt64.containsAny(in: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.setOptFloat.containsAny(in: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.setOptDouble.containsAny(in: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.setOptString.containsAny(in: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.setOptBinary.containsAny(in: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.setOptDate.containsAny(in: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.setOptDecimal.containsAny(in: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.setOptObjectId.containsAny(in: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(CustomPersistableCollections.self, "(ANY setOptUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.setOptUuid.containsAny(in: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } let colObj = ModernCollectionObject() let obj = objects().first! colObj.set.insert(obj) try! realm.write { realm.add(colObj) } assertQuery(ModernCollectionObject.self, "(ANY set IN %@)", values: [NSArray(array: [obj])], count: 1) { $0.set.containsAny(in: [obj]) } } // MARK: - Map private func validateAllKeys(_ name: String, _ lhs: (Query) -> Query) where T.Key == String { assertQuery(Root.self, "(ANY \(name).@allKeys == %@)", "foo", count: 1) { lhs($0).keys == "foo" } assertQuery(Root.self, "(ANY \(name).@allKeys != %@)", "foo", count: 1) { lhs($0).keys != "foo" } assertQuery(Root.self, "(ANY \(name).@allKeys CONTAINS[cd] %@)", "foo", count: 1) { lhs($0).keys.contains("foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys CONTAINS %@)", "foo", count: 1) { lhs($0).keys.contains("foo") } assertQuery(Root.self, "(ANY \(name).@allKeys BEGINSWITH[cd] %@)", "foo", count: 1) { lhs($0).keys.starts(with: "foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys BEGINSWITH %@)", "foo", count: 1) { lhs($0).keys.starts(with: "foo") } assertQuery(Root.self, "(ANY \(name).@allKeys ENDSWITH[cd] %@)", "foo", count: 1) { lhs($0).keys.ends(with: "foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys ENDSWITH %@)", "foo", count: 1) { lhs($0).keys.ends(with: "foo") } assertQuery(Root.self, "(ANY \(name).@allKeys LIKE[c] %@)", "foo", count: 1) { lhs($0).keys.like("foo", caseInsensitive: true) } assertQuery(Root.self, "(ANY \(name).@allKeys LIKE %@)", "foo", count: 1) { lhs($0).keys.like("foo") } } func testMapAllKeys() { validateAllKeys("mapBool", \Query.mapBool) validateAllKeys("mapInt", \Query.mapInt) validateAllKeys("mapInt8", \Query.mapInt8) validateAllKeys("mapInt16", \Query.mapInt16) validateAllKeys("mapInt32", \Query.mapInt32) validateAllKeys("mapInt64", \Query.mapInt64) validateAllKeys("mapFloat", \Query.mapFloat) validateAllKeys("mapDouble", \Query.mapDouble) validateAllKeys("mapString", \Query.mapString) validateAllKeys("mapBinary", \Query.mapBinary) validateAllKeys("mapDate", \Query.mapDate) validateAllKeys("mapDecimal", \Query.mapDecimal) validateAllKeys("mapObjectId", \Query.mapObjectId) validateAllKeys("mapUuid", \Query.mapUuid) validateAllKeys("mapAny", \Query.mapAny) validateAllKeys("mapInt", \Query.mapInt) validateAllKeys("mapInt8", \Query.mapInt8) validateAllKeys("mapInt16", \Query.mapInt16) validateAllKeys("mapInt32", \Query.mapInt32) validateAllKeys("mapInt64", \Query.mapInt64) validateAllKeys("mapFloat", \Query.mapFloat) validateAllKeys("mapDouble", \Query.mapDouble) validateAllKeys("mapString", \Query.mapString) validateAllKeys("mapBool", \Query.mapBool) validateAllKeys("mapInt", \Query.mapInt) validateAllKeys("mapInt8", \Query.mapInt8) validateAllKeys("mapInt16", \Query.mapInt16) validateAllKeys("mapInt32", \Query.mapInt32) validateAllKeys("mapInt64", \Query.mapInt64) validateAllKeys("mapFloat", \Query.mapFloat) validateAllKeys("mapDouble", \Query.mapDouble) validateAllKeys("mapString", \Query.mapString) validateAllKeys("mapBinary", \Query.mapBinary) validateAllKeys("mapDate", \Query.mapDate) validateAllKeys("mapDecimal", \Query.mapDecimal) validateAllKeys("mapObjectId", \Query.mapObjectId) validateAllKeys("mapUuid", \Query.mapUuid) validateAllKeys("mapOptBool", \Query.mapOptBool) validateAllKeys("mapOptInt", \Query.mapOptInt) validateAllKeys("mapOptInt8", \Query.mapOptInt8) validateAllKeys("mapOptInt16", \Query.mapOptInt16) validateAllKeys("mapOptInt32", \Query.mapOptInt32) validateAllKeys("mapOptInt64", \Query.mapOptInt64) validateAllKeys("mapOptFloat", \Query.mapOptFloat) validateAllKeys("mapOptDouble", \Query.mapOptDouble) validateAllKeys("mapOptString", \Query.mapOptString) validateAllKeys("mapOptBinary", \Query.mapOptBinary) validateAllKeys("mapOptDate", \Query.mapOptDate) validateAllKeys("mapOptDecimal", \Query.mapOptDecimal) validateAllKeys("mapOptObjectId", \Query.mapOptObjectId) validateAllKeys("mapOptUuid", \Query.mapOptUuid) validateAllKeys("mapIntOpt", \Query.mapIntOpt) validateAllKeys("mapInt8Opt", \Query.mapInt8Opt) validateAllKeys("mapInt16Opt", \Query.mapInt16Opt) validateAllKeys("mapInt32Opt", \Query.mapInt32Opt) validateAllKeys("mapInt64Opt", \Query.mapInt64Opt) validateAllKeys("mapFloatOpt", \Query.mapFloatOpt) validateAllKeys("mapDoubleOpt", \Query.mapDoubleOpt) validateAllKeys("mapStringOpt", \Query.mapStringOpt) validateAllKeys("mapOptBool", \Query.mapOptBool) validateAllKeys("mapOptInt", \Query.mapOptInt) validateAllKeys("mapOptInt8", \Query.mapOptInt8) validateAllKeys("mapOptInt16", \Query.mapOptInt16) validateAllKeys("mapOptInt32", \Query.mapOptInt32) validateAllKeys("mapOptInt64", \Query.mapOptInt64) validateAllKeys("mapOptFloat", \Query.mapOptFloat) validateAllKeys("mapOptDouble", \Query.mapOptDouble) validateAllKeys("mapOptString", \Query.mapOptString) validateAllKeys("mapOptBinary", \Query.mapOptBinary) validateAllKeys("mapOptDate", \Query.mapOptDate) validateAllKeys("mapOptDecimal", \Query.mapOptDecimal) validateAllKeys("mapOptObjectId", \Query.mapOptObjectId) validateAllKeys("mapOptUuid", \Query.mapOptUuid) } // swiftlint:disable unused_closure_parameter func testMapAllValues() { validateEquals("ANY mapBool.@allValues", \Query.mapBool.values, true) validateEquals("ANY mapInt.@allValues", \Query.mapInt.values, 1, notEqualCount: 1) validateNumericComparisons("ANY mapInt.@allValues", \Query.mapInt.values, 3, ltCount: 1) validateEquals("ANY mapInt8.@allValues", \Query.mapInt8.values, Int8(8), notEqualCount: 1) validateNumericComparisons("ANY mapInt8.@allValues", \Query.mapInt8.values, Int8(9), ltCount: 1) validateEquals("ANY mapInt16.@allValues", \Query.mapInt16.values, Int16(16), notEqualCount: 1) validateNumericComparisons("ANY mapInt16.@allValues", \Query.mapInt16.values, Int16(17), ltCount: 1) validateEquals("ANY mapInt32.@allValues", \Query.mapInt32.values, Int32(32), notEqualCount: 1) validateNumericComparisons("ANY mapInt32.@allValues", \Query.mapInt32.values, Int32(33), ltCount: 1) validateEquals("ANY mapInt64.@allValues", \Query.mapInt64.values, Int64(64), notEqualCount: 1) validateNumericComparisons("ANY mapInt64.@allValues", \Query.mapInt64.values, Int64(65), ltCount: 1) validateEquals("ANY mapFloat.@allValues", \Query.mapFloat.values, Float(5.55444333), notEqualCount: 1) validateNumericComparisons("ANY mapFloat.@allValues", \Query.mapFloat.values, Float(6.55444333), ltCount: 1) validateEquals("ANY mapDouble.@allValues", \Query.mapDouble.values, 123.456, notEqualCount: 1) validateNumericComparisons("ANY mapDouble.@allValues", \Query.mapDouble.values, 234.567, ltCount: 1) validateEquals("ANY mapString.@allValues", \Query.mapString.values, "Foo", notEqualCount: 1) validateStringOperations("ANY mapString.@allValues", \Query.mapString.values, ("Foo", "Foo", "Foo")) { equals, options in // Non-enum maps have the keys Foo and Foó, so !=[d] doesn't match any if options.contains(.diacriticInsensitive) { return equals ? 1 : 0 } return 1 } assertQuery(ModernAllTypesObject.self, "(ANY mapString.@allValues LIKE[c] %@)", "Foo", count: 1) { $0.mapString.values.like("Foo", caseInsensitive: true) } assertQuery(ModernAllTypesObject.self, "(ANY mapString.@allValues LIKE %@)", "Foo", count: 1) { $0.mapString.values.like("Foo") } validateEquals("ANY mapBinary.@allValues", \Query.mapBinary.values, Data(count: 64), notEqualCount: 1) validateEquals("ANY mapDate.@allValues", \Query.mapDate.values, Date(timeIntervalSince1970: 1000000), notEqualCount: 1) validateNumericComparisons("ANY mapDate.@allValues", \Query.mapDate.values, Date(timeIntervalSince1970: 2000000), ltCount: 1) validateEquals("ANY mapDecimal.@allValues", \Query.mapDecimal.values, Decimal128(123.456), notEqualCount: 1) validateNumericComparisons("ANY mapDecimal.@allValues", \Query.mapDecimal.values, Decimal128(234.567), ltCount: 1) validateEquals("ANY mapObjectId.@allValues", \Query.mapObjectId.values, ObjectId("61184062c1d8f096a3695046"), notEqualCount: 1) validateEquals("ANY mapUuid.@allValues", \Query.mapUuid.values, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, notEqualCount: 1) validateEquals("ANY mapAny.@allValues", \Query.mapAny.values, AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), notEqualCount: 1) validateEquals("ANY mapInt.@allValues", \Query.mapInt.values, EnumInt.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt.@allValues", \Query.mapInt.values, .value2, ltCount: 1) validateEquals("ANY mapInt8.@allValues", \Query.mapInt8.values, EnumInt8.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt8.@allValues", \Query.mapInt8.values, .value2, ltCount: 1) validateEquals("ANY mapInt16.@allValues", \Query.mapInt16.values, EnumInt16.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt16.@allValues", \Query.mapInt16.values, .value2, ltCount: 1) validateEquals("ANY mapInt32.@allValues", \Query.mapInt32.values, EnumInt32.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt32.@allValues", \Query.mapInt32.values, .value2, ltCount: 1) validateEquals("ANY mapInt64.@allValues", \Query.mapInt64.values, EnumInt64.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt64.@allValues", \Query.mapInt64.values, .value2, ltCount: 1) validateEquals("ANY mapFloat.@allValues", \Query.mapFloat.values, EnumFloat.value1, notEqualCount: 1) validateNumericComparisons("ANY mapFloat.@allValues", \Query.mapFloat.values, .value2, ltCount: 1) validateEquals("ANY mapDouble.@allValues", \Query.mapDouble.values, EnumDouble.value1, notEqualCount: 1) validateNumericComparisons("ANY mapDouble.@allValues", \Query.mapDouble.values, .value2, ltCount: 1) validateEquals("ANY mapString.@allValues", \Query.mapString.values, EnumString.value1, notEqualCount: 1) validateStringOperations("ANY mapString.@allValues", \Query.mapString.values, (.value1, .value1, .value1)) { equals, options in return 1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapString.@allValues LIKE[c] %@)", EnumString.value1, count: 1) { $0.mapString.values.like(.value1, caseInsensitive: true) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapString.@allValues LIKE %@)", EnumString.value1, count: 1) { $0.mapString.values.like(.value1) } validateEquals("ANY mapBool.@allValues", \Query.mapBool.values, BoolWrapper(persistedValue: true)) validateEquals("ANY mapInt.@allValues", \Query.mapInt.values, IntWrapper(persistedValue: 1), notEqualCount: 1) validateNumericComparisons("ANY mapInt.@allValues", \Query.mapInt.values, IntWrapper(persistedValue: 3), ltCount: 1) validateEquals("ANY mapInt8.@allValues", \Query.mapInt8.values, Int8Wrapper(persistedValue: Int8(8)), notEqualCount: 1) validateNumericComparisons("ANY mapInt8.@allValues", \Query.mapInt8.values, Int8Wrapper(persistedValue: Int8(9)), ltCount: 1) validateEquals("ANY mapInt16.@allValues", \Query.mapInt16.values, Int16Wrapper(persistedValue: Int16(16)), notEqualCount: 1) validateNumericComparisons("ANY mapInt16.@allValues", \Query.mapInt16.values, Int16Wrapper(persistedValue: Int16(17)), ltCount: 1) validateEquals("ANY mapInt32.@allValues", \Query.mapInt32.values, Int32Wrapper(persistedValue: Int32(32)), notEqualCount: 1) validateNumericComparisons("ANY mapInt32.@allValues", \Query.mapInt32.values, Int32Wrapper(persistedValue: Int32(33)), ltCount: 1) validateEquals("ANY mapInt64.@allValues", \Query.mapInt64.values, Int64Wrapper(persistedValue: Int64(64)), notEqualCount: 1) validateNumericComparisons("ANY mapInt64.@allValues", \Query.mapInt64.values, Int64Wrapper(persistedValue: Int64(65)), ltCount: 1) validateEquals("ANY mapFloat.@allValues", \Query.mapFloat.values, FloatWrapper(persistedValue: Float(5.55444333)), notEqualCount: 1) validateNumericComparisons("ANY mapFloat.@allValues", \Query.mapFloat.values, FloatWrapper(persistedValue: Float(6.55444333)), ltCount: 1) validateEquals("ANY mapDouble.@allValues", \Query.mapDouble.values, DoubleWrapper(persistedValue: 123.456), notEqualCount: 1) validateNumericComparisons("ANY mapDouble.@allValues", \Query.mapDouble.values, DoubleWrapper(persistedValue: 234.567), ltCount: 1) validateEquals("ANY mapString.@allValues", \Query.mapString.values, StringWrapper(persistedValue: "Foo"), notEqualCount: 1) validateStringOperations("ANY mapString.@allValues", \Query.mapString.values, (StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foo"))) { equals, options in // Non-enum maps have the keys Foo and Foó, so !=[d] doesn't match any if options.contains(.diacriticInsensitive) { return equals ? 1 : 0 } return 1 } assertQuery(CustomPersistableCollections.self, "(ANY mapString.@allValues LIKE[c] %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.mapString.values.like(StringWrapper(persistedValue: "Foo"), caseInsensitive: true) } assertQuery(CustomPersistableCollections.self, "(ANY mapString.@allValues LIKE %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.mapString.values.like(StringWrapper(persistedValue: "Foo")) } validateEquals("ANY mapBinary.@allValues", \Query.mapBinary.values, DataWrapper(persistedValue: Data(count: 64)), notEqualCount: 1) validateEquals("ANY mapDate.@allValues", \Query.mapDate.values, DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), notEqualCount: 1) validateNumericComparisons("ANY mapDate.@allValues", \Query.mapDate.values, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), ltCount: 1) validateEquals("ANY mapDecimal.@allValues", \Query.mapDecimal.values, Decimal128Wrapper(persistedValue: Decimal128(123.456)), notEqualCount: 1) validateNumericComparisons("ANY mapDecimal.@allValues", \Query.mapDecimal.values, Decimal128Wrapper(persistedValue: Decimal128(234.567)), ltCount: 1) validateEquals("ANY mapObjectId.@allValues", \Query.mapObjectId.values, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), notEqualCount: 1) validateEquals("ANY mapUuid.@allValues", \Query.mapUuid.values, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), notEqualCount: 1) validateEquals("ANY mapOptBool.@allValues", \Query.mapOptBool.values, true) validateEquals("ANY mapOptInt.@allValues", \Query.mapOptInt.values, 1, notEqualCount: 1) validateNumericComparisons("ANY mapOptInt.@allValues", \Query.mapOptInt.values, 3, ltCount: 1) validateEquals("ANY mapOptInt8.@allValues", \Query.mapOptInt8.values, Int8(8), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt8.@allValues", \Query.mapOptInt8.values, Int8(9), ltCount: 1) validateEquals("ANY mapOptInt16.@allValues", \Query.mapOptInt16.values, Int16(16), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt16.@allValues", \Query.mapOptInt16.values, Int16(17), ltCount: 1) validateEquals("ANY mapOptInt32.@allValues", \Query.mapOptInt32.values, Int32(32), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt32.@allValues", \Query.mapOptInt32.values, Int32(33), ltCount: 1) validateEquals("ANY mapOptInt64.@allValues", \Query.mapOptInt64.values, Int64(64), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt64.@allValues", \Query.mapOptInt64.values, Int64(65), ltCount: 1) validateEquals("ANY mapOptFloat.@allValues", \Query.mapOptFloat.values, Float(5.55444333), notEqualCount: 1) validateNumericComparisons("ANY mapOptFloat.@allValues", \Query.mapOptFloat.values, Float(6.55444333), ltCount: 1) validateEquals("ANY mapOptDouble.@allValues", \Query.mapOptDouble.values, 123.456, notEqualCount: 1) validateNumericComparisons("ANY mapOptDouble.@allValues", \Query.mapOptDouble.values, 234.567, ltCount: 1) validateEquals("ANY mapOptString.@allValues", \Query.mapOptString.values, "Foo", notEqualCount: 1) validateStringOperations("ANY mapOptString.@allValues", \Query.mapOptString.values, ("Foo", "Foo", "Foo")) { equals, options in // Non-enum maps have the keys Foo and Foó, so !=[d] doesn't match any if options.contains(.diacriticInsensitive) { return equals ? 1 : 0 } return 1 } assertQuery(ModernAllTypesObject.self, "(ANY mapOptString.@allValues LIKE[c] %@)", "Foo", count: 1) { $0.mapOptString.values.like("Foo", caseInsensitive: true) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptString.@allValues LIKE %@)", "Foo", count: 1) { $0.mapOptString.values.like("Foo") } validateEquals("ANY mapOptBinary.@allValues", \Query.mapOptBinary.values, Data(count: 64), notEqualCount: 1) validateEquals("ANY mapOptDate.@allValues", \Query.mapOptDate.values, Date(timeIntervalSince1970: 1000000), notEqualCount: 1) validateNumericComparisons("ANY mapOptDate.@allValues", \Query.mapOptDate.values, Date(timeIntervalSince1970: 2000000), ltCount: 1) validateEquals("ANY mapOptDecimal.@allValues", \Query.mapOptDecimal.values, Decimal128(123.456), notEqualCount: 1) validateNumericComparisons("ANY mapOptDecimal.@allValues", \Query.mapOptDecimal.values, Decimal128(234.567), ltCount: 1) validateEquals("ANY mapOptObjectId.@allValues", \Query.mapOptObjectId.values, ObjectId("61184062c1d8f096a3695046"), notEqualCount: 1) validateEquals("ANY mapOptUuid.@allValues", \Query.mapOptUuid.values, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, notEqualCount: 1) validateEquals("ANY mapIntOpt.@allValues", \Query.mapIntOpt.values, EnumInt.value1, notEqualCount: 1) validateNumericComparisons("ANY mapIntOpt.@allValues", \Query.mapIntOpt.values, .value2, ltCount: 1) validateEquals("ANY mapInt8Opt.@allValues", \Query.mapInt8Opt.values, EnumInt8.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt8Opt.@allValues", \Query.mapInt8Opt.values, .value2, ltCount: 1) validateEquals("ANY mapInt16Opt.@allValues", \Query.mapInt16Opt.values, EnumInt16.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt16Opt.@allValues", \Query.mapInt16Opt.values, .value2, ltCount: 1) validateEquals("ANY mapInt32Opt.@allValues", \Query.mapInt32Opt.values, EnumInt32.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt32Opt.@allValues", \Query.mapInt32Opt.values, .value2, ltCount: 1) validateEquals("ANY mapInt64Opt.@allValues", \Query.mapInt64Opt.values, EnumInt64.value1, notEqualCount: 1) validateNumericComparisons("ANY mapInt64Opt.@allValues", \Query.mapInt64Opt.values, .value2, ltCount: 1) validateEquals("ANY mapFloatOpt.@allValues", \Query.mapFloatOpt.values, EnumFloat.value1, notEqualCount: 1) validateNumericComparisons("ANY mapFloatOpt.@allValues", \Query.mapFloatOpt.values, .value2, ltCount: 1) validateEquals("ANY mapDoubleOpt.@allValues", \Query.mapDoubleOpt.values, EnumDouble.value1, notEqualCount: 1) validateNumericComparisons("ANY mapDoubleOpt.@allValues", \Query.mapDoubleOpt.values, .value2, ltCount: 1) validateEquals("ANY mapStringOpt.@allValues", \Query.mapStringOpt.values, EnumString.value1, notEqualCount: 1) validateStringOperations("ANY mapStringOpt.@allValues", \Query.mapStringOpt.values, (.value1, .value1, .value1)) { equals, options in return 1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapStringOpt.@allValues LIKE[c] %@)", EnumString.value1, count: 1) { $0.mapStringOpt.values.like(.value1, caseInsensitive: true) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapStringOpt.@allValues LIKE %@)", EnumString.value1, count: 1) { $0.mapStringOpt.values.like(.value1) } validateEquals("ANY mapOptBool.@allValues", \Query.mapOptBool.values, BoolWrapper(persistedValue: true)) validateEquals("ANY mapOptInt.@allValues", \Query.mapOptInt.values, IntWrapper(persistedValue: 1), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt.@allValues", \Query.mapOptInt.values, IntWrapper(persistedValue: 3), ltCount: 1) validateEquals("ANY mapOptInt8.@allValues", \Query.mapOptInt8.values, Int8Wrapper(persistedValue: Int8(8)), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt8.@allValues", \Query.mapOptInt8.values, Int8Wrapper(persistedValue: Int8(9)), ltCount: 1) validateEquals("ANY mapOptInt16.@allValues", \Query.mapOptInt16.values, Int16Wrapper(persistedValue: Int16(16)), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt16.@allValues", \Query.mapOptInt16.values, Int16Wrapper(persistedValue: Int16(17)), ltCount: 1) validateEquals("ANY mapOptInt32.@allValues", \Query.mapOptInt32.values, Int32Wrapper(persistedValue: Int32(32)), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt32.@allValues", \Query.mapOptInt32.values, Int32Wrapper(persistedValue: Int32(33)), ltCount: 1) validateEquals("ANY mapOptInt64.@allValues", \Query.mapOptInt64.values, Int64Wrapper(persistedValue: Int64(64)), notEqualCount: 1) validateNumericComparisons("ANY mapOptInt64.@allValues", \Query.mapOptInt64.values, Int64Wrapper(persistedValue: Int64(65)), ltCount: 1) validateEquals("ANY mapOptFloat.@allValues", \Query.mapOptFloat.values, FloatWrapper(persistedValue: Float(5.55444333)), notEqualCount: 1) validateNumericComparisons("ANY mapOptFloat.@allValues", \Query.mapOptFloat.values, FloatWrapper(persistedValue: Float(6.55444333)), ltCount: 1) validateEquals("ANY mapOptDouble.@allValues", \Query.mapOptDouble.values, DoubleWrapper(persistedValue: 123.456), notEqualCount: 1) validateNumericComparisons("ANY mapOptDouble.@allValues", \Query.mapOptDouble.values, DoubleWrapper(persistedValue: 234.567), ltCount: 1) validateEquals("ANY mapOptString.@allValues", \Query.mapOptString.values, StringWrapper(persistedValue: "Foo"), notEqualCount: 1) validateStringOperations("ANY mapOptString.@allValues", \Query.mapOptString.values, (StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foo"))) { equals, options in // Non-enum maps have the keys Foo and Foó, so !=[d] doesn't match any if options.contains(.diacriticInsensitive) { return equals ? 1 : 0 } return 1 } assertQuery(CustomPersistableCollections.self, "(ANY mapOptString.@allValues LIKE[c] %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.mapOptString.values.like(StringWrapper(persistedValue: "Foo"), caseInsensitive: true) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptString.@allValues LIKE %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.mapOptString.values.like(StringWrapper(persistedValue: "Foo")) } validateEquals("ANY mapOptBinary.@allValues", \Query.mapOptBinary.values, DataWrapper(persistedValue: Data(count: 64)), notEqualCount: 1) validateEquals("ANY mapOptDate.@allValues", \Query.mapOptDate.values, DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), notEqualCount: 1) validateNumericComparisons("ANY mapOptDate.@allValues", \Query.mapOptDate.values, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), ltCount: 1) validateEquals("ANY mapOptDecimal.@allValues", \Query.mapOptDecimal.values, Decimal128Wrapper(persistedValue: Decimal128(123.456)), notEqualCount: 1) validateNumericComparisons("ANY mapOptDecimal.@allValues", \Query.mapOptDecimal.values, Decimal128Wrapper(persistedValue: Decimal128(234.567)), ltCount: 1) validateEquals("ANY mapOptObjectId.@allValues", \Query.mapOptObjectId.values, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), notEqualCount: 1) validateEquals("ANY mapOptUuid.@allValues", \Query.mapOptUuid.values, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), notEqualCount: 1) } // swiftlint:enable unused_closure_parameter func testMapContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.map.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.map["foo"] = obj } XCTAssertEqual(result.count, 1) } private func validateMapSubscriptEquality(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Key == String { assertQuery(Root.self, "(\(name)[%@] == %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] == value } assertQuery(Root.self, "(\(name)[%@] != %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] != value } } private func validateMapSubscriptNumericComparisons(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Value.PersistedType: _QueryNumeric, T.Key == String { assertQuery(Root.self, "(\(name)[%@] > %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] > value } assertQuery(Root.self, "(\(name)[%@] >= %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] >= value } assertQuery(Root.self, "(\(name)[%@] < %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] < value } assertQuery(Root.self, "(\(name)[%@] <= %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] <= value } } private func validateMapSubscriptStringComparisons(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Value.PersistedType: _QueryString, T.Key == String { assertQuery(Root.self, "(\(name)[%@] CONTAINS[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].contains(value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] CONTAINS %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].contains(value) } assertQuery(Root.self, "(NOT \(name)[%@] CONTAINS %@)", values: ["foo", value], count: 0) { !lhs($0)["foo"].contains(value) } assertQuery(Root.self, "(\(name)[%@] BEGINSWITH[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].starts(with: value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] BEGINSWITH %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].starts(with: value) } assertQuery(Root.self, "(\(name)[%@] ENDSWITH[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].ends(with: value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] ENDSWITH %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].ends(with: value) } assertQuery(Root.self, "(\(name)[%@] LIKE[c] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].like(value, caseInsensitive: true) } assertQuery(Root.self, "(\(name)[%@] LIKE %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].like(value) } } func testMapAllKeysAllValuesSubscript() { validateMapSubscriptEquality("mapBool", \Query.mapBool, value: true) validateMapSubscriptEquality("mapInt", \Query.mapInt, value: 1) validateMapSubscriptNumericComparisons("mapInt", \Query.mapInt, value: 1) validateMapSubscriptEquality("mapInt8", \Query.mapInt8, value: Int8(8)) validateMapSubscriptNumericComparisons("mapInt8", \Query.mapInt8, value: Int8(8)) validateMapSubscriptEquality("mapInt16", \Query.mapInt16, value: Int16(16)) validateMapSubscriptNumericComparisons("mapInt16", \Query.mapInt16, value: Int16(16)) validateMapSubscriptEquality("mapInt32", \Query.mapInt32, value: Int32(32)) validateMapSubscriptNumericComparisons("mapInt32", \Query.mapInt32, value: Int32(32)) validateMapSubscriptEquality("mapInt64", \Query.mapInt64, value: Int64(64)) validateMapSubscriptNumericComparisons("mapInt64", \Query.mapInt64, value: Int64(64)) validateMapSubscriptEquality("mapFloat", \Query.mapFloat, value: Float(5.55444333)) validateMapSubscriptNumericComparisons("mapFloat", \Query.mapFloat, value: Float(5.55444333)) validateMapSubscriptEquality("mapDouble", \Query.mapDouble, value: 123.456) validateMapSubscriptNumericComparisons("mapDouble", \Query.mapDouble, value: 123.456) validateMapSubscriptEquality("mapString", \Query.mapString, value: "Foo") validateMapSubscriptStringComparisons("mapString", \Query.mapString, value: "Foo") validateMapSubscriptEquality("mapBinary", \Query.mapBinary, value: Data(count: 64)) validateMapSubscriptEquality("mapDate", \Query.mapDate, value: Date(timeIntervalSince1970: 1000000)) validateMapSubscriptNumericComparisons("mapDate", \Query.mapDate, value: Date(timeIntervalSince1970: 1000000)) validateMapSubscriptEquality("mapDecimal", \Query.mapDecimal, value: Decimal128(123.456)) validateMapSubscriptNumericComparisons("mapDecimal", \Query.mapDecimal, value: Decimal128(123.456)) validateMapSubscriptEquality("mapObjectId", \Query.mapObjectId, value: ObjectId("61184062c1d8f096a3695046")) validateMapSubscriptEquality("mapUuid", \Query.mapUuid, value: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) validateMapSubscriptEquality("mapAny", \Query.mapAny, value: AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046"))) validateMapSubscriptEquality("mapInt", \Query.mapInt, value: .value1) validateMapSubscriptNumericComparisons("mapInt", \Query.mapInt, value: .value1) validateMapSubscriptEquality("mapInt8", \Query.mapInt8, value: .value1) validateMapSubscriptNumericComparisons("mapInt8", \Query.mapInt8, value: .value1) validateMapSubscriptEquality("mapInt16", \Query.mapInt16, value: .value1) validateMapSubscriptNumericComparisons("mapInt16", \Query.mapInt16, value: .value1) validateMapSubscriptEquality("mapInt32", \Query.mapInt32, value: .value1) validateMapSubscriptNumericComparisons("mapInt32", \Query.mapInt32, value: .value1) validateMapSubscriptEquality("mapInt64", \Query.mapInt64, value: .value1) validateMapSubscriptNumericComparisons("mapInt64", \Query.mapInt64, value: .value1) validateMapSubscriptEquality("mapFloat", \Query.mapFloat, value: .value1) validateMapSubscriptNumericComparisons("mapFloat", \Query.mapFloat, value: .value1) validateMapSubscriptEquality("mapDouble", \Query.mapDouble, value: .value1) validateMapSubscriptNumericComparisons("mapDouble", \Query.mapDouble, value: .value1) validateMapSubscriptEquality("mapString", \Query.mapString, value: .value1) validateMapSubscriptStringComparisons("mapString", \Query.mapString, value: .value1) validateMapSubscriptEquality("mapBool", \Query.mapBool, value: BoolWrapper(persistedValue: true)) validateMapSubscriptEquality("mapInt", \Query.mapInt, value: IntWrapper(persistedValue: 1)) validateMapSubscriptNumericComparisons("mapInt", \Query.mapInt, value: IntWrapper(persistedValue: 1)) validateMapSubscriptEquality("mapInt8", \Query.mapInt8, value: Int8Wrapper(persistedValue: Int8(8))) validateMapSubscriptNumericComparisons("mapInt8", \Query.mapInt8, value: Int8Wrapper(persistedValue: Int8(8))) validateMapSubscriptEquality("mapInt16", \Query.mapInt16, value: Int16Wrapper(persistedValue: Int16(16))) validateMapSubscriptNumericComparisons("mapInt16", \Query.mapInt16, value: Int16Wrapper(persistedValue: Int16(16))) validateMapSubscriptEquality("mapInt32", \Query.mapInt32, value: Int32Wrapper(persistedValue: Int32(32))) validateMapSubscriptNumericComparisons("mapInt32", \Query.mapInt32, value: Int32Wrapper(persistedValue: Int32(32))) validateMapSubscriptEquality("mapInt64", \Query.mapInt64, value: Int64Wrapper(persistedValue: Int64(64))) validateMapSubscriptNumericComparisons("mapInt64", \Query.mapInt64, value: Int64Wrapper(persistedValue: Int64(64))) validateMapSubscriptEquality("mapFloat", \Query.mapFloat, value: FloatWrapper(persistedValue: Float(5.55444333))) validateMapSubscriptNumericComparisons("mapFloat", \Query.mapFloat, value: FloatWrapper(persistedValue: Float(5.55444333))) validateMapSubscriptEquality("mapDouble", \Query.mapDouble, value: DoubleWrapper(persistedValue: 123.456)) validateMapSubscriptNumericComparisons("mapDouble", \Query.mapDouble, value: DoubleWrapper(persistedValue: 123.456)) validateMapSubscriptEquality("mapString", \Query.mapString, value: StringWrapper(persistedValue: "Foo")) validateMapSubscriptStringComparisons("mapString", \Query.mapString, value: StringWrapper(persistedValue: "Foo")) validateMapSubscriptEquality("mapBinary", \Query.mapBinary, value: DataWrapper(persistedValue: Data(count: 64))) validateMapSubscriptEquality("mapDate", \Query.mapDate, value: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000))) validateMapSubscriptNumericComparisons("mapDate", \Query.mapDate, value: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000))) validateMapSubscriptEquality("mapDecimal", \Query.mapDecimal, value: Decimal128Wrapper(persistedValue: Decimal128(123.456))) validateMapSubscriptNumericComparisons("mapDecimal", \Query.mapDecimal, value: Decimal128Wrapper(persistedValue: Decimal128(123.456))) validateMapSubscriptEquality("mapObjectId", \Query.mapObjectId, value: ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046"))) validateMapSubscriptEquality("mapUuid", \Query.mapUuid, value: UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!)) validateMapSubscriptEquality("mapOptBool", \Query.mapOptBool, value: true) validateMapSubscriptEquality("mapOptInt", \Query.mapOptInt, value: 1) validateMapSubscriptNumericComparisons("mapOptInt", \Query.mapOptInt, value: 1) validateMapSubscriptEquality("mapOptInt8", \Query.mapOptInt8, value: Int8(8)) validateMapSubscriptNumericComparisons("mapOptInt8", \Query.mapOptInt8, value: Int8(8)) validateMapSubscriptEquality("mapOptInt16", \Query.mapOptInt16, value: Int16(16)) validateMapSubscriptNumericComparisons("mapOptInt16", \Query.mapOptInt16, value: Int16(16)) validateMapSubscriptEquality("mapOptInt32", \Query.mapOptInt32, value: Int32(32)) validateMapSubscriptNumericComparisons("mapOptInt32", \Query.mapOptInt32, value: Int32(32)) validateMapSubscriptEquality("mapOptInt64", \Query.mapOptInt64, value: Int64(64)) validateMapSubscriptNumericComparisons("mapOptInt64", \Query.mapOptInt64, value: Int64(64)) validateMapSubscriptEquality("mapOptFloat", \Query.mapOptFloat, value: Float(5.55444333)) validateMapSubscriptNumericComparisons("mapOptFloat", \Query.mapOptFloat, value: Float(5.55444333)) validateMapSubscriptEquality("mapOptDouble", \Query.mapOptDouble, value: 123.456) validateMapSubscriptNumericComparisons("mapOptDouble", \Query.mapOptDouble, value: 123.456) validateMapSubscriptEquality("mapOptString", \Query.mapOptString, value: "Foo") validateMapSubscriptStringComparisons("mapOptString", \Query.mapOptString, value: "Foo") validateMapSubscriptEquality("mapOptBinary", \Query.mapOptBinary, value: Data(count: 64)) validateMapSubscriptEquality("mapOptDate", \Query.mapOptDate, value: Date(timeIntervalSince1970: 1000000)) validateMapSubscriptNumericComparisons("mapOptDate", \Query.mapOptDate, value: Date(timeIntervalSince1970: 1000000)) validateMapSubscriptEquality("mapOptDecimal", \Query.mapOptDecimal, value: Decimal128(123.456)) validateMapSubscriptNumericComparisons("mapOptDecimal", \Query.mapOptDecimal, value: Decimal128(123.456)) validateMapSubscriptEquality("mapOptObjectId", \Query.mapOptObjectId, value: ObjectId("61184062c1d8f096a3695046")) validateMapSubscriptEquality("mapOptUuid", \Query.mapOptUuid, value: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) validateMapSubscriptEquality("mapIntOpt", \Query.mapIntOpt, value: .value1) validateMapSubscriptNumericComparisons("mapIntOpt", \Query.mapIntOpt, value: .value1) validateMapSubscriptEquality("mapInt8Opt", \Query.mapInt8Opt, value: .value1) validateMapSubscriptNumericComparisons("mapInt8Opt", \Query.mapInt8Opt, value: .value1) validateMapSubscriptEquality("mapInt16Opt", \Query.mapInt16Opt, value: .value1) validateMapSubscriptNumericComparisons("mapInt16Opt", \Query.mapInt16Opt, value: .value1) validateMapSubscriptEquality("mapInt32Opt", \Query.mapInt32Opt, value: .value1) validateMapSubscriptNumericComparisons("mapInt32Opt", \Query.mapInt32Opt, value: .value1) validateMapSubscriptEquality("mapInt64Opt", \Query.mapInt64Opt, value: .value1) validateMapSubscriptNumericComparisons("mapInt64Opt", \Query.mapInt64Opt, value: .value1) validateMapSubscriptEquality("mapFloatOpt", \Query.mapFloatOpt, value: .value1) validateMapSubscriptNumericComparisons("mapFloatOpt", \Query.mapFloatOpt, value: .value1) validateMapSubscriptEquality("mapDoubleOpt", \Query.mapDoubleOpt, value: .value1) validateMapSubscriptNumericComparisons("mapDoubleOpt", \Query.mapDoubleOpt, value: .value1) validateMapSubscriptEquality("mapStringOpt", \Query.mapStringOpt, value: .value1) validateMapSubscriptStringComparisons("mapStringOpt", \Query.mapStringOpt, value: .value1) validateMapSubscriptEquality("mapOptBool", \Query.mapOptBool, value: BoolWrapper(persistedValue: true)) validateMapSubscriptEquality("mapOptInt", \Query.mapOptInt, value: IntWrapper(persistedValue: 1)) validateMapSubscriptNumericComparisons("mapOptInt", \Query.mapOptInt, value: IntWrapper(persistedValue: 1)) validateMapSubscriptEquality("mapOptInt8", \Query.mapOptInt8, value: Int8Wrapper(persistedValue: Int8(8))) validateMapSubscriptNumericComparisons("mapOptInt8", \Query.mapOptInt8, value: Int8Wrapper(persistedValue: Int8(8))) validateMapSubscriptEquality("mapOptInt16", \Query.mapOptInt16, value: Int16Wrapper(persistedValue: Int16(16))) validateMapSubscriptNumericComparisons("mapOptInt16", \Query.mapOptInt16, value: Int16Wrapper(persistedValue: Int16(16))) validateMapSubscriptEquality("mapOptInt32", \Query.mapOptInt32, value: Int32Wrapper(persistedValue: Int32(32))) validateMapSubscriptNumericComparisons("mapOptInt32", \Query.mapOptInt32, value: Int32Wrapper(persistedValue: Int32(32))) validateMapSubscriptEquality("mapOptInt64", \Query.mapOptInt64, value: Int64Wrapper(persistedValue: Int64(64))) validateMapSubscriptNumericComparisons("mapOptInt64", \Query.mapOptInt64, value: Int64Wrapper(persistedValue: Int64(64))) validateMapSubscriptEquality("mapOptFloat", \Query.mapOptFloat, value: FloatWrapper(persistedValue: Float(5.55444333))) validateMapSubscriptNumericComparisons("mapOptFloat", \Query.mapOptFloat, value: FloatWrapper(persistedValue: Float(5.55444333))) validateMapSubscriptEquality("mapOptDouble", \Query.mapOptDouble, value: DoubleWrapper(persistedValue: 123.456)) validateMapSubscriptNumericComparisons("mapOptDouble", \Query.mapOptDouble, value: DoubleWrapper(persistedValue: 123.456)) validateMapSubscriptEquality("mapOptString", \Query.mapOptString, value: StringWrapper(persistedValue: "Foo")) validateMapSubscriptStringComparisons("mapOptString", \Query.mapOptString, value: StringWrapper(persistedValue: "Foo")) validateMapSubscriptEquality("mapOptBinary", \Query.mapOptBinary, value: DataWrapper(persistedValue: Data(count: 64))) validateMapSubscriptEquality("mapOptDate", \Query.mapOptDate, value: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000))) validateMapSubscriptNumericComparisons("mapOptDate", \Query.mapOptDate, value: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000))) validateMapSubscriptEquality("mapOptDecimal", \Query.mapOptDecimal, value: Decimal128Wrapper(persistedValue: Decimal128(123.456))) validateMapSubscriptNumericComparisons("mapOptDecimal", \Query.mapOptDecimal, value: Decimal128Wrapper(persistedValue: Decimal128(123.456))) validateMapSubscriptEquality("mapOptObjectId", \Query.mapOptObjectId, value: ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046"))) validateMapSubscriptEquality("mapOptUuid", \Query.mapOptUuid, value: UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!)) } func testMapSubscriptObject() { assertThrows(assertQuery(ModernCollectionObject.self, "", count: 0) { $0.map["foo"].objectCol.intCol == 5 }, reason: "Cannot apply key path to Map subscripts.") } func testMapContainsAnyInObject() { assertQuery(ModernAllTypesObject.self, "(ANY mapBool IN %@)", values: [NSArray(array: [true, true])], count: 1) { $0.mapBool.containsAny(in: [true, true]) } assertQuery(ModernAllTypesObject.self, "(ANY mapInt IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.mapInt.containsAny(in: [1, 3]) } assertQuery(ModernAllTypesObject.self, "(ANY mapInt8 IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.mapInt8.containsAny(in: [Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapInt16 IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.mapInt16.containsAny(in: [Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapInt32 IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.mapInt32.containsAny(in: [Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapInt64 IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.mapInt64.containsAny(in: [Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapFloat IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.mapFloat.containsAny(in: [Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapDouble IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.mapDouble.containsAny(in: [123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(ANY mapString IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.mapString.containsAny(in: ["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(ANY mapBinary IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.mapBinary.containsAny(in: [Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapDate IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.mapDate.containsAny(in: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapDecimal IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.mapDecimal.containsAny(in: [Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapObjectId IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.mapObjectId.containsAny(in: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(ANY mapUuid IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.mapUuid.containsAny(in: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(ANY mapAny IN %@)", values: [NSArray(array: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")])], count: 1) { $0.mapAny.containsAny(in: [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello")]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt IN %@)", values: [NSArray(array: [EnumInt.value1, EnumInt.value2])], count: 1) { $0.mapInt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt8 IN %@)", values: [NSArray(array: [EnumInt8.value1, EnumInt8.value2])], count: 1) { $0.mapInt8.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt16 IN %@)", values: [NSArray(array: [EnumInt16.value1, EnumInt16.value2])], count: 1) { $0.mapInt16.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt32 IN %@)", values: [NSArray(array: [EnumInt32.value1, EnumInt32.value2])], count: 1) { $0.mapInt32.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt64 IN %@)", values: [NSArray(array: [EnumInt64.value1, EnumInt64.value2])], count: 1) { $0.mapInt64.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapFloat IN %@)", values: [NSArray(array: [EnumFloat.value1, EnumFloat.value2])], count: 1) { $0.mapFloat.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapDouble IN %@)", values: [NSArray(array: [EnumDouble.value1, EnumDouble.value2])], count: 1) { $0.mapDouble.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapString IN %@)", values: [NSArray(array: [EnumString.value1, EnumString.value2])], count: 1) { $0.mapString.containsAny(in: [.value1, .value2]) } assertQuery(CustomPersistableCollections.self, "(ANY mapBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)])], count: 1) { $0.mapBool.containsAny(in: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.mapInt.containsAny(in: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.mapInt8.containsAny(in: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.mapInt16.containsAny(in: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.mapInt32.containsAny(in: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.mapInt64.containsAny(in: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.mapFloat.containsAny(in: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.mapDouble.containsAny(in: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.mapString.containsAny(in: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(CustomPersistableCollections.self, "(ANY mapBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.mapBinary.containsAny(in: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.mapDate.containsAny(in: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.mapDecimal.containsAny(in: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.mapObjectId.containsAny(in: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.mapUuid.containsAny(in: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptBool IN %@)", values: [NSArray(array: [true, true])], count: 1) { $0.mapOptBool.containsAny(in: [true, true]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptInt IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.mapOptInt.containsAny(in: [1, 3]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptInt8 IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.mapOptInt8.containsAny(in: [Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptInt16 IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.mapOptInt16.containsAny(in: [Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptInt32 IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.mapOptInt32.containsAny(in: [Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptInt64 IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.mapOptInt64.containsAny(in: [Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptFloat IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.mapOptFloat.containsAny(in: [Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptDouble IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.mapOptDouble.containsAny(in: [123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptString IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.mapOptString.containsAny(in: ["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptBinary IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.mapOptBinary.containsAny(in: [Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptDate IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.mapOptDate.containsAny(in: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptDecimal IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.mapOptDecimal.containsAny(in: [Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptObjectId IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.mapOptObjectId.containsAny(in: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(ANY mapOptUuid IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.mapOptUuid.containsAny(in: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapIntOpt IN %@)", values: [NSArray(array: [EnumInt.value1, EnumInt.value2])], count: 1) { $0.mapIntOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt8Opt IN %@)", values: [NSArray(array: [EnumInt8.value1, EnumInt8.value2])], count: 1) { $0.mapInt8Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt16Opt IN %@)", values: [NSArray(array: [EnumInt16.value1, EnumInt16.value2])], count: 1) { $0.mapInt16Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt32Opt IN %@)", values: [NSArray(array: [EnumInt32.value1, EnumInt32.value2])], count: 1) { $0.mapInt32Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapInt64Opt IN %@)", values: [NSArray(array: [EnumInt64.value1, EnumInt64.value2])], count: 1) { $0.mapInt64Opt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapFloatOpt IN %@)", values: [NSArray(array: [EnumFloat.value1, EnumFloat.value2])], count: 1) { $0.mapFloatOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapDoubleOpt IN %@)", values: [NSArray(array: [EnumDouble.value1, EnumDouble.value2])], count: 1) { $0.mapDoubleOpt.containsAny(in: [.value1, .value2]) } assertQuery(ModernCollectionsOfEnums.self, "(ANY mapStringOpt IN %@)", values: [NSArray(array: [EnumString.value1, EnumString.value2])], count: 1) { $0.mapStringOpt.containsAny(in: [.value1, .value2]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)])], count: 1) { $0.mapOptBool.containsAny(in: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: true)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.mapOptInt.containsAny(in: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.mapOptInt8.containsAny(in: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.mapOptInt16.containsAny(in: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.mapOptInt32.containsAny(in: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.mapOptInt64.containsAny(in: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.mapOptFloat.containsAny(in: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.mapOptDouble.containsAny(in: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.mapOptString.containsAny(in: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.mapOptBinary.containsAny(in: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.mapOptDate.containsAny(in: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.mapOptDecimal.containsAny(in: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.mapOptObjectId.containsAny(in: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(CustomPersistableCollections.self, "(ANY mapOptUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.mapOptUuid.containsAny(in: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } let colObj = ModernCollectionObject() let obj = objects().first! colObj.map["foo"] = obj try! realm.write { realm.add(colObj) } assertQuery(ModernCollectionObject.self, "(ANY map IN %@)", values: [NSArray(array: [obj])], count: 1) { $0.map.containsAny(in: [obj]) } } // MARK: - Linking Objects func testLinkingObjects() { let objects = Array(self.objects()) assertQuery("(%@ IN linkingObjects)", objects.first!, count: 0) { $0.linkingObjects.contains(objects.first!) } assertQuery("(ANY linkingObjects IN %@)", objects, count: 0) { $0.linkingObjects.containsAny(in: objects) } assertQuery("(NOT %@ IN linkingObjects)", objects.first!, count: 1) { !$0.linkingObjects.contains(objects.first!) } assertQuery("(NOT ANY linkingObjects IN %@)", objects, count: 1) { !$0.linkingObjects.containsAny(in: objects) } } // MARK: - Compound func testCompoundAnd() { assertQuery("((boolCol == %@) && (optBoolCol == %@))", values: [false, false], count: 1) { $0.boolCol == false && $0.optBoolCol == false } assertQuery("((boolCol == %@) && (optBoolCol == %@))", values: [false, false], count: 1) { ($0.boolCol == false) && ($0.optBoolCol == false) } // List assertQuery("((boolCol == %@) && (%@ IN arrayBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.arrayBool.contains(true) } assertQuery("((boolCol != %@) && (%@ IN arrayBool))", values: [true, true], count: 1) { $0.boolCol != true && $0.arrayBool.contains(true) } assertQuery("((boolCol == %@) && (%@ IN arrayOptBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.arrayOptBool.contains(true) } assertQuery("((boolCol != %@) && (%@ IN arrayOptBool))", values: [true, true], count: 1) { $0.boolCol != true && $0.arrayOptBool.contains(true) } // Set assertQuery("((boolCol == %@) && (%@ IN setBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.setBool.contains(true) } assertQuery("((boolCol != %@) && (%@ IN setBool))", values: [true, true], count: 1) { $0.boolCol != true && $0.setBool.contains(true) } assertQuery("((boolCol == %@) && (%@ IN setOptBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.setOptBool.contains(true) } assertQuery("((boolCol != %@) && (%@ IN setOptBool))", values: [true, true], count: 1) { $0.boolCol != true && $0.setOptBool.contains(true) } // Map assertQuery("((boolCol == %@) && (%@ IN mapBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.mapBool.contains(true) } assertQuery("((boolCol != %@) && (mapBool[%@] == %@))", values: [true, "foo", true], count: 1) { ($0.boolCol != true) && ($0.mapBool["foo"] == true) } assertQuery("(((boolCol != %@) && (mapBool[%@] == %@)) && (mapBool[%@] == %@))", values: [true, "foo", true, "bar", true], count: 1) { ($0.boolCol != true) && ($0.mapBool["foo"] == true) && ($0.mapBool["bar"] == true) } assertQuery("((boolCol == %@) && (%@ IN mapOptBool))", values: [false, true], count: 1) { $0.boolCol == false && $0.mapOptBool.contains(true) } assertQuery("((boolCol != %@) && (mapOptBool[%@] == %@))", values: [true, "foo", true], count: 1) { ($0.boolCol != true) && ($0.mapOptBool["foo"] == true) } assertQuery("(((boolCol != %@) && (mapOptBool[%@] == %@)) && (mapOptBool[%@] == %@))", values: [true, "foo", true, "bar", true], count: 1) { ($0.boolCol != true) && ($0.mapOptBool["foo"] == true) && ($0.mapOptBool["bar"] == true) } // Aggregates let sumarrayInt = 1 + 3 assertQuery("((((((arrayInt.@min <= %@) && (arrayInt.@max >= %@)) && (arrayInt.@sum == %@)) && (arrayInt.@count != %@)) && (arrayInt.@avg > %@)) && (arrayInt.@avg < %@))", values: [1, 3, sumarrayInt, 0, 1, 3], count: 1) { ($0.arrayInt.min <= 1) && ($0.arrayInt.max >= 3) && ($0.arrayInt.sum == sumarrayInt) && ($0.arrayInt.count != 0) && ($0.arrayInt.avg > 1) && ($0.arrayInt.avg < 3) } let sumarrayOptInt = 1 + 3 assertQuery("((((((arrayOptInt.@min <= %@) && (arrayOptInt.@max >= %@)) && (arrayOptInt.@sum == %@)) && (arrayOptInt.@count != %@)) && (arrayOptInt.@avg > %@)) && (arrayOptInt.@avg < %@))", values: [1, 3, sumarrayOptInt, 0, 1, 3], count: 1) { ($0.arrayOptInt.min <= 1) && ($0.arrayOptInt.max >= 3) && ($0.arrayOptInt.sum == sumarrayOptInt) && ($0.arrayOptInt.count != 0) && ($0.arrayOptInt.avg > 1) && ($0.arrayOptInt.avg < 3) } let summapInt = 1 + 3 assertQuery("((((((mapInt.@min <= %@) && (mapInt.@max >= %@)) && (mapInt.@sum == %@)) && (mapInt.@count != %@)) && (mapInt.@avg > %@)) && (mapInt.@avg < %@))", values: [1, 3, summapInt, 0, 1, 3], count: 1) { ($0.mapInt.min <= 1) && ($0.mapInt.max >= 3) && ($0.mapInt.sum == summapInt) && ($0.mapInt.count != 0) && ($0.mapInt.avg > 1) && ($0.mapInt.avg < 3) } let summapOptInt = 1 + 3 assertQuery("((((((mapOptInt.@min <= %@) && (mapOptInt.@max >= %@)) && (mapOptInt.@sum == %@)) && (mapOptInt.@count != %@)) && (mapOptInt.@avg > %@)) && (mapOptInt.@avg < %@))", values: [1, 3, summapOptInt, 0, 1, 3], count: 1) { ($0.mapOptInt.min <= 1) && ($0.mapOptInt.max >= 3) && ($0.mapOptInt.sum == summapOptInt) && ($0.mapOptInt.count != 0) && ($0.mapOptInt.avg > 1) && ($0.mapOptInt.avg < 3) } // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() let sumdoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.doubleCol <= %@) && (list.@max.doubleCol >= %@)) && (list.@sum.doubleCol == %@)) && (list.@min.doubleCol != %@)) && (list.@avg.doubleCol > %@)) && (list.@avg.doubleCol < %@))", values: [123.456, 345.678, sumdoubleCol, 234.567, 123.456, 345.678], count: 1) { $0.list.doubleCol.min <= 123.456 && $0.list.doubleCol.max >= 345.678 && $0.list.doubleCol.sum == sumdoubleCol && $0.list.doubleCol.min != 234.567 && $0.list.doubleCol.avg > 123.456 && $0.list.doubleCol.avg < 345.678 } let sumoptDoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.optDoubleCol <= %@) && (list.@max.optDoubleCol >= %@)) && (list.@sum.optDoubleCol == %@)) && (list.@min.optDoubleCol != %@)) && (list.@avg.optDoubleCol > %@)) && (list.@avg.optDoubleCol < %@))", values: [123.456, 345.678, sumoptDoubleCol, 234.567, 123.456, 345.678], count: 1) { $0.list.optDoubleCol.min <= 123.456 && $0.list.optDoubleCol.max >= 345.678 && $0.list.optDoubleCol.sum == sumoptDoubleCol && $0.list.optDoubleCol.min != 234.567 && $0.list.optDoubleCol.avg > 123.456 && $0.list.optDoubleCol.avg < 345.678 } } func testCompoundOr() { assertQuery(ModernAllTypesObject.self, "((boolCol == %@) || (intCol == %@))", values: [false, 3], count: 1) { $0.boolCol == false || $0.intCol == 3 } assertQuery(ModernAllTypesObject.self, "((boolCol == %@) || (intCol == %@))", values: [false, 3], count: 1) { ($0.boolCol == false) || ($0.intCol == 3) } assertQuery(ModernAllTypesObject.self, "((intCol == %@) || (int8Col == %@))", values: [3, Int8(9)], count: 1) { $0.intCol == 3 || $0.int8Col == Int8(9) } assertQuery(ModernAllTypesObject.self, "((intCol == %@) || (int8Col == %@))", values: [3, Int8(9)], count: 1) { ($0.intCol == 3) || ($0.int8Col == Int8(9)) } assertQuery(ModernAllTypesObject.self, "((int8Col == %@) || (int16Col == %@))", values: [Int8(9), Int16(17)], count: 1) { $0.int8Col == Int8(9) || $0.int16Col == Int16(17) } assertQuery(ModernAllTypesObject.self, "((int8Col == %@) || (int16Col == %@))", values: [Int8(9), Int16(17)], count: 1) { ($0.int8Col == Int8(9)) || ($0.int16Col == Int16(17)) } assertQuery(ModernAllTypesObject.self, "((int16Col == %@) || (int32Col == %@))", values: [Int16(17), Int32(33)], count: 1) { $0.int16Col == Int16(17) || $0.int32Col == Int32(33) } assertQuery(ModernAllTypesObject.self, "((int16Col == %@) || (int32Col == %@))", values: [Int16(17), Int32(33)], count: 1) { ($0.int16Col == Int16(17)) || ($0.int32Col == Int32(33)) } assertQuery(ModernAllTypesObject.self, "((int32Col == %@) || (int64Col == %@))", values: [Int32(33), Int64(65)], count: 1) { $0.int32Col == Int32(33) || $0.int64Col == Int64(65) } assertQuery(ModernAllTypesObject.self, "((int32Col == %@) || (int64Col == %@))", values: [Int32(33), Int64(65)], count: 1) { ($0.int32Col == Int32(33)) || ($0.int64Col == Int64(65)) } assertQuery(ModernAllTypesObject.self, "((int64Col == %@) || (floatCol == %@))", values: [Int64(65), Float(6.55444333)], count: 1) { $0.int64Col == Int64(65) || $0.floatCol == Float(6.55444333) } assertQuery(ModernAllTypesObject.self, "((int64Col == %@) || (floatCol == %@))", values: [Int64(65), Float(6.55444333)], count: 1) { ($0.int64Col == Int64(65)) || ($0.floatCol == Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((floatCol == %@) || (doubleCol == %@))", values: [Float(6.55444333), 234.567], count: 1) { $0.floatCol == Float(6.55444333) || $0.doubleCol == 234.567 } assertQuery(ModernAllTypesObject.self, "((floatCol == %@) || (doubleCol == %@))", values: [Float(6.55444333), 234.567], count: 1) { ($0.floatCol == Float(6.55444333)) || ($0.doubleCol == 234.567) } assertQuery(ModernAllTypesObject.self, "((doubleCol == %@) || (stringCol == %@))", values: [234.567, "Foó"], count: 1) { $0.doubleCol == 234.567 || $0.stringCol == "Foó" } assertQuery(ModernAllTypesObject.self, "((doubleCol == %@) || (stringCol == %@))", values: [234.567, "Foó"], count: 1) { ($0.doubleCol == 234.567) || ($0.stringCol == "Foó") } assertQuery(ModernAllTypesObject.self, "((stringCol == %@) || (binaryCol == %@))", values: ["Foó", Data(count: 128)], count: 1) { $0.stringCol == "Foó" || $0.binaryCol == Data(count: 128) } assertQuery(ModernAllTypesObject.self, "((stringCol == %@) || (binaryCol == %@))", values: ["Foó", Data(count: 128)], count: 1) { ($0.stringCol == "Foó") || ($0.binaryCol == Data(count: 128)) } assertQuery(ModernAllTypesObject.self, "((binaryCol == %@) || (dateCol == %@))", values: [Data(count: 128), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.binaryCol == Data(count: 128) || $0.dateCol == Date(timeIntervalSince1970: 2000000) } assertQuery(ModernAllTypesObject.self, "((binaryCol == %@) || (dateCol == %@))", values: [Data(count: 128), Date(timeIntervalSince1970: 2000000)], count: 1) { ($0.binaryCol == Data(count: 128)) || ($0.dateCol == Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((dateCol == %@) || (decimalCol == %@))", values: [Date(timeIntervalSince1970: 2000000), Decimal128(234.567)], count: 1) { $0.dateCol == Date(timeIntervalSince1970: 2000000) || $0.decimalCol == Decimal128(234.567) } assertQuery(ModernAllTypesObject.self, "((dateCol == %@) || (decimalCol == %@))", values: [Date(timeIntervalSince1970: 2000000), Decimal128(234.567)], count: 1) { ($0.dateCol == Date(timeIntervalSince1970: 2000000)) || ($0.decimalCol == Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((decimalCol == %@) || (objectIdCol == %@))", values: [Decimal128(234.567), ObjectId("61184062c1d8f096a3695045")], count: 1) { $0.decimalCol == Decimal128(234.567) || $0.objectIdCol == ObjectId("61184062c1d8f096a3695045") } assertQuery(ModernAllTypesObject.self, "((decimalCol == %@) || (objectIdCol == %@))", values: [Decimal128(234.567), ObjectId("61184062c1d8f096a3695045")], count: 1) { ($0.decimalCol == Decimal128(234.567)) || ($0.objectIdCol == ObjectId("61184062c1d8f096a3695045")) } assertQuery(ModernAllTypesObject.self, "((objectIdCol == %@) || (uuidCol == %@))", values: [ObjectId("61184062c1d8f096a3695045"), UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!], count: 1) { $0.objectIdCol == ObjectId("61184062c1d8f096a3695045") || $0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! } assertQuery(ModernAllTypesObject.self, "((objectIdCol == %@) || (uuidCol == %@))", values: [ObjectId("61184062c1d8f096a3695045"), UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!], count: 1) { ($0.objectIdCol == ObjectId("61184062c1d8f096a3695045")) || ($0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) } assertQuery(ModernAllTypesObject.self, "((uuidCol == %@) || (intEnumCol == %@))", values: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, ModernIntEnum.value2], count: 1) { $0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! || $0.intEnumCol == .value2 } assertQuery(ModernAllTypesObject.self, "((uuidCol == %@) || (intEnumCol == %@))", values: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, ModernIntEnum.value2], count: 1) { ($0.uuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) || ($0.intEnumCol == .value2) } assertQuery(ModernAllTypesObject.self, "((intEnumCol == %@) || (stringEnumCol == %@))", values: [ModernIntEnum.value2, ModernStringEnum.value2], count: 1) { $0.intEnumCol == .value2 || $0.stringEnumCol == .value2 } assertQuery(ModernAllTypesObject.self, "((intEnumCol == %@) || (stringEnumCol == %@))", values: [ModernIntEnum.value2, ModernStringEnum.value2], count: 1) { ($0.intEnumCol == .value2) || ($0.stringEnumCol == .value2) } assertQuery(AllCustomPersistableTypes.self, "((bool == %@) || (int == %@))", values: [BoolWrapper(persistedValue: false), IntWrapper(persistedValue: 3)], count: 1) { $0.bool == BoolWrapper(persistedValue: false) || $0.int == IntWrapper(persistedValue: 3) } assertQuery(AllCustomPersistableTypes.self, "((bool == %@) || (int == %@))", values: [BoolWrapper(persistedValue: false), IntWrapper(persistedValue: 3)], count: 1) { ($0.bool == BoolWrapper(persistedValue: false)) || ($0.int == IntWrapper(persistedValue: 3)) } assertQuery(AllCustomPersistableTypes.self, "((int == %@) || (int8 == %@))", values: [IntWrapper(persistedValue: 3), Int8Wrapper(persistedValue: Int8(9))], count: 1) { $0.int == IntWrapper(persistedValue: 3) || $0.int8 == Int8Wrapper(persistedValue: Int8(9)) } assertQuery(AllCustomPersistableTypes.self, "((int == %@) || (int8 == %@))", values: [IntWrapper(persistedValue: 3), Int8Wrapper(persistedValue: Int8(9))], count: 1) { ($0.int == IntWrapper(persistedValue: 3)) || ($0.int8 == Int8Wrapper(persistedValue: Int8(9))) } assertQuery(AllCustomPersistableTypes.self, "((int8 == %@) || (int16 == %@))", values: [Int8Wrapper(persistedValue: Int8(9)), Int16Wrapper(persistedValue: Int16(17))], count: 1) { $0.int8 == Int8Wrapper(persistedValue: Int8(9)) || $0.int16 == Int16Wrapper(persistedValue: Int16(17)) } assertQuery(AllCustomPersistableTypes.self, "((int8 == %@) || (int16 == %@))", values: [Int8Wrapper(persistedValue: Int8(9)), Int16Wrapper(persistedValue: Int16(17))], count: 1) { ($0.int8 == Int8Wrapper(persistedValue: Int8(9))) || ($0.int16 == Int16Wrapper(persistedValue: Int16(17))) } assertQuery(AllCustomPersistableTypes.self, "((int16 == %@) || (int32 == %@))", values: [Int16Wrapper(persistedValue: Int16(17)), Int32Wrapper(persistedValue: Int32(33))], count: 1) { $0.int16 == Int16Wrapper(persistedValue: Int16(17)) || $0.int32 == Int32Wrapper(persistedValue: Int32(33)) } assertQuery(AllCustomPersistableTypes.self, "((int16 == %@) || (int32 == %@))", values: [Int16Wrapper(persistedValue: Int16(17)), Int32Wrapper(persistedValue: Int32(33))], count: 1) { ($0.int16 == Int16Wrapper(persistedValue: Int16(17))) || ($0.int32 == Int32Wrapper(persistedValue: Int32(33))) } assertQuery(AllCustomPersistableTypes.self, "((int32 == %@) || (int64 == %@))", values: [Int32Wrapper(persistedValue: Int32(33)), Int64Wrapper(persistedValue: Int64(65))], count: 1) { $0.int32 == Int32Wrapper(persistedValue: Int32(33)) || $0.int64 == Int64Wrapper(persistedValue: Int64(65)) } assertQuery(AllCustomPersistableTypes.self, "((int32 == %@) || (int64 == %@))", values: [Int32Wrapper(persistedValue: Int32(33)), Int64Wrapper(persistedValue: Int64(65))], count: 1) { ($0.int32 == Int32Wrapper(persistedValue: Int32(33))) || ($0.int64 == Int64Wrapper(persistedValue: Int64(65))) } assertQuery(AllCustomPersistableTypes.self, "((int64 == %@) || (float == %@))", values: [Int64Wrapper(persistedValue: Int64(65)), FloatWrapper(persistedValue: Float(6.55444333))], count: 1) { $0.int64 == Int64Wrapper(persistedValue: Int64(65)) || $0.float == FloatWrapper(persistedValue: Float(6.55444333)) } assertQuery(AllCustomPersistableTypes.self, "((int64 == %@) || (float == %@))", values: [Int64Wrapper(persistedValue: Int64(65)), FloatWrapper(persistedValue: Float(6.55444333))], count: 1) { ($0.int64 == Int64Wrapper(persistedValue: Int64(65))) || ($0.float == FloatWrapper(persistedValue: Float(6.55444333))) } assertQuery(AllCustomPersistableTypes.self, "((float == %@) || (double == %@))", values: [FloatWrapper(persistedValue: Float(6.55444333)), DoubleWrapper(persistedValue: 234.567)], count: 1) { $0.float == FloatWrapper(persistedValue: Float(6.55444333)) || $0.double == DoubleWrapper(persistedValue: 234.567) } assertQuery(AllCustomPersistableTypes.self, "((float == %@) || (double == %@))", values: [FloatWrapper(persistedValue: Float(6.55444333)), DoubleWrapper(persistedValue: 234.567)], count: 1) { ($0.float == FloatWrapper(persistedValue: Float(6.55444333))) || ($0.double == DoubleWrapper(persistedValue: 234.567)) } assertQuery(AllCustomPersistableTypes.self, "((double == %@) || (string == %@))", values: [DoubleWrapper(persistedValue: 234.567), StringWrapper(persistedValue: "Foó")], count: 1) { $0.double == DoubleWrapper(persistedValue: 234.567) || $0.string == StringWrapper(persistedValue: "Foó") } assertQuery(AllCustomPersistableTypes.self, "((double == %@) || (string == %@))", values: [DoubleWrapper(persistedValue: 234.567), StringWrapper(persistedValue: "Foó")], count: 1) { ($0.double == DoubleWrapper(persistedValue: 234.567)) || ($0.string == StringWrapper(persistedValue: "Foó")) } assertQuery(AllCustomPersistableTypes.self, "((string == %@) || (binary == %@))", values: [StringWrapper(persistedValue: "Foó"), DataWrapper(persistedValue: Data(count: 128))], count: 1) { $0.string == StringWrapper(persistedValue: "Foó") || $0.binary == DataWrapper(persistedValue: Data(count: 128)) } assertQuery(AllCustomPersistableTypes.self, "((string == %@) || (binary == %@))", values: [StringWrapper(persistedValue: "Foó"), DataWrapper(persistedValue: Data(count: 128))], count: 1) { ($0.string == StringWrapper(persistedValue: "Foó")) || ($0.binary == DataWrapper(persistedValue: Data(count: 128))) } assertQuery(AllCustomPersistableTypes.self, "((binary == %@) || (date == %@))", values: [DataWrapper(persistedValue: Data(count: 128)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))], count: 1) { $0.binary == DataWrapper(persistedValue: Data(count: 128)) || $0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) } assertQuery(AllCustomPersistableTypes.self, "((binary == %@) || (date == %@))", values: [DataWrapper(persistedValue: Data(count: 128)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))], count: 1) { ($0.binary == DataWrapper(persistedValue: Data(count: 128))) || ($0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) } assertQuery(AllCustomPersistableTypes.self, "((date == %@) || (decimal == %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), Decimal128Wrapper(persistedValue: Decimal128(234.567))], count: 1) { $0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) || $0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) } assertQuery(AllCustomPersistableTypes.self, "((date == %@) || (decimal == %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), Decimal128Wrapper(persistedValue: Decimal128(234.567))], count: 1) { ($0.date == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) || ($0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567))) } assertQuery(AllCustomPersistableTypes.self, "((decimal == %@) || (objectId == %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(234.567)), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))], count: 1) { $0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) || $0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) } assertQuery(AllCustomPersistableTypes.self, "((decimal == %@) || (objectId == %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(234.567)), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))], count: 1) { ($0.decimal == Decimal128Wrapper(persistedValue: Decimal128(234.567))) || ($0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) } assertQuery(AllCustomPersistableTypes.self, "((objectId == %@) || (uuid == %@))", values: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)], count: 1) { $0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) || $0.uuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) } assertQuery(AllCustomPersistableTypes.self, "((objectId == %@) || (uuid == %@))", values: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)], count: 1) { ($0.objectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) || ($0.uuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) } assertQuery(ModernAllTypesObject.self, "((optBoolCol == %@) || (optIntCol == %@))", values: [false, 3], count: 1) { $0.optBoolCol == false || $0.optIntCol == 3 } assertQuery(ModernAllTypesObject.self, "((optBoolCol == %@) || (optIntCol == %@))", values: [false, 3], count: 1) { ($0.optBoolCol == false) || ($0.optIntCol == 3) } assertQuery(ModernAllTypesObject.self, "((optIntCol == %@) || (optInt8Col == %@))", values: [3, Int8(9)], count: 1) { $0.optIntCol == 3 || $0.optInt8Col == Int8(9) } assertQuery(ModernAllTypesObject.self, "((optIntCol == %@) || (optInt8Col == %@))", values: [3, Int8(9)], count: 1) { ($0.optIntCol == 3) || ($0.optInt8Col == Int8(9)) } assertQuery(ModernAllTypesObject.self, "((optInt8Col == %@) || (optInt16Col == %@))", values: [Int8(9), Int16(17)], count: 1) { $0.optInt8Col == Int8(9) || $0.optInt16Col == Int16(17) } assertQuery(ModernAllTypesObject.self, "((optInt8Col == %@) || (optInt16Col == %@))", values: [Int8(9), Int16(17)], count: 1) { ($0.optInt8Col == Int8(9)) || ($0.optInt16Col == Int16(17)) } assertQuery(ModernAllTypesObject.self, "((optInt16Col == %@) || (optInt32Col == %@))", values: [Int16(17), Int32(33)], count: 1) { $0.optInt16Col == Int16(17) || $0.optInt32Col == Int32(33) } assertQuery(ModernAllTypesObject.self, "((optInt16Col == %@) || (optInt32Col == %@))", values: [Int16(17), Int32(33)], count: 1) { ($0.optInt16Col == Int16(17)) || ($0.optInt32Col == Int32(33)) } assertQuery(ModernAllTypesObject.self, "((optInt32Col == %@) || (optInt64Col == %@))", values: [Int32(33), Int64(65)], count: 1) { $0.optInt32Col == Int32(33) || $0.optInt64Col == Int64(65) } assertQuery(ModernAllTypesObject.self, "((optInt32Col == %@) || (optInt64Col == %@))", values: [Int32(33), Int64(65)], count: 1) { ($0.optInt32Col == Int32(33)) || ($0.optInt64Col == Int64(65)) } assertQuery(ModernAllTypesObject.self, "((optInt64Col == %@) || (optFloatCol == %@))", values: [Int64(65), Float(6.55444333)], count: 1) { $0.optInt64Col == Int64(65) || $0.optFloatCol == Float(6.55444333) } assertQuery(ModernAllTypesObject.self, "((optInt64Col == %@) || (optFloatCol == %@))", values: [Int64(65), Float(6.55444333)], count: 1) { ($0.optInt64Col == Int64(65)) || ($0.optFloatCol == Float(6.55444333)) } assertQuery(ModernAllTypesObject.self, "((optFloatCol == %@) || (optDoubleCol == %@))", values: [Float(6.55444333), 234.567], count: 1) { $0.optFloatCol == Float(6.55444333) || $0.optDoubleCol == 234.567 } assertQuery(ModernAllTypesObject.self, "((optFloatCol == %@) || (optDoubleCol == %@))", values: [Float(6.55444333), 234.567], count: 1) { ($0.optFloatCol == Float(6.55444333)) || ($0.optDoubleCol == 234.567) } assertQuery(ModernAllTypesObject.self, "((optDoubleCol == %@) || (optStringCol == %@))", values: [234.567, "Foó"], count: 1) { $0.optDoubleCol == 234.567 || $0.optStringCol == "Foó" } assertQuery(ModernAllTypesObject.self, "((optDoubleCol == %@) || (optStringCol == %@))", values: [234.567, "Foó"], count: 1) { ($0.optDoubleCol == 234.567) || ($0.optStringCol == "Foó") } assertQuery(ModernAllTypesObject.self, "((optStringCol == %@) || (optBinaryCol == %@))", values: ["Foó", Data(count: 128)], count: 1) { $0.optStringCol == "Foó" || $0.optBinaryCol == Data(count: 128) } assertQuery(ModernAllTypesObject.self, "((optStringCol == %@) || (optBinaryCol == %@))", values: ["Foó", Data(count: 128)], count: 1) { ($0.optStringCol == "Foó") || ($0.optBinaryCol == Data(count: 128)) } assertQuery(ModernAllTypesObject.self, "((optBinaryCol == %@) || (optDateCol == %@))", values: [Data(count: 128), Date(timeIntervalSince1970: 2000000)], count: 1) { $0.optBinaryCol == Data(count: 128) || $0.optDateCol == Date(timeIntervalSince1970: 2000000) } assertQuery(ModernAllTypesObject.self, "((optBinaryCol == %@) || (optDateCol == %@))", values: [Data(count: 128), Date(timeIntervalSince1970: 2000000)], count: 1) { ($0.optBinaryCol == Data(count: 128)) || ($0.optDateCol == Date(timeIntervalSince1970: 2000000)) } assertQuery(ModernAllTypesObject.self, "((optDateCol == %@) || (optDecimalCol == %@))", values: [Date(timeIntervalSince1970: 2000000), Decimal128(234.567)], count: 1) { $0.optDateCol == Date(timeIntervalSince1970: 2000000) || $0.optDecimalCol == Decimal128(234.567) } assertQuery(ModernAllTypesObject.self, "((optDateCol == %@) || (optDecimalCol == %@))", values: [Date(timeIntervalSince1970: 2000000), Decimal128(234.567)], count: 1) { ($0.optDateCol == Date(timeIntervalSince1970: 2000000)) || ($0.optDecimalCol == Decimal128(234.567)) } assertQuery(ModernAllTypesObject.self, "((optDecimalCol == %@) || (optObjectIdCol == %@))", values: [Decimal128(234.567), ObjectId("61184062c1d8f096a3695045")], count: 1) { $0.optDecimalCol == Decimal128(234.567) || $0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045") } assertQuery(ModernAllTypesObject.self, "((optDecimalCol == %@) || (optObjectIdCol == %@))", values: [Decimal128(234.567), ObjectId("61184062c1d8f096a3695045")], count: 1) { ($0.optDecimalCol == Decimal128(234.567)) || ($0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045")) } assertQuery(ModernAllTypesObject.self, "((optObjectIdCol == %@) || (optUuidCol == %@))", values: [ObjectId("61184062c1d8f096a3695045"), UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!], count: 1) { $0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045") || $0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! } assertQuery(ModernAllTypesObject.self, "((optObjectIdCol == %@) || (optUuidCol == %@))", values: [ObjectId("61184062c1d8f096a3695045"), UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!], count: 1) { ($0.optObjectIdCol == ObjectId("61184062c1d8f096a3695045")) || ($0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) } assertQuery(ModernAllTypesObject.self, "((optUuidCol == %@) || (optIntEnumCol == %@))", values: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, ModernIntEnum.value2], count: 1) { $0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")! || $0.optIntEnumCol == .value2 } assertQuery(ModernAllTypesObject.self, "((optUuidCol == %@) || (optIntEnumCol == %@))", values: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, ModernIntEnum.value2], count: 1) { ($0.optUuidCol == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) || ($0.optIntEnumCol == .value2) } assertQuery(ModernAllTypesObject.self, "((optIntEnumCol == %@) || (optStringEnumCol == %@))", values: [ModernIntEnum.value2, ModernStringEnum.value2], count: 1) { $0.optIntEnumCol == .value2 || $0.optStringEnumCol == .value2 } assertQuery(ModernAllTypesObject.self, "((optIntEnumCol == %@) || (optStringEnumCol == %@))", values: [ModernIntEnum.value2, ModernStringEnum.value2], count: 1) { ($0.optIntEnumCol == .value2) || ($0.optStringEnumCol == .value2) } assertQuery(AllCustomPersistableTypes.self, "((optBool == %@) || (optInt == %@))", values: [BoolWrapper(persistedValue: false), IntWrapper(persistedValue: 3)], count: 1) { $0.optBool == BoolWrapper(persistedValue: false) || $0.optInt == IntWrapper(persistedValue: 3) } assertQuery(AllCustomPersistableTypes.self, "((optBool == %@) || (optInt == %@))", values: [BoolWrapper(persistedValue: false), IntWrapper(persistedValue: 3)], count: 1) { ($0.optBool == BoolWrapper(persistedValue: false)) || ($0.optInt == IntWrapper(persistedValue: 3)) } assertQuery(AllCustomPersistableTypes.self, "((optInt == %@) || (optInt8 == %@))", values: [IntWrapper(persistedValue: 3), Int8Wrapper(persistedValue: Int8(9))], count: 1) { $0.optInt == IntWrapper(persistedValue: 3) || $0.optInt8 == Int8Wrapper(persistedValue: Int8(9)) } assertQuery(AllCustomPersistableTypes.self, "((optInt == %@) || (optInt8 == %@))", values: [IntWrapper(persistedValue: 3), Int8Wrapper(persistedValue: Int8(9))], count: 1) { ($0.optInt == IntWrapper(persistedValue: 3)) || ($0.optInt8 == Int8Wrapper(persistedValue: Int8(9))) } assertQuery(AllCustomPersistableTypes.self, "((optInt8 == %@) || (optInt16 == %@))", values: [Int8Wrapper(persistedValue: Int8(9)), Int16Wrapper(persistedValue: Int16(17))], count: 1) { $0.optInt8 == Int8Wrapper(persistedValue: Int8(9)) || $0.optInt16 == Int16Wrapper(persistedValue: Int16(17)) } assertQuery(AllCustomPersistableTypes.self, "((optInt8 == %@) || (optInt16 == %@))", values: [Int8Wrapper(persistedValue: Int8(9)), Int16Wrapper(persistedValue: Int16(17))], count: 1) { ($0.optInt8 == Int8Wrapper(persistedValue: Int8(9))) || ($0.optInt16 == Int16Wrapper(persistedValue: Int16(17))) } assertQuery(AllCustomPersistableTypes.self, "((optInt16 == %@) || (optInt32 == %@))", values: [Int16Wrapper(persistedValue: Int16(17)), Int32Wrapper(persistedValue: Int32(33))], count: 1) { $0.optInt16 == Int16Wrapper(persistedValue: Int16(17)) || $0.optInt32 == Int32Wrapper(persistedValue: Int32(33)) } assertQuery(AllCustomPersistableTypes.self, "((optInt16 == %@) || (optInt32 == %@))", values: [Int16Wrapper(persistedValue: Int16(17)), Int32Wrapper(persistedValue: Int32(33))], count: 1) { ($0.optInt16 == Int16Wrapper(persistedValue: Int16(17))) || ($0.optInt32 == Int32Wrapper(persistedValue: Int32(33))) } assertQuery(AllCustomPersistableTypes.self, "((optInt32 == %@) || (optInt64 == %@))", values: [Int32Wrapper(persistedValue: Int32(33)), Int64Wrapper(persistedValue: Int64(65))], count: 1) { $0.optInt32 == Int32Wrapper(persistedValue: Int32(33)) || $0.optInt64 == Int64Wrapper(persistedValue: Int64(65)) } assertQuery(AllCustomPersistableTypes.self, "((optInt32 == %@) || (optInt64 == %@))", values: [Int32Wrapper(persistedValue: Int32(33)), Int64Wrapper(persistedValue: Int64(65))], count: 1) { ($0.optInt32 == Int32Wrapper(persistedValue: Int32(33))) || ($0.optInt64 == Int64Wrapper(persistedValue: Int64(65))) } assertQuery(AllCustomPersistableTypes.self, "((optInt64 == %@) || (optFloat == %@))", values: [Int64Wrapper(persistedValue: Int64(65)), FloatWrapper(persistedValue: Float(6.55444333))], count: 1) { $0.optInt64 == Int64Wrapper(persistedValue: Int64(65)) || $0.optFloat == FloatWrapper(persistedValue: Float(6.55444333)) } assertQuery(AllCustomPersistableTypes.self, "((optInt64 == %@) || (optFloat == %@))", values: [Int64Wrapper(persistedValue: Int64(65)), FloatWrapper(persistedValue: Float(6.55444333))], count: 1) { ($0.optInt64 == Int64Wrapper(persistedValue: Int64(65))) || ($0.optFloat == FloatWrapper(persistedValue: Float(6.55444333))) } assertQuery(AllCustomPersistableTypes.self, "((optFloat == %@) || (optDouble == %@))", values: [FloatWrapper(persistedValue: Float(6.55444333)), DoubleWrapper(persistedValue: 234.567)], count: 1) { $0.optFloat == FloatWrapper(persistedValue: Float(6.55444333)) || $0.optDouble == DoubleWrapper(persistedValue: 234.567) } assertQuery(AllCustomPersistableTypes.self, "((optFloat == %@) || (optDouble == %@))", values: [FloatWrapper(persistedValue: Float(6.55444333)), DoubleWrapper(persistedValue: 234.567)], count: 1) { ($0.optFloat == FloatWrapper(persistedValue: Float(6.55444333))) || ($0.optDouble == DoubleWrapper(persistedValue: 234.567)) } assertQuery(AllCustomPersistableTypes.self, "((optDouble == %@) || (optString == %@))", values: [DoubleWrapper(persistedValue: 234.567), StringWrapper(persistedValue: "Foó")], count: 1) { $0.optDouble == DoubleWrapper(persistedValue: 234.567) || $0.optString == StringWrapper(persistedValue: "Foó") } assertQuery(AllCustomPersistableTypes.self, "((optDouble == %@) || (optString == %@))", values: [DoubleWrapper(persistedValue: 234.567), StringWrapper(persistedValue: "Foó")], count: 1) { ($0.optDouble == DoubleWrapper(persistedValue: 234.567)) || ($0.optString == StringWrapper(persistedValue: "Foó")) } assertQuery(AllCustomPersistableTypes.self, "((optString == %@) || (optBinary == %@))", values: [StringWrapper(persistedValue: "Foó"), DataWrapper(persistedValue: Data(count: 128))], count: 1) { $0.optString == StringWrapper(persistedValue: "Foó") || $0.optBinary == DataWrapper(persistedValue: Data(count: 128)) } assertQuery(AllCustomPersistableTypes.self, "((optString == %@) || (optBinary == %@))", values: [StringWrapper(persistedValue: "Foó"), DataWrapper(persistedValue: Data(count: 128))], count: 1) { ($0.optString == StringWrapper(persistedValue: "Foó")) || ($0.optBinary == DataWrapper(persistedValue: Data(count: 128))) } assertQuery(AllCustomPersistableTypes.self, "((optBinary == %@) || (optDate == %@))", values: [DataWrapper(persistedValue: Data(count: 128)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))], count: 1) { $0.optBinary == DataWrapper(persistedValue: Data(count: 128)) || $0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) } assertQuery(AllCustomPersistableTypes.self, "((optBinary == %@) || (optDate == %@))", values: [DataWrapper(persistedValue: Data(count: 128)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))], count: 1) { ($0.optBinary == DataWrapper(persistedValue: Data(count: 128))) || ($0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) } assertQuery(AllCustomPersistableTypes.self, "((optDate == %@) || (optDecimal == %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), Decimal128Wrapper(persistedValue: Decimal128(234.567))], count: 1) { $0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)) || $0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) } assertQuery(AllCustomPersistableTypes.self, "((optDate == %@) || (optDecimal == %@))", values: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), Decimal128Wrapper(persistedValue: Decimal128(234.567))], count: 1) { ($0.optDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) || ($0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567))) } assertQuery(AllCustomPersistableTypes.self, "((optDecimal == %@) || (optObjectId == %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(234.567)), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))], count: 1) { $0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567)) || $0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) } assertQuery(AllCustomPersistableTypes.self, "((optDecimal == %@) || (optObjectId == %@))", values: [Decimal128Wrapper(persistedValue: Decimal128(234.567)), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))], count: 1) { ($0.optDecimal == Decimal128Wrapper(persistedValue: Decimal128(234.567))) || ($0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) } assertQuery(AllCustomPersistableTypes.self, "((optObjectId == %@) || (optUuid == %@))", values: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)], count: 1) { $0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")) || $0.optUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) } assertQuery(AllCustomPersistableTypes.self, "((optObjectId == %@) || (optUuid == %@))", values: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)], count: 1) { ($0.optObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) || ($0.optUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) } // List / Set assertQuery("((boolCol == %@) || (%@ IN arrayBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.arrayBool.contains(true) } assertQuery("((boolCol != %@) || (%@ IN arrayBool))", values: [true, true], count: 1) { $0.boolCol != true || $0.arrayBool.contains(true) } assertQuery("((boolCol == %@) || (%@ IN arrayOptBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.arrayOptBool.contains(true) } assertQuery("((boolCol != %@) || (%@ IN arrayOptBool))", values: [true, true], count: 1) { $0.boolCol != true || $0.arrayOptBool.contains(true) } assertQuery("((boolCol == %@) || (%@ IN setBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.setBool.contains(true) } assertQuery("((boolCol != %@) || (%@ IN setBool))", values: [true, true], count: 1) { $0.boolCol != true || $0.setBool.contains(true) } assertQuery("((boolCol == %@) || (%@ IN setOptBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.setOptBool.contains(true) } assertQuery("((boolCol != %@) || (%@ IN setOptBool))", values: [true, true], count: 1) { $0.boolCol != true || $0.setOptBool.contains(true) } // Map assertQuery("((boolCol == %@) || (%@ IN mapBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.mapBool.contains(true) } assertQuery("((boolCol != %@) || (mapBool[%@] == %@))", values: [true, "foo", true], count: 1) { ($0.boolCol != true) || ($0.mapBool["foo"] == true) } assertQuery("(((boolCol != %@) || (mapBool[%@] == %@)) || (mapBool[%@] == %@))", values: [true, "foo", true, "bar", true], count: 1) { ($0.boolCol != true) || ($0.mapBool["foo"] == true) || ($0.mapBool["bar"] == true) } assertQuery("((boolCol == %@) || (%@ IN mapOptBool))", values: [false, true], count: 1) { $0.boolCol == false || $0.mapOptBool.contains(true) } assertQuery("((boolCol != %@) || (mapOptBool[%@] == %@))", values: [true, "foo", true], count: 1) { ($0.boolCol != true) || ($0.mapOptBool["foo"] == true) } assertQuery("(((boolCol != %@) || (mapOptBool[%@] == %@)) || (mapOptBool[%@] == %@))", values: [true, "foo", true, "bar", true], count: 1) { ($0.boolCol != true) || ($0.mapOptBool["foo"] == true) || ($0.mapOptBool["bar"] == true) } assertQuery("((boolCol == %@) || (%@ IN mapInt))", values: [false, 3], count: 1) { $0.boolCol == false || $0.mapInt.contains(3) } assertQuery("((boolCol != %@) || (mapInt[%@] == %@))", values: [true, "foo", 3], count: 1) { ($0.boolCol != true) || ($0.mapInt["foo"] == 3) } assertQuery("(((boolCol != %@) || (mapInt[%@] == %@)) || (mapInt[%@] == %@))", values: [true, "foo", 1, "bar", 3], count: 1) { ($0.boolCol != true) || ($0.mapInt["foo"] == 1) || ($0.mapInt["bar"] == 3) } assertQuery("((boolCol == %@) || (%@ IN mapOptInt))", values: [false, 3], count: 1) { $0.boolCol == false || $0.mapOptInt.contains(3) } assertQuery("((boolCol != %@) || (mapOptInt[%@] == %@))", values: [true, "foo", 3], count: 1) { ($0.boolCol != true) || ($0.mapOptInt["foo"] == 3) } assertQuery("(((boolCol != %@) || (mapOptInt[%@] == %@)) || (mapOptInt[%@] == %@))", values: [true, "foo", 1, "bar", 3], count: 1) { ($0.boolCol != true) || ($0.mapOptInt["foo"] == 1) || ($0.mapOptInt["bar"] == 3) } // Aggregates let sumarrayInt = 1 + 3 assertQuery("((((((arrayInt.@min <= %@) || (arrayInt.@max >= %@)) || (arrayInt.@sum != %@)) || (arrayInt.@count == %@)) || (arrayInt.@avg > %@)) || (arrayInt.@avg < %@))", values: [1, 5, sumarrayInt, 0, 3, 1], count: 1) { ($0.arrayInt.min <= 1) || ($0.arrayInt.max >= 5) || ($0.arrayInt.sum != sumarrayInt) || ($0.arrayInt.count == 0) || ($0.arrayInt.avg > 3) || ($0.arrayInt.avg < 1) } let sumarrayOptInt = 1 + 3 assertQuery("((((((arrayOptInt.@min <= %@) || (arrayOptInt.@max >= %@)) || (arrayOptInt.@sum != %@)) || (arrayOptInt.@count == %@)) || (arrayOptInt.@avg > %@)) || (arrayOptInt.@avg < %@))", values: [1, 5, sumarrayOptInt, 0, 3, 1], count: 1) { ($0.arrayOptInt.min <= 1) || ($0.arrayOptInt.max >= 5) || ($0.arrayOptInt.sum != sumarrayOptInt) || ($0.arrayOptInt.count == 0) || ($0.arrayOptInt.avg > 3) || ($0.arrayOptInt.avg < 1) } // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() let sumdoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.doubleCol < %@) || (list.@max.doubleCol > %@)) || (list.@sum.doubleCol != %@)) || (list.@min.doubleCol == %@)) || (list.@avg.doubleCol >= %@)) || (list.@avg.doubleCol <= %@))", values: [123.456, 345.678, sumdoubleCol, 0, 345.678, 123.456], count: 0) { $0.list.doubleCol.min < 123.456 || $0.list.doubleCol.max > 345.678 || $0.list.doubleCol.sum != sumdoubleCol || $0.list.doubleCol.min == 0 || $0.list.doubleCol.avg >= 345.678 || $0.list.doubleCol.avg <= 123.456 } let sumoptDoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.optDoubleCol < %@) || (list.@max.optDoubleCol > %@)) || (list.@sum.optDoubleCol != %@)) || (list.@min.optDoubleCol == %@)) || (list.@avg.optDoubleCol >= %@)) || (list.@avg.optDoubleCol <= %@))", values: [123.456, 345.678, sumoptDoubleCol, 0, 345.678, 123.456], count: 0) { $0.list.optDoubleCol.min < 123.456 || $0.list.optDoubleCol.max > 345.678 || $0.list.optDoubleCol.sum != sumoptDoubleCol || $0.list.optDoubleCol.min == 0 || $0.list.optDoubleCol.avg >= 345.678 || $0.list.optDoubleCol.avg <= 123.456 } } func validateCompoundMixed( _ name1: String, _ lhs1: (Query) -> Query, _ value1: T, _ name2: String, _ lhs2: (Query) -> Query, _ value2: U) { assertQuery(Root.self, "(((\(name1) == %@) || (\(name2) == %@)) && ((\(name1) != %@) || (\(name2) != %@)))", values: [value1, value2, value1, value2], count: 0) { (lhs1($0) == value1 || lhs2($0) == value2) && (lhs1($0) != value1 || lhs2($0) != value2) } assertQuery(Root.self, "((\(name1) == %@) || (\(name2) == %@))", values: [value1, value2], count: 1) { (lhs1($0) == value1) || (lhs2($0) == value2) } } func validateCompoundString( _ name1: String, _ lhs1: (Query) -> Query, _ value1: T, _ name2: String, _ lhs2: (Query) -> Query, _ value2: U) where U.PersistedType: _QueryBinary { assertQuery(Root.self, "(NOT ((\(name1) == %@) || (\(name2) CONTAINS %@)) && (\(name2) == %@))", values: [value1, value2, value2], count: 0) { !(lhs1($0) == value1 || lhs2($0).contains(value2)) && (lhs2($0) == value2) } } func testCompoundMixed() { validateCompoundMixed("boolCol", \Query.boolCol, false, "intCol", \Query.intCol, 3) validateCompoundMixed("intCol", \Query.intCol, 3, "int8Col", \Query.int8Col, Int8(9)) validateCompoundMixed("int8Col", \Query.int8Col, Int8(9), "int16Col", \Query.int16Col, Int16(17)) validateCompoundMixed("int16Col", \Query.int16Col, Int16(17), "int32Col", \Query.int32Col, Int32(33)) validateCompoundMixed("int32Col", \Query.int32Col, Int32(33), "int64Col", \Query.int64Col, Int64(65)) validateCompoundMixed("int64Col", \Query.int64Col, Int64(65), "floatCol", \Query.floatCol, Float(6.55444333)) validateCompoundMixed("floatCol", \Query.floatCol, Float(6.55444333), "doubleCol", \Query.doubleCol, 234.567) validateCompoundMixed("doubleCol", \Query.doubleCol, 234.567, "stringCol", \Query.stringCol, "Foó") validateCompoundString("doubleCol", \Query.doubleCol, 234.567, "stringCol", \Query.stringCol, "Foó") validateCompoundMixed("stringCol", \Query.stringCol, "Foó", "binaryCol", \Query.binaryCol, Data(count: 128)) validateCompoundMixed("binaryCol", \Query.binaryCol, Data(count: 128), "dateCol", \Query.dateCol, Date(timeIntervalSince1970: 2000000)) validateCompoundMixed("dateCol", \Query.dateCol, Date(timeIntervalSince1970: 2000000), "decimalCol", \Query.decimalCol, Decimal128(234.567)) validateCompoundMixed("decimalCol", \Query.decimalCol, Decimal128(234.567), "objectIdCol", \Query.objectIdCol, ObjectId("61184062c1d8f096a3695045")) validateCompoundMixed("objectIdCol", \Query.objectIdCol, ObjectId("61184062c1d8f096a3695045"), "uuidCol", \Query.uuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) validateCompoundMixed("uuidCol", \Query.uuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, "intEnumCol", \Query.intEnumCol, .value2) validateCompoundMixed("intEnumCol", \Query.intEnumCol, .value2, "stringEnumCol", \Query.stringEnumCol, .value2) validateCompoundMixed("bool", \Query.bool, BoolWrapper(persistedValue: false), "int", \Query.int, IntWrapper(persistedValue: 3)) validateCompoundMixed("int", \Query.int, IntWrapper(persistedValue: 3), "int8", \Query.int8, Int8Wrapper(persistedValue: Int8(9))) validateCompoundMixed("int8", \Query.int8, Int8Wrapper(persistedValue: Int8(9)), "int16", \Query.int16, Int16Wrapper(persistedValue: Int16(17))) validateCompoundMixed("int16", \Query.int16, Int16Wrapper(persistedValue: Int16(17)), "int32", \Query.int32, Int32Wrapper(persistedValue: Int32(33))) validateCompoundMixed("int32", \Query.int32, Int32Wrapper(persistedValue: Int32(33)), "int64", \Query.int64, Int64Wrapper(persistedValue: Int64(65))) validateCompoundMixed("int64", \Query.int64, Int64Wrapper(persistedValue: Int64(65)), "float", \Query.float, FloatWrapper(persistedValue: Float(6.55444333))) validateCompoundMixed("float", \Query.float, FloatWrapper(persistedValue: Float(6.55444333)), "double", \Query.double, DoubleWrapper(persistedValue: 234.567)) validateCompoundMixed("double", \Query.double, DoubleWrapper(persistedValue: 234.567), "string", \Query.string, StringWrapper(persistedValue: "Foó")) validateCompoundString("double", \Query.double, DoubleWrapper(persistedValue: 234.567), "string", \Query.string, StringWrapper(persistedValue: "Foó")) validateCompoundMixed("string", \Query.string, StringWrapper(persistedValue: "Foó"), "binary", \Query.binary, DataWrapper(persistedValue: Data(count: 128))) validateCompoundMixed("binary", \Query.binary, DataWrapper(persistedValue: Data(count: 128)), "date", \Query.date, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateCompoundMixed("date", \Query.date, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), "decimal", \Query.decimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateCompoundMixed("decimal", \Query.decimal, Decimal128Wrapper(persistedValue: Decimal128(234.567)), "objectId", \Query.objectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) validateCompoundMixed("objectId", \Query.objectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), "uuid", \Query.uuid, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) validateCompoundMixed("optBoolCol", \Query.optBoolCol, false, "optIntCol", \Query.optIntCol, 3) validateCompoundMixed("optIntCol", \Query.optIntCol, 3, "optInt8Col", \Query.optInt8Col, Int8(9)) validateCompoundMixed("optInt8Col", \Query.optInt8Col, Int8(9), "optInt16Col", \Query.optInt16Col, Int16(17)) validateCompoundMixed("optInt16Col", \Query.optInt16Col, Int16(17), "optInt32Col", \Query.optInt32Col, Int32(33)) validateCompoundMixed("optInt32Col", \Query.optInt32Col, Int32(33), "optInt64Col", \Query.optInt64Col, Int64(65)) validateCompoundMixed("optInt64Col", \Query.optInt64Col, Int64(65), "optFloatCol", \Query.optFloatCol, Float(6.55444333)) validateCompoundMixed("optFloatCol", \Query.optFloatCol, Float(6.55444333), "optDoubleCol", \Query.optDoubleCol, 234.567) validateCompoundMixed("optDoubleCol", \Query.optDoubleCol, 234.567, "optStringCol", \Query.optStringCol, "Foó") validateCompoundString("optDoubleCol", \Query.optDoubleCol, 234.567, "optStringCol", \Query.optStringCol, "Foó") validateCompoundMixed("optStringCol", \Query.optStringCol, "Foó", "optBinaryCol", \Query.optBinaryCol, Data(count: 128)) validateCompoundMixed("optBinaryCol", \Query.optBinaryCol, Data(count: 128), "optDateCol", \Query.optDateCol, Date(timeIntervalSince1970: 2000000)) validateCompoundMixed("optDateCol", \Query.optDateCol, Date(timeIntervalSince1970: 2000000), "optDecimalCol", \Query.optDecimalCol, Decimal128(234.567)) validateCompoundMixed("optDecimalCol", \Query.optDecimalCol, Decimal128(234.567), "optObjectIdCol", \Query.optObjectIdCol, ObjectId("61184062c1d8f096a3695045")) validateCompoundMixed("optObjectIdCol", \Query.optObjectIdCol, ObjectId("61184062c1d8f096a3695045"), "optUuidCol", \Query.optUuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!) validateCompoundMixed("optUuidCol", \Query.optUuidCol, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, "optIntEnumCol", \Query.optIntEnumCol, .value2) validateCompoundMixed("optIntEnumCol", \Query.optIntEnumCol, .value2, "optStringEnumCol", \Query.optStringEnumCol, .value2) validateCompoundMixed("optBool", \Query.optBool, BoolWrapper(persistedValue: false), "optInt", \Query.optInt, IntWrapper(persistedValue: 3)) validateCompoundMixed("optInt", \Query.optInt, IntWrapper(persistedValue: 3), "optInt8", \Query.optInt8, Int8Wrapper(persistedValue: Int8(9))) validateCompoundMixed("optInt8", \Query.optInt8, Int8Wrapper(persistedValue: Int8(9)), "optInt16", \Query.optInt16, Int16Wrapper(persistedValue: Int16(17))) validateCompoundMixed("optInt16", \Query.optInt16, Int16Wrapper(persistedValue: Int16(17)), "optInt32", \Query.optInt32, Int32Wrapper(persistedValue: Int32(33))) validateCompoundMixed("optInt32", \Query.optInt32, Int32Wrapper(persistedValue: Int32(33)), "optInt64", \Query.optInt64, Int64Wrapper(persistedValue: Int64(65))) validateCompoundMixed("optInt64", \Query.optInt64, Int64Wrapper(persistedValue: Int64(65)), "optFloat", \Query.optFloat, FloatWrapper(persistedValue: Float(6.55444333))) validateCompoundMixed("optFloat", \Query.optFloat, FloatWrapper(persistedValue: Float(6.55444333)), "optDouble", \Query.optDouble, DoubleWrapper(persistedValue: 234.567)) validateCompoundMixed("optDouble", \Query.optDouble, DoubleWrapper(persistedValue: 234.567), "optString", \Query.optString, StringWrapper(persistedValue: "Foó")) validateCompoundString("optDouble", \Query.optDouble, DoubleWrapper(persistedValue: 234.567), "optString", \Query.optString, StringWrapper(persistedValue: "Foó")) validateCompoundMixed("optString", \Query.optString, StringWrapper(persistedValue: "Foó"), "optBinary", \Query.optBinary, DataWrapper(persistedValue: Data(count: 128))) validateCompoundMixed("optBinary", \Query.optBinary, DataWrapper(persistedValue: Data(count: 128)), "optDate", \Query.optDate, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))) validateCompoundMixed("optDate", \Query.optDate, DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000)), "optDecimal", \Query.optDecimal, Decimal128Wrapper(persistedValue: Decimal128(234.567))) validateCompoundMixed("optDecimal", \Query.optDecimal, Decimal128Wrapper(persistedValue: Decimal128(234.567)), "optObjectId", \Query.optObjectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))) validateCompoundMixed("optObjectId", \Query.optObjectId, ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045")), "optUuid", \Query.optUuid, UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)) // Aggregates let sumarrayInt = 1 + 3 assertQuery("(((((arrayInt.@min <= %@) || (arrayInt.@max >= %@)) && (arrayInt.@sum == %@)) && (arrayInt.@count != %@)) && ((arrayInt.@avg > %@) && (arrayInt.@avg < %@)))", values: [1, 5, sumarrayInt, 0, 1, 5], count: 1) { (($0.arrayInt.min <= 1) || ($0.arrayInt.max >= 5)) && ($0.arrayInt.sum == sumarrayInt) && ($0.arrayInt.count != 0) && ($0.arrayInt.avg > 1 && $0.arrayInt.avg < 5) } let sumarrayOptInt = 1 + 3 assertQuery("(((((arrayOptInt.@min <= %@) || (arrayOptInt.@max >= %@)) && (arrayOptInt.@sum == %@)) && (arrayOptInt.@count != %@)) && ((arrayOptInt.@avg > %@) && (arrayOptInt.@avg < %@)))", values: [1, 5, sumarrayOptInt, 0, 1, 5], count: 1) { (($0.arrayOptInt.min <= 1) || ($0.arrayOptInt.max >= 5)) && ($0.arrayOptInt.sum == sumarrayOptInt) && ($0.arrayOptInt.count != 0) && ($0.arrayOptInt.avg > 1 && $0.arrayOptInt.avg < 5) } let summapInt = 1 + 3 assertQuery("(((((mapInt.@min <= %@) || (mapInt.@max >= %@)) && (mapInt.@sum == %@)) && (mapInt.@count != %@)) && ((mapInt.@avg > %@) && (mapInt.@avg < %@)))", values: [1, 5, summapInt, 0, 1, 5], count: 1) { (($0.mapInt.min <= 1) || ($0.mapInt.max >= 5)) && ($0.mapInt.sum == summapInt) && ($0.mapInt.count != 0) && ($0.mapInt.avg > 1 && $0.mapInt.avg < 5) } let summapOptInt = 1 + 3 assertQuery("(((((mapOptInt.@min <= %@) || (mapOptInt.@max >= %@)) && (mapOptInt.@sum == %@)) && (mapOptInt.@count != %@)) && ((mapOptInt.@avg > %@) && (mapOptInt.@avg < %@)))", values: [1, 5, summapOptInt, 0, 1, 5], count: 1) { (($0.mapOptInt.min <= 1) || ($0.mapOptInt.max >= 5)) && ($0.mapOptInt.sum == summapOptInt) && ($0.mapOptInt.count != 0) && ($0.mapOptInt.avg > 1 && $0.mapOptInt.avg < 5) } // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() let sumdoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "(((((list.@min.doubleCol <= %@) || (list.@max.doubleCol >= %@)) && (list.@sum.doubleCol == %@)) && (list.@sum.doubleCol != %@)) && ((list.@avg.doubleCol > %@) && (list.@avg.doubleCol < %@)))", values: [123.456, 345.678, sumdoubleCol, 0, 123.456, 345.678], count: 1) { ($0.list.doubleCol.min <= 123.456 || $0.list.doubleCol.max >= 345.678) && $0.list.doubleCol.sum == sumdoubleCol && $0.list.doubleCol.sum != 0 && ($0.list.doubleCol.avg > 123.456 && $0.list.doubleCol.avg < 345.678) } let sumoptDoubleCol = 123.456 + 234.567 + 345.678 assertQuery(LinkToModernAllTypesObject.self, "(((((list.@min.optDoubleCol <= %@) || (list.@max.optDoubleCol >= %@)) && (list.@sum.optDoubleCol == %@)) && (list.@sum.optDoubleCol != %@)) && ((list.@avg.optDoubleCol > %@) && (list.@avg.optDoubleCol < %@)))", values: [123.456, 345.678, sumoptDoubleCol, 0, 123.456, 345.678], count: 1) { ($0.list.optDoubleCol.min <= 123.456 || $0.list.optDoubleCol.max >= 345.678) && $0.list.optDoubleCol.sum == sumoptDoubleCol && $0.list.optDoubleCol.sum != 0 && ($0.list.optDoubleCol.avg > 123.456 && $0.list.optDoubleCol.avg < 345.678) } } func testAny() { assertQuery(ModernAllTypesObject.self, "(ANY arrayBool == %@)", true, count: 1) { $0.arrayBool == true } assertQuery(ModernAllTypesObject.self, "(ANY arrayInt == %@)", 1, count: 1) { $0.arrayInt == 1 } assertQuery(ModernAllTypesObject.self, "(ANY arrayInt8 == %@)", Int8(8), count: 1) { $0.arrayInt8 == Int8(8) } assertQuery(ModernAllTypesObject.self, "(ANY arrayInt16 == %@)", Int16(16), count: 1) { $0.arrayInt16 == Int16(16) } assertQuery(ModernAllTypesObject.self, "(ANY arrayInt32 == %@)", Int32(32), count: 1) { $0.arrayInt32 == Int32(32) } assertQuery(ModernAllTypesObject.self, "(ANY arrayInt64 == %@)", Int64(64), count: 1) { $0.arrayInt64 == Int64(64) } assertQuery(ModernAllTypesObject.self, "(ANY arrayFloat == %@)", Float(5.55444333), count: 1) { $0.arrayFloat == Float(5.55444333) } assertQuery(ModernAllTypesObject.self, "(ANY arrayDouble == %@)", 123.456, count: 1) { $0.arrayDouble == 123.456 } assertQuery(ModernAllTypesObject.self, "(ANY arrayString == %@)", "Foo", count: 1) { $0.arrayString == "Foo" } assertQuery(ModernAllTypesObject.self, "(ANY arrayBinary == %@)", Data(count: 64), count: 1) { $0.arrayBinary == Data(count: 64) } assertQuery(ModernAllTypesObject.self, "(ANY arrayDate == %@)", Date(timeIntervalSince1970: 1000000), count: 1) { $0.arrayDate == Date(timeIntervalSince1970: 1000000) } assertQuery(ModernAllTypesObject.self, "(ANY arrayDecimal == %@)", Decimal128(123.456), count: 1) { $0.arrayDecimal == Decimal128(123.456) } assertQuery(ModernAllTypesObject.self, "(ANY arrayObjectId == %@)", ObjectId("61184062c1d8f096a3695046"), count: 1) { $0.arrayObjectId == ObjectId("61184062c1d8f096a3695046") } assertQuery(ModernAllTypesObject.self, "(ANY arrayUuid == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, count: 1) { $0.arrayUuid == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! } assertQuery(ModernAllTypesObject.self, "(ANY arrayAny == %@)", AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.arrayAny == AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")) } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt == %@)", EnumInt.value1, count: 1) { $0.listInt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt8 == %@)", EnumInt8.value1, count: 1) { $0.listInt8 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt16 == %@)", EnumInt16.value1, count: 1) { $0.listInt16 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt32 == %@)", EnumInt32.value1, count: 1) { $0.listInt32 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt64 == %@)", EnumInt64.value1, count: 1) { $0.listInt64 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listFloat == %@)", EnumFloat.value1, count: 1) { $0.listFloat == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listDouble == %@)", EnumDouble.value1, count: 1) { $0.listDouble == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listString == %@)", EnumString.value1, count: 1) { $0.listString == .value1 } assertQuery(CustomPersistableCollections.self, "(ANY listBool == %@)", BoolWrapper(persistedValue: true), count: 1) { $0.listBool == BoolWrapper(persistedValue: true) } assertQuery(CustomPersistableCollections.self, "(ANY listInt == %@)", IntWrapper(persistedValue: 1), count: 1) { $0.listInt == IntWrapper(persistedValue: 1) } assertQuery(CustomPersistableCollections.self, "(ANY listInt8 == %@)", Int8Wrapper(persistedValue: Int8(8)), count: 1) { $0.listInt8 == Int8Wrapper(persistedValue: Int8(8)) } assertQuery(CustomPersistableCollections.self, "(ANY listInt16 == %@)", Int16Wrapper(persistedValue: Int16(16)), count: 1) { $0.listInt16 == Int16Wrapper(persistedValue: Int16(16)) } assertQuery(CustomPersistableCollections.self, "(ANY listInt32 == %@)", Int32Wrapper(persistedValue: Int32(32)), count: 1) { $0.listInt32 == Int32Wrapper(persistedValue: Int32(32)) } assertQuery(CustomPersistableCollections.self, "(ANY listInt64 == %@)", Int64Wrapper(persistedValue: Int64(64)), count: 1) { $0.listInt64 == Int64Wrapper(persistedValue: Int64(64)) } assertQuery(CustomPersistableCollections.self, "(ANY listFloat == %@)", FloatWrapper(persistedValue: Float(5.55444333)), count: 1) { $0.listFloat == FloatWrapper(persistedValue: Float(5.55444333)) } assertQuery(CustomPersistableCollections.self, "(ANY listDouble == %@)", DoubleWrapper(persistedValue: 123.456), count: 1) { $0.listDouble == DoubleWrapper(persistedValue: 123.456) } assertQuery(CustomPersistableCollections.self, "(ANY listString == %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.listString == StringWrapper(persistedValue: "Foo") } assertQuery(CustomPersistableCollections.self, "(ANY listBinary == %@)", DataWrapper(persistedValue: Data(count: 64)), count: 1) { $0.listBinary == DataWrapper(persistedValue: Data(count: 64)) } assertQuery(CustomPersistableCollections.self, "(ANY listDate == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), count: 1) { $0.listDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) } assertQuery(CustomPersistableCollections.self, "(ANY listDecimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(123.456)), count: 1) { $0.listDecimal == Decimal128Wrapper(persistedValue: Decimal128(123.456)) } assertQuery(CustomPersistableCollections.self, "(ANY listObjectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.listObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) } assertQuery(CustomPersistableCollections.self, "(ANY listUuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 1) { $0.listUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptBool == %@)", true, count: 1) { $0.arrayOptBool == true } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptInt == %@)", 1, count: 1) { $0.arrayOptInt == 1 } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptInt8 == %@)", Int8(8), count: 1) { $0.arrayOptInt8 == Int8(8) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptInt16 == %@)", Int16(16), count: 1) { $0.arrayOptInt16 == Int16(16) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptInt32 == %@)", Int32(32), count: 1) { $0.arrayOptInt32 == Int32(32) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptInt64 == %@)", Int64(64), count: 1) { $0.arrayOptInt64 == Int64(64) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptFloat == %@)", Float(5.55444333), count: 1) { $0.arrayOptFloat == Float(5.55444333) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptDouble == %@)", 123.456, count: 1) { $0.arrayOptDouble == 123.456 } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptString == %@)", "Foo", count: 1) { $0.arrayOptString == "Foo" } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptBinary == %@)", Data(count: 64), count: 1) { $0.arrayOptBinary == Data(count: 64) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptDate == %@)", Date(timeIntervalSince1970: 1000000), count: 1) { $0.arrayOptDate == Date(timeIntervalSince1970: 1000000) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptDecimal == %@)", Decimal128(123.456), count: 1) { $0.arrayOptDecimal == Decimal128(123.456) } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptObjectId == %@)", ObjectId("61184062c1d8f096a3695046"), count: 1) { $0.arrayOptObjectId == ObjectId("61184062c1d8f096a3695046") } assertQuery(ModernAllTypesObject.self, "(ANY arrayOptUuid == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, count: 1) { $0.arrayOptUuid == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! } assertQuery(ModernCollectionsOfEnums.self, "(ANY listIntOpt == %@)", EnumInt.value1, count: 1) { $0.listIntOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt8Opt == %@)", EnumInt8.value1, count: 1) { $0.listInt8Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt16Opt == %@)", EnumInt16.value1, count: 1) { $0.listInt16Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt32Opt == %@)", EnumInt32.value1, count: 1) { $0.listInt32Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listInt64Opt == %@)", EnumInt64.value1, count: 1) { $0.listInt64Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listFloatOpt == %@)", EnumFloat.value1, count: 1) { $0.listFloatOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listDoubleOpt == %@)", EnumDouble.value1, count: 1) { $0.listDoubleOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY listStringOpt == %@)", EnumString.value1, count: 1) { $0.listStringOpt == .value1 } assertQuery(CustomPersistableCollections.self, "(ANY listOptBool == %@)", BoolWrapper(persistedValue: true), count: 1) { $0.listOptBool == BoolWrapper(persistedValue: true) } assertQuery(CustomPersistableCollections.self, "(ANY listOptInt == %@)", IntWrapper(persistedValue: 1), count: 1) { $0.listOptInt == IntWrapper(persistedValue: 1) } assertQuery(CustomPersistableCollections.self, "(ANY listOptInt8 == %@)", Int8Wrapper(persistedValue: Int8(8)), count: 1) { $0.listOptInt8 == Int8Wrapper(persistedValue: Int8(8)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptInt16 == %@)", Int16Wrapper(persistedValue: Int16(16)), count: 1) { $0.listOptInt16 == Int16Wrapper(persistedValue: Int16(16)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptInt32 == %@)", Int32Wrapper(persistedValue: Int32(32)), count: 1) { $0.listOptInt32 == Int32Wrapper(persistedValue: Int32(32)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptInt64 == %@)", Int64Wrapper(persistedValue: Int64(64)), count: 1) { $0.listOptInt64 == Int64Wrapper(persistedValue: Int64(64)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptFloat == %@)", FloatWrapper(persistedValue: Float(5.55444333)), count: 1) { $0.listOptFloat == FloatWrapper(persistedValue: Float(5.55444333)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptDouble == %@)", DoubleWrapper(persistedValue: 123.456), count: 1) { $0.listOptDouble == DoubleWrapper(persistedValue: 123.456) } assertQuery(CustomPersistableCollections.self, "(ANY listOptString == %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.listOptString == StringWrapper(persistedValue: "Foo") } assertQuery(CustomPersistableCollections.self, "(ANY listOptBinary == %@)", DataWrapper(persistedValue: Data(count: 64)), count: 1) { $0.listOptBinary == DataWrapper(persistedValue: Data(count: 64)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptDate == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), count: 1) { $0.listOptDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptDecimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(123.456)), count: 1) { $0.listOptDecimal == Decimal128Wrapper(persistedValue: Decimal128(123.456)) } assertQuery(CustomPersistableCollections.self, "(ANY listOptObjectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.listOptObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) } assertQuery(CustomPersistableCollections.self, "(ANY listOptUuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 1) { $0.listOptUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } assertQuery(ModernAllTypesObject.self, "(ANY setBool == %@)", true, count: 1) { $0.setBool == true } assertQuery(ModernAllTypesObject.self, "(ANY setInt == %@)", 1, count: 1) { $0.setInt == 1 } assertQuery(ModernAllTypesObject.self, "(ANY setInt8 == %@)", Int8(8), count: 1) { $0.setInt8 == Int8(8) } assertQuery(ModernAllTypesObject.self, "(ANY setInt16 == %@)", Int16(16), count: 1) { $0.setInt16 == Int16(16) } assertQuery(ModernAllTypesObject.self, "(ANY setInt32 == %@)", Int32(32), count: 1) { $0.setInt32 == Int32(32) } assertQuery(ModernAllTypesObject.self, "(ANY setInt64 == %@)", Int64(64), count: 1) { $0.setInt64 == Int64(64) } assertQuery(ModernAllTypesObject.self, "(ANY setFloat == %@)", Float(5.55444333), count: 1) { $0.setFloat == Float(5.55444333) } assertQuery(ModernAllTypesObject.self, "(ANY setDouble == %@)", 123.456, count: 1) { $0.setDouble == 123.456 } assertQuery(ModernAllTypesObject.self, "(ANY setString == %@)", "Foo", count: 1) { $0.setString == "Foo" } assertQuery(ModernAllTypesObject.self, "(ANY setBinary == %@)", Data(count: 64), count: 1) { $0.setBinary == Data(count: 64) } assertQuery(ModernAllTypesObject.self, "(ANY setDate == %@)", Date(timeIntervalSince1970: 1000000), count: 1) { $0.setDate == Date(timeIntervalSince1970: 1000000) } assertQuery(ModernAllTypesObject.self, "(ANY setDecimal == %@)", Decimal128(123.456), count: 1) { $0.setDecimal == Decimal128(123.456) } assertQuery(ModernAllTypesObject.self, "(ANY setObjectId == %@)", ObjectId("61184062c1d8f096a3695046"), count: 1) { $0.setObjectId == ObjectId("61184062c1d8f096a3695046") } assertQuery(ModernAllTypesObject.self, "(ANY setUuid == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, count: 1) { $0.setUuid == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! } assertQuery(ModernAllTypesObject.self, "(ANY setAny == %@)", AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.setAny == AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")) } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt == %@)", EnumInt.value1, count: 1) { $0.setInt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt8 == %@)", EnumInt8.value1, count: 1) { $0.setInt8 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt16 == %@)", EnumInt16.value1, count: 1) { $0.setInt16 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt32 == %@)", EnumInt32.value1, count: 1) { $0.setInt32 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt64 == %@)", EnumInt64.value1, count: 1) { $0.setInt64 == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setFloat == %@)", EnumFloat.value1, count: 1) { $0.setFloat == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setDouble == %@)", EnumDouble.value1, count: 1) { $0.setDouble == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setString == %@)", EnumString.value1, count: 1) { $0.setString == .value1 } assertQuery(CustomPersistableCollections.self, "(ANY setBool == %@)", BoolWrapper(persistedValue: true), count: 1) { $0.setBool == BoolWrapper(persistedValue: true) } assertQuery(CustomPersistableCollections.self, "(ANY setInt == %@)", IntWrapper(persistedValue: 1), count: 1) { $0.setInt == IntWrapper(persistedValue: 1) } assertQuery(CustomPersistableCollections.self, "(ANY setInt8 == %@)", Int8Wrapper(persistedValue: Int8(8)), count: 1) { $0.setInt8 == Int8Wrapper(persistedValue: Int8(8)) } assertQuery(CustomPersistableCollections.self, "(ANY setInt16 == %@)", Int16Wrapper(persistedValue: Int16(16)), count: 1) { $0.setInt16 == Int16Wrapper(persistedValue: Int16(16)) } assertQuery(CustomPersistableCollections.self, "(ANY setInt32 == %@)", Int32Wrapper(persistedValue: Int32(32)), count: 1) { $0.setInt32 == Int32Wrapper(persistedValue: Int32(32)) } assertQuery(CustomPersistableCollections.self, "(ANY setInt64 == %@)", Int64Wrapper(persistedValue: Int64(64)), count: 1) { $0.setInt64 == Int64Wrapper(persistedValue: Int64(64)) } assertQuery(CustomPersistableCollections.self, "(ANY setFloat == %@)", FloatWrapper(persistedValue: Float(5.55444333)), count: 1) { $0.setFloat == FloatWrapper(persistedValue: Float(5.55444333)) } assertQuery(CustomPersistableCollections.self, "(ANY setDouble == %@)", DoubleWrapper(persistedValue: 123.456), count: 1) { $0.setDouble == DoubleWrapper(persistedValue: 123.456) } assertQuery(CustomPersistableCollections.self, "(ANY setString == %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.setString == StringWrapper(persistedValue: "Foo") } assertQuery(CustomPersistableCollections.self, "(ANY setBinary == %@)", DataWrapper(persistedValue: Data(count: 64)), count: 1) { $0.setBinary == DataWrapper(persistedValue: Data(count: 64)) } assertQuery(CustomPersistableCollections.self, "(ANY setDate == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), count: 1) { $0.setDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) } assertQuery(CustomPersistableCollections.self, "(ANY setDecimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(123.456)), count: 1) { $0.setDecimal == Decimal128Wrapper(persistedValue: Decimal128(123.456)) } assertQuery(CustomPersistableCollections.self, "(ANY setObjectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.setObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) } assertQuery(CustomPersistableCollections.self, "(ANY setUuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 1) { $0.setUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } assertQuery(ModernAllTypesObject.self, "(ANY setOptBool == %@)", true, count: 1) { $0.setOptBool == true } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt == %@)", 1, count: 1) { $0.setOptInt == 1 } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt8 == %@)", Int8(8), count: 1) { $0.setOptInt8 == Int8(8) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt16 == %@)", Int16(16), count: 1) { $0.setOptInt16 == Int16(16) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt32 == %@)", Int32(32), count: 1) { $0.setOptInt32 == Int32(32) } assertQuery(ModernAllTypesObject.self, "(ANY setOptInt64 == %@)", Int64(64), count: 1) { $0.setOptInt64 == Int64(64) } assertQuery(ModernAllTypesObject.self, "(ANY setOptFloat == %@)", Float(5.55444333), count: 1) { $0.setOptFloat == Float(5.55444333) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDouble == %@)", 123.456, count: 1) { $0.setOptDouble == 123.456 } assertQuery(ModernAllTypesObject.self, "(ANY setOptString == %@)", "Foo", count: 1) { $0.setOptString == "Foo" } assertQuery(ModernAllTypesObject.self, "(ANY setOptBinary == %@)", Data(count: 64), count: 1) { $0.setOptBinary == Data(count: 64) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDate == %@)", Date(timeIntervalSince1970: 1000000), count: 1) { $0.setOptDate == Date(timeIntervalSince1970: 1000000) } assertQuery(ModernAllTypesObject.self, "(ANY setOptDecimal == %@)", Decimal128(123.456), count: 1) { $0.setOptDecimal == Decimal128(123.456) } assertQuery(ModernAllTypesObject.self, "(ANY setOptObjectId == %@)", ObjectId("61184062c1d8f096a3695046"), count: 1) { $0.setOptObjectId == ObjectId("61184062c1d8f096a3695046") } assertQuery(ModernAllTypesObject.self, "(ANY setOptUuid == %@)", UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, count: 1) { $0.setOptUuid == UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")! } assertQuery(ModernCollectionsOfEnums.self, "(ANY setIntOpt == %@)", EnumInt.value1, count: 1) { $0.setIntOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt8Opt == %@)", EnumInt8.value1, count: 1) { $0.setInt8Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt16Opt == %@)", EnumInt16.value1, count: 1) { $0.setInt16Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt32Opt == %@)", EnumInt32.value1, count: 1) { $0.setInt32Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setInt64Opt == %@)", EnumInt64.value1, count: 1) { $0.setInt64Opt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setFloatOpt == %@)", EnumFloat.value1, count: 1) { $0.setFloatOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setDoubleOpt == %@)", EnumDouble.value1, count: 1) { $0.setDoubleOpt == .value1 } assertQuery(ModernCollectionsOfEnums.self, "(ANY setStringOpt == %@)", EnumString.value1, count: 1) { $0.setStringOpt == .value1 } assertQuery(CustomPersistableCollections.self, "(ANY setOptBool == %@)", BoolWrapper(persistedValue: true), count: 1) { $0.setOptBool == BoolWrapper(persistedValue: true) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt == %@)", IntWrapper(persistedValue: 1), count: 1) { $0.setOptInt == IntWrapper(persistedValue: 1) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt8 == %@)", Int8Wrapper(persistedValue: Int8(8)), count: 1) { $0.setOptInt8 == Int8Wrapper(persistedValue: Int8(8)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt16 == %@)", Int16Wrapper(persistedValue: Int16(16)), count: 1) { $0.setOptInt16 == Int16Wrapper(persistedValue: Int16(16)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt32 == %@)", Int32Wrapper(persistedValue: Int32(32)), count: 1) { $0.setOptInt32 == Int32Wrapper(persistedValue: Int32(32)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptInt64 == %@)", Int64Wrapper(persistedValue: Int64(64)), count: 1) { $0.setOptInt64 == Int64Wrapper(persistedValue: Int64(64)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptFloat == %@)", FloatWrapper(persistedValue: Float(5.55444333)), count: 1) { $0.setOptFloat == FloatWrapper(persistedValue: Float(5.55444333)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDouble == %@)", DoubleWrapper(persistedValue: 123.456), count: 1) { $0.setOptDouble == DoubleWrapper(persistedValue: 123.456) } assertQuery(CustomPersistableCollections.self, "(ANY setOptString == %@)", StringWrapper(persistedValue: "Foo"), count: 1) { $0.setOptString == StringWrapper(persistedValue: "Foo") } assertQuery(CustomPersistableCollections.self, "(ANY setOptBinary == %@)", DataWrapper(persistedValue: Data(count: 64)), count: 1) { $0.setOptBinary == DataWrapper(persistedValue: Data(count: 64)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDate == %@)", DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), count: 1) { $0.setOptDate == DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptDecimal == %@)", Decimal128Wrapper(persistedValue: Decimal128(123.456)), count: 1) { $0.setOptDecimal == Decimal128Wrapper(persistedValue: Decimal128(123.456)) } assertQuery(CustomPersistableCollections.self, "(ANY setOptObjectId == %@)", ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), count: 1) { $0.setOptObjectId == ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")) } assertQuery(CustomPersistableCollections.self, "(ANY setOptUuid == %@)", UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), count: 1) { $0.setOptUuid == UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!) } assertQuery("(((ANY arrayCol.intCol != %@) && (ANY arrayCol.objectCol.intCol > %@)) && ((ANY setCol.intCol != %@) && (ANY setCol.objectCol.intCol > %@)))", values: [123, 456, 123, 456], count: 0) { (($0.arrayCol.intCol != 123) && ($0.arrayCol.objectCol.intCol > 456)) && (($0.setCol.intCol != 123) && ($0.setCol.objectCol.intCol > 456)) } } func testSubquery() { // List // Count of results will be 0 because there are no `ModernAllTypesObject`s in the list. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 0) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(arrayCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [5, "Bar", 0], count: 0) { $0.intCol == 5 && ($0.arrayCol.stringCol == "Bar").count == 0 } // Set // Will be 0 results because there are no `ModernAllTypesObject`s in the set. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 0) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [5, "Bar", 0], count: 0) { $0.intCol == 5 && ($0.setCol.stringCol == "Bar").count == 0 } let object = objects().first! try! realm.write { let modernObj = ModernAllTypesObject(value: ["intCol": 5, "stringCol": "Foo"]) object.arrayCol.append(modernObj) object.setCol.insert(modernObj) } // Results count should now be 1 assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.arrayInt.@count >= %@)).@count > %@)", values: [0, 0], count: 1) { ($0.arrayCol.arrayInt.count >= 0).count > 0 } // Subquery in a subquery assertQuery("(SUBQUERY(arrayCol, $col1, (($col1.arrayInt.@count >= %@) && (SUBQUERY(arrayCol, $col2, ($col2.intCol != %@)).@count > %@))).@count > %@)", values: [0, 123, 0, 0], count: 0) { ($0.arrayCol.arrayInt.count >= 0 && ($0.arrayCol.intCol != 123).count > 0).count > 0 } assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 1) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("(SUBQUERY(arrayCol, $col1, (($col1.intCol > %@) && ($col1.intCol <= %@))).@count > %@)", values: [0, 5, 0], count: 1) { ($0.arrayCol.intCol > 0 && $0.arrayCol.intCol <= 5 ).count > 0 } assertQuery("((SUBQUERY(arrayCol, $col1, ($col1.intCol == %@)).@count == %@) && (SUBQUERY(arrayCol, $col2, ($col2.stringCol == %@)).@count == %@))", values: [5, 1, "Bar", 0], count: 1) { ($0.arrayCol.intCol == 5).count == 1 && ($0.arrayCol.stringCol == "Bar").count == 0 } // Set // Will be 0 results because there are no `ModernAllTypesObject`s in the set. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 1) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [3, "Bar", 0], count: 1) { ($0.intCol == 3) && ($0.setCol.stringCol == "Bar").count == 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, (($col1.intCol == %@) && ($col1.stringCol != %@))).@count == %@))", values: [3, 5, "Blah", 1], count: 1) { ($0.intCol == 3) && (((($0.setCol.intCol == 5) && ($0.setCol.stringCol != "Blah"))).count == 1) } // Column comparison assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.stringCol == stringCol)).@count == %@)", 0, count: 1) { ($0.arrayCol.stringCol == $0.stringCol).count == 0 } assertThrows(assertQuery("", count: 1) { ($0.stringCol == $0.stringCol).count == 0 }, reason: "Subqueries must contain a keypath starting with a collection.") } // MARK: - Collection Aggregations private func validateAverage(_ name: String, _ average: T.Element, _ min: T.Element, _ lhs: (Query) -> Query) where T.Element.PersistedType: _QueryNumeric, T.Element: QueryValue { assertQuery(Root.self, "(object.\(name).@avg == %@)", average, count: 1) { lhs($0).avg == average } assertQuery(Root.self, "(object.\(name).@avg == %@)", min, count: 0) { lhs($0).avg == min } assertQuery(Root.self, "(object.\(name).@avg != %@)", average, count: 0) { lhs($0).avg != average } assertQuery(Root.self, "(object.\(name).@avg != %@)", min, count: 1) { lhs($0).avg != min } assertQuery(Root.self, "(object.\(name).@avg > %@)", average, count: 0) { lhs($0).avg > average } assertQuery(Root.self, "(object.\(name).@avg > %@)", min, count: 1) { lhs($0).avg > min } assertQuery(Root.self, "(object.\(name).@avg < %@)", average, count: 0) { lhs($0).avg < average } assertQuery(Root.self, "(object.\(name).@avg >= %@)", average, count: 1) { lhs($0).avg >= average } assertQuery(Root.self, "(object.\(name).@avg <= %@)", average, count: 1) { lhs($0).avg <= average } } private func validateAverage(_ name: String, _ average: T.Value, _ min: T.Value, _ lhs: (Query) -> Query) where T.Value.PersistedType: _QueryNumeric, T.Value: QueryValue { assertQuery(Root.self, "(object.\(name).@avg == %@)", average, count: 1) { lhs($0).avg == average } assertQuery(Root.self, "(object.\(name).@avg == %@)", min, count: 0) { lhs($0).avg == min } assertQuery(Root.self, "(object.\(name).@avg != %@)", average, count: 0) { lhs($0).avg != average } assertQuery(Root.self, "(object.\(name).@avg != %@)", min, count: 1) { lhs($0).avg != min } assertQuery(Root.self, "(object.\(name).@avg > %@)", average, count: 0) { lhs($0).avg > average } assertQuery(Root.self, "(object.\(name).@avg > %@)", min, count: 1) { lhs($0).avg > min } assertQuery(Root.self, "(object.\(name).@avg < %@)", average, count: 0) { lhs($0).avg < average } assertQuery(Root.self, "(object.\(name).@avg >= %@)", average, count: 1) { lhs($0).avg >= average } assertQuery(Root.self, "(object.\(name).@avg <= %@)", average, count: 1) { lhs($0).avg <= average } } func testCollectionAggregatesAvg() { initLinkedCollectionAggregatesObject() validateAverage("arrayInt", Int.average(), 1, \Query.object.arrayInt) validateAverage("arrayInt8", Int8.average(), Int8(8), \Query.object.arrayInt8) validateAverage("arrayInt16", Int16.average(), Int16(16), \Query.object.arrayInt16) validateAverage("arrayInt32", Int32.average(), Int32(32), \Query.object.arrayInt32) validateAverage("arrayInt64", Int64.average(), Int64(64), \Query.object.arrayInt64) validateAverage("arrayFloat", Float.average(), Float(5.55444333), \Query.object.arrayFloat) validateAverage("arrayDouble", Double.average(), 123.456, \Query.object.arrayDouble) validateAverage("arrayDecimal", Decimal128.average(), Decimal128(123.456), \Query.object.arrayDecimal) validateAverage("listInt", EnumInt.average(), EnumInt.value1.rawValue, \Query.object.listInt.rawValue) validateAverage("listInt8", EnumInt8.average(), EnumInt8.value1.rawValue, \Query.object.listInt8.rawValue) validateAverage("listInt16", EnumInt16.average(), EnumInt16.value1.rawValue, \Query.object.listInt16.rawValue) validateAverage("listInt32", EnumInt32.average(), EnumInt32.value1.rawValue, \Query.object.listInt32.rawValue) validateAverage("listInt64", EnumInt64.average(), EnumInt64.value1.rawValue, \Query.object.listInt64.rawValue) validateAverage("listDouble", EnumDouble.average(), EnumDouble.value1.rawValue, \Query.object.listDouble.rawValue) validateAverage("listInt", IntWrapper.average(), IntWrapper(persistedValue: 1), \Query.object.listInt) validateAverage("listInt8", Int8Wrapper.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.listInt8) validateAverage("listInt16", Int16Wrapper.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.listInt16) validateAverage("listInt32", Int32Wrapper.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.listInt32) validateAverage("listInt64", Int64Wrapper.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.listInt64) validateAverage("listFloat", FloatWrapper.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.listFloat) validateAverage("listDouble", DoubleWrapper.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.listDouble) validateAverage("listDecimal", Decimal128Wrapper.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.listDecimal) validateAverage("arrayOptInt", Int?.average(), 1, \Query.object.arrayOptInt) validateAverage("arrayOptInt8", Int8?.average(), Int8(8), \Query.object.arrayOptInt8) validateAverage("arrayOptInt16", Int16?.average(), Int16(16), \Query.object.arrayOptInt16) validateAverage("arrayOptInt32", Int32?.average(), Int32(32), \Query.object.arrayOptInt32) validateAverage("arrayOptInt64", Int64?.average(), Int64(64), \Query.object.arrayOptInt64) validateAverage("arrayOptFloat", Float?.average(), Float(5.55444333), \Query.object.arrayOptFloat) validateAverage("arrayOptDouble", Double?.average(), 123.456, \Query.object.arrayOptDouble) validateAverage("arrayOptDecimal", Decimal128?.average(), Decimal128(123.456), \Query.object.arrayOptDecimal) validateAverage("listIntOpt", EnumInt?.average(), EnumInt.value1.rawValue, \Query.object.listIntOpt.rawValue) validateAverage("listInt8Opt", EnumInt8?.average(), EnumInt8.value1.rawValue, \Query.object.listInt8Opt.rawValue) validateAverage("listInt16Opt", EnumInt16?.average(), EnumInt16.value1.rawValue, \Query.object.listInt16Opt.rawValue) validateAverage("listInt32Opt", EnumInt32?.average(), EnumInt32.value1.rawValue, \Query.object.listInt32Opt.rawValue) validateAverage("listInt64Opt", EnumInt64?.average(), EnumInt64.value1.rawValue, \Query.object.listInt64Opt.rawValue) validateAverage("listDoubleOpt", EnumDouble?.average(), EnumDouble.value1.rawValue, \Query.object.listDoubleOpt.rawValue) validateAverage("listOptInt", IntWrapper?.average(), IntWrapper(persistedValue: 1), \Query.object.listOptInt) validateAverage("listOptInt8", Int8Wrapper?.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.listOptInt8) validateAverage("listOptInt16", Int16Wrapper?.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.listOptInt16) validateAverage("listOptInt32", Int32Wrapper?.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.listOptInt32) validateAverage("listOptInt64", Int64Wrapper?.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.listOptInt64) validateAverage("listOptFloat", FloatWrapper?.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.listOptFloat) validateAverage("listOptDouble", DoubleWrapper?.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.listOptDouble) validateAverage("listOptDecimal", Decimal128Wrapper?.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.listOptDecimal) validateAverage("setInt", Int.average(), 1, \Query.object.setInt) validateAverage("setInt8", Int8.average(), Int8(8), \Query.object.setInt8) validateAverage("setInt16", Int16.average(), Int16(16), \Query.object.setInt16) validateAverage("setInt32", Int32.average(), Int32(32), \Query.object.setInt32) validateAverage("setInt64", Int64.average(), Int64(64), \Query.object.setInt64) validateAverage("setFloat", Float.average(), Float(5.55444333), \Query.object.setFloat) validateAverage("setDouble", Double.average(), 123.456, \Query.object.setDouble) validateAverage("setDecimal", Decimal128.average(), Decimal128(123.456), \Query.object.setDecimal) validateAverage("setInt", EnumInt.average(), EnumInt.value1.rawValue, \Query.object.setInt.rawValue) validateAverage("setInt8", EnumInt8.average(), EnumInt8.value1.rawValue, \Query.object.setInt8.rawValue) validateAverage("setInt16", EnumInt16.average(), EnumInt16.value1.rawValue, \Query.object.setInt16.rawValue) validateAverage("setInt32", EnumInt32.average(), EnumInt32.value1.rawValue, \Query.object.setInt32.rawValue) validateAverage("setInt64", EnumInt64.average(), EnumInt64.value1.rawValue, \Query.object.setInt64.rawValue) validateAverage("setDouble", EnumDouble.average(), EnumDouble.value1.rawValue, \Query.object.setDouble.rawValue) validateAverage("setInt", IntWrapper.average(), IntWrapper(persistedValue: 1), \Query.object.setInt) validateAverage("setInt8", Int8Wrapper.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.setInt8) validateAverage("setInt16", Int16Wrapper.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.setInt16) validateAverage("setInt32", Int32Wrapper.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.setInt32) validateAverage("setInt64", Int64Wrapper.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.setInt64) validateAverage("setFloat", FloatWrapper.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.setFloat) validateAverage("setDouble", DoubleWrapper.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.setDouble) validateAverage("setDecimal", Decimal128Wrapper.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.setDecimal) validateAverage("setOptInt", Int?.average(), 1, \Query.object.setOptInt) validateAverage("setOptInt8", Int8?.average(), Int8(8), \Query.object.setOptInt8) validateAverage("setOptInt16", Int16?.average(), Int16(16), \Query.object.setOptInt16) validateAverage("setOptInt32", Int32?.average(), Int32(32), \Query.object.setOptInt32) validateAverage("setOptInt64", Int64?.average(), Int64(64), \Query.object.setOptInt64) validateAverage("setOptFloat", Float?.average(), Float(5.55444333), \Query.object.setOptFloat) validateAverage("setOptDouble", Double?.average(), 123.456, \Query.object.setOptDouble) validateAverage("setOptDecimal", Decimal128?.average(), Decimal128(123.456), \Query.object.setOptDecimal) validateAverage("setIntOpt", EnumInt?.average(), EnumInt.value1.rawValue, \Query.object.setIntOpt.rawValue) validateAverage("setInt8Opt", EnumInt8?.average(), EnumInt8.value1.rawValue, \Query.object.setInt8Opt.rawValue) validateAverage("setInt16Opt", EnumInt16?.average(), EnumInt16.value1.rawValue, \Query.object.setInt16Opt.rawValue) validateAverage("setInt32Opt", EnumInt32?.average(), EnumInt32.value1.rawValue, \Query.object.setInt32Opt.rawValue) validateAverage("setInt64Opt", EnumInt64?.average(), EnumInt64.value1.rawValue, \Query.object.setInt64Opt.rawValue) validateAverage("setDoubleOpt", EnumDouble?.average(), EnumDouble.value1.rawValue, \Query.object.setDoubleOpt.rawValue) validateAverage("setOptInt", IntWrapper?.average(), IntWrapper(persistedValue: 1), \Query.object.setOptInt) validateAverage("setOptInt8", Int8Wrapper?.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.setOptInt8) validateAverage("setOptInt16", Int16Wrapper?.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.setOptInt16) validateAverage("setOptInt32", Int32Wrapper?.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.setOptInt32) validateAverage("setOptInt64", Int64Wrapper?.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.setOptInt64) validateAverage("setOptFloat", FloatWrapper?.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.setOptFloat) validateAverage("setOptDouble", DoubleWrapper?.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.setOptDouble) validateAverage("setOptDecimal", Decimal128Wrapper?.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.setOptDecimal) validateAverage("mapInt", Int.average(), 1, \Query.object.mapInt) validateAverage("mapInt8", Int8.average(), Int8(8), \Query.object.mapInt8) validateAverage("mapInt16", Int16.average(), Int16(16), \Query.object.mapInt16) validateAverage("mapInt32", Int32.average(), Int32(32), \Query.object.mapInt32) validateAverage("mapInt64", Int64.average(), Int64(64), \Query.object.mapInt64) validateAverage("mapFloat", Float.average(), Float(5.55444333), \Query.object.mapFloat) validateAverage("mapDouble", Double.average(), 123.456, \Query.object.mapDouble) validateAverage("mapDecimal", Decimal128.average(), Decimal128(123.456), \Query.object.mapDecimal) validateAverage("mapInt", EnumInt.average(), EnumInt.value1.rawValue, \Query.object.mapInt.rawValue) validateAverage("mapInt8", EnumInt8.average(), EnumInt8.value1.rawValue, \Query.object.mapInt8.rawValue) validateAverage("mapInt16", EnumInt16.average(), EnumInt16.value1.rawValue, \Query.object.mapInt16.rawValue) validateAverage("mapInt32", EnumInt32.average(), EnumInt32.value1.rawValue, \Query.object.mapInt32.rawValue) validateAverage("mapInt64", EnumInt64.average(), EnumInt64.value1.rawValue, \Query.object.mapInt64.rawValue) validateAverage("mapDouble", EnumDouble.average(), EnumDouble.value1.rawValue, \Query.object.mapDouble.rawValue) validateAverage("mapInt", IntWrapper.average(), IntWrapper(persistedValue: 1), \Query.object.mapInt) validateAverage("mapInt8", Int8Wrapper.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.mapInt8) validateAverage("mapInt16", Int16Wrapper.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.mapInt16) validateAverage("mapInt32", Int32Wrapper.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.mapInt32) validateAverage("mapInt64", Int64Wrapper.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.mapInt64) validateAverage("mapFloat", FloatWrapper.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.mapFloat) validateAverage("mapDouble", DoubleWrapper.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.mapDouble) validateAverage("mapDecimal", Decimal128Wrapper.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.mapDecimal) validateAverage("mapOptInt", Int?.average(), 1, \Query.object.mapOptInt) validateAverage("mapOptInt8", Int8?.average(), Int8(8), \Query.object.mapOptInt8) validateAverage("mapOptInt16", Int16?.average(), Int16(16), \Query.object.mapOptInt16) validateAverage("mapOptInt32", Int32?.average(), Int32(32), \Query.object.mapOptInt32) validateAverage("mapOptInt64", Int64?.average(), Int64(64), \Query.object.mapOptInt64) validateAverage("mapOptFloat", Float?.average(), Float(5.55444333), \Query.object.mapOptFloat) validateAverage("mapOptDouble", Double?.average(), 123.456, \Query.object.mapOptDouble) validateAverage("mapOptDecimal", Decimal128?.average(), Decimal128(123.456), \Query.object.mapOptDecimal) validateAverage("mapIntOpt", EnumInt?.average(), EnumInt.value1.rawValue, \Query.object.mapIntOpt.rawValue) validateAverage("mapInt8Opt", EnumInt8?.average(), EnumInt8.value1.rawValue, \Query.object.mapInt8Opt.rawValue) validateAverage("mapInt16Opt", EnumInt16?.average(), EnumInt16.value1.rawValue, \Query.object.mapInt16Opt.rawValue) validateAverage("mapInt32Opt", EnumInt32?.average(), EnumInt32.value1.rawValue, \Query.object.mapInt32Opt.rawValue) validateAverage("mapInt64Opt", EnumInt64?.average(), EnumInt64.value1.rawValue, \Query.object.mapInt64Opt.rawValue) validateAverage("mapDoubleOpt", EnumDouble?.average(), EnumDouble.value1.rawValue, \Query.object.mapDoubleOpt.rawValue) validateAverage("mapOptInt", IntWrapper?.average(), IntWrapper(persistedValue: 1), \Query.object.mapOptInt) validateAverage("mapOptInt8", Int8Wrapper?.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.mapOptInt8) validateAverage("mapOptInt16", Int16Wrapper?.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.mapOptInt16) validateAverage("mapOptInt32", Int32Wrapper?.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.mapOptInt32) validateAverage("mapOptInt64", Int64Wrapper?.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.mapOptInt64) validateAverage("mapOptFloat", FloatWrapper?.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.mapOptFloat) validateAverage("mapOptDouble", DoubleWrapper?.average(), DoubleWrapper(persistedValue: 123.456), \Query.object.mapOptDouble) validateAverage("mapOptDecimal", Decimal128Wrapper?.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.mapOptDecimal) } private func validateSum(_ name: String, _ sum: T.Element, _ min: T.Element, _ lhs: (Query) -> Query) where T.Element.PersistedType: _QueryNumeric, T.Element: QueryValue { assertQuery(Root.self, "(object.\(name).@sum == %@)", sum, count: 1) { lhs($0).sum == sum } assertQuery(Root.self, "(object.\(name).@sum == %@)", min, count: 0) { lhs($0).sum == min } assertQuery(Root.self, "(object.\(name).@sum != %@)", sum, count: 0) { lhs($0).sum != sum } assertQuery(Root.self, "(object.\(name).@sum != %@)", min, count: 1) { lhs($0).sum != min } assertQuery(Root.self, "(object.\(name).@sum > %@)", sum, count: 0) { lhs($0).sum > sum } assertQuery(Root.self, "(object.\(name).@sum > %@)", min, count: 1) { lhs($0).sum > min } assertQuery(Root.self, "(object.\(name).@sum < %@)", sum, count: 0) { lhs($0).sum < sum } assertQuery(Root.self, "(object.\(name).@sum >= %@)", sum, count: 1) { lhs($0).sum >= sum } assertQuery(Root.self, "(object.\(name).@sum <= %@)", sum, count: 1) { lhs($0).sum <= sum } } private func validateSum(_ name: String, _ sum: T.Value, _ min: T.Value, _ lhs: (Query) -> Query) where T.Value.PersistedType: _QueryNumeric, T.Value: QueryValue { assertQuery(Root.self, "(object.\(name).@sum == %@)", sum, count: 1) { lhs($0).sum == sum } assertQuery(Root.self, "(object.\(name).@sum == %@)", min, count: 0) { lhs($0).sum == min } assertQuery(Root.self, "(object.\(name).@sum != %@)", sum, count: 0) { lhs($0).sum != sum } assertQuery(Root.self, "(object.\(name).@sum != %@)", min, count: 1) { lhs($0).sum != min } assertQuery(Root.self, "(object.\(name).@sum > %@)", sum, count: 0) { lhs($0).sum > sum } assertQuery(Root.self, "(object.\(name).@sum > %@)", min, count: 1) { lhs($0).sum > min } assertQuery(Root.self, "(object.\(name).@sum < %@)", sum, count: 0) { lhs($0).sum < sum } assertQuery(Root.self, "(object.\(name).@sum >= %@)", sum, count: 1) { lhs($0).sum >= sum } assertQuery(Root.self, "(object.\(name).@sum <= %@)", sum, count: 1) { lhs($0).sum <= sum } } func testCollectionAggregatesSum() { initLinkedCollectionAggregatesObject() validateSum("arrayInt", Int.sum(), 1, \Query.object.arrayInt) validateSum("arrayInt8", Int8.sum(), Int8(8), \Query.object.arrayInt8) validateSum("arrayInt16", Int16.sum(), Int16(16), \Query.object.arrayInt16) validateSum("arrayInt32", Int32.sum(), Int32(32), \Query.object.arrayInt32) validateSum("arrayInt64", Int64.sum(), Int64(64), \Query.object.arrayInt64) validateSum("arrayFloat", Float.sum(), Float(5.55444333), \Query.object.arrayFloat) validateSum("arrayDouble", Double.sum(), 123.456, \Query.object.arrayDouble) validateSum("arrayDecimal", Decimal128.sum(), Decimal128(123.456), \Query.object.arrayDecimal) validateSum("listInt", EnumInt.sum(), EnumInt.value1.rawValue, \Query.object.listInt.rawValue) validateSum("listInt8", EnumInt8.sum(), EnumInt8.value1.rawValue, \Query.object.listInt8.rawValue) validateSum("listInt16", EnumInt16.sum(), EnumInt16.value1.rawValue, \Query.object.listInt16.rawValue) validateSum("listInt32", EnumInt32.sum(), EnumInt32.value1.rawValue, \Query.object.listInt32.rawValue) validateSum("listInt64", EnumInt64.sum(), EnumInt64.value1.rawValue, \Query.object.listInt64.rawValue) validateSum("listDouble", EnumDouble.sum(), EnumDouble.value1.rawValue, \Query.object.listDouble.rawValue) validateSum("listInt", IntWrapper.sum(), IntWrapper(persistedValue: 1), \Query.object.listInt) validateSum("listInt8", Int8Wrapper.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.listInt8) validateSum("listInt16", Int16Wrapper.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.listInt16) validateSum("listInt32", Int32Wrapper.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.listInt32) validateSum("listInt64", Int64Wrapper.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.listInt64) validateSum("listFloat", FloatWrapper.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.listFloat) validateSum("listDouble", DoubleWrapper.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.listDouble) validateSum("listDecimal", Decimal128Wrapper.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.listDecimal) validateSum("arrayOptInt", Int?.sum(), 1, \Query.object.arrayOptInt) validateSum("arrayOptInt8", Int8?.sum(), Int8(8), \Query.object.arrayOptInt8) validateSum("arrayOptInt16", Int16?.sum(), Int16(16), \Query.object.arrayOptInt16) validateSum("arrayOptInt32", Int32?.sum(), Int32(32), \Query.object.arrayOptInt32) validateSum("arrayOptInt64", Int64?.sum(), Int64(64), \Query.object.arrayOptInt64) validateSum("arrayOptFloat", Float?.sum(), Float(5.55444333), \Query.object.arrayOptFloat) validateSum("arrayOptDouble", Double?.sum(), 123.456, \Query.object.arrayOptDouble) validateSum("arrayOptDecimal", Decimal128?.sum(), Decimal128(123.456), \Query.object.arrayOptDecimal) validateSum("listIntOpt", EnumInt?.sum(), EnumInt.value1.rawValue, \Query.object.listIntOpt.rawValue) validateSum("listInt8Opt", EnumInt8?.sum(), EnumInt8.value1.rawValue, \Query.object.listInt8Opt.rawValue) validateSum("listInt16Opt", EnumInt16?.sum(), EnumInt16.value1.rawValue, \Query.object.listInt16Opt.rawValue) validateSum("listInt32Opt", EnumInt32?.sum(), EnumInt32.value1.rawValue, \Query.object.listInt32Opt.rawValue) validateSum("listInt64Opt", EnumInt64?.sum(), EnumInt64.value1.rawValue, \Query.object.listInt64Opt.rawValue) validateSum("listDoubleOpt", EnumDouble?.sum(), EnumDouble.value1.rawValue, \Query.object.listDoubleOpt.rawValue) validateSum("listOptInt", IntWrapper?.sum(), IntWrapper(persistedValue: 1), \Query.object.listOptInt) validateSum("listOptInt8", Int8Wrapper?.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.listOptInt8) validateSum("listOptInt16", Int16Wrapper?.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.listOptInt16) validateSum("listOptInt32", Int32Wrapper?.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.listOptInt32) validateSum("listOptInt64", Int64Wrapper?.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.listOptInt64) validateSum("listOptFloat", FloatWrapper?.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.listOptFloat) validateSum("listOptDouble", DoubleWrapper?.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.listOptDouble) validateSum("listOptDecimal", Decimal128Wrapper?.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.listOptDecimal) validateSum("setInt", Int.sum(), 1, \Query.object.setInt) validateSum("setInt8", Int8.sum(), Int8(8), \Query.object.setInt8) validateSum("setInt16", Int16.sum(), Int16(16), \Query.object.setInt16) validateSum("setInt32", Int32.sum(), Int32(32), \Query.object.setInt32) validateSum("setInt64", Int64.sum(), Int64(64), \Query.object.setInt64) validateSum("setFloat", Float.sum(), Float(5.55444333), \Query.object.setFloat) validateSum("setDouble", Double.sum(), 123.456, \Query.object.setDouble) validateSum("setDecimal", Decimal128.sum(), Decimal128(123.456), \Query.object.setDecimal) validateSum("setInt", EnumInt.sum(), EnumInt.value1.rawValue, \Query.object.setInt.rawValue) validateSum("setInt8", EnumInt8.sum(), EnumInt8.value1.rawValue, \Query.object.setInt8.rawValue) validateSum("setInt16", EnumInt16.sum(), EnumInt16.value1.rawValue, \Query.object.setInt16.rawValue) validateSum("setInt32", EnumInt32.sum(), EnumInt32.value1.rawValue, \Query.object.setInt32.rawValue) validateSum("setInt64", EnumInt64.sum(), EnumInt64.value1.rawValue, \Query.object.setInt64.rawValue) validateSum("setDouble", EnumDouble.sum(), EnumDouble.value1.rawValue, \Query.object.setDouble.rawValue) validateSum("setInt", IntWrapper.sum(), IntWrapper(persistedValue: 1), \Query.object.setInt) validateSum("setInt8", Int8Wrapper.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.setInt8) validateSum("setInt16", Int16Wrapper.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.setInt16) validateSum("setInt32", Int32Wrapper.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.setInt32) validateSum("setInt64", Int64Wrapper.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.setInt64) validateSum("setFloat", FloatWrapper.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.setFloat) validateSum("setDouble", DoubleWrapper.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.setDouble) validateSum("setDecimal", Decimal128Wrapper.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.setDecimal) validateSum("setOptInt", Int?.sum(), 1, \Query.object.setOptInt) validateSum("setOptInt8", Int8?.sum(), Int8(8), \Query.object.setOptInt8) validateSum("setOptInt16", Int16?.sum(), Int16(16), \Query.object.setOptInt16) validateSum("setOptInt32", Int32?.sum(), Int32(32), \Query.object.setOptInt32) validateSum("setOptInt64", Int64?.sum(), Int64(64), \Query.object.setOptInt64) validateSum("setOptFloat", Float?.sum(), Float(5.55444333), \Query.object.setOptFloat) validateSum("setOptDouble", Double?.sum(), 123.456, \Query.object.setOptDouble) validateSum("setOptDecimal", Decimal128?.sum(), Decimal128(123.456), \Query.object.setOptDecimal) validateSum("setIntOpt", EnumInt?.sum(), EnumInt.value1.rawValue, \Query.object.setIntOpt.rawValue) validateSum("setInt8Opt", EnumInt8?.sum(), EnumInt8.value1.rawValue, \Query.object.setInt8Opt.rawValue) validateSum("setInt16Opt", EnumInt16?.sum(), EnumInt16.value1.rawValue, \Query.object.setInt16Opt.rawValue) validateSum("setInt32Opt", EnumInt32?.sum(), EnumInt32.value1.rawValue, \Query.object.setInt32Opt.rawValue) validateSum("setInt64Opt", EnumInt64?.sum(), EnumInt64.value1.rawValue, \Query.object.setInt64Opt.rawValue) validateSum("setDoubleOpt", EnumDouble?.sum(), EnumDouble.value1.rawValue, \Query.object.setDoubleOpt.rawValue) validateSum("setOptInt", IntWrapper?.sum(), IntWrapper(persistedValue: 1), \Query.object.setOptInt) validateSum("setOptInt8", Int8Wrapper?.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.setOptInt8) validateSum("setOptInt16", Int16Wrapper?.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.setOptInt16) validateSum("setOptInt32", Int32Wrapper?.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.setOptInt32) validateSum("setOptInt64", Int64Wrapper?.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.setOptInt64) validateSum("setOptFloat", FloatWrapper?.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.setOptFloat) validateSum("setOptDouble", DoubleWrapper?.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.setOptDouble) validateSum("setOptDecimal", Decimal128Wrapper?.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.setOptDecimal) validateSum("mapInt", Int.sum(), 1, \Query.object.mapInt) validateSum("mapInt8", Int8.sum(), Int8(8), \Query.object.mapInt8) validateSum("mapInt16", Int16.sum(), Int16(16), \Query.object.mapInt16) validateSum("mapInt32", Int32.sum(), Int32(32), \Query.object.mapInt32) validateSum("mapInt64", Int64.sum(), Int64(64), \Query.object.mapInt64) validateSum("mapFloat", Float.sum(), Float(5.55444333), \Query.object.mapFloat) validateSum("mapDouble", Double.sum(), 123.456, \Query.object.mapDouble) validateSum("mapDecimal", Decimal128.sum(), Decimal128(123.456), \Query.object.mapDecimal) validateSum("mapInt", EnumInt.sum(), EnumInt.value1.rawValue, \Query.object.mapInt.rawValue) validateSum("mapInt8", EnumInt8.sum(), EnumInt8.value1.rawValue, \Query.object.mapInt8.rawValue) validateSum("mapInt16", EnumInt16.sum(), EnumInt16.value1.rawValue, \Query.object.mapInt16.rawValue) validateSum("mapInt32", EnumInt32.sum(), EnumInt32.value1.rawValue, \Query.object.mapInt32.rawValue) validateSum("mapInt64", EnumInt64.sum(), EnumInt64.value1.rawValue, \Query.object.mapInt64.rawValue) validateSum("mapDouble", EnumDouble.sum(), EnumDouble.value1.rawValue, \Query.object.mapDouble.rawValue) validateSum("mapInt", IntWrapper.sum(), IntWrapper(persistedValue: 1), \Query.object.mapInt) validateSum("mapInt8", Int8Wrapper.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.mapInt8) validateSum("mapInt16", Int16Wrapper.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.mapInt16) validateSum("mapInt32", Int32Wrapper.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.mapInt32) validateSum("mapInt64", Int64Wrapper.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.mapInt64) validateSum("mapFloat", FloatWrapper.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.mapFloat) validateSum("mapDouble", DoubleWrapper.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.mapDouble) validateSum("mapDecimal", Decimal128Wrapper.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.mapDecimal) validateSum("mapOptInt", Int?.sum(), 1, \Query.object.mapOptInt) validateSum("mapOptInt8", Int8?.sum(), Int8(8), \Query.object.mapOptInt8) validateSum("mapOptInt16", Int16?.sum(), Int16(16), \Query.object.mapOptInt16) validateSum("mapOptInt32", Int32?.sum(), Int32(32), \Query.object.mapOptInt32) validateSum("mapOptInt64", Int64?.sum(), Int64(64), \Query.object.mapOptInt64) validateSum("mapOptFloat", Float?.sum(), Float(5.55444333), \Query.object.mapOptFloat) validateSum("mapOptDouble", Double?.sum(), 123.456, \Query.object.mapOptDouble) validateSum("mapOptDecimal", Decimal128?.sum(), Decimal128(123.456), \Query.object.mapOptDecimal) validateSum("mapIntOpt", EnumInt?.sum(), EnumInt.value1.rawValue, \Query.object.mapIntOpt.rawValue) validateSum("mapInt8Opt", EnumInt8?.sum(), EnumInt8.value1.rawValue, \Query.object.mapInt8Opt.rawValue) validateSum("mapInt16Opt", EnumInt16?.sum(), EnumInt16.value1.rawValue, \Query.object.mapInt16Opt.rawValue) validateSum("mapInt32Opt", EnumInt32?.sum(), EnumInt32.value1.rawValue, \Query.object.mapInt32Opt.rawValue) validateSum("mapInt64Opt", EnumInt64?.sum(), EnumInt64.value1.rawValue, \Query.object.mapInt64Opt.rawValue) validateSum("mapDoubleOpt", EnumDouble?.sum(), EnumDouble.value1.rawValue, \Query.object.mapDoubleOpt.rawValue) validateSum("mapOptInt", IntWrapper?.sum(), IntWrapper(persistedValue: 1), \Query.object.mapOptInt) validateSum("mapOptInt8", Int8Wrapper?.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.object.mapOptInt8) validateSum("mapOptInt16", Int16Wrapper?.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.object.mapOptInt16) validateSum("mapOptInt32", Int32Wrapper?.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.object.mapOptInt32) validateSum("mapOptInt64", Int64Wrapper?.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.object.mapOptInt64) validateSum("mapOptFloat", FloatWrapper?.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.object.mapOptFloat) validateSum("mapOptDouble", DoubleWrapper?.sum(), DoubleWrapper(persistedValue: 123.456), \Query.object.mapOptDouble) validateSum("mapOptDecimal", Decimal128Wrapper?.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.object.mapOptDecimal) } private func validateMin(_ name: String, min: T.Element, max: T.Element, _ lhs: (Query) -> Query) where T.Element.PersistedType: _QueryNumeric, T.Element: QueryValue { assertQuery(Root.self, "(object.\(name).@min == %@)", min, count: 1) { lhs($0).min == min } assertQuery(Root.self, "(object.\(name).@min == %@)", max, count: 0) { lhs($0).min == max } assertQuery(Root.self, "(object.\(name).@min != %@)", min, count: 0) { lhs($0).min != min } assertQuery(Root.self, "(object.\(name).@min != %@)", max, count: 1) { lhs($0).min != max } assertQuery(Root.self, "(object.\(name).@min > %@)", min, count: 0) { lhs($0).min > min } assertQuery(Root.self, "(object.\(name).@min < %@)", min, count: 0) { lhs($0).min < min } assertQuery(Root.self, "(object.\(name).@min >= %@)", min, count: 1) { lhs($0).min >= min } assertQuery(Root.self, "(object.\(name).@min <= %@)", min, count: 1) { lhs($0).min <= min } } private func validateMin(_ name: String, min: T.Value, max: T.Value, _ lhs: (Query) -> Query) where T.Value.PersistedType: _QueryNumeric, T.Value: QueryValue { assertQuery(Root.self, "(object.\(name).@min == %@)", min, count: 1) { lhs($0).min == min } assertQuery(Root.self, "(object.\(name).@min == %@)", max, count: 0) { lhs($0).min == max } assertQuery(Root.self, "(object.\(name).@min != %@)", min, count: 0) { lhs($0).min != min } assertQuery(Root.self, "(object.\(name).@min != %@)", max, count: 1) { lhs($0).min != max } assertQuery(Root.self, "(object.\(name).@min > %@)", min, count: 0) { lhs($0).min > min } assertQuery(Root.self, "(object.\(name).@min < %@)", min, count: 0) { lhs($0).min < min } assertQuery(Root.self, "(object.\(name).@min >= %@)", min, count: 1) { lhs($0).min >= min } assertQuery(Root.self, "(object.\(name).@min <= %@)", min, count: 1) { lhs($0).min <= min } } func testCollectionAggregatesMin() { initLinkedCollectionAggregatesObject() validateMin("arrayInt", min: 1, max: 5, \Query.object.arrayInt) validateMin("arrayInt8", min: Int8(8), max: Int8(10), \Query.object.arrayInt8) validateMin("arrayInt16", min: Int16(16), max: Int16(18), \Query.object.arrayInt16) validateMin("arrayInt32", min: Int32(32), max: Int32(34), \Query.object.arrayInt32) validateMin("arrayInt64", min: Int64(64), max: Int64(66), \Query.object.arrayInt64) validateMin("arrayFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.arrayFloat) validateMin("arrayDouble", min: 123.456, max: 345.678, \Query.object.arrayDouble) validateMin("arrayDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.arrayDecimal) validateMin("listInt", min: .value1, max: .value3, \Query.object.listInt) validateMin("listInt8", min: .value1, max: .value3, \Query.object.listInt8) validateMin("listInt16", min: .value1, max: .value3, \Query.object.listInt16) validateMin("listInt32", min: .value1, max: .value3, \Query.object.listInt32) validateMin("listInt64", min: .value1, max: .value3, \Query.object.listInt64) validateMin("listDouble", min: .value1, max: .value3, \Query.object.listDouble) validateMin("listInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.listInt) validateMin("listInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.listInt8) validateMin("listInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.listInt16) validateMin("listInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.listInt32) validateMin("listInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.listInt64) validateMin("listFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.listFloat) validateMin("listDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.listDouble) validateMin("listDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.listDecimal) validateMin("arrayOptInt", min: 1, max: 5, \Query.object.arrayOptInt) validateMin("arrayOptInt8", min: Int8(8), max: Int8(10), \Query.object.arrayOptInt8) validateMin("arrayOptInt16", min: Int16(16), max: Int16(18), \Query.object.arrayOptInt16) validateMin("arrayOptInt32", min: Int32(32), max: Int32(34), \Query.object.arrayOptInt32) validateMin("arrayOptInt64", min: Int64(64), max: Int64(66), \Query.object.arrayOptInt64) validateMin("arrayOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.arrayOptFloat) validateMin("arrayOptDouble", min: 123.456, max: 345.678, \Query.object.arrayOptDouble) validateMin("arrayOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.arrayOptDecimal) validateMin("listIntOpt", min: .value1, max: .value3, \Query.object.listIntOpt) validateMin("listInt8Opt", min: .value1, max: .value3, \Query.object.listInt8Opt) validateMin("listInt16Opt", min: .value1, max: .value3, \Query.object.listInt16Opt) validateMin("listInt32Opt", min: .value1, max: .value3, \Query.object.listInt32Opt) validateMin("listInt64Opt", min: .value1, max: .value3, \Query.object.listInt64Opt) validateMin("listDoubleOpt", min: .value1, max: .value3, \Query.object.listDoubleOpt) validateMin("listOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.listOptInt) validateMin("listOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.listOptInt8) validateMin("listOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.listOptInt16) validateMin("listOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.listOptInt32) validateMin("listOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.listOptInt64) validateMin("listOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.listOptFloat) validateMin("listOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.listOptDouble) validateMin("listOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.listOptDecimal) validateMin("setInt", min: 1, max: 5, \Query.object.setInt) validateMin("setInt8", min: Int8(8), max: Int8(10), \Query.object.setInt8) validateMin("setInt16", min: Int16(16), max: Int16(18), \Query.object.setInt16) validateMin("setInt32", min: Int32(32), max: Int32(34), \Query.object.setInt32) validateMin("setInt64", min: Int64(64), max: Int64(66), \Query.object.setInt64) validateMin("setFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.setFloat) validateMin("setDouble", min: 123.456, max: 345.678, \Query.object.setDouble) validateMin("setDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.setDecimal) validateMin("setInt", min: .value1, max: .value3, \Query.object.setInt) validateMin("setInt8", min: .value1, max: .value3, \Query.object.setInt8) validateMin("setInt16", min: .value1, max: .value3, \Query.object.setInt16) validateMin("setInt32", min: .value1, max: .value3, \Query.object.setInt32) validateMin("setInt64", min: .value1, max: .value3, \Query.object.setInt64) validateMin("setDouble", min: .value1, max: .value3, \Query.object.setDouble) validateMin("setInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.setInt) validateMin("setInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.setInt8) validateMin("setInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.setInt16) validateMin("setInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.setInt32) validateMin("setInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.setInt64) validateMin("setFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.setFloat) validateMin("setDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.setDouble) validateMin("setDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.setDecimal) validateMin("setOptInt", min: 1, max: 5, \Query.object.setOptInt) validateMin("setOptInt8", min: Int8(8), max: Int8(10), \Query.object.setOptInt8) validateMin("setOptInt16", min: Int16(16), max: Int16(18), \Query.object.setOptInt16) validateMin("setOptInt32", min: Int32(32), max: Int32(34), \Query.object.setOptInt32) validateMin("setOptInt64", min: Int64(64), max: Int64(66), \Query.object.setOptInt64) validateMin("setOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.setOptFloat) validateMin("setOptDouble", min: 123.456, max: 345.678, \Query.object.setOptDouble) validateMin("setOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.setOptDecimal) validateMin("setIntOpt", min: .value1, max: .value3, \Query.object.setIntOpt) validateMin("setInt8Opt", min: .value1, max: .value3, \Query.object.setInt8Opt) validateMin("setInt16Opt", min: .value1, max: .value3, \Query.object.setInt16Opt) validateMin("setInt32Opt", min: .value1, max: .value3, \Query.object.setInt32Opt) validateMin("setInt64Opt", min: .value1, max: .value3, \Query.object.setInt64Opt) validateMin("setDoubleOpt", min: .value1, max: .value3, \Query.object.setDoubleOpt) validateMin("setOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.setOptInt) validateMin("setOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.setOptInt8) validateMin("setOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.setOptInt16) validateMin("setOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.setOptInt32) validateMin("setOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.setOptInt64) validateMin("setOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.setOptFloat) validateMin("setOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.setOptDouble) validateMin("setOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.setOptDecimal) validateMin("mapInt", min: 1, max: 5, \Query.object.mapInt) validateMin("mapInt8", min: Int8(8), max: Int8(10), \Query.object.mapInt8) validateMin("mapInt16", min: Int16(16), max: Int16(18), \Query.object.mapInt16) validateMin("mapInt32", min: Int32(32), max: Int32(34), \Query.object.mapInt32) validateMin("mapInt64", min: Int64(64), max: Int64(66), \Query.object.mapInt64) validateMin("mapFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.mapFloat) validateMin("mapDouble", min: 123.456, max: 345.678, \Query.object.mapDouble) validateMin("mapDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.mapDecimal) validateMin("mapInt", min: .value1, max: .value3, \Query.object.mapInt) validateMin("mapInt8", min: .value1, max: .value3, \Query.object.mapInt8) validateMin("mapInt16", min: .value1, max: .value3, \Query.object.mapInt16) validateMin("mapInt32", min: .value1, max: .value3, \Query.object.mapInt32) validateMin("mapInt64", min: .value1, max: .value3, \Query.object.mapInt64) validateMin("mapDouble", min: .value1, max: .value3, \Query.object.mapDouble) validateMin("mapInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.mapInt) validateMin("mapInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.mapInt8) validateMin("mapInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.mapInt16) validateMin("mapInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.mapInt32) validateMin("mapInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.mapInt64) validateMin("mapFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.mapFloat) validateMin("mapDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.mapDouble) validateMin("mapDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.mapDecimal) validateMin("mapOptInt", min: 1, max: 5, \Query.object.mapOptInt) validateMin("mapOptInt8", min: Int8(8), max: Int8(10), \Query.object.mapOptInt8) validateMin("mapOptInt16", min: Int16(16), max: Int16(18), \Query.object.mapOptInt16) validateMin("mapOptInt32", min: Int32(32), max: Int32(34), \Query.object.mapOptInt32) validateMin("mapOptInt64", min: Int64(64), max: Int64(66), \Query.object.mapOptInt64) validateMin("mapOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.mapOptFloat) validateMin("mapOptDouble", min: 123.456, max: 345.678, \Query.object.mapOptDouble) validateMin("mapOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.mapOptDecimal) validateMin("mapIntOpt", min: .value1, max: .value3, \Query.object.mapIntOpt) validateMin("mapInt8Opt", min: .value1, max: .value3, \Query.object.mapInt8Opt) validateMin("mapInt16Opt", min: .value1, max: .value3, \Query.object.mapInt16Opt) validateMin("mapInt32Opt", min: .value1, max: .value3, \Query.object.mapInt32Opt) validateMin("mapInt64Opt", min: .value1, max: .value3, \Query.object.mapInt64Opt) validateMin("mapDoubleOpt", min: .value1, max: .value3, \Query.object.mapDoubleOpt) validateMin("mapOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.mapOptInt) validateMin("mapOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.mapOptInt8) validateMin("mapOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.mapOptInt16) validateMin("mapOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.mapOptInt32) validateMin("mapOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.mapOptInt64) validateMin("mapOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.mapOptFloat) validateMin("mapOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.mapOptDouble) validateMin("mapOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.mapOptDecimal) } private func validateMax(_ name: String, min: T.Element, max: T.Element, _ lhs: (Query) -> Query) where T.Element.PersistedType: _QueryNumeric, T.Element: QueryValue { assertQuery(Root.self, "(object.\(name).@max == %@)", max, count: 1) { lhs($0).max == max } assertQuery(Root.self, "(object.\(name).@max == %@)", min, count: 0) { lhs($0).max == min } assertQuery(Root.self, "(object.\(name).@max != %@)", max, count: 0) { lhs($0).max != max } assertQuery(Root.self, "(object.\(name).@max != %@)", min, count: 1) { lhs($0).max != min } assertQuery(Root.self, "(object.\(name).@max > %@)", max, count: 0) { lhs($0).max > max } assertQuery(Root.self, "(object.\(name).@max < %@)", max, count: 0) { lhs($0).max < max } assertQuery(Root.self, "(object.\(name).@max >= %@)", max, count: 1) { lhs($0).max >= max } assertQuery(Root.self, "(object.\(name).@max <= %@)", max, count: 1) { lhs($0).max <= max } } private func validateMax(_ name: String, min: T.Value, max: T.Value, _ lhs: (Query) -> Query) where T.Value.PersistedType: _QueryNumeric, T.Value: QueryValue { assertQuery(Root.self, "(object.\(name).@max == %@)", max, count: 1) { lhs($0).max == max } assertQuery(Root.self, "(object.\(name).@max == %@)", min, count: 0) { lhs($0).max == min } assertQuery(Root.self, "(object.\(name).@max != %@)", max, count: 0) { lhs($0).max != max } assertQuery(Root.self, "(object.\(name).@max != %@)", min, count: 1) { lhs($0).max != min } assertQuery(Root.self, "(object.\(name).@max > %@)", max, count: 0) { lhs($0).max > max } assertQuery(Root.self, "(object.\(name).@max < %@)", max, count: 0) { lhs($0).max < max } assertQuery(Root.self, "(object.\(name).@max >= %@)", max, count: 1) { lhs($0).max >= max } assertQuery(Root.self, "(object.\(name).@max <= %@)", max, count: 1) { lhs($0).max <= max } } func testCollectionAggregatesMax() { initLinkedCollectionAggregatesObject() validateMax("arrayInt", min: 1, max: 5, \Query.object.arrayInt) validateMax("arrayInt8", min: Int8(8), max: Int8(10), \Query.object.arrayInt8) validateMax("arrayInt16", min: Int16(16), max: Int16(18), \Query.object.arrayInt16) validateMax("arrayInt32", min: Int32(32), max: Int32(34), \Query.object.arrayInt32) validateMax("arrayInt64", min: Int64(64), max: Int64(66), \Query.object.arrayInt64) validateMax("arrayFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.arrayFloat) validateMax("arrayDouble", min: 123.456, max: 345.678, \Query.object.arrayDouble) validateMax("arrayDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.arrayDecimal) validateMax("listInt", min: .value1, max: .value3, \Query.object.listInt) validateMax("listInt8", min: .value1, max: .value3, \Query.object.listInt8) validateMax("listInt16", min: .value1, max: .value3, \Query.object.listInt16) validateMax("listInt32", min: .value1, max: .value3, \Query.object.listInt32) validateMax("listInt64", min: .value1, max: .value3, \Query.object.listInt64) validateMax("listDouble", min: .value1, max: .value3, \Query.object.listDouble) validateMax("listInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.listInt) validateMax("listInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.listInt8) validateMax("listInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.listInt16) validateMax("listInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.listInt32) validateMax("listInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.listInt64) validateMax("listFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.listFloat) validateMax("listDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.listDouble) validateMax("listDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.listDecimal) validateMax("arrayOptInt", min: 1, max: 5, \Query.object.arrayOptInt) validateMax("arrayOptInt8", min: Int8(8), max: Int8(10), \Query.object.arrayOptInt8) validateMax("arrayOptInt16", min: Int16(16), max: Int16(18), \Query.object.arrayOptInt16) validateMax("arrayOptInt32", min: Int32(32), max: Int32(34), \Query.object.arrayOptInt32) validateMax("arrayOptInt64", min: Int64(64), max: Int64(66), \Query.object.arrayOptInt64) validateMax("arrayOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.arrayOptFloat) validateMax("arrayOptDouble", min: 123.456, max: 345.678, \Query.object.arrayOptDouble) validateMax("arrayOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.arrayOptDecimal) validateMax("listIntOpt", min: .value1, max: .value3, \Query.object.listIntOpt) validateMax("listInt8Opt", min: .value1, max: .value3, \Query.object.listInt8Opt) validateMax("listInt16Opt", min: .value1, max: .value3, \Query.object.listInt16Opt) validateMax("listInt32Opt", min: .value1, max: .value3, \Query.object.listInt32Opt) validateMax("listInt64Opt", min: .value1, max: .value3, \Query.object.listInt64Opt) validateMax("listDoubleOpt", min: .value1, max: .value3, \Query.object.listDoubleOpt) validateMax("listOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.listOptInt) validateMax("listOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.listOptInt8) validateMax("listOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.listOptInt16) validateMax("listOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.listOptInt32) validateMax("listOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.listOptInt64) validateMax("listOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.listOptFloat) validateMax("listOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.listOptDouble) validateMax("listOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.listOptDecimal) validateMax("setInt", min: 1, max: 5, \Query.object.setInt) validateMax("setInt8", min: Int8(8), max: Int8(10), \Query.object.setInt8) validateMax("setInt16", min: Int16(16), max: Int16(18), \Query.object.setInt16) validateMax("setInt32", min: Int32(32), max: Int32(34), \Query.object.setInt32) validateMax("setInt64", min: Int64(64), max: Int64(66), \Query.object.setInt64) validateMax("setFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.setFloat) validateMax("setDouble", min: 123.456, max: 345.678, \Query.object.setDouble) validateMax("setDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.setDecimal) validateMax("setInt", min: .value1, max: .value3, \Query.object.setInt) validateMax("setInt8", min: .value1, max: .value3, \Query.object.setInt8) validateMax("setInt16", min: .value1, max: .value3, \Query.object.setInt16) validateMax("setInt32", min: .value1, max: .value3, \Query.object.setInt32) validateMax("setInt64", min: .value1, max: .value3, \Query.object.setInt64) validateMax("setDouble", min: .value1, max: .value3, \Query.object.setDouble) validateMax("setInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.setInt) validateMax("setInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.setInt8) validateMax("setInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.setInt16) validateMax("setInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.setInt32) validateMax("setInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.setInt64) validateMax("setFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.setFloat) validateMax("setDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.setDouble) validateMax("setDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.setDecimal) validateMax("setOptInt", min: 1, max: 5, \Query.object.setOptInt) validateMax("setOptInt8", min: Int8(8), max: Int8(10), \Query.object.setOptInt8) validateMax("setOptInt16", min: Int16(16), max: Int16(18), \Query.object.setOptInt16) validateMax("setOptInt32", min: Int32(32), max: Int32(34), \Query.object.setOptInt32) validateMax("setOptInt64", min: Int64(64), max: Int64(66), \Query.object.setOptInt64) validateMax("setOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.setOptFloat) validateMax("setOptDouble", min: 123.456, max: 345.678, \Query.object.setOptDouble) validateMax("setOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.setOptDecimal) validateMax("setIntOpt", min: .value1, max: .value3, \Query.object.setIntOpt) validateMax("setInt8Opt", min: .value1, max: .value3, \Query.object.setInt8Opt) validateMax("setInt16Opt", min: .value1, max: .value3, \Query.object.setInt16Opt) validateMax("setInt32Opt", min: .value1, max: .value3, \Query.object.setInt32Opt) validateMax("setInt64Opt", min: .value1, max: .value3, \Query.object.setInt64Opt) validateMax("setDoubleOpt", min: .value1, max: .value3, \Query.object.setDoubleOpt) validateMax("setOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.setOptInt) validateMax("setOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.setOptInt8) validateMax("setOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.setOptInt16) validateMax("setOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.setOptInt32) validateMax("setOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.setOptInt64) validateMax("setOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.setOptFloat) validateMax("setOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.setOptDouble) validateMax("setOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.setOptDecimal) validateMax("mapInt", min: 1, max: 5, \Query.object.mapInt) validateMax("mapInt8", min: Int8(8), max: Int8(10), \Query.object.mapInt8) validateMax("mapInt16", min: Int16(16), max: Int16(18), \Query.object.mapInt16) validateMax("mapInt32", min: Int32(32), max: Int32(34), \Query.object.mapInt32) validateMax("mapInt64", min: Int64(64), max: Int64(66), \Query.object.mapInt64) validateMax("mapFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.mapFloat) validateMax("mapDouble", min: 123.456, max: 345.678, \Query.object.mapDouble) validateMax("mapDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.mapDecimal) validateMax("mapInt", min: .value1, max: .value3, \Query.object.mapInt) validateMax("mapInt8", min: .value1, max: .value3, \Query.object.mapInt8) validateMax("mapInt16", min: .value1, max: .value3, \Query.object.mapInt16) validateMax("mapInt32", min: .value1, max: .value3, \Query.object.mapInt32) validateMax("mapInt64", min: .value1, max: .value3, \Query.object.mapInt64) validateMax("mapDouble", min: .value1, max: .value3, \Query.object.mapDouble) validateMax("mapInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.mapInt) validateMax("mapInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.mapInt8) validateMax("mapInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.mapInt16) validateMax("mapInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.mapInt32) validateMax("mapInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.mapInt64) validateMax("mapFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.mapFloat) validateMax("mapDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.mapDouble) validateMax("mapDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.mapDecimal) validateMax("mapOptInt", min: 1, max: 5, \Query.object.mapOptInt) validateMax("mapOptInt8", min: Int8(8), max: Int8(10), \Query.object.mapOptInt8) validateMax("mapOptInt16", min: Int16(16), max: Int16(18), \Query.object.mapOptInt16) validateMax("mapOptInt32", min: Int32(32), max: Int32(34), \Query.object.mapOptInt32) validateMax("mapOptInt64", min: Int64(64), max: Int64(66), \Query.object.mapOptInt64) validateMax("mapOptFloat", min: Float(5.55444333), max: Float(7.55444333), \Query.object.mapOptFloat) validateMax("mapOptDouble", min: 123.456, max: 345.678, \Query.object.mapOptDouble) validateMax("mapOptDecimal", min: Decimal128(123.456), max: Decimal128(345.678), \Query.object.mapOptDecimal) validateMax("mapIntOpt", min: .value1, max: .value3, \Query.object.mapIntOpt) validateMax("mapInt8Opt", min: .value1, max: .value3, \Query.object.mapInt8Opt) validateMax("mapInt16Opt", min: .value1, max: .value3, \Query.object.mapInt16Opt) validateMax("mapInt32Opt", min: .value1, max: .value3, \Query.object.mapInt32Opt) validateMax("mapInt64Opt", min: .value1, max: .value3, \Query.object.mapInt64Opt) validateMax("mapDoubleOpt", min: .value1, max: .value3, \Query.object.mapDoubleOpt) validateMax("mapOptInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.object.mapOptInt) validateMax("mapOptInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.object.mapOptInt8) validateMax("mapOptInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.object.mapOptInt16) validateMax("mapOptInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.object.mapOptInt32) validateMax("mapOptInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.object.mapOptInt64) validateMax("mapOptFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.object.mapOptFloat) validateMax("mapOptDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.object.mapOptDouble) validateMax("mapOptDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.object.mapOptDecimal) } // @Count private func validateCount(_ name: String, _ lhs: (Query) -> Query) { assertQuery(Root.self, "(object.\(name).@count == %@)", 3, count: 1) { lhs($0).count == 3 } assertQuery(Root.self, "(object.\(name).@count == %@)", 0, count: 0) { lhs($0).count == 0 } assertQuery(Root.self, "(object.\(name).@count != %@)", 3, count: 0) { lhs($0).count != 3 } assertQuery(Root.self, "(object.\(name).@count != %@)", 2, count: 1) { lhs($0).count != 2 } assertQuery(Root.self, "(object.\(name).@count < %@)", 3, count: 0) { lhs($0).count < 3 } assertQuery(Root.self, "(object.\(name).@count < %@)", 4, count: 1) { lhs($0).count < 4 } assertQuery(Root.self, "(object.\(name).@count > %@)", 2, count: 1) { lhs($0).count > 2 } assertQuery(Root.self, "(object.\(name).@count > %@)", 3, count: 0) { lhs($0).count > 3 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 2, count: 0) { lhs($0).count <= 2 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 3, count: 1) { lhs($0).count <= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 3, count: 1) { lhs($0).count >= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 4, count: 0) { lhs($0).count >= 4 } } private func validateCount(_ name: String, _ lhs: (Query) -> Query) { assertQuery(Root.self, "(object.\(name).@count == %@)", 3, count: 1) { lhs($0).count == 3 } assertQuery(Root.self, "(object.\(name).@count == %@)", 0, count: 0) { lhs($0).count == 0 } assertQuery(Root.self, "(object.\(name).@count != %@)", 3, count: 0) { lhs($0).count != 3 } assertQuery(Root.self, "(object.\(name).@count != %@)", 2, count: 1) { lhs($0).count != 2 } assertQuery(Root.self, "(object.\(name).@count < %@)", 3, count: 0) { lhs($0).count < 3 } assertQuery(Root.self, "(object.\(name).@count < %@)", 4, count: 1) { lhs($0).count < 4 } assertQuery(Root.self, "(object.\(name).@count > %@)", 2, count: 1) { lhs($0).count > 2 } assertQuery(Root.self, "(object.\(name).@count > %@)", 3, count: 0) { lhs($0).count > 3 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 2, count: 0) { lhs($0).count <= 2 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 3, count: 1) { lhs($0).count <= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 3, count: 1) { lhs($0).count >= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 4, count: 0) { lhs($0).count >= 4 } } func testCollectionAggregatesCount() { initLinkedCollectionAggregatesObject() validateCount("arrayInt", \Query.object.arrayInt) validateCount("arrayInt8", \Query.object.arrayInt8) validateCount("arrayInt16", \Query.object.arrayInt16) validateCount("arrayInt32", \Query.object.arrayInt32) validateCount("arrayInt64", \Query.object.arrayInt64) validateCount("arrayFloat", \Query.object.arrayFloat) validateCount("arrayDouble", \Query.object.arrayDouble) validateCount("arrayString", \Query.object.arrayString) validateCount("arrayBinary", \Query.object.arrayBinary) validateCount("arrayDate", \Query.object.arrayDate) validateCount("arrayDecimal", \Query.object.arrayDecimal) validateCount("arrayObjectId", \Query.object.arrayObjectId) validateCount("arrayUuid", \Query.object.arrayUuid) validateCount("arrayAny", \Query.object.arrayAny) validateCount("listInt", \Query.object.listInt) validateCount("listInt8", \Query.object.listInt8) validateCount("listInt16", \Query.object.listInt16) validateCount("listInt32", \Query.object.listInt32) validateCount("listInt64", \Query.object.listInt64) validateCount("listFloat", \Query.object.listFloat) validateCount("listDouble", \Query.object.listDouble) validateCount("listString", \Query.object.listString) validateCount("listInt", \Query.object.listInt) validateCount("listInt8", \Query.object.listInt8) validateCount("listInt16", \Query.object.listInt16) validateCount("listInt32", \Query.object.listInt32) validateCount("listInt64", \Query.object.listInt64) validateCount("listFloat", \Query.object.listFloat) validateCount("listDouble", \Query.object.listDouble) validateCount("listString", \Query.object.listString) validateCount("listBinary", \Query.object.listBinary) validateCount("listDate", \Query.object.listDate) validateCount("listDecimal", \Query.object.listDecimal) validateCount("listObjectId", \Query.object.listObjectId) validateCount("listUuid", \Query.object.listUuid) validateCount("arrayOptInt", \Query.object.arrayOptInt) validateCount("arrayOptInt8", \Query.object.arrayOptInt8) validateCount("arrayOptInt16", \Query.object.arrayOptInt16) validateCount("arrayOptInt32", \Query.object.arrayOptInt32) validateCount("arrayOptInt64", \Query.object.arrayOptInt64) validateCount("arrayOptFloat", \Query.object.arrayOptFloat) validateCount("arrayOptDouble", \Query.object.arrayOptDouble) validateCount("arrayOptString", \Query.object.arrayOptString) validateCount("arrayOptBinary", \Query.object.arrayOptBinary) validateCount("arrayOptDate", \Query.object.arrayOptDate) validateCount("arrayOptDecimal", \Query.object.arrayOptDecimal) validateCount("arrayOptObjectId", \Query.object.arrayOptObjectId) validateCount("arrayOptUuid", \Query.object.arrayOptUuid) validateCount("listIntOpt", \Query.object.listIntOpt) validateCount("listInt8Opt", \Query.object.listInt8Opt) validateCount("listInt16Opt", \Query.object.listInt16Opt) validateCount("listInt32Opt", \Query.object.listInt32Opt) validateCount("listInt64Opt", \Query.object.listInt64Opt) validateCount("listFloatOpt", \Query.object.listFloatOpt) validateCount("listDoubleOpt", \Query.object.listDoubleOpt) validateCount("listStringOpt", \Query.object.listStringOpt) validateCount("listOptInt", \Query.object.listOptInt) validateCount("listOptInt8", \Query.object.listOptInt8) validateCount("listOptInt16", \Query.object.listOptInt16) validateCount("listOptInt32", \Query.object.listOptInt32) validateCount("listOptInt64", \Query.object.listOptInt64) validateCount("listOptFloat", \Query.object.listOptFloat) validateCount("listOptDouble", \Query.object.listOptDouble) validateCount("listOptString", \Query.object.listOptString) validateCount("listOptBinary", \Query.object.listOptBinary) validateCount("listOptDate", \Query.object.listOptDate) validateCount("listOptDecimal", \Query.object.listOptDecimal) validateCount("listOptObjectId", \Query.object.listOptObjectId) validateCount("listOptUuid", \Query.object.listOptUuid) validateCount("setInt", \Query.object.setInt) validateCount("setInt8", \Query.object.setInt8) validateCount("setInt16", \Query.object.setInt16) validateCount("setInt32", \Query.object.setInt32) validateCount("setInt64", \Query.object.setInt64) validateCount("setFloat", \Query.object.setFloat) validateCount("setDouble", \Query.object.setDouble) validateCount("setString", \Query.object.setString) validateCount("setBinary", \Query.object.setBinary) validateCount("setDate", \Query.object.setDate) validateCount("setDecimal", \Query.object.setDecimal) validateCount("setObjectId", \Query.object.setObjectId) validateCount("setUuid", \Query.object.setUuid) validateCount("setAny", \Query.object.setAny) validateCount("setInt", \Query.object.setInt) validateCount("setInt8", \Query.object.setInt8) validateCount("setInt16", \Query.object.setInt16) validateCount("setInt32", \Query.object.setInt32) validateCount("setInt64", \Query.object.setInt64) validateCount("setFloat", \Query.object.setFloat) validateCount("setDouble", \Query.object.setDouble) validateCount("setString", \Query.object.setString) validateCount("setInt", \Query.object.setInt) validateCount("setInt8", \Query.object.setInt8) validateCount("setInt16", \Query.object.setInt16) validateCount("setInt32", \Query.object.setInt32) validateCount("setInt64", \Query.object.setInt64) validateCount("setFloat", \Query.object.setFloat) validateCount("setDouble", \Query.object.setDouble) validateCount("setString", \Query.object.setString) validateCount("setBinary", \Query.object.setBinary) validateCount("setDate", \Query.object.setDate) validateCount("setDecimal", \Query.object.setDecimal) validateCount("setObjectId", \Query.object.setObjectId) validateCount("setUuid", \Query.object.setUuid) validateCount("setOptInt", \Query.object.setOptInt) validateCount("setOptInt8", \Query.object.setOptInt8) validateCount("setOptInt16", \Query.object.setOptInt16) validateCount("setOptInt32", \Query.object.setOptInt32) validateCount("setOptInt64", \Query.object.setOptInt64) validateCount("setOptFloat", \Query.object.setOptFloat) validateCount("setOptDouble", \Query.object.setOptDouble) validateCount("setOptString", \Query.object.setOptString) validateCount("setOptBinary", \Query.object.setOptBinary) validateCount("setOptDate", \Query.object.setOptDate) validateCount("setOptDecimal", \Query.object.setOptDecimal) validateCount("setOptObjectId", \Query.object.setOptObjectId) validateCount("setOptUuid", \Query.object.setOptUuid) validateCount("setIntOpt", \Query.object.setIntOpt) validateCount("setInt8Opt", \Query.object.setInt8Opt) validateCount("setInt16Opt", \Query.object.setInt16Opt) validateCount("setInt32Opt", \Query.object.setInt32Opt) validateCount("setInt64Opt", \Query.object.setInt64Opt) validateCount("setFloatOpt", \Query.object.setFloatOpt) validateCount("setDoubleOpt", \Query.object.setDoubleOpt) validateCount("setStringOpt", \Query.object.setStringOpt) validateCount("setOptInt", \Query.object.setOptInt) validateCount("setOptInt8", \Query.object.setOptInt8) validateCount("setOptInt16", \Query.object.setOptInt16) validateCount("setOptInt32", \Query.object.setOptInt32) validateCount("setOptInt64", \Query.object.setOptInt64) validateCount("setOptFloat", \Query.object.setOptFloat) validateCount("setOptDouble", \Query.object.setOptDouble) validateCount("setOptString", \Query.object.setOptString) validateCount("setOptBinary", \Query.object.setOptBinary) validateCount("setOptDate", \Query.object.setOptDate) validateCount("setOptDecimal", \Query.object.setOptDecimal) validateCount("setOptObjectId", \Query.object.setOptObjectId) validateCount("setOptUuid", \Query.object.setOptUuid) validateCount("mapInt", \Query.object.mapInt) validateCount("mapInt8", \Query.object.mapInt8) validateCount("mapInt16", \Query.object.mapInt16) validateCount("mapInt32", \Query.object.mapInt32) validateCount("mapInt64", \Query.object.mapInt64) validateCount("mapFloat", \Query.object.mapFloat) validateCount("mapDouble", \Query.object.mapDouble) validateCount("mapString", \Query.object.mapString) validateCount("mapBinary", \Query.object.mapBinary) validateCount("mapDate", \Query.object.mapDate) validateCount("mapDecimal", \Query.object.mapDecimal) validateCount("mapObjectId", \Query.object.mapObjectId) validateCount("mapUuid", \Query.object.mapUuid) validateCount("mapAny", \Query.object.mapAny) validateCount("mapInt", \Query.object.mapInt) validateCount("mapInt8", \Query.object.mapInt8) validateCount("mapInt16", \Query.object.mapInt16) validateCount("mapInt32", \Query.object.mapInt32) validateCount("mapInt64", \Query.object.mapInt64) validateCount("mapFloat", \Query.object.mapFloat) validateCount("mapDouble", \Query.object.mapDouble) validateCount("mapString", \Query.object.mapString) validateCount("mapInt", \Query.object.mapInt) validateCount("mapInt8", \Query.object.mapInt8) validateCount("mapInt16", \Query.object.mapInt16) validateCount("mapInt32", \Query.object.mapInt32) validateCount("mapInt64", \Query.object.mapInt64) validateCount("mapFloat", \Query.object.mapFloat) validateCount("mapDouble", \Query.object.mapDouble) validateCount("mapString", \Query.object.mapString) validateCount("mapBinary", \Query.object.mapBinary) validateCount("mapDate", \Query.object.mapDate) validateCount("mapDecimal", \Query.object.mapDecimal) validateCount("mapObjectId", \Query.object.mapObjectId) validateCount("mapUuid", \Query.object.mapUuid) validateCount("mapOptInt", \Query.object.mapOptInt) validateCount("mapOptInt8", \Query.object.mapOptInt8) validateCount("mapOptInt16", \Query.object.mapOptInt16) validateCount("mapOptInt32", \Query.object.mapOptInt32) validateCount("mapOptInt64", \Query.object.mapOptInt64) validateCount("mapOptFloat", \Query.object.mapOptFloat) validateCount("mapOptDouble", \Query.object.mapOptDouble) validateCount("mapOptString", \Query.object.mapOptString) validateCount("mapOptBinary", \Query.object.mapOptBinary) validateCount("mapOptDate", \Query.object.mapOptDate) validateCount("mapOptDecimal", \Query.object.mapOptDecimal) validateCount("mapOptObjectId", \Query.object.mapOptObjectId) validateCount("mapOptUuid", \Query.object.mapOptUuid) validateCount("mapIntOpt", \Query.object.mapIntOpt) validateCount("mapInt8Opt", \Query.object.mapInt8Opt) validateCount("mapInt16Opt", \Query.object.mapInt16Opt) validateCount("mapInt32Opt", \Query.object.mapInt32Opt) validateCount("mapInt64Opt", \Query.object.mapInt64Opt) validateCount("mapFloatOpt", \Query.object.mapFloatOpt) validateCount("mapDoubleOpt", \Query.object.mapDoubleOpt) validateCount("mapStringOpt", \Query.object.mapStringOpt) validateCount("mapOptInt", \Query.object.mapOptInt) validateCount("mapOptInt8", \Query.object.mapOptInt8) validateCount("mapOptInt16", \Query.object.mapOptInt16) validateCount("mapOptInt32", \Query.object.mapOptInt32) validateCount("mapOptInt64", \Query.object.mapOptInt64) validateCount("mapOptFloat", \Query.object.mapOptFloat) validateCount("mapOptDouble", \Query.object.mapOptDouble) validateCount("mapOptString", \Query.object.mapOptString) validateCount("mapOptBinary", \Query.object.mapOptBinary) validateCount("mapOptDate", \Query.object.mapOptDate) validateCount("mapOptDecimal", \Query.object.mapOptDecimal) validateCount("mapOptObjectId", \Query.object.mapOptObjectId) validateCount("mapOptUuid", \Query.object.mapOptUuid) } // MARK: - Keypath Collection Aggregations private func validateKeypathAverage(_ name: String, _ average: T, _ min: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@avg.\(name) == %@)", average, count: 1) { lhs($0).avg == average } assertQuery(Root.self, "(list.@avg.\(name) == %@)", min, count: 0) { lhs($0).avg == min } assertQuery(Root.self, "(list.@avg.\(name) != %@)", average, count: 0) { lhs($0).avg != average } assertQuery(Root.self, "(list.@avg.\(name) != %@)", min, count: 1) { lhs($0).avg != min } assertQuery(Root.self, "(list.@avg.\(name) > %@)", average, count: 0) { lhs($0).avg > average } assertQuery(Root.self, "(list.@avg.\(name) > %@)", min, count: 1) { lhs($0).avg > min } assertQuery(Root.self, "(list.@avg.\(name) < %@)", average, count: 0) { lhs($0).avg < average } assertQuery(Root.self, "(list.@avg.\(name) >= %@)", average, count: 1) { lhs($0).avg >= average } assertQuery(Root.self, "(list.@avg.\(name) <= %@)", average, count: 1) { lhs($0).avg <= average } } func testKeypathCollectionAggregatesAvg() { createKeypathCollectionAggregatesObject() validateKeypathAverage("intCol", Int.average(), 1, \Query.list.intCol) validateKeypathAverage("int8Col", Int8.average(), Int8(8), \Query.list.int8Col) validateKeypathAverage("int16Col", Int16.average(), Int16(16), \Query.list.int16Col) validateKeypathAverage("int32Col", Int32.average(), Int32(32), \Query.list.int32Col) validateKeypathAverage("int64Col", Int64.average(), Int64(64), \Query.list.int64Col) validateKeypathAverage("floatCol", Float.average(), Float(5.55444333), \Query.list.floatCol) validateKeypathAverage("doubleCol", Double.average(), 123.456, \Query.list.doubleCol) validateKeypathAverage("decimalCol", Decimal128.average(), Decimal128(123.456), \Query.list.decimalCol) validateKeypathAverage("intEnumCol", ModernIntEnum.average(), ModernIntEnum.value1.rawValue, \Query.list.intEnumCol.rawValue) validateKeypathAverage("int", IntWrapper.average(), IntWrapper(persistedValue: 1), \Query.list.int) validateKeypathAverage("int8", Int8Wrapper.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.list.int8) validateKeypathAverage("int16", Int16Wrapper.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.list.int16) validateKeypathAverage("int32", Int32Wrapper.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.list.int32) validateKeypathAverage("int64", Int64Wrapper.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.list.int64) validateKeypathAverage("float", FloatWrapper.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.list.float) validateKeypathAverage("double", DoubleWrapper.average(), DoubleWrapper(persistedValue: 123.456), \Query.list.double) validateKeypathAverage("decimal", Decimal128Wrapper.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.list.decimal) validateKeypathAverage("optIntCol", Int?.average(), 1, \Query.list.optIntCol) validateKeypathAverage("optInt8Col", Int8?.average(), Int8(8), \Query.list.optInt8Col) validateKeypathAverage("optInt16Col", Int16?.average(), Int16(16), \Query.list.optInt16Col) validateKeypathAverage("optInt32Col", Int32?.average(), Int32(32), \Query.list.optInt32Col) validateKeypathAverage("optInt64Col", Int64?.average(), Int64(64), \Query.list.optInt64Col) validateKeypathAverage("optFloatCol", Float?.average(), Float(5.55444333), \Query.list.optFloatCol) validateKeypathAverage("optDoubleCol", Double?.average(), 123.456, \Query.list.optDoubleCol) validateKeypathAverage("optDecimalCol", Decimal128?.average(), Decimal128(123.456), \Query.list.optDecimalCol) validateKeypathAverage("optIntEnumCol", ModernIntEnum.average(), ModernIntEnum.value1.rawValue, \Query.list.optIntEnumCol.rawValue) validateKeypathAverage("optInt", IntWrapper?.average(), IntWrapper(persistedValue: 1), \Query.list.optInt) validateKeypathAverage("optInt8", Int8Wrapper?.average(), Int8Wrapper(persistedValue: Int8(8)), \Query.list.optInt8) validateKeypathAverage("optInt16", Int16Wrapper?.average(), Int16Wrapper(persistedValue: Int16(16)), \Query.list.optInt16) validateKeypathAverage("optInt32", Int32Wrapper?.average(), Int32Wrapper(persistedValue: Int32(32)), \Query.list.optInt32) validateKeypathAverage("optInt64", Int64Wrapper?.average(), Int64Wrapper(persistedValue: Int64(64)), \Query.list.optInt64) validateKeypathAverage("optFloat", FloatWrapper?.average(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.list.optFloat) validateKeypathAverage("optDouble", DoubleWrapper?.average(), DoubleWrapper(persistedValue: 123.456), \Query.list.optDouble) validateKeypathAverage("optDecimal", Decimal128Wrapper?.average(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.list.optDecimal) } private func validateKeypathSum(_ name: String, _ sum: T, _ min: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@sum.\(name) == %@)", sum, count: 1) { lhs($0).sum == sum } assertQuery(Root.self, "(list.@sum.\(name) == %@)", min, count: 0) { lhs($0).sum == min } assertQuery(Root.self, "(list.@sum.\(name) != %@)", sum, count: 0) { lhs($0).sum != sum } assertQuery(Root.self, "(list.@sum.\(name) != %@)", min, count: 1) { lhs($0).sum != min } assertQuery(Root.self, "(list.@sum.\(name) > %@)", sum, count: 0) { lhs($0).sum > sum } assertQuery(Root.self, "(list.@sum.\(name) > %@)", min, count: 1) { lhs($0).sum > min } assertQuery(Root.self, "(list.@sum.\(name) < %@)", sum, count: 0) { lhs($0).sum < sum } assertQuery(Root.self, "(list.@sum.\(name) >= %@)", sum, count: 1) { lhs($0).sum >= sum } assertQuery(Root.self, "(list.@sum.\(name) <= %@)", sum, count: 1) { lhs($0).sum <= sum } } func testKeypathCollectionAggregatesSum() { createKeypathCollectionAggregatesObject() validateKeypathSum("intCol", Int.sum(), 1, \Query.list.intCol) validateKeypathSum("int8Col", Int8.sum(), Int8(8), \Query.list.int8Col) validateKeypathSum("int16Col", Int16.sum(), Int16(16), \Query.list.int16Col) validateKeypathSum("int32Col", Int32.sum(), Int32(32), \Query.list.int32Col) validateKeypathSum("int64Col", Int64.sum(), Int64(64), \Query.list.int64Col) validateKeypathSum("floatCol", Float.sum(), Float(5.55444333), \Query.list.floatCol) validateKeypathSum("doubleCol", Double.sum(), 123.456, \Query.list.doubleCol) validateKeypathSum("decimalCol", Decimal128.sum(), Decimal128(123.456), \Query.list.decimalCol) validateKeypathSum("intEnumCol", ModernIntEnum.sum(), ModernIntEnum.value1.rawValue, \Query.list.intEnumCol.rawValue) validateKeypathSum("int", IntWrapper.sum(), IntWrapper(persistedValue: 1), \Query.list.int) validateKeypathSum("int8", Int8Wrapper.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.list.int8) validateKeypathSum("int16", Int16Wrapper.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.list.int16) validateKeypathSum("int32", Int32Wrapper.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.list.int32) validateKeypathSum("int64", Int64Wrapper.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.list.int64) validateKeypathSum("float", FloatWrapper.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.list.float) validateKeypathSum("double", DoubleWrapper.sum(), DoubleWrapper(persistedValue: 123.456), \Query.list.double) validateKeypathSum("decimal", Decimal128Wrapper.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.list.decimal) validateKeypathSum("optIntCol", Int?.sum(), 1, \Query.list.optIntCol) validateKeypathSum("optInt8Col", Int8?.sum(), Int8(8), \Query.list.optInt8Col) validateKeypathSum("optInt16Col", Int16?.sum(), Int16(16), \Query.list.optInt16Col) validateKeypathSum("optInt32Col", Int32?.sum(), Int32(32), \Query.list.optInt32Col) validateKeypathSum("optInt64Col", Int64?.sum(), Int64(64), \Query.list.optInt64Col) validateKeypathSum("optFloatCol", Float?.sum(), Float(5.55444333), \Query.list.optFloatCol) validateKeypathSum("optDoubleCol", Double?.sum(), 123.456, \Query.list.optDoubleCol) validateKeypathSum("optDecimalCol", Decimal128?.sum(), Decimal128(123.456), \Query.list.optDecimalCol) validateKeypathSum("optIntEnumCol", ModernIntEnum.sum(), ModernIntEnum.value1.rawValue, \Query.list.optIntEnumCol.rawValue) validateKeypathSum("optInt", IntWrapper?.sum(), IntWrapper(persistedValue: 1), \Query.list.optInt) validateKeypathSum("optInt8", Int8Wrapper?.sum(), Int8Wrapper(persistedValue: Int8(8)), \Query.list.optInt8) validateKeypathSum("optInt16", Int16Wrapper?.sum(), Int16Wrapper(persistedValue: Int16(16)), \Query.list.optInt16) validateKeypathSum("optInt32", Int32Wrapper?.sum(), Int32Wrapper(persistedValue: Int32(32)), \Query.list.optInt32) validateKeypathSum("optInt64", Int64Wrapper?.sum(), Int64Wrapper(persistedValue: Int64(64)), \Query.list.optInt64) validateKeypathSum("optFloat", FloatWrapper?.sum(), FloatWrapper(persistedValue: Float(5.55444333)), \Query.list.optFloat) validateKeypathSum("optDouble", DoubleWrapper?.sum(), DoubleWrapper(persistedValue: 123.456), \Query.list.optDouble) validateKeypathSum("optDecimal", Decimal128Wrapper?.sum(), Decimal128Wrapper(persistedValue: Decimal128(123.456)), \Query.list.optDecimal) } private func validateKeypathMin(_ name: String, min: T, max: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@min.\(name) == %@)", min, count: 1) { lhs($0).min == min } assertQuery(Root.self, "(list.@min.\(name) == %@)", max, count: 0) { lhs($0).min == max } assertQuery(Root.self, "(list.@min.\(name) != %@)", min, count: 0) { lhs($0).min != min } assertQuery(Root.self, "(list.@min.\(name) != %@)", max, count: 1) { lhs($0).min != max } assertQuery(Root.self, "(list.@min.\(name) > %@)", min, count: 0) { lhs($0).min > min } assertQuery(Root.self, "(list.@min.\(name) < %@)", min, count: 0) { lhs($0).min < min } assertQuery(Root.self, "(list.@min.\(name) >= %@)", min, count: 1) { lhs($0).min >= min } assertQuery(Root.self, "(list.@min.\(name) <= %@)", min, count: 1) { lhs($0).min <= min } } func testKeypathCollectionAggregatesMin() { createKeypathCollectionAggregatesObject() validateKeypathMin("intCol", min: 1, max: 5, \Query.list.intCol) validateKeypathMin("int8Col", min: Int8(8), max: Int8(10), \Query.list.int8Col) validateKeypathMin("int16Col", min: Int16(16), max: Int16(18), \Query.list.int16Col) validateKeypathMin("int32Col", min: Int32(32), max: Int32(34), \Query.list.int32Col) validateKeypathMin("int64Col", min: Int64(64), max: Int64(66), \Query.list.int64Col) validateKeypathMin("floatCol", min: Float(5.55444333), max: Float(7.55444333), \Query.list.floatCol) validateKeypathMin("doubleCol", min: 123.456, max: 345.678, \Query.list.doubleCol) validateKeypathMin("dateCol", min: Date(timeIntervalSince1970: 1000000), max: Date(timeIntervalSince1970: 3000000), \Query.list.dateCol) validateKeypathMin("decimalCol", min: Decimal128(123.456), max: Decimal128(345.678), \Query.list.decimalCol) validateKeypathMin("intEnumCol", min: ModernIntEnum.value1.rawValue, max: ModernIntEnum.value3.rawValue, \Query.list.intEnumCol.rawValue) validateKeypathMin("int", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.list.int) validateKeypathMin("int8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.list.int8) validateKeypathMin("int16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.list.int16) validateKeypathMin("int32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.list.int32) validateKeypathMin("int64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.list.int64) validateKeypathMin("float", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.list.float) validateKeypathMin("double", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.list.double) validateKeypathMin("date", min: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), max: DateWrapper(persistedValue: Date(timeIntervalSince1970: 3000000)), \Query.list.date) validateKeypathMin("decimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.list.decimal) validateKeypathMin("optIntCol", min: 1, max: 5, \Query.list.optIntCol) validateKeypathMin("optInt8Col", min: Int8(8), max: Int8(10), \Query.list.optInt8Col) validateKeypathMin("optInt16Col", min: Int16(16), max: Int16(18), \Query.list.optInt16Col) validateKeypathMin("optInt32Col", min: Int32(32), max: Int32(34), \Query.list.optInt32Col) validateKeypathMin("optInt64Col", min: Int64(64), max: Int64(66), \Query.list.optInt64Col) validateKeypathMin("optFloatCol", min: Float(5.55444333), max: Float(7.55444333), \Query.list.optFloatCol) validateKeypathMin("optDoubleCol", min: 123.456, max: 345.678, \Query.list.optDoubleCol) validateKeypathMin("optDateCol", min: Date(timeIntervalSince1970: 1000000), max: Date(timeIntervalSince1970: 3000000), \Query.list.optDateCol) validateKeypathMin("optDecimalCol", min: Decimal128(123.456), max: Decimal128(345.678), \Query.list.optDecimalCol) validateKeypathMin("optIntEnumCol", min: ModernIntEnum.value1.rawValue, max: ModernIntEnum.value3.rawValue, \Query.list.optIntEnumCol.rawValue) validateKeypathMin("optInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.list.optInt) validateKeypathMin("optInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.list.optInt8) validateKeypathMin("optInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.list.optInt16) validateKeypathMin("optInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.list.optInt32) validateKeypathMin("optInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.list.optInt64) validateKeypathMin("optFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.list.optFloat) validateKeypathMin("optDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.list.optDouble) validateKeypathMin("optDate", min: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), max: DateWrapper(persistedValue: Date(timeIntervalSince1970: 3000000)), \Query.list.optDate) validateKeypathMin("optDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.list.optDecimal) } private func validateKeypathMax(_ name: String, min: T, max: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@max.\(name) == %@)", max, count: 1) { lhs($0).max == max } assertQuery(Root.self, "(list.@max.\(name) == %@)", min, count: 0) { lhs($0).max == min } assertQuery(Root.self, "(list.@max.\(name) != %@)", max, count: 0) { lhs($0).max != max } assertQuery(Root.self, "(list.@max.\(name) != %@)", min, count: 1) { lhs($0).max != min } assertQuery(Root.self, "(list.@max.\(name) > %@)", max, count: 0) { lhs($0).max > max } assertQuery(Root.self, "(list.@max.\(name) < %@)", max, count: 0) { lhs($0).max < max } assertQuery(Root.self, "(list.@max.\(name) >= %@)", max, count: 1) { lhs($0).max >= max } assertQuery(Root.self, "(list.@max.\(name) <= %@)", max, count: 1) { lhs($0).max <= max } } func testKeypathCollectionAggregatesMax() { createKeypathCollectionAggregatesObject() validateKeypathMax("intCol", min: 1, max: 5, \Query.list.intCol) validateKeypathMax("int8Col", min: Int8(8), max: Int8(10), \Query.list.int8Col) validateKeypathMax("int16Col", min: Int16(16), max: Int16(18), \Query.list.int16Col) validateKeypathMax("int32Col", min: Int32(32), max: Int32(34), \Query.list.int32Col) validateKeypathMax("int64Col", min: Int64(64), max: Int64(66), \Query.list.int64Col) validateKeypathMax("floatCol", min: Float(5.55444333), max: Float(7.55444333), \Query.list.floatCol) validateKeypathMax("doubleCol", min: 123.456, max: 345.678, \Query.list.doubleCol) validateKeypathMax("dateCol", min: Date(timeIntervalSince1970: 1000000), max: Date(timeIntervalSince1970: 3000000), \Query.list.dateCol) validateKeypathMax("decimalCol", min: Decimal128(123.456), max: Decimal128(345.678), \Query.list.decimalCol) validateKeypathMax("intEnumCol", min: ModernIntEnum.value1.rawValue, max: ModernIntEnum.value3.rawValue, \Query.list.intEnumCol.rawValue) validateKeypathMax("int", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.list.int) validateKeypathMax("int8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.list.int8) validateKeypathMax("int16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.list.int16) validateKeypathMax("int32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.list.int32) validateKeypathMax("int64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.list.int64) validateKeypathMax("float", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.list.float) validateKeypathMax("double", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.list.double) validateKeypathMax("date", min: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), max: DateWrapper(persistedValue: Date(timeIntervalSince1970: 3000000)), \Query.list.date) validateKeypathMax("decimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.list.decimal) validateKeypathMax("optIntCol", min: 1, max: 5, \Query.list.optIntCol) validateKeypathMax("optInt8Col", min: Int8(8), max: Int8(10), \Query.list.optInt8Col) validateKeypathMax("optInt16Col", min: Int16(16), max: Int16(18), \Query.list.optInt16Col) validateKeypathMax("optInt32Col", min: Int32(32), max: Int32(34), \Query.list.optInt32Col) validateKeypathMax("optInt64Col", min: Int64(64), max: Int64(66), \Query.list.optInt64Col) validateKeypathMax("optFloatCol", min: Float(5.55444333), max: Float(7.55444333), \Query.list.optFloatCol) validateKeypathMax("optDoubleCol", min: 123.456, max: 345.678, \Query.list.optDoubleCol) validateKeypathMax("optDateCol", min: Date(timeIntervalSince1970: 1000000), max: Date(timeIntervalSince1970: 3000000), \Query.list.optDateCol) validateKeypathMax("optDecimalCol", min: Decimal128(123.456), max: Decimal128(345.678), \Query.list.optDecimalCol) validateKeypathMax("optIntEnumCol", min: ModernIntEnum.value1.rawValue, max: ModernIntEnum.value3.rawValue, \Query.list.optIntEnumCol.rawValue) validateKeypathMax("optInt", min: IntWrapper(persistedValue: 1), max: IntWrapper(persistedValue: 5), \Query.list.optInt) validateKeypathMax("optInt8", min: Int8Wrapper(persistedValue: Int8(8)), max: Int8Wrapper(persistedValue: Int8(10)), \Query.list.optInt8) validateKeypathMax("optInt16", min: Int16Wrapper(persistedValue: Int16(16)), max: Int16Wrapper(persistedValue: Int16(18)), \Query.list.optInt16) validateKeypathMax("optInt32", min: Int32Wrapper(persistedValue: Int32(32)), max: Int32Wrapper(persistedValue: Int32(34)), \Query.list.optInt32) validateKeypathMax("optInt64", min: Int64Wrapper(persistedValue: Int64(64)), max: Int64Wrapper(persistedValue: Int64(66)), \Query.list.optInt64) validateKeypathMax("optFloat", min: FloatWrapper(persistedValue: Float(5.55444333)), max: FloatWrapper(persistedValue: Float(7.55444333)), \Query.list.optFloat) validateKeypathMax("optDouble", min: DoubleWrapper(persistedValue: 123.456), max: DoubleWrapper(persistedValue: 345.678), \Query.list.optDouble) validateKeypathMax("optDate", min: DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), max: DateWrapper(persistedValue: Date(timeIntervalSince1970: 3000000)), \Query.list.optDate) validateKeypathMax("optDecimal", min: Decimal128Wrapper(persistedValue: Decimal128(123.456)), max: Decimal128Wrapper(persistedValue: Decimal128(345.678)), \Query.list.optDecimal) } func testAggregateNotSupported() { assertThrows(assertQuery("", count: 0) { $0.intCol.avg == 1 }, reason: "Invalid keypath 'intCol.@avg': Property 'ModernAllTypesObject.intCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.doubleCol.max != 1 }, reason: "Invalid keypath 'doubleCol.@max': Property 'ModernAllTypesObject.doubleCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.dateCol.min > Date() }, reason: "Invalid keypath 'dateCol.@min': Property 'ModernAllTypesObject.dateCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.decimalCol.sum < 1 }, reason: "Invalid keypath 'decimalCol.@sum': Property 'ModernAllTypesObject.decimalCol' is not a link or collection and can only appear at the end of a keypath.") } // MARK: Column comparison func testColumnComparison() { // Basic comparison assertQuery("(stringEnumCol == stringEnumCol)", count: 1) { $0.stringEnumCol == $0.stringEnumCol } assertQuery("(stringCol != stringCol)", count: 0) { $0.stringCol != $0.stringCol } assertQuery("(stringEnumCol != stringEnumCol)", count: 0) { $0.stringEnumCol != $0.stringEnumCol } assertQuery("(stringEnumCol > stringEnumCol)", count: 0) { $0.stringEnumCol > $0.stringEnumCol } assertQuery("(stringCol > stringCol)", count: 0) { $0.stringCol > $0.stringCol } assertQuery("(stringEnumCol >= stringEnumCol)", count: 1) { $0.stringEnumCol >= $0.stringEnumCol } assertQuery("(stringCol >= stringCol)", count: 1) { $0.stringCol >= $0.stringCol } assertQuery("(stringEnumCol < stringEnumCol)", count: 0) { $0.stringEnumCol < $0.stringEnumCol } assertQuery("(stringCol < stringCol)", count: 0) { $0.stringCol < $0.stringCol } assertQuery("(stringEnumCol <= stringEnumCol)", count: 1) { $0.stringEnumCol <= $0.stringEnumCol } assertQuery("(stringCol <= stringCol)", count: 1) { $0.stringCol <= $0.stringCol } assertThrows(assertQuery("", count: 1) { $0.arrayCol == $0.arrayCol }, reason: "Comparing two collection columns is not permitted.") assertThrows(assertQuery("", count: 1) { $0.arrayCol != $0.arrayCol }, reason: "Comparing two collection columns is not permitted.") assertQuery("(intCol > intCol)", count: 0) { $0.intCol > $0.intCol } assertQuery("(intEnumCol > intEnumCol)", count: 0) { $0.intEnumCol > $0.intEnumCol } assertQuery("(intCol >= intCol)", count: 1) { $0.intCol >= $0.intCol } assertQuery("(intEnumCol >= intEnumCol)", count: 1) { $0.intEnumCol >= $0.intEnumCol } assertQuery("(intCol < intCol)", count: 0) { $0.intCol < $0.intCol } assertQuery("(intEnumCol < intEnumCol)", count: 0) { $0.intEnumCol < $0.intEnumCol } assertQuery("(intCol <= intCol)", count: 1) { $0.intCol <= $0.intCol } assertQuery("(intEnumCol <= intEnumCol)", count: 1) { $0.intEnumCol <= $0.intEnumCol } assertQuery("(optStringCol == optStringCol)", count: 1) { $0.optStringCol == $0.optStringCol } assertQuery("(optStringCol != optStringCol)", count: 0) { $0.optStringCol != $0.optStringCol } assertQuery("(optIntCol > optIntCol)", count: 0) { $0.optIntCol > $0.optIntCol } assertQuery("(optIntCol >= optIntCol)", count: 1) { $0.optIntCol >= $0.optIntCol } assertQuery("(optIntCol < optIntCol)", count: 0) { $0.optIntCol < $0.optIntCol } assertQuery("(optIntCol <= optIntCol)", count: 1) { $0.optIntCol <= $0.optIntCol } // Basic comparison with one level depth assertQuery("(objectCol.stringCol == objectCol.stringCol)", count: 1) { $0.objectCol.stringCol == $0.objectCol.stringCol } assertQuery("(objectCol.stringCol != objectCol.stringCol)", count: 0) { $0.objectCol.stringCol != $0.objectCol.stringCol } assertQuery("(objectCol.intCol > objectCol.intCol)", count: 0) { $0.objectCol.intCol > $0.objectCol.intCol } assertQuery("(objectCol.intCol >= objectCol.intCol)", count: 1) { $0.objectCol.intCol >= $0.objectCol.intCol } assertQuery("(objectCol.intCol < objectCol.intCol)", count: 0) { $0.objectCol.intCol < $0.objectCol.intCol } assertQuery("(objectCol.intCol <= objectCol.intCol)", count: 1) { $0.objectCol.intCol <= $0.objectCol.intCol } assertQuery("(objectCol.optStringCol == objectCol.optStringCol)", count: 1) { $0.objectCol.optStringCol == $0.objectCol.optStringCol } assertQuery("(objectCol.optStringCol != objectCol.optStringCol)", count: 0) { $0.objectCol.optStringCol != $0.objectCol.optStringCol } assertQuery("(objectCol.optIntCol > objectCol.optIntCol)", count: 0) { $0.objectCol.optIntCol > $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol >= objectCol.optIntCol)", count: 1) { $0.objectCol.optIntCol >= $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol < objectCol.optIntCol)", count: 0) { $0.objectCol.optIntCol < $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol <= objectCol.optIntCol)", count: 1) { $0.objectCol.optIntCol <= $0.objectCol.optIntCol } // String comparison assertQuery("(stringCol CONTAINS[cd] stringCol)", count: 1) { $0.stringCol.contains($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.stringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol ENDSWITH[cd] stringCol)", count: 1) { $0.stringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol LIKE[c] stringCol)", count: 1) { $0.stringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(stringCol ==[cd] stringCol)", count: 1) { $0.stringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol !=[cd] stringCol)", count: 0) { $0.stringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } // String with optional col assertQuery("(stringCol CONTAINS[cd] optStringCol)", count: 1) { $0.stringCol.contains($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol CONTAINS[cd] optStringCol)", count: 1) { $0.optStringCol.contains($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol CONTAINS[cd] stringCol)", count: 1) { $0.optStringCol.contains($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.stringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol BEGINSWITH[cd] optStringCol)", count: 1) { $0.optStringCol.starts(with: $0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.optStringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol ENDSWITH[cd] stringCol)", count: 1) { $0.stringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ENDSWITH[cd] optStringCol)", count: 1) { $0.optStringCol.ends(with: $0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ENDSWITH[cd] stringCol)", count: 1) { $0.optStringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol LIKE[c] stringCol)", count: 1) { $0.stringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(optStringCol LIKE[c] optStringCol)", count: 1) { $0.optStringCol.like($0.optStringCol, caseInsensitive: true) } assertQuery("(optStringCol LIKE[c] stringCol)", count: 1) { $0.optStringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(stringCol ==[cd] stringCol)", count: 1) { $0.stringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ==[cd] optStringCol)", count: 1) { $0.optStringCol.equals($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ==[cd] stringCol)", count: 1) { $0.optStringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol !=[cd] stringCol)", count: 0) { $0.stringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol !=[cd] optStringCol)", count: 0) { $0.optStringCol.notEquals($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol !=[cd] stringCol)", count: 0) { $0.optStringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } } // MARK: - ContainsIn func testContainsIn() { assertQuery(ModernAllTypesObject.self, "(boolCol IN %@)", values: [NSArray(array: [true, false])], count: 1) { $0.boolCol.in([true, false]) } assertQuery(ModernAllTypesObject.self, "(NOT boolCol IN %@)", values: [NSArray(array: [false])], count: 0) { !$0.boolCol.in([false]) } assertQuery(ModernAllTypesObject.self, "(intCol IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.intCol.in([1, 3]) } assertQuery(ModernAllTypesObject.self, "(NOT intCol IN %@)", values: [NSArray(array: [3])], count: 0) { !$0.intCol.in([3]) } assertQuery(ModernAllTypesObject.self, "(int8Col IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.int8Col.in([Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(NOT int8Col IN %@)", values: [NSArray(array: [Int8(9)])], count: 0) { !$0.int8Col.in([Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(int16Col IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.int16Col.in([Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(NOT int16Col IN %@)", values: [NSArray(array: [Int16(17)])], count: 0) { !$0.int16Col.in([Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(int32Col IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.int32Col.in([Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(NOT int32Col IN %@)", values: [NSArray(array: [Int32(33)])], count: 0) { !$0.int32Col.in([Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(int64Col IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.int64Col.in([Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(NOT int64Col IN %@)", values: [NSArray(array: [Int64(65)])], count: 0) { !$0.int64Col.in([Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(floatCol IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.floatCol.in([Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(NOT floatCol IN %@)", values: [NSArray(array: [Float(6.55444333)])], count: 0) { !$0.floatCol.in([Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(doubleCol IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.doubleCol.in([123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(NOT doubleCol IN %@)", values: [NSArray(array: [234.567])], count: 0) { !$0.doubleCol.in([234.567]) } assertQuery(ModernAllTypesObject.self, "(stringCol IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.stringCol.in(["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(NOT stringCol IN %@)", values: [NSArray(array: ["Foó"])], count: 0) { !$0.stringCol.in(["Foó"]) } assertQuery(ModernAllTypesObject.self, "(binaryCol IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.binaryCol.in([Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(NOT binaryCol IN %@)", values: [NSArray(array: [Data(count: 128)])], count: 0) { !$0.binaryCol.in([Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(dateCol IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.dateCol.in([Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(NOT dateCol IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 2000000)])], count: 0) { !$0.dateCol.in([Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(decimalCol IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.decimalCol.in([Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(NOT decimalCol IN %@)", values: [NSArray(array: [Decimal128(234.567)])], count: 0) { !$0.decimalCol.in([Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(objectIdCol IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.objectIdCol.in([ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(NOT objectIdCol IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695045")])], count: 0) { !$0.objectIdCol.in([ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(uuidCol IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.uuidCol.in([UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(NOT uuidCol IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 0) { !$0.uuidCol.in([UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(intEnumCol IN %@)", values: [NSArray(array: [ModernIntEnum.value1, ModernIntEnum.value2])], count: 1) { $0.intEnumCol.in([ModernIntEnum.value1, ModernIntEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(NOT intEnumCol IN %@)", values: [NSArray(array: [ModernIntEnum.value2])], count: 0) { !$0.intEnumCol.in([ModernIntEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(stringEnumCol IN %@)", values: [NSArray(array: [ModernStringEnum.value1, ModernStringEnum.value2])], count: 1) { $0.stringEnumCol.in([ModernStringEnum.value1, ModernStringEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(NOT stringEnumCol IN %@)", values: [NSArray(array: [ModernStringEnum.value2])], count: 0) { !$0.stringEnumCol.in([ModernStringEnum.value2]) } assertQuery(AllCustomPersistableTypes.self, "(bool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: false)])], count: 1) { $0.bool.in([BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: false)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT bool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: false)])], count: 0) { !$0.bool.in([BoolWrapper(persistedValue: false)]) } assertQuery(AllCustomPersistableTypes.self, "(int IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.int.in([IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT int IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 3)])], count: 0) { !$0.int.in([IntWrapper(persistedValue: 3)]) } assertQuery(AllCustomPersistableTypes.self, "(int8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.int8.in([Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT int8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(9))])], count: 0) { !$0.int8.in([Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(AllCustomPersistableTypes.self, "(int16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.int16.in([Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT int16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(17))])], count: 0) { !$0.int16.in([Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(AllCustomPersistableTypes.self, "(int32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.int32.in([Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT int32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(33))])], count: 0) { !$0.int32.in([Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(AllCustomPersistableTypes.self, "(int64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.int64.in([Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT int64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(65))])], count: 0) { !$0.int64.in([Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(AllCustomPersistableTypes.self, "(float IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.float.in([FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT float IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(6.55444333))])], count: 0) { !$0.float.in([FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(AllCustomPersistableTypes.self, "(double IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.double.in([DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT double IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 234.567)])], count: 0) { !$0.double.in([DoubleWrapper(persistedValue: 234.567)]) } assertQuery(AllCustomPersistableTypes.self, "(string IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.string.in([StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(AllCustomPersistableTypes.self, "(NOT string IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foó")])], count: 0) { !$0.string.in([StringWrapper(persistedValue: "Foó")]) } assertQuery(AllCustomPersistableTypes.self, "(binary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.binary.in([DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT binary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 128))])], count: 0) { !$0.binary.in([DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(AllCustomPersistableTypes.self, "(date IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.date.in([DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT date IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 0) { !$0.date.in([DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(AllCustomPersistableTypes.self, "(decimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.decimal.in([Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT decimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 0) { !$0.decimal.in([Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(AllCustomPersistableTypes.self, "(objectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.objectId.in([ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT objectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 0) { !$0.objectId.in([ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(AllCustomPersistableTypes.self, "(uuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.uuid.in([UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT uuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 0) { !$0.uuid.in([UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } assertQuery(ModernAllTypesObject.self, "(optBoolCol IN %@)", values: [NSArray(array: [true, false])], count: 1) { $0.optBoolCol.in([true, false]) } assertQuery(ModernAllTypesObject.self, "(NOT optBoolCol IN %@)", values: [NSArray(array: [false])], count: 0) { !$0.optBoolCol.in([false]) } assertQuery(ModernAllTypesObject.self, "(optIntCol IN %@)", values: [NSArray(array: [1, 3])], count: 1) { $0.optIntCol.in([1, 3]) } assertQuery(ModernAllTypesObject.self, "(NOT optIntCol IN %@)", values: [NSArray(array: [3])], count: 0) { !$0.optIntCol.in([3]) } assertQuery(ModernAllTypesObject.self, "(optInt8Col IN %@)", values: [NSArray(array: [Int8(8), Int8(9)])], count: 1) { $0.optInt8Col.in([Int8(8), Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(NOT optInt8Col IN %@)", values: [NSArray(array: [Int8(9)])], count: 0) { !$0.optInt8Col.in([Int8(9)]) } assertQuery(ModernAllTypesObject.self, "(optInt16Col IN %@)", values: [NSArray(array: [Int16(16), Int16(17)])], count: 1) { $0.optInt16Col.in([Int16(16), Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(NOT optInt16Col IN %@)", values: [NSArray(array: [Int16(17)])], count: 0) { !$0.optInt16Col.in([Int16(17)]) } assertQuery(ModernAllTypesObject.self, "(optInt32Col IN %@)", values: [NSArray(array: [Int32(32), Int32(33)])], count: 1) { $0.optInt32Col.in([Int32(32), Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(NOT optInt32Col IN %@)", values: [NSArray(array: [Int32(33)])], count: 0) { !$0.optInt32Col.in([Int32(33)]) } assertQuery(ModernAllTypesObject.self, "(optInt64Col IN %@)", values: [NSArray(array: [Int64(64), Int64(65)])], count: 1) { $0.optInt64Col.in([Int64(64), Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(NOT optInt64Col IN %@)", values: [NSArray(array: [Int64(65)])], count: 0) { !$0.optInt64Col.in([Int64(65)]) } assertQuery(ModernAllTypesObject.self, "(optFloatCol IN %@)", values: [NSArray(array: [Float(5.55444333), Float(6.55444333)])], count: 1) { $0.optFloatCol.in([Float(5.55444333), Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(NOT optFloatCol IN %@)", values: [NSArray(array: [Float(6.55444333)])], count: 0) { !$0.optFloatCol.in([Float(6.55444333)]) } assertQuery(ModernAllTypesObject.self, "(optDoubleCol IN %@)", values: [NSArray(array: [123.456, 234.567])], count: 1) { $0.optDoubleCol.in([123.456, 234.567]) } assertQuery(ModernAllTypesObject.self, "(NOT optDoubleCol IN %@)", values: [NSArray(array: [234.567])], count: 0) { !$0.optDoubleCol.in([234.567]) } assertQuery(ModernAllTypesObject.self, "(optStringCol IN %@)", values: [NSArray(array: ["Foo", "Foó"])], count: 1) { $0.optStringCol.in(["Foo", "Foó"]) } assertQuery(ModernAllTypesObject.self, "(NOT optStringCol IN %@)", values: [NSArray(array: ["Foó"])], count: 0) { !$0.optStringCol.in(["Foó"]) } assertQuery(ModernAllTypesObject.self, "(optBinaryCol IN %@)", values: [NSArray(array: [Data(count: 64), Data(count: 128)])], count: 1) { $0.optBinaryCol.in([Data(count: 64), Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(NOT optBinaryCol IN %@)", values: [NSArray(array: [Data(count: 128)])], count: 0) { !$0.optBinaryCol.in([Data(count: 128)]) } assertQuery(ModernAllTypesObject.self, "(optDateCol IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)])], count: 1) { $0.optDateCol.in([Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(NOT optDateCol IN %@)", values: [NSArray(array: [Date(timeIntervalSince1970: 2000000)])], count: 0) { !$0.optDateCol.in([Date(timeIntervalSince1970: 2000000)]) } assertQuery(ModernAllTypesObject.self, "(optDecimalCol IN %@)", values: [NSArray(array: [Decimal128(123.456), Decimal128(234.567)])], count: 1) { $0.optDecimalCol.in([Decimal128(123.456), Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(NOT optDecimalCol IN %@)", values: [NSArray(array: [Decimal128(234.567)])], count: 0) { !$0.optDecimalCol.in([Decimal128(234.567)]) } assertQuery(ModernAllTypesObject.self, "(optObjectIdCol IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")])], count: 1) { $0.optObjectIdCol.in([ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(NOT optObjectIdCol IN %@)", values: [NSArray(array: [ObjectId("61184062c1d8f096a3695045")])], count: 0) { !$0.optObjectIdCol.in([ObjectId("61184062c1d8f096a3695045")]) } assertQuery(ModernAllTypesObject.self, "(optUuidCol IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 1) { $0.optUuidCol.in([UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(NOT optUuidCol IN %@)", values: [NSArray(array: [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!])], count: 0) { !$0.optUuidCol.in([UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!]) } assertQuery(ModernAllTypesObject.self, "(optIntEnumCol IN %@)", values: [NSArray(array: [ModernIntEnum.value1, ModernIntEnum.value2])], count: 1) { $0.optIntEnumCol.in([ModernIntEnum.value1, ModernIntEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(NOT optIntEnumCol IN %@)", values: [NSArray(array: [ModernIntEnum.value2])], count: 0) { !$0.optIntEnumCol.in([ModernIntEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(optStringEnumCol IN %@)", values: [NSArray(array: [ModernStringEnum.value1, ModernStringEnum.value2])], count: 1) { $0.optStringEnumCol.in([ModernStringEnum.value1, ModernStringEnum.value2]) } assertQuery(ModernAllTypesObject.self, "(NOT optStringEnumCol IN %@)", values: [NSArray(array: [ModernStringEnum.value2])], count: 0) { !$0.optStringEnumCol.in([ModernStringEnum.value2]) } assertQuery(AllCustomPersistableTypes.self, "(optBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: false)])], count: 1) { $0.optBool.in([BoolWrapper(persistedValue: true), BoolWrapper(persistedValue: false)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optBool IN %@)", values: [NSArray(array: [BoolWrapper(persistedValue: false)])], count: 0) { !$0.optBool.in([BoolWrapper(persistedValue: false)]) } assertQuery(AllCustomPersistableTypes.self, "(optInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)])], count: 1) { $0.optInt.in([IntWrapper(persistedValue: 1), IntWrapper(persistedValue: 3)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optInt IN %@)", values: [NSArray(array: [IntWrapper(persistedValue: 3)])], count: 0) { !$0.optInt.in([IntWrapper(persistedValue: 3)]) } assertQuery(AllCustomPersistableTypes.self, "(optInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))])], count: 1) { $0.optInt8.in([Int8Wrapper(persistedValue: Int8(8)), Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optInt8 IN %@)", values: [NSArray(array: [Int8Wrapper(persistedValue: Int8(9))])], count: 0) { !$0.optInt8.in([Int8Wrapper(persistedValue: Int8(9))]) } assertQuery(AllCustomPersistableTypes.self, "(optInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))])], count: 1) { $0.optInt16.in([Int16Wrapper(persistedValue: Int16(16)), Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optInt16 IN %@)", values: [NSArray(array: [Int16Wrapper(persistedValue: Int16(17))])], count: 0) { !$0.optInt16.in([Int16Wrapper(persistedValue: Int16(17))]) } assertQuery(AllCustomPersistableTypes.self, "(optInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))])], count: 1) { $0.optInt32.in([Int32Wrapper(persistedValue: Int32(32)), Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optInt32 IN %@)", values: [NSArray(array: [Int32Wrapper(persistedValue: Int32(33))])], count: 0) { !$0.optInt32.in([Int32Wrapper(persistedValue: Int32(33))]) } assertQuery(AllCustomPersistableTypes.self, "(optInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))])], count: 1) { $0.optInt64.in([Int64Wrapper(persistedValue: Int64(64)), Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optInt64 IN %@)", values: [NSArray(array: [Int64Wrapper(persistedValue: Int64(65))])], count: 0) { !$0.optInt64.in([Int64Wrapper(persistedValue: Int64(65))]) } assertQuery(AllCustomPersistableTypes.self, "(optFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))])], count: 1) { $0.optFloat.in([FloatWrapper(persistedValue: Float(5.55444333)), FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optFloat IN %@)", values: [NSArray(array: [FloatWrapper(persistedValue: Float(6.55444333))])], count: 0) { !$0.optFloat.in([FloatWrapper(persistedValue: Float(6.55444333))]) } assertQuery(AllCustomPersistableTypes.self, "(optDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)])], count: 1) { $0.optDouble.in([DoubleWrapper(persistedValue: 123.456), DoubleWrapper(persistedValue: 234.567)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optDouble IN %@)", values: [NSArray(array: [DoubleWrapper(persistedValue: 234.567)])], count: 0) { !$0.optDouble.in([DoubleWrapper(persistedValue: 234.567)]) } assertQuery(AllCustomPersistableTypes.self, "(optString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")])], count: 1) { $0.optString.in([StringWrapper(persistedValue: "Foo"), StringWrapper(persistedValue: "Foó")]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optString IN %@)", values: [NSArray(array: [StringWrapper(persistedValue: "Foó")])], count: 0) { !$0.optString.in([StringWrapper(persistedValue: "Foó")]) } assertQuery(AllCustomPersistableTypes.self, "(optBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))])], count: 1) { $0.optBinary.in([DataWrapper(persistedValue: Data(count: 64)), DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optBinary IN %@)", values: [NSArray(array: [DataWrapper(persistedValue: Data(count: 128))])], count: 0) { !$0.optBinary.in([DataWrapper(persistedValue: Data(count: 128))]) } assertQuery(AllCustomPersistableTypes.self, "(optDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 1) { $0.optDate.in([DateWrapper(persistedValue: Date(timeIntervalSince1970: 1000000)), DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optDate IN %@)", values: [NSArray(array: [DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))])], count: 0) { !$0.optDate.in([DateWrapper(persistedValue: Date(timeIntervalSince1970: 2000000))]) } assertQuery(AllCustomPersistableTypes.self, "(optDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 1) { $0.optDecimal.in([Decimal128Wrapper(persistedValue: Decimal128(123.456)), Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optDecimal IN %@)", values: [NSArray(array: [Decimal128Wrapper(persistedValue: Decimal128(234.567))])], count: 0) { !$0.optDecimal.in([Decimal128Wrapper(persistedValue: Decimal128(234.567))]) } assertQuery(AllCustomPersistableTypes.self, "(optObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 1) { $0.optObjectId.in([ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695046")), ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optObjectId IN %@)", values: [NSArray(array: [ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))])], count: 0) { !$0.optObjectId.in([ObjectIdWrapper(persistedValue: ObjectId("61184062c1d8f096a3695045"))]) } assertQuery(AllCustomPersistableTypes.self, "(optUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 1) { $0.optUuid.in([UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!), UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } assertQuery(AllCustomPersistableTypes.self, "(NOT optUuid IN %@)", values: [NSArray(array: [UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)])], count: 0) { !$0.optUuid.in([UUIDWrapper(persistedValue: UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!)]) } } } private protocol LinkToTestObject: Object { associatedtype Child: Object var object: Child? { get } var list: List { get } var set: MutableSet { get } var map: Map { get } } extension LinkToCustomPersistableCollections: LinkToTestObject {} extension LinkToModernCollectionsOfEnums: LinkToTestObject {} extension LinkToAllCustomPersistableTypes: LinkToTestObject {} extension LinkToModernAllTypesObject: LinkToTestObject {} private protocol QueryValue { static func queryValues() -> [Self] } extension Bool: QueryValue { static func queryValues() -> [Bool] { return [true, true, false] } } extension BoolWrapper: QueryValue { static func queryValues() -> [BoolWrapper] { return Bool.queryValues().map(BoolWrapper.init) } } extension Int: QueryValue { static func queryValues() -> [Int] { return [1, 3, 5] } } extension IntWrapper: QueryValue { static func queryValues() -> [IntWrapper] { return Int.queryValues().map(IntWrapper.init) } } extension EnumInt: QueryValue { static func queryValues() -> [EnumInt] { return [.value1, .value2, .value3] } } extension Int8: QueryValue { static func queryValues() -> [Int8] { return [Int8(8), Int8(9), Int8(10)] } } extension Int8Wrapper: QueryValue { static func queryValues() -> [Int8Wrapper] { return Int8.queryValues().map(Int8Wrapper.init) } } extension EnumInt8: QueryValue { static func queryValues() -> [EnumInt8] { return [.value1, .value2, .value3] } } extension Int16: QueryValue { static func queryValues() -> [Int16] { return [Int16(16), Int16(17), Int16(18)] } } extension Int16Wrapper: QueryValue { static func queryValues() -> [Int16Wrapper] { return Int16.queryValues().map(Int16Wrapper.init) } } extension EnumInt16: QueryValue { static func queryValues() -> [EnumInt16] { return [.value1, .value2, .value3] } } extension Int32: QueryValue { static func queryValues() -> [Int32] { return [Int32(32), Int32(33), Int32(34)] } } extension Int32Wrapper: QueryValue { static func queryValues() -> [Int32Wrapper] { return Int32.queryValues().map(Int32Wrapper.init) } } extension EnumInt32: QueryValue { static func queryValues() -> [EnumInt32] { return [.value1, .value2, .value3] } } extension Int64: QueryValue { static func queryValues() -> [Int64] { return [Int64(64), Int64(65), Int64(66)] } } extension Int64Wrapper: QueryValue { static func queryValues() -> [Int64Wrapper] { return Int64.queryValues().map(Int64Wrapper.init) } } extension EnumInt64: QueryValue { static func queryValues() -> [EnumInt64] { return [.value1, .value2, .value3] } } extension Float: QueryValue { static func queryValues() -> [Float] { return [Float(5.55444333), Float(6.55444333), Float(7.55444333)] } } extension FloatWrapper: QueryValue { static func queryValues() -> [FloatWrapper] { return Float.queryValues().map(FloatWrapper.init) } } extension EnumFloat: QueryValue { static func queryValues() -> [EnumFloat] { return [.value1, .value2, .value3] } } extension Double: QueryValue { static func queryValues() -> [Double] { return [123.456, 234.567, 345.678] } } extension DoubleWrapper: QueryValue { static func queryValues() -> [DoubleWrapper] { return Double.queryValues().map(DoubleWrapper.init) } } extension EnumDouble: QueryValue { static func queryValues() -> [EnumDouble] { return [.value1, .value2, .value3] } } extension String: QueryValue { static func queryValues() -> [String] { return ["Foo", "Foó", "foo"] } } extension StringWrapper: QueryValue { static func queryValues() -> [StringWrapper] { return String.queryValues().map(StringWrapper.init) } } extension EnumString: QueryValue { static func queryValues() -> [EnumString] { return [.value1, .value2, .value3] } } extension Data: QueryValue { static func queryValues() -> [Data] { return [Data(count: 64), Data(count: 128), Data(count: 256)] } } extension DataWrapper: QueryValue { static func queryValues() -> [DataWrapper] { return Data.queryValues().map(DataWrapper.init) } } extension Date: QueryValue { static func queryValues() -> [Date] { return [Date(timeIntervalSince1970: 1000000), Date(timeIntervalSince1970: 2000000), Date(timeIntervalSince1970: 3000000)] } } extension DateWrapper: QueryValue { static func queryValues() -> [DateWrapper] { return Date.queryValues().map(DateWrapper.init) } } extension Decimal128: QueryValue { static func queryValues() -> [Decimal128] { return [Decimal128(123.456), Decimal128(234.567), Decimal128(345.678)] } } extension Decimal128Wrapper: QueryValue { static func queryValues() -> [Decimal128Wrapper] { return Decimal128.queryValues().map(Decimal128Wrapper.init) } } extension ObjectId: QueryValue { static func queryValues() -> [ObjectId] { return [ObjectId("61184062c1d8f096a3695046"), ObjectId("61184062c1d8f096a3695045"), ObjectId("61184062c1d8f096a3695044")] } } extension ObjectIdWrapper: QueryValue { static func queryValues() -> [ObjectIdWrapper] { return ObjectId.queryValues().map(ObjectIdWrapper.init) } } extension UUID: QueryValue { static func queryValues() -> [UUID] { return [UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!, UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d08e")!] } } extension UUIDWrapper: QueryValue { static func queryValues() -> [UUIDWrapper] { return UUID.queryValues().map(UUIDWrapper.init) } } extension ModernIntEnum: QueryValue { static func queryValues() -> [ModernIntEnum] { return [.value1, .value2, .value3] } fileprivate static func sum() -> Int { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> Int { return Self.value2.rawValue } } extension AnyRealmValue: QueryValue { static func queryValues() -> [AnyRealmValue] { return [AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046")), AnyRealmValue.string("Hello"), AnyRealmValue.int(123)] } } extension Optional: QueryValue where Wrapped: QueryValue { static func queryValues() -> [Self] { return Wrapped.queryValues().map(Self.init) } } private protocol AddableQueryValue { associatedtype SumType static func sum() -> SumType static func average() -> SumType } extension Int: AddableQueryValue { fileprivate typealias SumType = Int fileprivate static func sum() -> SumType { return 1 + 3 + 5 } fileprivate static func average() -> SumType { return sum() / 3 } } extension IntWrapper: AddableQueryValue { fileprivate typealias SumType = IntWrapper fileprivate static func sum() -> SumType { return IntWrapper(persistedValue: Int.sum()) } fileprivate static func average() -> SumType { return IntWrapper(persistedValue: Int.average()) } } extension EnumInt: AddableQueryValue { fileprivate typealias SumType = Int fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int8: AddableQueryValue { fileprivate typealias SumType = Int8 fileprivate static func sum() -> SumType { return Int8(8) + Int8(9) + Int8(10) } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int8Wrapper: AddableQueryValue { fileprivate typealias SumType = Int8Wrapper fileprivate static func sum() -> SumType { return Int8Wrapper(persistedValue: Int8.sum()) } fileprivate static func average() -> SumType { return Int8Wrapper(persistedValue: Int8.average()) } } extension EnumInt8: AddableQueryValue { fileprivate typealias SumType = Int8 fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int16: AddableQueryValue { fileprivate typealias SumType = Int16 fileprivate static func sum() -> SumType { return Int16(16) + Int16(17) + Int16(18) } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int16Wrapper: AddableQueryValue { fileprivate typealias SumType = Int16Wrapper fileprivate static func sum() -> SumType { return Int16Wrapper(persistedValue: Int16.sum()) } fileprivate static func average() -> SumType { return Int16Wrapper(persistedValue: Int16.average()) } } extension EnumInt16: AddableQueryValue { fileprivate typealias SumType = Int16 fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int32: AddableQueryValue { fileprivate typealias SumType = Int32 fileprivate static func sum() -> SumType { return Int32(32) + Int32(33) + Int32(34) } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int32Wrapper: AddableQueryValue { fileprivate typealias SumType = Int32Wrapper fileprivate static func sum() -> SumType { return Int32Wrapper(persistedValue: Int32.sum()) } fileprivate static func average() -> SumType { return Int32Wrapper(persistedValue: Int32.average()) } } extension EnumInt32: AddableQueryValue { fileprivate typealias SumType = Int32 fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int64: AddableQueryValue { fileprivate typealias SumType = Int64 fileprivate static func sum() -> SumType { return Int64(64) + Int64(65) + Int64(66) } fileprivate static func average() -> SumType { return sum() / 3 } } extension Int64Wrapper: AddableQueryValue { fileprivate typealias SumType = Int64Wrapper fileprivate static func sum() -> SumType { return Int64Wrapper(persistedValue: Int64.sum()) } fileprivate static func average() -> SumType { return Int64Wrapper(persistedValue: Int64.average()) } } extension EnumInt64: AddableQueryValue { fileprivate typealias SumType = Int64 fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Float: AddableQueryValue { fileprivate typealias SumType = Float fileprivate static func sum() -> SumType { return Float(5.55444333) + Float(6.55444333) + Float(7.55444333) } fileprivate static func average() -> SumType { return sum() / 3 } } extension FloatWrapper: AddableQueryValue { fileprivate typealias SumType = FloatWrapper fileprivate static func sum() -> SumType { return FloatWrapper(persistedValue: Float.sum()) } fileprivate static func average() -> SumType { return FloatWrapper(persistedValue: Float.average()) } } extension EnumFloat: AddableQueryValue { fileprivate typealias SumType = Float fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Double: AddableQueryValue { fileprivate typealias SumType = Double fileprivate static func sum() -> SumType { return 123.456 + 234.567 + 345.678 } fileprivate static func average() -> SumType { return sum() / 3 } } extension DoubleWrapper: AddableQueryValue { fileprivate typealias SumType = DoubleWrapper fileprivate static func sum() -> SumType { return DoubleWrapper(persistedValue: Double.sum()) } fileprivate static func average() -> SumType { return DoubleWrapper(persistedValue: Double.average()) } } extension EnumDouble: AddableQueryValue { fileprivate typealias SumType = Double fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } extension Decimal128: AddableQueryValue { fileprivate typealias SumType = Decimal128 fileprivate static func sum() -> SumType { return Decimal128(123.456) + Decimal128(234.567) + Decimal128(345.678) } fileprivate static func average() -> SumType { return sum() / 3 } } extension Decimal128Wrapper: AddableQueryValue { fileprivate typealias SumType = Decimal128Wrapper fileprivate static func sum() -> SumType { return Decimal128Wrapper(persistedValue: Decimal128.sum()) } fileprivate static func average() -> SumType { return Decimal128Wrapper(persistedValue: Decimal128.average()) } } extension Optional: AddableQueryValue where Wrapped: AddableQueryValue { fileprivate typealias SumType = Optional fileprivate static func sum() -> SumType { return .some(Wrapped.sum()) } fileprivate static func average() -> SumType { return .some(Wrapped.average()) } } ================================================ FILE: RealmSwift/Tests/QueryTests.swift.gyb ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift // This file is generated from a template. Do not edit directly. // swiftlint:disable large_tuple vertical_parameter_alignment %{ # How to use: # # $ wget https://github.com/apple/swift/raw/main/utils/gyb # $ wget https://github.com/apple/swift/raw/main/utils/gyb.py # $ chmod +x gyb # # ./YOUR_GYB_LOCATION/gyb --line-directive '' -o QueryTests.swift QueryTests.swift.gyb }% %{ import collections import itertools import sys def lowerFirst(str): return str[0].lower() + str[1:] class Property(object): def __init__(self, colName, values, type, category, enumName=''): self.isEnum = enumName != '' self.colName = colName self.rawValueName = colName if not self.isEnum else colName + '.rawValue' self.comparableName = self.rawValueName self.values = values self.type = type self.category = category self.enumName = enumName self.typeName = enumName if enumName != '' else type self.className = 'ModernAllTypesObject' self.linkingClassName = 'LinkToModernAllTypesObject' def value(self, index): return self.values[index] def enumValue(self, index): return self.enumName + self.values[index] def rawValue(self, index): return self.value(index) if not self.isEnum else self.enumValue(index) + '.rawValue' def comparableValue(self, index): return self.rawValue(index) def wrap(self, value): if '?' in self.type: return '{}.some({})'.format(self.type, value) return value class WrapperProperty(object): def __init__(self, base, collection, optional): self.baseName = base.name self.isEnum = False self.colName = ( collection + (('Opt' if collection else 'opt') if optional else '') + (base.colName() if collection or optional else lowerFirst(base.colName())) ) self.values = [self.wrap(v) for v in (base.collectionValues if collection else base.values)] self.rawValueName = self.colName self.comparableName = self.colName + '.persistableValue' self.type = base.name + 'Wrapper' + ('?' if optional else '') self.category = base.category self.enumName = '' self.typeName = self.type if collection: self.className = 'CustomPersistableCollections' self.linkingClassName = 'LinkToCustomPersistableCollections' else: self.className = 'AllCustomPersistableTypes' self.linkingClassName = 'LinkToAllCustomPersistableTypes' def value(self, index): return self.values[index] def enumValue(self, index): return self.values[index] def rawValue(self, index): return self.values[index] def comparableValue(self, index): return self.value(index) + '.persistableValue' def wrap(self, value): return '{}Wrapper(persistedValue: {})'.format(self.baseName, value) class EnumProperty(Property): def __init__(self, type, collection, category, optional=False): super(EnumProperty, self).__init__(collection + type + ('Opt' if optional else ''), ['.value1', '.value2', '.value3'], 'Enum' + type + ('?' if optional else ''), category, 'Enum' + type) self.className = 'ModernCollectionsOfEnums' self.linkingClassName = 'LinkToModernCollectionsOfEnums' class Type(object): def __init__(self, name, category, values, collectionValues=None): self.name = name self.values = values self.collectionValues = collectionValues or values self.category = category def colName(self): if self.name == 'Decimal128': return 'Decimal' if self.name == 'UUID': return 'Uuid' if self.name == 'Data': return 'Binary' return self.name def hasEnum(self): return (self.category == 'numeric' or self.category == 'string') and self.name != 'Decimal128' and self.name != 'Date' types = [ Type('Bool', 'bool', ['true', 'false'], ['true', 'true', 'false']), Type('Int', 'numeric', ['1', '3', '5']), Type('Int8', 'numeric', ['Int8(8)', 'Int8(9)', 'Int8(10)']), Type('Int16', 'numeric', ['Int16(16)', 'Int16(17)', 'Int16(18)']), Type('Int32', 'numeric', ['Int32(32)', 'Int32(33)', 'Int32(34)']), Type('Int64', 'numeric', ['Int64(64)', 'Int64(65)', 'Int64(66)']), Type('Float', 'numeric', ['Float(5.55444333)', 'Float(6.55444333)', 'Float(7.55444333)']), Type('Double', 'numeric', ['123.456', '234.567', '345.678']), Type('String', 'string', ['"Foo"', '"Foó"', '"foo"']), Type('Data', 'binary', ['Data(count: 64)', 'Data(count: 128)'], ['Data(count: 64)', 'Data(count: 128)', 'Data(count: 256)']), Type('Date', 'numeric', ['Date(timeIntervalSince1970: 1000000)', 'Date(timeIntervalSince1970: 2000000)', 'Date(timeIntervalSince1970: 3000000)']), Type('Decimal128', 'numeric', ['Decimal128(123.456)', 'Decimal128(234.567)', 'Decimal128(345.678)']), Type('ObjectId', 'objectId', ['ObjectId("61184062c1d8f096a3695046")', 'ObjectId("61184062c1d8f096a3695045")', 'ObjectId("61184062c1d8f096a3695044")']), Type('UUID', 'uuid', ['UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!', 'UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09f")!', 'UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d08e")!']) ] properties = [Property(lowerFirst(t.colName()) + 'Col', t.values, t.name, t.category) for t in types] optProperties = [Property('opt' + t.colName() + 'Col', t.values, t.name + '?', t.category) for t in types] listProperties = [Property('array' + t.colName(), t.collectionValues, t.name, t.category) for t in types] optListProperties = [Property('arrayOpt' + t.colName(), t.collectionValues, t.name + '?', t.category) for t in types] setProperties = [Property('set' + t.colName(), t.collectionValues, t.name, t.category) for t in types] optSetProperties = [Property('setOpt' + t.colName(), t.collectionValues, t.name + '?', t.category) for t in types] mapProperties = [Property('map' + t.colName(), t.collectionValues, t.name, t.category) for t in types] optMapProperties = [Property('mapOpt' + t.colName(), t.collectionValues, t.name + '?', t.category) for t in types] properties += [ Property('intEnumCol', ['.value1', '.value2', '.value3'], 'Int', 'numeric', 'ModernIntEnum'), Property('stringEnumCol', ['.value1', '.value2'], 'String', 'string', 'ModernStringEnum'), ] optProperties += [ Property('optIntEnumCol', ['.value1', '.value2', '.value3'], 'Int?', 'numeric', 'ModernIntEnum'), Property('optStringEnumCol', ['.value1', '.value2'], 'String?', 'string', 'ModernStringEnum'), ] anyCollectionValues = ['AnyRealmValue.objectId(ObjectId("61184062c1d8f096a3695046"))', 'AnyRealmValue.string("Hello")', 'AnyRealmValue.int(123)'] listProperties += [ Property('arrayAny', anyCollectionValues, 'AnyRealmValue', 'any'), ] setProperties += [ Property('setAny', anyCollectionValues, 'AnyRealmValue', 'any'), ] mapProperties += [ Property('mapAny', anyCollectionValues, 'AnyRealmValue', 'any'), ] listProperties += [ EnumProperty(t.name, 'list', t.category) for t in types if t.hasEnum() ] optListProperties += [ EnumProperty(t.name, 'list', t.category, True) for t in types if t.hasEnum() ] setProperties += [ EnumProperty(t.name, 'set', t.category) for t in types if t.hasEnum() ] optSetProperties += [ EnumProperty(t.name, 'set', t.category, True) for t in types if t.hasEnum() ] mapProperties += [ EnumProperty(t.name, 'map', t.category) for t in types if t.hasEnum() ] optMapProperties += [ EnumProperty(t.name, 'map', t.category, True) for t in types if t.hasEnum() ] properties += [WrapperProperty(t, '', False) for t in types] optProperties += [WrapperProperty(t, '', True) for t in types] listProperties += [WrapperProperty(t, 'list', False) for t in types] optListProperties += [WrapperProperty(t, 'list', True) for t in types] setProperties += [WrapperProperty(t, 'set', False) for t in types] optSetProperties += [WrapperProperty(t, 'set', True) for t in types] mapProperties += [WrapperProperty(t, 'map', False) for t in types] optMapProperties += [WrapperProperty(t, 'map', True) for t in types] anyRealmValues = [ Property('', ['.none'], '', 'null', 'AnyRealmValue'), Property('', ['.int(123)'], '', 'numeric', 'AnyRealmValue'), Property('', ['.bool(true)'], '', 'bool', 'AnyRealmValue'), Property('', ['.float(123.456)'], '', 'numeric', 'AnyRealmValue'), Property('', ['.double(123.456)'], '', 'numeric', 'AnyRealmValue'), Property('', ['.string("FooBar")'], '', 'string', 'AnyRealmValue'), Property('', ['.data(Data(count: 64))'], '', 'binary', 'AnyRealmValue'), Property('', ['.date(Date(timeIntervalSince1970: 1000000))'], '', 'numeric', 'AnyRealmValue'), Property('', ['.object(circleObject)'], '', 'object', 'AnyRealmValue'), Property('', ['.objectId(ObjectId("61184062c1d8f096a3695046"))'], '', 'objectId', 'AnyRealmValue'), Property('', ['.decimal128(123.456)'], '', 'numeric', 'AnyRealmValue'), Property('', ['.uuid(UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!)'], '', 'uuid', 'AnyRealmValue'), ] classNames = set((p.className, p.linkingClassName) for p in properties + listProperties) def numeric(properties): return (p for p in properties if p.category == 'numeric') def canSum(properties): # FIXME: EnumFloat depends on https://github.com/realm/realm-core/pull/5081 return (p for p in properties if p.category == 'numeric' and not p.type.strip('?') in ['Date', 'DateWrapper', 'EnumFloat']) def string(properties): return (p for p in properties if p.category == 'string') def binary(properties): return (p for p in properties if p.category == 'binary') def groupByClass(properties): return [list(p) for _, p in itertools.groupby(properties, lambda x: x.className)] }% class QueryTests: TestCase { private var realm: Realm! // MARK: Test data population private func objects() -> Results { realm.objects(ModernAllTypesObject.self) } private func getOrCreate(_ type: T.Type) -> T { if let object = realm.objects(T.self).first { return object } let object = T() try! realm.write { realm.add(object) } return object } private func collectionObject() -> ModernCollectionObject { return getOrCreate(ModernCollectionObject.self) } private func setAnyRealmValueCol(with value: AnyRealmValue, object: ModernAllTypesObject) { try! realm.write { object.anyCol = value } } private var circleObject: ModernCircleObject { return getOrCreate(ModernCircleObject.self) } override func setUp() { realm = inMemoryRealm("QueryTests") try! realm.write { % for className, _ in classNames: let obj${className} = ${className}() % end % for property in properties + optProperties: obj${property.className}.${property.colName} = ${property.value(1)} % end % for property in (p for p in listProperties + optListProperties): obj${property.className}.${property.colName}.append(objectsIn: [${property.value(0)}, ${property.value(1)}]) % end % for property in (p for p in setProperties + optSetProperties): obj${property.className}.${property.colName}.insert(objectsIn: [${property.value(0)}, ${property.value(1)}]) % end % for property in (p for p in mapProperties + optMapProperties): obj${property.className}.${property.colName}["foo"] = ${property.value(0)} obj${property.className}.${property.colName}["bar"] = ${property.value(1)} % end % for className in set(p.className for p in properties + listProperties): realm.add(obj${className}) % end } } override func tearDown() { realm = nil } private func createKeypathCollectionAggregatesObject() { realm.beginWrite() realm.deleteAll() % for className, linkingClassName in classNames: let parent${linkingClassName} = realm.create(${linkingClassName}.self) let children${className} = [${className}(), ${className}(), ${className}()] parent${linkingClassName}.list.append(objectsIn: children${className}) % end % for property in numeric(properties + optProperties): initForKeypathCollectionAggregates(children${property.className}, \.${property.colName}) % end try! realm.commitWrite() } private func initForKeypathCollectionAggregates( _ objects: [O], _ keyPath: ReferenceWritableKeyPath) { for (obj, value) in zip(objects, T.queryValues()) { obj[keyPath: keyPath] = value } } private func initLinkedCollectionAggregatesObject() { realm.beginWrite() realm.deleteAll() % for (parent, child) in set((p.linkingClassName, p.className) for p in listProperties): let parent${parent} = realm.create(${parent}.self) let obj${child} = ${child}() parent${parent}["object"] = obj${child} % end % for property in listProperties + optListProperties + setProperties + optSetProperties: obj${property.className}["${property.colName}"] = ${property.type}.queryValues() % end % for property in mapProperties + optMapProperties: populateMap(obj${property.className}.${property.colName}) % end try! realm.commitWrite() } private func populateMap(_ map: Map) { let values = T.queryValues() map["foo"] = values[2] map["bar"] = values[1] map["baz"] = values[0] } // MARK: - Assertion Helpers private func assertCount(_ expectedCount: Int, _ query: ((Query) -> Query)) { let results = realm.objects(T.self).where(query) XCTAssertEqual(results.count, expectedCount) } private func assertPredicate( _ predicate: String, _ values: [Any], _ query: ((Query) -> Query)) { let (queryStr, constructedValues) = query(Query._constructForTesting())._constructPredicate() XCTAssertEqual(queryStr, predicate) XCTAssertEqual(constructedValues.count, values.count) XCTAssertEqual(NSPredicate(format: queryStr, argumentArray: constructedValues), NSPredicate(format: predicate, argumentArray: values)) } private func assertQuery(_ predicate: String, _ value: Any, count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, [value], query) } private func assertQuery(_ type: T.Type, _ predicate: String, _ value: Any, count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, [value], query) } private func assertQuery(_ predicate: String, values: [Any] = [], count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, values, query) } private func assertQuery(_ type: T.Type, _ predicate: String, values: [Any] = [], count expectedCount: Int, _ query: ((Query) -> Query)) { assertCount(expectedCount, query) assertPredicate(predicate, values, query) } // MARK: - Basic Comparison func validateEquals( _ name: String, _ lhs: (Query) -> Query, _ value: T, equalCount: Int = 1, notEqualCount: Int = 0) { assertQuery(Root.self, "(\(name) == %@)", value, count: equalCount) { lhs($0) == value } assertQuery(Root.self, "(\(name) != %@)", value, count: notEqualCount) { lhs($0) != value } } func validateEqualsNil( _ name: String, _ lhs: (Query) -> Query) { assertQuery(Root.self, "(\(name) == %@)", NSNull(), count: 0) { lhs($0) == nil } assertQuery(Root.self, "(\(name) != %@)", NSNull(), count: 1) { lhs($0) != nil } } func testEquals() { % for property in properties + optProperties: validateEquals("${property.colName}", \Query<${property.className}>.${property.colName}, ${property.enumValue(1)}) % end % for property in optProperties: validateEqualsNil("${property.colName}", \Query<${property.className}>.${property.colName}) % end } func testImplicitBooleanOperation() { assertQuery(ModernAllTypesObject.self, "boolCol == true", count: 0, { $0.boolCol }) assertQuery(ModernAllTypesObject.self, "boolCol == false", count: 1, { !$0.boolCol }) initLinkedCollectionAggregatesObject() assertQuery(LinkToModernAllTypesObject.self, "object.boolCol == true", count: 0, { $0.object.boolCol }) assertQuery(LinkToModernAllTypesObject.self, "object.boolCol == false", count: 1, { !$0.object.boolCol }) let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() object.object = nestedObject try! realm.write { realm.add(object) } assertQuery(ModernEmbeddedParentObject.self, "object.bool == true", count: 0, { $0.object.bool }) assertQuery(ModernEmbeddedParentObject.self, "object.bool == false", count: 1, { !$0.object.bool }) assertQuery(ModernAllTypesObject.self, "((intCol == %@) && boolCol == true)", 0, count: 0, { $0.intCol == 0 && $0.boolCol }) assertQuery(ModernAllTypesObject.self, "(boolCol == true && (intCol == %@))", 0, count: 0, { $0.boolCol && $0.intCol == 0 }) assertQuery(ModernAllTypesObject.self, "((intCol == %@) || boolCol == false)", 0, count: 1, { $0.intCol == 0 || !$0.boolCol }) assertQuery(ModernAllTypesObject.self, "(boolCol == false || (intCol == %@))", 0, count: 1, { !$0.boolCol || $0.intCol == 0 }) } func testEqualAnyRealmValue() { let circleObject = self.circleObject let object = objects()[0] % for value in anyRealmValues: setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol == %@)", ${value.enumValue(0)}, count: 1) { $0.anyCol == ${value.value(0)} } % end } func testEqualAnyRealmList() { let circleObject = self.circleObject let object = objects()[0] let list = List() % for value in anyRealmValues: list.removeAll() list.append(${value.value(0)}) setAnyRealmValueCol(with: .list(list), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.list(list), count: 1) { let list = List() list.append(${value.value(0)}) return $0.anyCol == .list(list) } % end } func testEqualAnyRealmDictionary() { let circleObject = self.circleObject let object = objects()[0] let dictionary = Map() % for value in anyRealmValues: dictionary.removeAll() dictionary["key"] = AnyRealmValue${value.value(0)} setAnyRealmValueCol(with: .dictionary(dictionary), object: object) assertQuery("(anyCol == %@)", AnyRealmValue.dictionary(dictionary), count: 1) { let dictionary = Map() dictionary["key"] = AnyRealmValue${value.value(0)} return $0.anyCol == .dictionary(dictionary) } % end } func testNestedAnyRealmList() { let object = objects()[0] let subArray2: AnyRealmValue = AnyRealmValue.fromArray([ .string("john"), .bool(false) ]) let subArray3: AnyRealmValue = AnyRealmValue.fromArray([ subArray2, .double(76.54) ]) let subArray4: AnyRealmValue = AnyRealmValue.fromArray([ subArray3, .int(99)]) let array: Array = [ subArray4, .float(20.34) ] setAnyRealmValueCol(with: AnyRealmValue.fromArray(array), object: object) assertQuery("(anyCol[%@] == %@)", values: [1, AnyRealmValue.float(20.34)], count: 1) { $0.anyCol[1] == .float(20.34) } assertQuery("(anyCol[%K] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol.any == .float(20.34) } assertQuery("(anyCol[%@][%@] == %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] == .int(99) } assertQuery("(anyCol[%@][%@][%@] == %@)", values: [0, 0, 1, AnyRealmValue.double(76.54)], count: 1) { $0.anyCol[0][0][1] == .double(76.54) } assertQuery("(anyCol[%@][%@][%K] == %@)", values: [0, 0, "#any", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol[0][0].any == .double(76.54) } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: [0, 0, 0, 0, AnyRealmValue.string("john")], count: 1) { $0.anyCol[0][0][0][0] == .string("john") } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: [0, 0, 0, 1, AnyRealmValue.bool(false)], count: 1) { $0.anyCol[0][0][0][1] == .bool(false) } assertQuery("(anyCol[%@][%@][%@][%K] == %@)", values: [0, 0, 0, "#any", AnyRealmValue.string("john")], count: 1) { $0.anyCol[0][0][0].any == .string("john") } assertQuery("(anyCol[%@][%@][%@][%K] == %@)", values: [0, 0, 0, "#any", AnyRealmValue.bool(false)], count: 1) { $0.anyCol[0][0][0].any == .bool(false) } assertQuery("(anyCol[%@][%@] >= %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] >= .int(99) } assertQuery("(anyCol[%@][%@] <= %@)", values: [0, 1, AnyRealmValue.int(99)], count: 1) { $0.anyCol[0][1] <= .int(99) } assertQuery("(anyCol[%@][%@] != %@)", values: [0, 1, AnyRealmValue.int(99)], count: 0) { $0.anyCol[0][1] != .int(99) } assertQuery("(anyCol[%@] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 0) { $0.anyCol["#any"] == .float(20.34) } } func testNestedAnyRealmDictionary() { let object = objects()[0] let subDict2: AnyRealmValue = AnyRealmValue.fromDictionary(["key6": .string("john"), "key7": .bool(false)]) let subDict3: AnyRealmValue = AnyRealmValue.fromDictionary(["key4": subDict2, "key5": .double(76.54)]) let subDict4: AnyRealmValue = AnyRealmValue.fromDictionary(["key2": subDict3, "key3": .int(99)]) let dict: Dictionary = [ "key0": subDict4, "key1": .float(20.34) ] setAnyRealmValueCol(with: AnyRealmValue.fromDictionary(dict), object: object) assertQuery("(anyCol[%@] == %@)", values: ["key1", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol["key1"] == .float(20.34) } assertQuery("(anyCol[%K] == %@)", values: ["#any", AnyRealmValue.float(20.34)], count: 1) { $0.anyCol.any == .float(20.34) } assertQuery("(anyCol[%@][%@] == %@)", values: ["key0", "key3", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"]["key3"] == .int(99) } assertQuery("(anyCol[%@][%K] == %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any == .int(99) } assertQuery("(anyCol[%@][%@][%@] == %@)", values: ["key0", "key2", "key5", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol["key0"]["key2"]["key5"] == .double(76.54) } assertQuery("(anyCol[%@][%@][%K] == %@)", values: ["key0", "key2", "#any", AnyRealmValue.double(76.54)], count: 1) { $0.anyCol["key0"]["key2"].any == .double(76.54) } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: ["key0", "key2", "key4", "key6", AnyRealmValue.string("john")], count: 1) { $0.anyCol["key0"]["key2"]["key4"]["key6"] == .string("john") } assertQuery("(anyCol[%@][%@][%@][%@] == %@)", values: ["key0", "key2", "key4", "key7", AnyRealmValue.bool(false)], count: 1) { $0.anyCol["key0"]["key2"]["key4"]["key7"] == .bool(false) } assertQuery("(anyCol[%@][%K] >= %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any >= .int(99) } assertQuery("(anyCol[%@][%K] <= %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 1) { $0.anyCol["key0"].any <= .int(99) } assertQuery("(anyCol[%@][%@] != %@)", values: ["key0", "key3", AnyRealmValue.int(99)], count: 0) { $0.anyCol["key0"]["key3"] != .int(99) } assertQuery("(anyCol[%@][%@] == %@)", values: ["key0", "#any", AnyRealmValue.int(99)], count: 0) { $0.anyCol["key0"]["#any"] == .int(99) } } func testEqualObject() { let nestedObject = ModernAllTypesObject() let object = objects().first! try! realm.write { object.objectCol = nestedObject } assertQuery("(objectCol == %@)", nestedObject, count: 1) { $0.objectCol == nestedObject } } func testEqualEmbeddedObject() { let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() nestedObject.value = 123 object.object = nestedObject try! realm.write { realm.add(object) } let result1 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object == nestedObject } XCTAssertEqual(result1.count, 1) let nestedObject2 = ModernEmbeddedTreeObject1() nestedObject2.value = 123 let result2 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object == nestedObject2 } XCTAssertEqual(result2.count, 0) } private func createLinksToMappedEmbeddedObject() { try! realm.write { let obj = realm.objects(AllCustomPersistableTypes.self).first! obj.object = EmbeddedObjectWrapper(value: 2) _ = realm.create(LinkToAllCustomPersistableTypes.self, value: [obj, [obj], [obj], ["1": obj]]) } } func testEqualMappedToEmbeddedObject() { createLinksToMappedEmbeddedObject() assertQuery(AllCustomPersistableTypes.self, "(object.value == %@)", 2, count: 1) { $0.object.persistableValue.value == 2 } assertQuery(AllCustomPersistableTypes.self, "(object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.object == EmbeddedObjectWrapper(value: 2) } assertQuery(AllCustomPersistableTypes.self, "(object.value == %@)", 3, count: 0) { $0.object.persistableValue.value == 3 } assertQuery(AllCustomPersistableTypes.self, "(object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.object == EmbeddedObjectWrapper(value: 3) } % for modifier, path, keyPath in [('', 'object', 'object'), ('ANY ', 'list', 'list'), ('ANY ', 'set', 'set'), ('ANY ', 'map.values', 'map.@allValues')]: assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object.value == %@)", 2, count: 1) { $0.${path}.object.persistableValue.value == 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object == %@)", EmbeddedObjectWrapper(value: 2), count: 1) { $0.${path}.object == EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object.value == %@)", 3, count: 0) { $0.${path}.object.persistableValue.value == 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object == %@)", EmbeddedObjectWrapper(value: 3), count: 0) { $0.${path}.object == EmbeddedObjectWrapper(value: 3) } % end } func testMemberwiseEquality() { realm.beginWrite() let obj1 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["a", "b"])) let obj2 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["a", "c"])) let obj3 = AddressSwiftWrapper(persistedValue: AddressSwift(value: ["b", "b"])) let linkObj1 = realm.create(LinkToAddressSwiftWrapper.self, value: [obj1, obj1]) let linkObj2 = realm.create(LinkToAddressSwiftWrapper.self, value: [obj2, obj2]) _ = realm.create(LinkToAddressSwiftWrapper.self, value: [obj3, obj3]) // Test basic equality assertQuery(LinkToAddressSwiftWrapper.self, "(object == %@)", obj1, count: 1) { $0.object == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(object != %@)", obj1, count: 2) { $0.object != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(optObject == %@)", obj1, count: 1) { $0.optObject == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(optObject != %@)", obj1, count: 2) { $0.optObject != obj1 } // Verify that the expanded comparison nested groups correctly. If it doesn't // start/end a subgroup, it'd end up as ((x or y) and z) instead of (x or (y and z)). assertQuery(LinkToAddressSwiftWrapper.self, "((object.city != %@) || (object == %@))", values: ["c", obj1], count: 3) { $0.object.persistableValue.city != "c" || $0.object == obj1 } // Check for ((x and y) or Z) rather than (x and (y or z)) assertQuery(LinkToAddressSwiftWrapper.self, "((object == %@) || (object.city != %@))", values: [obj1, "c"], count: 3) { $0.object == obj1 || $0.object.persistableValue.city != "c" } // Basic equality in collections linkObj1.list.append(obj1) linkObj1.map["foo"] = obj1 linkObj1.optMap["foo"] = obj1 assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list == %@)", obj1, count: 1) { $0.list == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list != %@)", obj1, count: 0) { $0.list != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY map.@allValues == %@)", obj1, count: 1) { $0.map.values == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY map.@allValues != %@)", obj1, count: 0) { $0.map.values != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY optMap.@allValues != %@)", obj1, count: 0) { $0.optMap.values != obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY optMap.@allValues == %@)", obj1, count: 1) { $0.optMap.values == obj1 } // Verify that collections use a subquery. If they didn't, this object would // now match as it has objects which match each property separately linkObj2.list.append(obj2) linkObj2.list.append(obj3) assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list == %@)", obj1, count: 1) { $0.list == obj1 } assertQuery(LinkToAddressSwiftWrapper.self, "(ANY list != %@)", obj1, count: 1) { $0.list != obj1 } realm.cancelWrite() } func testInvalidMemberwiseEquality() { assertThrows(assertQuery(LinkToWrapperForTypeWithObjectLink.self, "", count: 0) { $0.link == WrapperForTypeWithObjectLink() }, reason: "Unsupported property 'TypeWithObjectLink.value' for memberwise equality query: object links are not implemented.") assertThrows(assertQuery(LinkToWrapperForTypeWithCollection.self, "", count: 0) { $0.link == WrapperForTypeWithCollection() }, reason: "Unsupported property 'TypeWithCollection.list' for memberwise equality query: equality on collections is not implemented.") } func testNotEqualAnyRealmValue() { let circleObject = self.circleObject let object = objects()[0] % for value in anyRealmValues: setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol != %@)", ${value.enumValue(0)}, count: 0) { $0.anyCol != ${value.value(0)} } % end } func testNotEqualObject() { let nestedObject = ModernAllTypesObject() let object = objects().first! try! realm.write { object.objectCol = nestedObject } // Count will be one because nestedObject.objectCol will be nil assertQuery("(objectCol != %@)", nestedObject, count: 1) { $0.objectCol != nestedObject } } func testNotEqualEmbeddedObject() { let object = ModernEmbeddedParentObject() let nestedObject = ModernEmbeddedTreeObject1() nestedObject.value = 123 object.object = nestedObject try! realm.write { realm.add(object) } let result1 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object != nestedObject } XCTAssertEqual(result1.count, 0) let nestedObject2 = ModernEmbeddedTreeObject1() nestedObject2.value = 123 let result2 = realm.objects(ModernEmbeddedParentObject.self).where { $0.object != nestedObject2 } XCTAssertEqual(result2.count, 1) } func testNotEqualMappedToEmbeddedObject() { createLinksToMappedEmbeddedObject() assertQuery(AllCustomPersistableTypes.self, "(object.value != %@)", 2, count: 0) { $0.object.persistableValue.value != 2 } assertQuery(AllCustomPersistableTypes.self, "(object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.object != EmbeddedObjectWrapper(value: 2) } assertQuery(AllCustomPersistableTypes.self, "(object.value != %@)", 3, count: 1) { $0.object.persistableValue.value != 3 } assertQuery(AllCustomPersistableTypes.self, "(object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.object != EmbeddedObjectWrapper(value: 3) } % for modifier, path, keyPath in [('', 'object', 'object'), ('ANY ', 'list', 'list'), ('ANY ', 'set', 'set'), ('ANY ', 'map.values', 'map.@allValues')]: assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object.value != %@)", 2, count: 0) { $0.${path}.object.persistableValue.value != 2 } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object != %@)", EmbeddedObjectWrapper(value: 2), count: 0) { $0.${path}.object != EmbeddedObjectWrapper(value: 2) } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object.value != %@)", 3, count: 1) { $0.${path}.object.persistableValue.value != 3 } assertQuery(LinkToAllCustomPersistableTypes.self, "(${modifier}${keyPath}.object != %@)", EmbeddedObjectWrapper(value: 3), count: 1) { $0.${path}.object != EmbeddedObjectWrapper(value: 3) } % end } func validateNumericComparisons( _ name: String, _ lhs: (Query) -> Query, _ value: T, count: Int = 1, ltCount: Int = 0, gtCount: Int = 0) where T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(\(name) > %@)", value, count: gtCount) { lhs($0) > value } assertQuery(Root.self, "(\(name) >= %@)", value, count: count) { lhs($0) >= value } assertQuery(Root.self, "(\(name) < %@)", value, count: ltCount) { lhs($0) < value } assertQuery(Root.self, "(\(name) <= %@)", value, count: count) { lhs($0) <= value } } func testNumericComparisons() { % for property in numeric(properties + optProperties): validateNumericComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, ${property.value(1)}) % end % for property in numeric(optProperties): validateNumericComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, nil, count: 0) % end } func validateStringComparisons( _ name: String, _ lhs: (Query) -> Query, _ value: T, letCount: Int = 1, getCount: Int = 1, ltCount: Int = 0, gtCount: Int = 0) where T.PersistedType: _QueryString { assertQuery(Root.self, "(\(name) > %@)", value, count: gtCount) { lhs($0) > value } assertQuery(Root.self, "(\(name) >= %@)", value, count: getCount) { lhs($0) >= value } assertQuery(Root.self, "(\(name) < %@)", value, count: ltCount) { lhs($0) < value } assertQuery(Root.self, "(\(name) <= %@)", value, count: letCount) { lhs($0) <= value } } func testStringComparisons() { % for property in string(properties + optProperties): validateStringComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, ${property.value(1)}) % end % for property in string(optProperties): validateStringComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, nil, letCount: 0, gtCount: 1) % end } func testGreaterThanNumericAnyRealmValue() { let object = objects()[0] % for value in numeric(anyRealmValues): setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol > %@)", ${value.enumValue(0)}, count: 0) { $0.anyCol > ${value.value(0)} } assertQuery("(anyCol >= %@)", ${value.enumValue(0)}, count: 1) { $0.anyCol >= ${value.value(0)} } % end } func testGreaterThanStringAnyRealmValue() { let object = objects()[0] % for value in string(anyRealmValues): setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol > %@)", ${value.enumValue(0)}, count: 0) { $0.anyCol > ${value.value(0)} } assertQuery("(anyCol >= %@)", ${value.enumValue(0)}, count: 1) { $0.anyCol >= ${value.value(0)} } % end } func testLessThanNumericAnyRealmValue() { let object = objects()[0] % for value in numeric(anyRealmValues): setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol < %@)", ${value.enumValue(0)}, count: 0) { $0.anyCol < ${value.value(0)} } assertQuery("(anyCol <= %@)", ${value.enumValue(0)}, count: 1) { $0.anyCol <= ${value.value(0)} } % end } func testLessThanStringAnyRealmValue() { let object = objects()[0] % for value in string(anyRealmValues): setAnyRealmValueCol(with: ${value.value(0)}, object: object) assertQuery("(anyCol < %@)", ${value.enumValue(0)}, count: 0) { $0.anyCol < ${value.value(0)} } assertQuery("(anyCol <= %@)", ${value.enumValue(0)}, count: 1) { $0.anyCol <= ${value.value(0)} } % end } private func validateNumericContains( _ name: String, _ lhs: (Query) -> Query) { let values = T.queryValues() assertQuery(Root.self, "((\(name) >= %@) && (\(name) < %@))", values: [values[0], values[2]], count: 1) { lhs($0).contains(values[0]..= %@) && (\(name) < %@))", values: [values[0], values[1]], count: 0) { lhs($0).contains(values[0]..( _ name: String, _ lhs: (Query) -> Query) where T.Wrapped: Comparable & QueryValue { let values = T.Wrapped.queryValues() assertQuery(Root.self, "((\(name) >= %@) && (\(name) < %@))", values: [values[0], values[2]], count: 1) { lhs($0).contains(values[0]..= %@) && (\(name) < %@))", values: [values[0], values[1]], count: 0) { lhs($0).contains(values[0]...${property.comparableName}) % end } // MARK: - Strings let stringModifiers: [(String, StringOptions)] = [ ("", []), ("[c]", [.caseInsensitive]), ("[d]", [.diacriticInsensitive]), ("[cd]", [.caseInsensitive, .diacriticInsensitive]), ] private func validateStringOperations( _ name: String, _ lhs: (Query) -> Query, _ values: (T, T, T), count: (Bool, StringOptions) -> Int) where T.PersistedType: _QueryString { let (full, prefix, suffix) = values for (modifier, options) in stringModifiers { let matchingCount = count(true, options) let notMatchingCount = count(false, options) assertQuery(Root.self, "(\(name) ==\(modifier) %@)", full, count: matchingCount) { lhs($0).equals(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) ==\(modifier) %@)", full, count: 1 - matchingCount) { !lhs($0).equals(full, options: [options]) } assertQuery(Root.self, "(\(name) !=\(modifier) %@)", full, count: notMatchingCount) { lhs($0).notEquals(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) !=\(modifier) %@)", full, count: 1 - notMatchingCount) { !lhs($0).notEquals(full, options: [options]) } assertQuery(Root.self, "(\(name) CONTAINS\(modifier) %@)", full, count: matchingCount) { lhs($0).contains(full, options: [options]) } assertQuery(Root.self, "(NOT \(name) CONTAINS\(modifier) %@)", full, count: 1 - matchingCount) { !lhs($0).contains(full, options: [options]) } assertQuery(Root.self, "(\(name) BEGINSWITH\(modifier) %@)", prefix, count: matchingCount) { lhs($0).starts(with: prefix, options: [options]) } assertQuery(Root.self, "(NOT \(name) BEGINSWITH\(modifier) %@)", prefix, count: 1 - matchingCount) { !lhs($0).starts(with: prefix, options: [options]) } assertQuery(Root.self, "(\(name) ENDSWITH\(modifier) %@)", suffix, count: matchingCount) { lhs($0).ends(with: suffix, options: [options]) } assertQuery(Root.self, "(NOT \(name) ENDSWITH\(modifier) %@)", suffix, count: 1 - matchingCount) { !lhs($0).ends(with: suffix, options: [options]) } } } % def ifEnum(p, c1, c2): return c1 if p.isEnum else c2 func testStringOperations() { % for p in string(properties + optProperties): validateStringOperations("${p.colName}", \Query<${p.className}>.${p.rawValueName}, (${p.wrap('"Foó"')}, ${p.wrap('"Fo"')}, ${p.wrap('"oó"')}), count: { (equals, _) in equals ? ${ifEnum(p, 0, 1)} : ${ifEnum(p, 1, 0)} }) % end } private func validateStringLike( _ name: String, _ lhs: (Query) -> Query, _ strings: [(T, Int, Int)], canMatch: Bool) where T.PersistedType: _QueryString { for (str, sensitiveCount, insensitiveCount) in strings { assertQuery(Root.self, "(\(name) LIKE %@)", str, count: canMatch ? sensitiveCount : 0) { lhs($0).like(str) } assertQuery(Root.self, "(\(name) LIKE[c] %@)", str, count: canMatch ? insensitiveCount : 0) { lhs($0).like(str, caseInsensitive: true) } } } func testStringLike() { let likeStrings: [(String, Int, Int)] = [ ("Foó", 1, 1), ("f*", 0, 1), ("*ó", 1, 1), ("f?ó", 0, 1), ("f*ó", 0, 1), ("f??ó", 0, 0), ("*o*", 1, 1), ("*O*", 0, 1), ("?o?", 1, 1), ("?O?", 0, 1) ] % for property in string(properties + optProperties): validateStringLike("${property.colName}", \Query<${property.className}>.${property.rawValueName}, likeStrings.map { (${property.wrap('$0.0')}, $0.1, $0.2) }, canMatch: ${ifEnum(property, 'false', 'true')}) % end } private func validateStringLexicographicalComparison( _ name: String, _ lhs: (Query) -> Query, _ strings: [(T, Int, Int, Int, Int)]) where T.PersistedType: _QueryString { for (str, gtCount, getCount, ltCount, letCount) in strings { assertQuery(Root.self, "(\(name) > %@)", str, count: gtCount) { lhs($0) > str } assertQuery(Root.self, "(\(name) >= %@)", str, count: getCount) { lhs($0) >= str } assertQuery(Root.self, "(\(name) < %@)", str, count: ltCount) { lhs($0) < str } assertQuery(Root.self, "(\(name) <= %@)", str, count: letCount) { lhs($0) <= str } } } func testLexicographicalComparison() throws { let obj = objects().first! try realm.write { obj.stringEnumCol = .value4 obj.optStringEnumCol = .value4 } let likeStrings: [(String, Int, Int, Int, Int)] = [ ("Foó", 0, 1, 0, 1), ("Foo", 1, 1, 0, 0), ("f*", 0, 0, 1, 1), ("*ó", 1, 1, 0, 0), ("f?ó", 0, 0, 1, 1), ("f*ó", 0, 0, 1, 1), ("f??ó", 0, 0, 1, 1), ("*o*", 1, 1, 0, 0), ("*O*", 1, 1, 0, 0), ("?o?", 1, 1, 0, 0), ("?O?", 1, 1, 0, 0), ("Foô", 0, 0, 1, 1), ("Fpó", 0, 0, 1, 1), ("Goó", 0, 0, 1, 1), ("Foò", 1, 1, 0, 0), ("Fnó", 1, 1, 0, 0), ("Eoó", 1, 1, 0, 0), ] % for property in string(properties + optProperties): validateStringLexicographicalComparison("${property.colName}", \Query<${property.className}>.${property.rawValueName}, likeStrings.map { (${property.wrap('$0.0')}, $0.1, $0.2, $0.3, $0.4) }) % end } // MARK: - Data func validateData(_ name: String, _ lhs: (Query) -> Query, zeroData: T, oneData: T) where T.PersistedType: _QueryBinary { assertQuery(Root.self, "(\(name) BEGINSWITH %@)", zeroData, count: 1) { lhs($0).starts(with: zeroData) } assertQuery(Root.self, "(NOT \(name) BEGINSWITH %@)", zeroData, count: 0) { !lhs($0).starts(with: zeroData) } assertQuery(Root.self, "(\(name) ENDSWITH %@)", zeroData, count: 1) { lhs($0).ends(with: zeroData) } assertQuery(Root.self, "(NOT \(name) ENDSWITH %@)", zeroData, count: 0) { !lhs($0).ends(with: zeroData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", zeroData, count: 1) { lhs($0).contains(zeroData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", zeroData, count: 0) { !lhs($0).contains(zeroData) } assertQuery(Root.self, "(\(name) == %@)", zeroData, count: 0) { lhs($0).equals(zeroData) } assertQuery(Root.self, "(NOT \(name) == %@)", zeroData, count: 1) { !lhs($0).equals(zeroData) } assertQuery(Root.self, "(\(name) != %@)", zeroData, count: 1) { lhs($0).notEquals(zeroData) } assertQuery(Root.self, "(NOT \(name) != %@)", zeroData, count: 0) { !lhs($0).notEquals(zeroData) } assertQuery(Root.self, "(\(name) BEGINSWITH %@)", oneData, count: 0) { lhs($0).starts(with: oneData) } assertQuery(Root.self, "(\(name) ENDSWITH %@)", oneData, count: 0) { lhs($0).ends(with: oneData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", oneData, count: 0) { lhs($0).contains(oneData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", oneData, count: 1) { !lhs($0).contains(oneData) } assertQuery(Root.self, "(\(name) CONTAINS %@)", oneData, count: 0) { lhs($0).contains(oneData) } assertQuery(Root.self, "(NOT \(name) CONTAINS %@)", oneData, count: 1) { !lhs($0).contains(oneData) } assertQuery(Root.self, "(\(name) == %@)", oneData, count: 0) { lhs($0).equals(oneData) } assertQuery(Root.self, "(NOT \(name) == %@)", oneData, count: 1) { !lhs($0).equals(oneData) } assertQuery(Root.self, "(\(name) != %@)", oneData, count: 1) { lhs($0).notEquals(oneData) } assertQuery(Root.self, "(NOT \(name) != %@)", oneData, count: 0) { !lhs($0).notEquals(oneData) } } func testBinarySearchQueries() { % for property in binary(properties + optProperties): validateData("${property.colName}", \Query<${property.className}>.${property.colName}, zeroData: ${property.wrap('Data(count: 28)')}, oneData: ${property.wrap('Data(repeating: 1, count: 28)')}) % end } // MARK: - Array/Set % for (protocol, element) in [('RealmCollection', 'Element'), ('RealmKeyedCollection', 'Value')]: private func validateCollectionContains(_ name: String, _ lhs: (Query) -> Query) where T.${element}: QueryValue { let values = T.${element}.queryValues() assertQuery(Root.self, "(%@ IN \(name))", values[0], count: 1) { lhs($0).contains(values[0]) } assertQuery(Root.self, "(%@ IN \(name))", values[2], count: 0) { lhs($0).contains(values[2]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[0], count: 0) { !lhs($0).contains(values[0]) } assertQuery(Root.self, "(NOT %@ IN \(name))", values[2], count: 1) { !lhs($0).contains(values[2]) } } private func validateCollectionContainsNil(_ name: String, _ lhs: (Query) -> Query) where T.${element}: QueryValue & ExpressibleByNilLiteral { assertQuery(Root.self, "(%@ IN \(name))", NSNull(), count: 0) { lhs($0).contains(nil) } assertQuery(Root.self, "(NOT %@ IN \(name))", NSNull(), count: 1) { !lhs($0).contains(nil) } } % end func testCollectionContainsElement() { % for property in listProperties + optListProperties + setProperties + optSetProperties + mapProperties + optMapProperties: validateCollectionContains("${property.colName}", \Query<${property.className}>.${property.colName}) % end % for property in optListProperties + optSetProperties + optMapProperties: validateCollectionContainsNil("${property.colName}", \Query<${property.className}>.${property.colName}) % end } func testListContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.list.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.list.append(obj) } XCTAssertEqual(result.count, 1) } func testCollectionContainsRange() { % for property in numeric(listProperties + optListProperties + setProperties + optSetProperties + mapProperties + optMapProperties): assertQuery(${property.className}.self, "((${property.colName}.@min >= %@) && (${property.colName}.@max <= %@))", values: [${property.comparableValue(0)}, ${property.comparableValue(1)}], count: 1) { $0.${property.comparableName}.contains(${property.comparableValue(0)}...${property.comparableValue(1)}) } assertQuery(${property.className}.self, "((${property.colName}.@min >= %@) && (${property.colName}.@max < %@))", values: [${property.comparableValue(0)}, ${property.comparableValue(1)}], count: 0) { $0.${property.comparableName}.contains(${property.comparableValue(0)}..<${property.comparableValue(1)}) } % end } func testListContainsAnyInObject() { % for property in listProperties + optListProperties: assertQuery(${property.className}.self, "(ANY ${property.colName} IN %@)", values: [NSArray(array: [${property.enumValue(0)}, ${property.enumValue(1)}])], count: 1) { $0.${property.colName}.containsAny(in: [${property.value(0)}, ${property.value(1)}]) } % end let colObj = ModernCollectionObject() let obj = objects().first! colObj.list.append(obj) try! realm.write { realm.add(colObj) } assertQuery(ModernCollectionObject.self, "(ANY list IN %@)", values: [NSArray(array: [obj])], count: 1) { $0.list.containsAny(in: [obj]) } } func testCollectionFromProperty() { try! realm.write { % for className, linkingClassName in classNames: let obj${className} = realm.objects(${className}.self).first! _ = realm.create(${linkingClassName}.self, value: [ "list": [obj${className}], "set": [obj${className}], "map": ["foo": obj${className}] ]) % end } func test( _ type: Root.Type, _ predicate: String, _ value: Any, _ q1: ((Query) -> Query), _ q2: ((Query) -> Query)) { assertPredicate(predicate, [value], q1) assertPredicate(predicate, [value], q2) let obj = realm.objects(Root.self).first! XCTAssertEqual(obj.list.where(q1).count, 1) XCTAssertEqual(obj.set.where(q1).count, 1) XCTAssertEqual(obj.map.where(q2).count, 1) } // swiftlint:disable opening_brace % for property in properties + optProperties: test(${property.linkingClassName}.self, "(${property.colName} == %@)", ${property.enumValue(1)}, { $0.${property.colName} == ${property.value(1)} }, { $0.${property.colName} == ${property.value(1)} }) % end // swiftlint:enable opening_brace } func testSetContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.set.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.set.insert(obj) } XCTAssertEqual(result.count, 1) } func testSetContainsAnyInObject() { % for property in setProperties + optSetProperties: assertQuery(${property.className}.self, "(ANY ${property.colName} IN %@)", values: [NSArray(array: [${property.enumValue(0)}, ${property.enumValue(1)}])], count: 1) { $0.${property.colName}.containsAny(in: [${property.value(0)}, ${property.value(1)}]) } % end let colObj = ModernCollectionObject() let obj = objects().first! colObj.set.insert(obj) try! realm.write { realm.add(colObj) } assertQuery(ModernCollectionObject.self, "(ANY set IN %@)", values: [NSArray(array: [obj])], count: 1) { $0.set.containsAny(in: [obj]) } } // MARK: - Map private func validateAllKeys(_ name: String, _ lhs: (Query) -> Query) where T.Key == String { assertQuery(Root.self, "(ANY \(name).@allKeys == %@)", "foo", count: 1) { lhs($0).keys == "foo" } assertQuery(Root.self, "(ANY \(name).@allKeys != %@)", "foo", count: 1) { lhs($0).keys != "foo" } assertQuery(Root.self, "(ANY \(name).@allKeys CONTAINS[cd] %@)", "foo", count: 1) { lhs($0).keys.contains("foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys CONTAINS %@)", "foo", count: 1) { lhs($0).keys.contains("foo") } assertQuery(Root.self, "(ANY \(name).@allKeys BEGINSWITH[cd] %@)", "foo", count: 1) { lhs($0).keys.starts(with: "foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys BEGINSWITH %@)", "foo", count: 1) { lhs($0).keys.starts(with: "foo") } assertQuery(Root.self, "(ANY \(name).@allKeys ENDSWITH[cd] %@)", "foo", count: 1) { lhs($0).keys.ends(with: "foo", options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(ANY \(name).@allKeys ENDSWITH %@)", "foo", count: 1) { lhs($0).keys.ends(with: "foo") } assertQuery(Root.self, "(ANY \(name).@allKeys LIKE[c] %@)", "foo", count: 1) { lhs($0).keys.like("foo", caseInsensitive: true) } assertQuery(Root.self, "(ANY \(name).@allKeys LIKE %@)", "foo", count: 1) { lhs($0).keys.like("foo") } } func testMapAllKeys() { % for property in mapProperties + optMapProperties: validateAllKeys("${property.colName}", \Query<${property.className}>.${property.colName}) % end } // swiftlint:disable unused_closure_parameter func testMapAllValues() { % for property in mapProperties + optMapProperties: % notEqualCount = ', notEqualCount: 1' if property.category != 'bool' else '' validateEquals("ANY ${property.colName}.@allValues", \Query<${property.className}>.${property.colName}.values, ${property.enumValue(0)}${notEqualCount}) % if property.category == 'numeric': validateNumericComparisons("ANY ${property.colName}.@allValues", \Query<${property.className}>.${property.colName}.values, ${property.value(1)}, ltCount: 1) % end % if property.category == 'string': validateStringOperations("ANY ${property.colName}.@allValues", \Query<${property.className}>.${property.colName}.values, (${property.value(0)}, ${property.value(0)}, ${property.value(0)})) { equals, options in % if not property.isEnum: // Non-enum maps have the keys Foo and Foó, so !=[d] doesn't match any if options.contains(.diacriticInsensitive) { return equals ? 1 : 0 } % end return 1 } assertQuery(${property.className}.self, "(ANY ${property.colName}.@allValues LIKE[c] %@)", ${property.enumValue(0)}, count: 1) { $0.${property.colName}.values.like(${property.value(0)}, caseInsensitive: true) } assertQuery(${property.className}.self, "(ANY ${property.colName}.@allValues LIKE %@)", ${property.enumValue(0)}, count: 1) { $0.${property.colName}.values.like(${property.value(0)}) } % end % end } // swiftlint:enable unused_closure_parameter func testMapContainsObject() { let obj = objects().first! let colObj = collectionObject() let result = realm.objects(ModernCollectionObject.self).where { $0.map.contains(obj) } XCTAssertEqual(result.count, 0) try! realm.write { colObj.map["foo"] = obj } XCTAssertEqual(result.count, 1) } private func validateMapSubscriptEquality(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Key == String { assertQuery(Root.self, "(\(name)[%@] == %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] == value } assertQuery(Root.self, "(\(name)[%@] != %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] != value } } private func validateMapSubscriptNumericComparisons(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Value.PersistedType: _QueryNumeric, T.Key == String { assertQuery(Root.self, "(\(name)[%@] > %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] > value } assertQuery(Root.self, "(\(name)[%@] >= %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] >= value } assertQuery(Root.self, "(\(name)[%@] < %@)", values: ["foo", value], count: 0) { lhs($0)["foo"] < value } assertQuery(Root.self, "(\(name)[%@] <= %@)", values: ["foo", value], count: 1) { lhs($0)["foo"] <= value } } private func validateMapSubscriptStringComparisons(_ name: String, _ lhs: (Query) -> Query, value: T.Value) where T.Value.PersistedType: _QueryString, T.Key == String { assertQuery(Root.self, "(\(name)[%@] CONTAINS[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].contains(value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] CONTAINS %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].contains(value) } assertQuery(Root.self, "(NOT \(name)[%@] CONTAINS %@)", values: ["foo", value], count: 0) { !lhs($0)["foo"].contains(value) } assertQuery(Root.self, "(\(name)[%@] BEGINSWITH[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].starts(with: value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] BEGINSWITH %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].starts(with: value) } assertQuery(Root.self, "(\(name)[%@] ENDSWITH[cd] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].ends(with: value, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery(Root.self, "(\(name)[%@] ENDSWITH %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].ends(with: value) } assertQuery(Root.self, "(\(name)[%@] LIKE[c] %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].like(value, caseInsensitive: true) } assertQuery(Root.self, "(\(name)[%@] LIKE %@)", values: ["foo", value], count: 1) { lhs($0)["foo"].like(value) } } func testMapAllKeysAllValuesSubscript() { % for property in mapProperties + optMapProperties: validateMapSubscriptEquality("${property.colName}", \Query<${property.className}>.${property.colName}, value: ${property.value(0)}) % if property.category == 'numeric': validateMapSubscriptNumericComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, value: ${property.value(0)}) % end % if property.category == 'string': validateMapSubscriptStringComparisons("${property.colName}", \Query<${property.className}>.${property.colName}, value: ${property.value(0)}) % end % end } func testMapSubscriptObject() { assertThrows(assertQuery(ModernCollectionObject.self, "", count: 0) { $0.map["foo"].objectCol.intCol == 5 }, reason: "Cannot apply key path to Map subscripts.") } func testMapContainsAnyInObject() { % for property in mapProperties + optMapProperties: assertQuery(${property.className}.self, "(ANY ${property.colName} IN %@)", values: [NSArray(array: [${property.enumValue(0)}, ${property.enumValue(1)}])], count: 1) { $0.${property.colName}.containsAny(in: [${property.value(0)}, ${property.value(1)}]) } % end let colObj = ModernCollectionObject() let obj = objects().first! colObj.map["foo"] = obj try! realm.write { realm.add(colObj) } assertQuery(ModernCollectionObject.self, "(ANY map IN %@)", values: [NSArray(array: [obj])], count: 1) { $0.map.containsAny(in: [obj]) } } // MARK: - Linking Objects func testLinkingObjects() { let objects = Array(self.objects()) assertQuery("(%@ IN linkingObjects)", objects.first!, count: 0) { $0.linkingObjects.contains(objects.first!) } assertQuery("(ANY linkingObjects IN %@)", objects, count: 0) { $0.linkingObjects.containsAny(in: objects) } assertQuery("(NOT %@ IN linkingObjects)", objects.first!, count: 1) { !$0.linkingObjects.contains(objects.first!) } assertQuery("(NOT ANY linkingObjects IN %@)", objects, count: 1) { !$0.linkingObjects.containsAny(in: objects) } } // MARK: - Compound func testCompoundAnd() { % p1 = properties[0] % p2 = optProperties[0] assertQuery("((${p1.colName} == %@) && (${p2.colName} == %@))", values: [${p1.enumValue(1)}, ${p2.enumValue(1)}], count: 1) { $0.${p1.colName} == ${p1.value(1)} && $0.${p2.colName} == ${p2.value(1)} } assertQuery("((${p1.colName} == %@) && (${p2.colName} == %@))", values: [${p1.enumValue(1)}, ${p2.enumValue(1)}], count: 1) { ($0.${p1.colName} == ${p1.value(1)}) && ($0.${p2.colName} == ${p2.value(1)}) } // List % for listProperty in [listProperties[0], optListProperties[0]]: % property = properties[0] % value1 = property.enumValue(1) % wrongValue = property.enumValue(0) % value2 = listProperty.enumValue(1) assertQuery("((${property.colName} == %@) && (%@ IN ${listProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} == ${property.value(1)} && $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % if not property.isEnum: assertQuery("((${property.colName} != %@) && (%@ IN ${listProperty.colName}))", values: [${wrongValue}, ${value2}], count: 1) { $0.${property.colName} != ${property.value(0)} && $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % else: assertQuery("((${property.colName} != %@) && (%@ IN ${listProperty.colName}))", values: [${value1}, ${value2}], count: 0) { $0.${property.colName} != ${property.value(1)} && $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % end % end // Set % for setProperty in [setProperties[0], optSetProperties[0]]: % value1 = property.enumValue(1) % wrongValue = property.enumValue(0) % value2 = setProperty.enumValue(1) assertQuery("((${property.colName} == %@) && (%@ IN ${setProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} == ${property.value(1)} && $0.${setProperty.colName}.contains(${setProperty.value(1)}) } % if not property.isEnum: assertQuery("((${property.colName} != %@) && (%@ IN ${setProperty.colName}))", values: [${wrongValue}, ${value2}], count: 1) { $0.${property.colName} != ${property.value(0)} && $0.${setProperty.colName}.contains(${setProperty.value(1)}) } % else: assertQuery("((${property.colName} != %@) && (%@ IN ${setProperty.colName}))", values: [${value1}, ${value2}], count: 0) { $0.${property.colName} != ${property.value(1)} && $0.${setProperty.colName}.contains(${setProperty.value(1)}) } % end % end // Map % for mapProperty in [mapProperties[0], optMapProperties[0]]: % value1 = property.enumValue(1) % wrongValue = property.enumValue(0) % value2 = mapProperty.enumValue(1) assertQuery("((${property.colName} == %@) && (%@ IN ${mapProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} == ${property.value(1)} && $0.${mapProperty.colName}.contains(${mapProperty.value(1)}) } % if not property.isEnum: assertQuery("((${property.colName} != %@) && (${mapProperty.colName}[%@] == %@))", values: [${wrongValue}, "foo", ${value2}], count: 1) { ($0.${property.colName} != ${property.value(0)}) && ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(1)}) } assertQuery("(((${property.colName} != %@) && (${mapProperty.colName}[%@] == %@)) && (${mapProperty.colName}[%@] == %@))", values: [${wrongValue}, "foo", ${mapProperty.value(0)}, "bar", ${mapProperty.value(1)}], count: 1) { ($0.${property.colName} != ${property.value(0)}) && ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(0)}) && ($0.${mapProperty.colName}["bar"] == ${mapProperty.value(1)}) } % else: assertQuery("((${property.colName} != %@) && (${mapProperty.colName}[%@] == %@))", values: [${value1}, "foo", ${value2}], count: 0) { ($0.${property.colName} != ${property.value(1)}) && ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(1)}) } % end % end // Aggregates % for listProperty in [listProperties[1], optListProperties[1], mapProperties[1], optMapProperties[1]]: let sum${listProperty.colName} = ${listProperty.value(0)} + ${listProperty.value(1)} assertQuery("((((((${listProperty.colName}.@min <= %@) && (${listProperty.colName}.@max >= %@)) && (${listProperty.colName}.@sum == %@)) && (${listProperty.colName}.@count != %@)) && (${listProperty.colName}.@avg > %@)) && (${listProperty.colName}.@avg < %@))", values: [${listProperty.value(0)}, ${listProperty.value(1)}, sum${listProperty.colName}, 0, ${listProperty.value(0)}, ${listProperty.value(1)}], count: 1) { ($0.${listProperty.colName}.min <= ${listProperty.value(0)}) && ($0.${listProperty.colName}.max >= ${listProperty.value(1)}) && ($0.${listProperty.colName}.sum == sum${listProperty.colName}) && ($0.${listProperty.colName}.count != 0) && ($0.${listProperty.colName}.avg > ${listProperty.value(0)}) && ($0.${listProperty.colName}.avg < ${listProperty.value(1)}) } % end // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() % for property in [properties[7], optProperties[7]]: let sum${property.colName} = ${property.value(0)} + ${property.value(1)} + ${property.value(2)} assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.${property.colName} <= %@) && (list.@max.${property.colName} >= %@)) && (list.@sum.${property.colName} == %@)) && (list.@min.${property.colName} != %@)) && (list.@avg.${property.colName} > %@)) && (list.@avg.${property.colName} < %@))", values: [${property.value(0)}, ${property.value(2)}, sum${property.colName}, ${property.value(1)}, ${property.value(0)}, ${property.value(2)}], count: 1) { $0.list.${property.colName}.min <= ${property.value(0)} && $0.list.${property.colName}.max >= ${property.value(2)} && $0.list.${property.colName}.sum == sum${property.colName} && $0.list.${property.colName}.min != ${property.value(1)} && $0.list.${property.colName}.avg > ${property.value(0)} && $0.list.${property.colName}.avg < ${property.value(2)} } % end } func testCompoundOr() { % for props in groupByClass(properties + optProperties): % for p1, p2 in zip(props, props[1:]): assertQuery(${p1.className}.self, "((${p1.colName} == %@) || (${p2.colName} == %@))", values: [${p1.enumValue(1)}, ${p2.enumValue(1)}], count: 1) { $0.${p1.colName} == ${p1.value(1)} || $0.${p2.colName} == ${p2.value(1)} } assertQuery(${p1.className}.self, "((${p1.colName} == %@) || (${p2.colName} == %@))", values: [${p1.enumValue(1)}, ${p2.enumValue(1)}], count: 1) { ($0.${p1.colName} == ${p1.value(1)}) || ($0.${p2.colName} == ${p2.value(1)}) } % end % end // List / Set % for listProperty in [listProperties[0], optListProperties[0], setProperties[0], optSetProperties[0]]: % property = properties[0] % value1 = property.enumValue(1) % wrongValue = property.enumValue(0) % value2 = listProperty.enumValue(1) assertQuery("((${property.colName} == %@) || (%@ IN ${listProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} == ${property.value(1)} || $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % if not property.isEnum: assertQuery("((${property.colName} != %@) || (%@ IN ${listProperty.colName}))", values: [${wrongValue}, ${value2}], count: 1) { $0.${property.colName} != ${property.value(0)} || $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % else: assertQuery("((${property.colName} != %@) || (%@ IN ${listProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} != ${property.value(1)} || $0.${listProperty.colName}.contains(${listProperty.value(1)}) } % end % end // Map % for mapProperty in [mapProperties[0], optMapProperties[0], mapProperties[1], optMapProperties[1]]: % property = properties[0] % value1 = property.enumValue(1) % wrongValue = property.enumValue(0) % value2 = mapProperty.enumValue(1) assertQuery("((${property.colName} == %@) || (%@ IN ${mapProperty.colName}))", values: [${value1}, ${value2}], count: 1) { $0.${property.colName} == ${property.value(1)} || $0.${mapProperty.colName}.contains(${mapProperty.value(1)}) } % if not property.isEnum: assertQuery("((${property.colName} != %@) || (${mapProperty.colName}[%@] == %@))", values: [${wrongValue}, "foo", ${value2}], count: 1) { ($0.${property.colName} != ${property.value(0)}) || ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(1)}) } assertQuery("(((${property.colName} != %@) || (${mapProperty.colName}[%@] == %@)) || (${mapProperty.colName}[%@] == %@))", values: [${wrongValue}, "foo", ${mapProperty.value(0)}, "bar", ${mapProperty.value(1)}], count: 1) { ($0.${property.colName} != ${property.value(0)}) || ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(0)}) || ($0.${mapProperty.colName}["bar"] == ${mapProperty.value(1)}) } % else: assertQuery("(${property.colName} != %@ || (${mapProperty.colName}[%@] == %@))", values: [${value1}, "foo", ${value2}], count: 1) { ($0.${property.colName} != ${property.value(1)}) || ($0.${mapProperty.colName}["foo"] == ${mapProperty.value(1)}) } % end % end // Aggregates % for listProperty in [listProperties[1], optListProperties[1]]: let sum${listProperty.colName} = ${listProperty.value(0)} + ${listProperty.value(1)} assertQuery("((((((${listProperty.colName}.@min <= %@) || (${listProperty.colName}.@max >= %@)) || (${listProperty.colName}.@sum != %@)) || (${listProperty.colName}.@count == %@)) || (${listProperty.colName}.@avg > %@)) || (${listProperty.colName}.@avg < %@))", values: [${listProperty.value(0)}, ${listProperty.value(2)}, sum${listProperty.colName}, 0, ${listProperty.value(1)}, ${listProperty.value(0)}], count: 1) { ($0.${listProperty.colName}.min <= ${listProperty.value(0)}) || ($0.${listProperty.colName}.max >= ${listProperty.value(2)}) || ($0.${listProperty.colName}.sum != sum${listProperty.colName}) || ($0.${listProperty.colName}.count == 0) || ($0.${listProperty.colName}.avg > ${listProperty.value(1)}) || ($0.${listProperty.colName}.avg < ${listProperty.value(0)}) } % end // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() % for property in [properties[7], optProperties[7]]: let sum${property.colName} = ${property.value(0)} + ${property.value(1)} + ${property.value(2)} assertQuery(LinkToModernAllTypesObject.self, "((((((list.@min.${property.colName} < %@) || (list.@max.${property.colName} > %@)) || (list.@sum.${property.colName} != %@)) || (list.@min.${property.colName} == %@)) || (list.@avg.${property.colName} >= %@)) || (list.@avg.${property.colName} <= %@))", values: [${property.value(0)}, ${property.value(2)}, sum${property.colName}, 0, ${property.value(2)}, ${property.value(0)}], count: 0) { $0.list.${property.colName}.min < ${property.value(0)} || $0.list.${property.colName}.max > ${property.value(2)} || $0.list.${property.colName}.sum != sum${property.colName} || $0.list.${property.colName}.min == 0 || $0.list.${property.colName}.avg >= ${property.value(2)} || $0.list.${property.colName}.avg <= ${property.value(0)} } % end } func validateCompoundMixed( _ name1: String, _ lhs1: (Query) -> Query, _ value1: T, _ name2: String, _ lhs2: (Query) -> Query, _ value2: U) { assertQuery(Root.self, "(((\(name1) == %@) || (\(name2) == %@)) && ((\(name1) != %@) || (\(name2) != %@)))", values: [value1, value2, value1, value2], count: 0) { (lhs1($0) == value1 || lhs2($0) == value2) && (lhs1($0) != value1 || lhs2($0) != value2) } assertQuery(Root.self, "((\(name1) == %@) || (\(name2) == %@))", values: [value1, value2], count: 1) { (lhs1($0) == value1) || (lhs2($0) == value2) } } func validateCompoundString( _ name1: String, _ lhs1: (Query) -> Query, _ value1: T, _ name2: String, _ lhs2: (Query) -> Query, _ value2: U) where U.PersistedType: _QueryBinary { assertQuery(Root.self, "(NOT ((\(name1) == %@) || (\(name2) CONTAINS %@)) && (\(name2) == %@))", values: [value1, value2, value2], count: 0) { !(lhs1($0) == value1 || lhs2($0).contains(value2)) && (lhs2($0) == value2) } } func testCompoundMixed() { % for props in groupByClass(properties + optProperties): % for p1, p2 in zip(props, props[1:]): validateCompoundMixed("${p1.colName}", \Query<${p1.className}>.${p1.colName}, ${p1.value(1)}, "${p2.colName}", \Query<${p2.className}>.${p2.colName}, ${p2.value(1)}) % if p2.enumName == '' and p2.category == 'string': validateCompoundString("${p1.colName}", \Query<${p1.className}>.${p1.colName}, ${p1.value(1)}, "${p2.colName}", \Query<${p2.className}>.${p2.colName}, ${p2.value(1)}) % end % end % end // Aggregates % for listProperty in [listProperties[1], optListProperties[1], mapProperties[1], optMapProperties[1]]: let sum${listProperty.colName} = ${listProperty.value(0)} + ${listProperty.value(1)} assertQuery("(((((${listProperty.colName}.@min <= %@) || (${listProperty.colName}.@max >= %@)) && (${listProperty.colName}.@sum == %@)) && (${listProperty.colName}.@count != %@)) && ((${listProperty.colName}.@avg > %@) && (${listProperty.colName}.@avg < %@)))", values: [${listProperty.value(0)}, ${listProperty.value(2)}, sum${listProperty.colName}, 0, ${listProperty.value(0)}, ${listProperty.value(2)}], count: 1) { (($0.${listProperty.colName}.min <= ${listProperty.value(0)}) || ($0.${listProperty.colName}.max >= ${listProperty.value(2)})) && ($0.${listProperty.colName}.sum == sum${listProperty.colName}) && ($0.${listProperty.colName}.count != 0) && ($0.${listProperty.colName}.avg > ${listProperty.value(0)} && $0.${listProperty.colName}.avg < ${listProperty.value(2)}) } % end // Keypath Collection Aggregates createKeypathCollectionAggregatesObject() % for property in [properties[7], optProperties[7]]: let sum${property.colName} = ${property.value(0)} + ${property.value(1)} + ${property.value(2)} assertQuery(LinkToModernAllTypesObject.self, "(((((list.@min.${property.colName} <= %@) || (list.@max.${property.colName} >= %@)) && (list.@sum.${property.colName} == %@)) && (list.@sum.${property.colName} != %@)) && ((list.@avg.${property.colName} > %@) && (list.@avg.${property.colName} < %@)))", values: [${property.value(0)}, ${property.value(2)}, sum${property.colName}, 0, ${property.value(0)}, ${property.value(2)}], count: 1) { ($0.list.${property.colName}.min <= ${property.value(0)} || $0.list.${property.colName}.max >= ${property.value(2)}) && $0.list.${property.colName}.sum == sum${property.colName} && $0.list.${property.colName}.sum != 0 && ($0.list.${property.colName}.avg > ${property.value(0)} && $0.list.${property.colName}.avg < ${property.value(2)}) } % end } func testAny() { % for property in listProperties + optListProperties + setProperties + optSetProperties: assertQuery(${property.className}.self, "(ANY ${property.colName} == %@)", ${property.enumValue(0)}, count: 1) { $0.${property.colName} == ${property.value(0)} } % end assertQuery("(((ANY arrayCol.intCol != %@) && (ANY arrayCol.objectCol.intCol > %@)) && ((ANY setCol.intCol != %@) && (ANY setCol.objectCol.intCol > %@)))", values: [123, 456, 123, 456], count: 0) { (($0.arrayCol.intCol != 123) && ($0.arrayCol.objectCol.intCol > 456)) && (($0.setCol.intCol != 123) && ($0.setCol.objectCol.intCol > 456)) } } func testSubquery() { // List // Count of results will be 0 because there are no `ModernAllTypesObject`s in the list. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 0) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(arrayCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [5, "Bar", 0], count: 0) { $0.intCol == 5 && ($0.arrayCol.stringCol == "Bar").count == 0 } // Set // Will be 0 results because there are no `ModernAllTypesObject`s in the set. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 0) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [5, "Bar", 0], count: 0) { $0.intCol == 5 && ($0.setCol.stringCol == "Bar").count == 0 } let object = objects().first! try! realm.write { let modernObj = ModernAllTypesObject(value: ["intCol": 5, "stringCol": "Foo"]) object.arrayCol.append(modernObj) object.setCol.insert(modernObj) } // Results count should now be 1 assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.arrayInt.@count >= %@)).@count > %@)", values: [0, 0], count: 1) { ($0.arrayCol.arrayInt.count >= 0).count > 0 } // Subquery in a subquery assertQuery("(SUBQUERY(arrayCol, $col1, (($col1.arrayInt.@count >= %@) && (SUBQUERY(arrayCol, $col2, ($col2.intCol != %@)).@count > %@))).@count > %@)", values: [0, 123, 0, 0], count: 0) { ($0.arrayCol.arrayInt.count >= 0 && ($0.arrayCol.intCol != 123).count > 0).count > 0 } assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 1) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("(SUBQUERY(arrayCol, $col1, (($col1.intCol > %@) && ($col1.intCol <= %@))).@count > %@)", values: [0, 5, 0], count: 1) { ($0.arrayCol.intCol > 0 && $0.arrayCol.intCol <= 5 ).count > 0 } assertQuery("((SUBQUERY(arrayCol, $col1, ($col1.intCol == %@)).@count == %@) && (SUBQUERY(arrayCol, $col2, ($col2.stringCol == %@)).@count == %@))", values: [5, 1, "Bar", 0], count: 1) { ($0.arrayCol.intCol == 5).count == 1 && ($0.arrayCol.stringCol == "Bar").count == 0 } // Set // Will be 0 results because there are no `ModernAllTypesObject`s in the set. assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.intCol != %@)).@count > %@)", values: [123, 0], count: 1) { ($0.arrayCol.intCol != 123).count > 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, ($col1.stringCol == %@)).@count == %@))", values: [3, "Bar", 0], count: 1) { ($0.intCol == 3) && ($0.setCol.stringCol == "Bar").count == 0 } assertQuery("((intCol == %@) && (SUBQUERY(setCol, $col1, (($col1.intCol == %@) && ($col1.stringCol != %@))).@count == %@))", values: [3, 5, "Blah", 1], count: 1) { ($0.intCol == 3) && (((($0.setCol.intCol == 5) && ($0.setCol.stringCol != "Blah"))).count == 1) } // Column comparison assertQuery("(SUBQUERY(arrayCol, $col1, ($col1.stringCol == stringCol)).@count == %@)", 0, count: 1) { ($0.arrayCol.stringCol == $0.stringCol).count == 0 } assertThrows(assertQuery("", count: 1) { ($0.stringCol == $0.stringCol).count == 0 }, reason: "Subqueries must contain a keypath starting with a collection.") } // MARK: - Collection Aggregations % for (short, long, ushort, ulong) in [('avg', 'average', 'Avg', 'Average'), ('sum', 'sum', 'Sum', 'Sum')]: % for (protocol, element) in [('RealmCollection', 'Element'), ('RealmKeyedCollection', 'Value')]: private func validate${ulong}(_ name: String, _ ${long}: T.${element}, _ min: T.${element}, _ lhs: (Query) -> Query) where T.${element}.PersistedType: _QueryNumeric, T.${element}: QueryValue { assertQuery(Root.self, "(object.\(name).@${short} == %@)", ${long}, count: 1) { lhs($0).${short} == ${long} } assertQuery(Root.self, "(object.\(name).@${short} == %@)", min, count: 0) { lhs($0).${short} == min } assertQuery(Root.self, "(object.\(name).@${short} != %@)", ${long}, count: 0) { lhs($0).${short} != ${long} } assertQuery(Root.self, "(object.\(name).@${short} != %@)", min, count: 1) { lhs($0).${short} != min } assertQuery(Root.self, "(object.\(name).@${short} > %@)", ${long}, count: 0) { lhs($0).${short} > ${long} } assertQuery(Root.self, "(object.\(name).@${short} > %@)", min, count: 1) { lhs($0).${short} > min } assertQuery(Root.self, "(object.\(name).@${short} < %@)", ${long}, count: 0) { lhs($0).${short} < ${long} } assertQuery(Root.self, "(object.\(name).@${short} >= %@)", ${long}, count: 1) { lhs($0).${short} >= ${long} } assertQuery(Root.self, "(object.\(name).@${short} <= %@)", ${long}, count: 1) { lhs($0).${short} <= ${long} } } % end func testCollectionAggregates${ushort}() { initLinkedCollectionAggregatesObject() % for property in canSum(listProperties + optListProperties + setProperties + optSetProperties + mapProperties + optMapProperties): validate${ulong}("${property.colName}", ${property.type}.${long}(), ${property.rawValue(0)}, \Query<${property.linkingClassName}>.object.${property.rawValueName}) % end } %end % for (lower, upper, other) in [('min', 'Min', 'max'), ('max', 'Max', 'min')]: % for (protocol, element) in [('RealmCollection', 'Element'), ('RealmKeyedCollection', 'Value')]: private func validate${upper}(_ name: String, min: T.${element}, max: T.${element}, _ lhs: (Query) -> Query) where T.${element}.PersistedType: _QueryNumeric, T.${element}: QueryValue { assertQuery(Root.self, "(object.\(name).@${lower} == %@)", ${lower}, count: 1) { lhs($0).${lower} == ${lower} } assertQuery(Root.self, "(object.\(name).@${lower} == %@)", ${other}, count: 0) { lhs($0).${lower} == ${other} } assertQuery(Root.self, "(object.\(name).@${lower} != %@)", ${lower}, count: 0) { lhs($0).${lower} != ${lower} } assertQuery(Root.self, "(object.\(name).@${lower} != %@)", ${other}, count: 1) { lhs($0).${lower} != ${other} } assertQuery(Root.self, "(object.\(name).@${lower} > %@)", ${lower}, count: 0) { lhs($0).${lower} > ${lower} } assertQuery(Root.self, "(object.\(name).@${lower} < %@)", ${lower}, count: 0) { lhs($0).${lower} < ${lower} } assertQuery(Root.self, "(object.\(name).@${lower} >= %@)", ${lower}, count: 1) { lhs($0).${lower} >= ${lower} } assertQuery(Root.self, "(object.\(name).@${lower} <= %@)", ${lower}, count: 1) { lhs($0).${lower} <= ${lower} } } % end func testCollectionAggregates${upper}() { initLinkedCollectionAggregatesObject() % for property in canSum(listProperties + optListProperties + setProperties + optSetProperties + mapProperties + optMapProperties): validate${upper}("${property.colName}", min: ${property.value(0)}, max: ${property.value(2)}, \Query<${property.linkingClassName}>.object.${property.colName}) % end } %end // @Count % for (protocol, element) in [('RealmCollection', 'Element'), ('RealmKeyedCollection', 'Value')]: private func validateCount(_ name: String, _ lhs: (Query) -> Query) { assertQuery(Root.self, "(object.\(name).@count == %@)", 3, count: 1) { lhs($0).count == 3 } assertQuery(Root.self, "(object.\(name).@count == %@)", 0, count: 0) { lhs($0).count == 0 } assertQuery(Root.self, "(object.\(name).@count != %@)", 3, count: 0) { lhs($0).count != 3 } assertQuery(Root.self, "(object.\(name).@count != %@)", 2, count: 1) { lhs($0).count != 2 } assertQuery(Root.self, "(object.\(name).@count < %@)", 3, count: 0) { lhs($0).count < 3 } assertQuery(Root.self, "(object.\(name).@count < %@)", 4, count: 1) { lhs($0).count < 4 } assertQuery(Root.self, "(object.\(name).@count > %@)", 2, count: 1) { lhs($0).count > 2 } assertQuery(Root.self, "(object.\(name).@count > %@)", 3, count: 0) { lhs($0).count > 3 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 2, count: 0) { lhs($0).count <= 2 } assertQuery(Root.self, "(object.\(name).@count <= %@)", 3, count: 1) { lhs($0).count <= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 3, count: 1) { lhs($0).count >= 3 } assertQuery(Root.self, "(object.\(name).@count >= %@)", 4, count: 0) { lhs($0).count >= 4 } } % end func testCollectionAggregatesCount() { initLinkedCollectionAggregatesObject() % for property in (p for p in listProperties + optListProperties + setProperties + optSetProperties + mapProperties + optMapProperties if p.category != 'bool'): validateCount("${property.colName}", \Query<${property.linkingClassName}>.object.${property.colName}) % end } // MARK: - Keypath Collection Aggregations % for (short, long, ushort, ulong) in [('avg', 'average', 'Avg', 'Average'), ('sum', 'sum', 'Sum', 'Sum')]: private func validateKeypath${ulong}(_ name: String, _ ${long}: T, _ min: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@${short}.\(name) == %@)", ${long}, count: 1) { lhs($0).${short} == ${long} } assertQuery(Root.self, "(list.@${short}.\(name) == %@)", min, count: 0) { lhs($0).${short} == min } assertQuery(Root.self, "(list.@${short}.\(name) != %@)", ${long}, count: 0) { lhs($0).${short} != ${long} } assertQuery(Root.self, "(list.@${short}.\(name) != %@)", min, count: 1) { lhs($0).${short} != min } assertQuery(Root.self, "(list.@${short}.\(name) > %@)", ${long}, count: 0) { lhs($0).${short} > ${long} } assertQuery(Root.self, "(list.@${short}.\(name) > %@)", min, count: 1) { lhs($0).${short} > min } assertQuery(Root.self, "(list.@${short}.\(name) < %@)", ${long}, count: 0) { lhs($0).${short} < ${long} } assertQuery(Root.self, "(list.@${short}.\(name) >= %@)", ${long}, count: 1) { lhs($0).${short} >= ${long} } assertQuery(Root.self, "(list.@${short}.\(name) <= %@)", ${long}, count: 1) { lhs($0).${short} <= ${long} } } func testKeypathCollectionAggregates${ushort}() { createKeypathCollectionAggregatesObject() % for property in canSum(properties + optProperties): validateKeypath${ulong}("${property.colName}", ${property.typeName}.${long}(), ${property.rawValue(0)}, \Query<${property.linkingClassName}>.list.${property.rawValueName}) % end } %end % for (lower, upper, other) in [('min', 'Min', 'max'), ('max', 'Max', 'min')]: private func validateKeypath${upper}(_ name: String, min: T, max: T, _ lhs: (Query) -> Query) where T: _Persistable & QueryValue, T.PersistedType: _QueryNumeric { assertQuery(Root.self, "(list.@${lower}.\(name) == %@)", ${lower}, count: 1) { lhs($0).${lower} == ${lower} } assertQuery(Root.self, "(list.@${lower}.\(name) == %@)", ${other}, count: 0) { lhs($0).${lower} == ${other} } assertQuery(Root.self, "(list.@${lower}.\(name) != %@)", ${lower}, count: 0) { lhs($0).${lower} != ${lower} } assertQuery(Root.self, "(list.@${lower}.\(name) != %@)", ${other}, count: 1) { lhs($0).${lower} != ${other} } assertQuery(Root.self, "(list.@${lower}.\(name) > %@)", ${lower}, count: 0) { lhs($0).${lower} > ${lower} } assertQuery(Root.self, "(list.@${lower}.\(name) < %@)", ${lower}, count: 0) { lhs($0).${lower} < ${lower} } assertQuery(Root.self, "(list.@${lower}.\(name) >= %@)", ${lower}, count: 1) { lhs($0).${lower} >= ${lower} } assertQuery(Root.self, "(list.@${lower}.\(name) <= %@)", ${lower}, count: 1) { lhs($0).${lower} <= ${lower} } } func testKeypathCollectionAggregates${upper}() { createKeypathCollectionAggregatesObject() % for property in numeric(properties + optProperties): validateKeypath${upper}("${property.colName}", min: ${property.rawValue(0)}, max: ${property.rawValue(2)}, \Query<${property.linkingClassName}>.list.${property.rawValueName}) % end } %end func testAggregateNotSupported() { assertThrows(assertQuery("", count: 0) { $0.intCol.avg == 1 }, reason: "Invalid keypath 'intCol.@avg': Property 'ModernAllTypesObject.intCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.doubleCol.max != 1 }, reason: "Invalid keypath 'doubleCol.@max': Property 'ModernAllTypesObject.doubleCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.dateCol.min > Date() }, reason: "Invalid keypath 'dateCol.@min': Property 'ModernAllTypesObject.dateCol' is not a link or collection and can only appear at the end of a keypath.") assertThrows(assertQuery("", count: 0) { $0.decimalCol.sum < 1 }, reason: "Invalid keypath 'decimalCol.@sum': Property 'ModernAllTypesObject.decimalCol' is not a link or collection and can only appear at the end of a keypath.") } // MARK: Column comparison func testColumnComparison() { // Basic comparison assertQuery("(stringEnumCol == stringEnumCol)", count: 1) { $0.stringEnumCol == $0.stringEnumCol } assertQuery("(stringCol != stringCol)", count: 0) { $0.stringCol != $0.stringCol } assertQuery("(stringEnumCol != stringEnumCol)", count: 0) { $0.stringEnumCol != $0.stringEnumCol } assertQuery("(stringEnumCol > stringEnumCol)", count: 0) { $0.stringEnumCol > $0.stringEnumCol } assertQuery("(stringCol > stringCol)", count: 0) { $0.stringCol > $0.stringCol } assertQuery("(stringEnumCol >= stringEnumCol)", count: 1) { $0.stringEnumCol >= $0.stringEnumCol } assertQuery("(stringCol >= stringCol)", count: 1) { $0.stringCol >= $0.stringCol } assertQuery("(stringEnumCol < stringEnumCol)", count: 0) { $0.stringEnumCol < $0.stringEnumCol } assertQuery("(stringCol < stringCol)", count: 0) { $0.stringCol < $0.stringCol } assertQuery("(stringEnumCol <= stringEnumCol)", count: 1) { $0.stringEnumCol <= $0.stringEnumCol } assertQuery("(stringCol <= stringCol)", count: 1) { $0.stringCol <= $0.stringCol } assertThrows(assertQuery("", count: 1) { $0.arrayCol == $0.arrayCol }, reason: "Comparing two collection columns is not permitted.") assertThrows(assertQuery("", count: 1) { $0.arrayCol != $0.arrayCol }, reason: "Comparing two collection columns is not permitted.") assertQuery("(intCol > intCol)", count: 0) { $0.intCol > $0.intCol } assertQuery("(intEnumCol > intEnumCol)", count: 0) { $0.intEnumCol > $0.intEnumCol } assertQuery("(intCol >= intCol)", count: 1) { $0.intCol >= $0.intCol } assertQuery("(intEnumCol >= intEnumCol)", count: 1) { $0.intEnumCol >= $0.intEnumCol } assertQuery("(intCol < intCol)", count: 0) { $0.intCol < $0.intCol } assertQuery("(intEnumCol < intEnumCol)", count: 0) { $0.intEnumCol < $0.intEnumCol } assertQuery("(intCol <= intCol)", count: 1) { $0.intCol <= $0.intCol } assertQuery("(intEnumCol <= intEnumCol)", count: 1) { $0.intEnumCol <= $0.intEnumCol } assertQuery("(optStringCol == optStringCol)", count: 1) { $0.optStringCol == $0.optStringCol } assertQuery("(optStringCol != optStringCol)", count: 0) { $0.optStringCol != $0.optStringCol } assertQuery("(optIntCol > optIntCol)", count: 0) { $0.optIntCol > $0.optIntCol } assertQuery("(optIntCol >= optIntCol)", count: 1) { $0.optIntCol >= $0.optIntCol } assertQuery("(optIntCol < optIntCol)", count: 0) { $0.optIntCol < $0.optIntCol } assertQuery("(optIntCol <= optIntCol)", count: 1) { $0.optIntCol <= $0.optIntCol } // Basic comparison with one level depth assertQuery("(objectCol.stringCol == objectCol.stringCol)", count: 1) { $0.objectCol.stringCol == $0.objectCol.stringCol } assertQuery("(objectCol.stringCol != objectCol.stringCol)", count: 0) { $0.objectCol.stringCol != $0.objectCol.stringCol } assertQuery("(objectCol.intCol > objectCol.intCol)", count: 0) { $0.objectCol.intCol > $0.objectCol.intCol } assertQuery("(objectCol.intCol >= objectCol.intCol)", count: 1) { $0.objectCol.intCol >= $0.objectCol.intCol } assertQuery("(objectCol.intCol < objectCol.intCol)", count: 0) { $0.objectCol.intCol < $0.objectCol.intCol } assertQuery("(objectCol.intCol <= objectCol.intCol)", count: 1) { $0.objectCol.intCol <= $0.objectCol.intCol } assertQuery("(objectCol.optStringCol == objectCol.optStringCol)", count: 1) { $0.objectCol.optStringCol == $0.objectCol.optStringCol } assertQuery("(objectCol.optStringCol != objectCol.optStringCol)", count: 0) { $0.objectCol.optStringCol != $0.objectCol.optStringCol } assertQuery("(objectCol.optIntCol > objectCol.optIntCol)", count: 0) { $0.objectCol.optIntCol > $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol >= objectCol.optIntCol)", count: 1) { $0.objectCol.optIntCol >= $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol < objectCol.optIntCol)", count: 0) { $0.objectCol.optIntCol < $0.objectCol.optIntCol } assertQuery("(objectCol.optIntCol <= objectCol.optIntCol)", count: 1) { $0.objectCol.optIntCol <= $0.objectCol.optIntCol } // String comparison assertQuery("(stringCol CONTAINS[cd] stringCol)", count: 1) { $0.stringCol.contains($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.stringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol ENDSWITH[cd] stringCol)", count: 1) { $0.stringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol LIKE[c] stringCol)", count: 1) { $0.stringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(stringCol ==[cd] stringCol)", count: 1) { $0.stringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol !=[cd] stringCol)", count: 0) { $0.stringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } // String with optional col assertQuery("(stringCol CONTAINS[cd] optStringCol)", count: 1) { $0.stringCol.contains($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol CONTAINS[cd] optStringCol)", count: 1) { $0.optStringCol.contains($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol CONTAINS[cd] stringCol)", count: 1) { $0.optStringCol.contains($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.stringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol BEGINSWITH[cd] optStringCol)", count: 1) { $0.optStringCol.starts(with: $0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol BEGINSWITH[cd] stringCol)", count: 1) { $0.optStringCol.starts(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol ENDSWITH[cd] stringCol)", count: 1) { $0.stringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ENDSWITH[cd] optStringCol)", count: 1) { $0.optStringCol.ends(with: $0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ENDSWITH[cd] stringCol)", count: 1) { $0.optStringCol.ends(with: $0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol LIKE[c] stringCol)", count: 1) { $0.stringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(optStringCol LIKE[c] optStringCol)", count: 1) { $0.optStringCol.like($0.optStringCol, caseInsensitive: true) } assertQuery("(optStringCol LIKE[c] stringCol)", count: 1) { $0.optStringCol.like($0.stringCol, caseInsensitive: true) } assertQuery("(stringCol ==[cd] stringCol)", count: 1) { $0.stringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ==[cd] optStringCol)", count: 1) { $0.optStringCol.equals($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol ==[cd] stringCol)", count: 1) { $0.optStringCol.equals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(stringCol !=[cd] stringCol)", count: 0) { $0.stringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol !=[cd] optStringCol)", count: 0) { $0.optStringCol.notEquals($0.optStringCol, options: [.caseInsensitive, .diacriticInsensitive]) } assertQuery("(optStringCol !=[cd] stringCol)", count: 0) { $0.optStringCol.notEquals($0.stringCol, options: [.caseInsensitive, .diacriticInsensitive]) } } // MARK: - ContainsIn func testContainsIn() { % for property in properties + optProperties: assertQuery(${property.className}.self, "(${property.colName} IN %@)", values: [NSArray(array: [${property.enumValue(0)}, ${property.enumValue(1)}])], count: 1) { $0.${property.colName}.in([${property.enumValue(0)}, ${property.enumValue(1)}]) } assertQuery(${property.className}.self, "(NOT ${property.colName} IN %@)", values: [NSArray(array: [${property.enumValue(1)}])], count: 0) { !$0.${property.colName}.in([${property.enumValue(1)}]) } % end } } private protocol LinkToTestObject: Object { associatedtype Child: Object var object: Child? { get } var list: List { get } var set: MutableSet { get } var map: Map { get } } % for _, linkingClassName in classNames: extension ${linkingClassName}: LinkToTestObject {} % end private protocol QueryValue { static func queryValues() -> [Self] } % for type in types: extension ${type.name}: QueryValue { static func queryValues() -> [${type.name}] { return [${type.collectionValues[0]}, ${type.collectionValues[1]}, ${type.collectionValues[2]}] } } extension ${type.name}Wrapper: QueryValue { static func queryValues() -> [${type.name}Wrapper] { return ${type.name}.queryValues().map(${type.name}Wrapper.init) } } % if type.hasEnum(): extension Enum${type.name}: QueryValue { static func queryValues() -> [Enum${type.name}] { return [.value1, .value2, .value3] } } % end % end extension ModernIntEnum: QueryValue { static func queryValues() -> [ModernIntEnum] { return [.value1, .value2, .value3] } fileprivate static func sum() -> Int { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> Int { return Self.value2.rawValue } } extension AnyRealmValue: QueryValue { static func queryValues() -> [AnyRealmValue] { return [${anyCollectionValues[0]}, ${anyCollectionValues[1]}, ${anyCollectionValues[2]}] } } extension Optional: QueryValue where Wrapped: QueryValue { static func queryValues() -> [Self] { return Wrapped.queryValues().map(Self.init) } } private protocol AddableQueryValue { associatedtype SumType static func sum() -> SumType static func average() -> SumType } % for type in (t for t in types if t.category == 'numeric' and t.name != 'Date'): extension ${type.name}: AddableQueryValue { fileprivate typealias SumType = ${type.name} fileprivate static func sum() -> SumType { return ${type.collectionValues[0]} + ${type.collectionValues[1]} + ${type.collectionValues[2]} } fileprivate static func average() -> SumType { return sum() / 3 } } extension ${type.name}Wrapper: AddableQueryValue { fileprivate typealias SumType = ${type.name}Wrapper fileprivate static func sum() -> SumType { return ${type.name}Wrapper(persistedValue: ${type.name}.sum()) } fileprivate static func average() -> SumType { return ${type.name}Wrapper(persistedValue: ${type.name}.average()) } } % if type.hasEnum(): extension Enum${type.name}: AddableQueryValue { fileprivate typealias SumType = ${type.name} fileprivate static func sum() -> SumType { return Self.value1.rawValue + Self.value2.rawValue + Self.value3.rawValue } fileprivate static func average() -> SumType { return sum() / 3 } } % end % end extension Optional: AddableQueryValue where Wrapped: AddableQueryValue { fileprivate typealias SumType = Optional fileprivate static func sum() -> SumType { return .some(Wrapped.sum()) } fileprivate static func average() -> SumType { return .some(Wrapped.average()) } } ================================================ FILE: RealmSwift/Tests/RealmCollectionTypeTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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. // //////////////////////////////////////////////////////////////////////////// // swiftlint:disable type_name import XCTest import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif class CTTAggregateObject: Object { @Persisted var intCol = 0 @Persisted var int8Col = 0 @Persisted var int16Col = 0 @Persisted var int32Col = 0 @Persisted var int64Col = 0 @Persisted var floatCol = 0 as Float @Persisted var doubleCol = 0.0 @Persisted var boolCol = false @Persisted var dateCol = Date() @Persisted var trueCol = true @Persisted var stringListCol: List @Persisted var stringSetCol: MutableSet @Persisted var linkCol: CTTLinkTarget? @Persisted var childIntCol: CTTIntegerObject? } class CTTIntegerObject: Object { @Persisted var intCol = 0 } class CTTAggregateObjectList: Object { @Persisted var list: List } class CTTAggregateObjectSet: Object { @Persisted var set: MutableSet } class CTTNullableStringObjectWithLink: Object { @Persisted var stringCol: String? = "" @Persisted var linkCol: CTTLinkTarget? } class CTTLinkTarget: Object { @Persisted var id = 0 @Persisted(originProperty: "linkCol") var stringObjects: LinkingObjects @Persisted(originProperty: "linkCol") var aggregateObjects: LinkingObjects } class CTTStringList: Object { @Persisted var array: List } class CTTStringSet: Object { @Persisted var set: MutableSet } struct Config { static let config = Realm.Configuration(inMemoryIdentifier: "collection", objectTypes: [CTTAggregateObject.self, CTTNullableStringObjectWithLink.self, CTTIntegerObject.self, CTTAggregateObjectList.self, CTTAggregateObjectSet.self, CTTNullableStringObjectWithLink.self, CTTLinkTarget.self, CTTStringList.self, CTTStringSet.self, SwiftDoubleListOfSwiftObject.self, SwiftListOfSwiftObject.self, SwiftObject.self, SwiftBoolObject.self]) } class RealmCollectionTests: TestCase where Collection.Element == CTTNullableStringObjectWithLink, Collection.Index == Int, AggregateCollection.Element == CTTAggregateObject, AggregateCollection.Index == Int { var str1: CTTNullableStringObjectWithLink! var str2: CTTNullableStringObjectWithLink! var collection: Collection! func realm() -> Realm { return try! Realm(configuration: Config.config) } func getCollection(_ realm: Realm) -> Collection { fatalError("Abstract method. Try running tests using Control-U.") } func getAggregateableCollectionInWrite(_ realm: Realm) -> AggregateCollection { fatalError("Abstract method. Try running tests using Control-U.") } func getAggregateableCollection() -> AggregateCollection { let realm = self.realm() return try! realm.write { getAggregateableCollectionInWrite(realm) } } func makeAggregateableObjectsInWriteTransaction() -> [CTTAggregateObject] { let obj1 = CTTAggregateObject() obj1.intCol = 1 obj1.int8Col = 1 obj1.int16Col = 1 obj1.int32Col = 1 obj1.int64Col = 1 obj1.floatCol = 1.1 obj1.doubleCol = 1.11 obj1.dateCol = Date(timeIntervalSince1970: 1) obj1.boolCol = false let obj2 = CTTAggregateObject() obj2.intCol = 2 obj2.int8Col = 2 obj2.int16Col = 2 obj2.int32Col = 2 obj2.int64Col = 2 obj2.floatCol = 2.2 obj2.doubleCol = 2.22 obj2.dateCol = Date(timeIntervalSince1970: 2) obj2.boolCol = false let obj3 = CTTAggregateObject() obj3.intCol = 3 obj3.int8Col = 3 obj3.int16Col = 3 obj3.int32Col = 3 obj3.int64Col = 3 obj3.floatCol = 2.2 obj3.doubleCol = 2.22 obj3.dateCol = Date(timeIntervalSince1970: 2) obj3.boolCol = false self.realm().add([obj1, obj2, obj3]) return [obj1, obj2, obj3] } func makeAggregateableObjects() -> [CTTAggregateObject] { return try! self.realm().write { makeAggregateableObjectsInWriteTransaction() } } override func setUp() { super.setUp() let target1 = CTTLinkTarget() target1.id = 1 let str1 = CTTNullableStringObjectWithLink() str1.stringCol = "1" str1.linkCol = target1 self.str1 = str1 let str2 = CTTNullableStringObjectWithLink() str2.stringCol = "2" str2.linkCol = target1 self.str2 = str2 let realm = self.realm() try! realm.write { realm.add(str1) realm.add(str2) realm.add(target1) collection = getCollection(realm) } } override func tearDown() { str1 = nil str2 = nil collection = nil super.tearDown() } override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(RealmCollectionTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func testRealm() { XCTAssertEqual(collection.realm!.configuration.fileURL, self.realm().configuration.fileURL) } func testDescription() { // swiftlint:disable:next line_length assertMatches(collection.description, "Results <0x[0-9a-f]+> \\(\n\t\\[0\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 1;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\},\n\t\\[1\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 2;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\}\n\\)") } func testCount() { XCTAssertEqual(2, collection.count) XCTAssertEqual(1, collection.filter("stringCol = '1'").count) XCTAssertEqual(1, collection.filter("stringCol = '2'").count) XCTAssertEqual(0, collection.filter("stringCol = '0'").count) XCTAssertEqual(1, collection.where { $0.stringCol == "1" }.count) XCTAssertEqual(1, collection.where { $0.stringCol == "2" }.count) XCTAssertEqual(0, collection.where { $0.stringCol == "0" }.count) } func testIndexOfObject() { XCTAssertEqual(0, collection.index(of: str1)!) XCTAssertEqual(1, collection.index(of: str2)!) let str1Only = collection.filter("stringCol = '1'") XCTAssertEqual(0, str1Only.index(of: str1)!) XCTAssertNil(str1Only.index(of: str2)) let str1OnlyQuery = collection.where { $0.stringCol == "1" } XCTAssertEqual(0, str1OnlyQuery.index(of: str1)!) XCTAssertNil(str1OnlyQuery.index(of: str2)) } func testIndexOfPredicate() { let pred1 = NSPredicate(format: "stringCol = '1'") let pred2 = NSPredicate(format: "stringCol = '2'") let pred3 = NSPredicate(format: "stringCol = '3'") XCTAssertEqual(0, collection.index(matching: pred1)!) XCTAssertEqual(1, collection.index(matching: pred2)!) XCTAssertNil(collection.index(matching: pred3)) } func testIndexOfQuery() { XCTAssertEqual(0, collection.index(matching: { $0.stringCol == "1" })!) XCTAssertEqual(1, collection.index(matching: { $0.stringCol == "2" })!) XCTAssertNil(collection.index(matching: { $0.stringCol == "3" })) } func testIndexOfFormat() { XCTAssertEqual(0, collection.index(matching: "stringCol = '1'")!) XCTAssertEqual(0, collection.index(matching: "stringCol = %@", "1")!) XCTAssertEqual(1, collection.index(matching: "stringCol = %@", "2")!) XCTAssertNil(collection.index(matching: "stringCol = %@", "3")) } func testSubscript() { assertEqual(str1, collection[0]) assertEqual(str2, collection[1]) assertThrows(collection[200]) assertThrows(collection[-200]) } func testObjectsAtIndexes() { assertThrows(collection.objects(at: [0, 10])) let objs = collection.objects(at: [0, 1]) assertEqual(str1, objs[0]) assertEqual(str2, objs[1]) } func testFirst() { assertEqual(str1, collection.first!) assertEqual(str2, collection.filter("stringCol = '2'").first!) XCTAssertNil(collection.filter("stringCol = '3'").first) assertEqual(str2, collection.where { $0.stringCol == "2" }.first!) XCTAssertNil(collection.where { $0.stringCol == "3" }.first) } func testLast() { assertEqual(str2, collection.last!) assertEqual(str2, collection.filter("stringCol = '2'").last!) XCTAssertNil(collection.filter("stringCol = '3'").last) assertEqual(str2, collection.where { $0.stringCol == "2" }.last!) XCTAssertNil(collection.where { $0.stringCol == "3" }.last) } func testValueForKey() { let expected = Array(collection.map { $0.stringCol }) let actual = collection.value(forKey: "stringCol") as! [String]? XCTAssertEqual(expected as! [String], actual!) assertEqual(Array(collection), collection.value(forKey: "self") as! [CTTNullableStringObjectWithLink]) } func testSetValueForKey() { try! self.realm().write { collection.setValue("hi there!", forKey: "stringCol") } let expected = Array((0..(_ changes: RealmCollectionChange) { switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) XCTAssertFalse(gotInitial) gotInitial = true case let .update(collection, deletions, _, _): XCTAssertEqual(collection.count, 0) XCTAssertEqual(deletions, [0, 1]) XCTAssertFalse(gotChange) gotChange = true case .error(let error): XCTFail("Unexpected error: \(error)") } expectation.fulfill() } func checkFiltered(_ changes: RealmCollectionChange) { switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) XCTAssertFalse(gotInitial) gotInitial = true case let .update(_, _, _, modifications): XCTAssertEqual(modifications, [0]) XCTAssertFalse(gotChange) gotChange = true case .error(let error): XCTFail("Unexpected error: \(error)") } expectation.fulfill() } func observe(_ tsr: ThreadSafeReference) async throws -> NotificationToken { let realm = try await openRealm(configuration: Config.config, actor: self) return try XCTUnwrap(realm.resolve(tsr)).observe(check) } } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testObserveOnActor() async throws { let actor = TestActor() await actor.expect("initial notification") let token = await collection.observe(on: actor) { actor, changes in actor.check(changes) } await fulfillment(of: [actor.expectation]) await actor.expect("change notification") let realm = self.realm() try realm.write { realm.delete(collection) } await fulfillment(of: [actor.expectation]) token.invalidate() } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testObserveInsideActor() async throws { let actor = TestActor() await actor.expect("initial notification") let token = try await actor.observe(ThreadSafeReference(to: collection)) await fulfillment(of: [actor.expectation]) await actor.expect("change notification") let realm = self.realm() try realm.write { realm.delete(collection) } await fulfillment(of: [actor.expectation]) token.invalidate() } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testCancelTaskForObservationInit() async throws { let task = Locked?>(wrappedValue: nil) task.wrappedValue = Task { @MainActor in task.wrappedValue!.cancel() let token = await collection.observe(on: CustomGlobalActor.shared) { _, _ in XCTFail("should not have been registered") } XCTAssertFalse(token.invalidate()) } await _ = task.wrappedValue!.value } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testObserveOnActorWithStringKeyPath() async throws { let actor = TestActor() await actor.expect("initial notification") let token = await collection.observe(keyPaths: ["stringCol"], on: actor) { actor, changes in actor.checkFiltered(changes) } await fulfillment(of: [actor.expectation]) await actor.expect("change notification") let realm = self.realm() try realm.write { collection[0].stringCol = "a" collection[1].linkCol = nil } await fulfillment(of: [actor.expectation]) token.invalidate() } @MainActor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testObserveOnActorWithKeyPath() async throws { let actor = TestActor() await actor.expect("initial notification") let token = await collection.observe(keyPaths: [\.stringCol], on: actor) { actor, changes in actor.checkFiltered(changes) } await fulfillment(of: [actor.expectation]) await actor.expect("change notification") let realm = self.realm() try realm.write { collection[0].stringCol = "a" collection[1].linkCol = nil } await fulfillment(of: [actor.expectation]) token.invalidate() } @MainActor func testObserveKeyPath() { var ex = expectation(description: "initial notification") let token0 = collection.observe(keyPaths: ["stringCol"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, [0]) case .error: XCTFail("error not expected") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect a change notification for the token observing `stringCol` keypath. ex = self.expectation(description: "change notification") nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realm() realm.beginWrite() let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.stringCol = "changed" try! realm.commitWrite() } waitForExpectations(timeout: 0.1, handler: nil) token0.invalidate() } func expectNoChange(fn: @Sendable @escaping (Realm) -> Void) { let ex = self.expectation(description: "refresh") let token = self.realm().observe { _, _ in ex.fulfill() } nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realm() realm.beginWrite() fn(realm) try! realm.commitWrite() } wait(for: [ex], timeout: 0.2) token.invalidate() } @MainActor func testObserveKeyPathNoChange() { let ex = expectation(description: "initial notification") let token0 = collection.observe(keyPaths: ["stringCol"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) default: XCTFail("Unexpected change: \(changes)") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect no notification for `stringCol` key path because only `linkCol.id` will be modified. expectNoChange { realm in let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 } token0.invalidate() } @MainActor func testObserveKeyPathWithLink() { var ex = expectation(description: "initial notification") let token = collection.observe(keyPaths: ["linkCol.id"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) // The reason two column changes are expected here is because the // single CTTLinkTarget object that is modified is linked to two origin objects. // The 0, 1 index refers to the origin objects. XCTAssertEqual(modifications, [0, 1]) case .error: XCTFail("error not expected") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Only expect a change notification for `linkCol.id` keypath. ex = self.expectation(description: "change notification") nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realm() realm.beginWrite() let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 try! realm.commitWrite() } waitForExpectations(timeout: 0.1, handler: nil) token.invalidate() } @MainActor func testObserveKeyPathWithLinkNoChange() { let ex = expectation(description: "initial notification") let token = collection.observe(keyPaths: ["linkCol.id"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) default: XCTFail("Unexpected change: \(changes)") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect no notification for `linkCol.id` key path because only `stringCol` will be modified. expectNoChange { realm in let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.stringCol = "changed" } token.invalidate() } @MainActor func testObserveKeyPathWithLinkNoChangeList() { let ex = expectation(description: "initial notification") let token = collection.observe(keyPaths: ["linkCol"]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) default: XCTFail("Unexpected change: \(changes)") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect no notification for `linkCol` key path because only `linkCol.id` will be modified. expectNoChange { realm in let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 } token.invalidate() } @MainActor func testObservePartialKeyPath() { var ex = expectation(description: "initial notification") let token0 = collection.observe(keyPaths: [\.stringCol]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) XCTAssertEqual(modifications, [0]) case .error: XCTFail("error not expected") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect a change notification for the token observing `stringCol` keypath. ex = self.expectation(description: "change notification") nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realm() realm.beginWrite() let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.stringCol = "changed" try! realm.commitWrite() } waitForExpectations(timeout: 0.1, handler: nil) token0.invalidate() } @MainActor func testObservePartialKeyPathNoChange() { let ex = expectation(description: "initial notification") let token0 = collection.observe(keyPaths: [\.stringCol]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) default: XCTFail("Unexpected change: \(changes)") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect no notification for `stringCol` key path because only `linkCol.id` will be modified. expectNoChange { realm in let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 } token0.invalidate() } @MainActor func testObservePartialKeyPathWithLink() { var ex = expectation(description: "initial notification") let token = collection.observe(keyPaths: [\.linkCol?.id]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(_, let deletions, let insertions, let modifications): XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, []) // The reason two column changes are expected here is because the // single CTTLinkTarget object that is modified is linked to two origin objects. // The 0, 1 index refers to the origin objects. XCTAssertEqual(modifications, [0, 1]) case .error: XCTFail("error not expected") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Only expect a change notification for `linkCol.id` keypath. ex = self.expectation(description: "change notification") nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = unsafeSelf.realm() realm.beginWrite() let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 try! realm.commitWrite() } waitForExpectations(timeout: 0.2, handler: nil) token.invalidate() } @MainActor func testObservePartialKeyPathWithLinkNoChangeList() { let ex = expectation(description: "initial notification") let token = collection.observe(keyPaths: [\.linkCol]) { (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) default: XCTFail("Unexpected change: \(changes)") } ex.fulfill() } waitForExpectations(timeout: 0.2, handler: nil) // Expect no notification for `linkCol` key path because only `linkCol.id` will be modified. expectNoChange { realm in let obj = realm.objects(CTTNullableStringObjectWithLink.self).first! obj.linkCol!.id = 2 } token.invalidate() } func testObserveOnQueue() { let sema = DispatchSemaphore(value: 0) let token = collection.observe(keyPaths: nil, on: queue) { @Sendable (changes: RealmCollectionChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 2) case .update(let collection, let deletions, _, _): XCTAssertEqual(collection.count, 0) XCTAssertEqual(deletions, [0, 1]) case .error: XCTFail("Shouldn't happen") } sema.signal() } sema.wait() let realm = self.realm() try! realm.write { realm.delete(collection) } sema.wait() token.invalidate() } func testValueForKeyPath() { XCTAssertEqual(["1", "2"], collection.value(forKeyPath: "@unionOfObjects.stringCol") as! NSArray?) let theCollection = getAggregateableCollection() XCTAssertEqual(3, (theCollection.value(forKeyPath: "@count") as! NSNumber?)?.int64Value) XCTAssertEqual(3, (theCollection.value(forKeyPath: "@max.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(1, (theCollection.value(forKeyPath: "@min.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(6, (theCollection.value(forKeyPath: "@sum.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(2.0, (theCollection.value(forKeyPath: "@avg.intCol") as! NSNumber?)?.doubleValue) } func testInvalidate() { XCTAssertFalse(collection.isInvalidated) self.realm().invalidate() XCTAssertTrue(collection.realm == nil || collection.isInvalidated) } func testIsFrozen() { XCTAssertFalse(collection.isFrozen) XCTAssertTrue(collection.freeze().isFrozen) } func testThaw() { let frozen = collection.freeze() XCTAssertTrue(frozen.isFrozen) let frozenRealm = frozen.realm! assertThrows(try! frozenRealm.write {}, reason: "Can't perform transactions on a frozen Realm") let live = frozen.thaw() XCTAssertFalse(live!.isFrozen) let liveRealm = live!.realm! try! liveRealm.write { liveRealm.delete(live!) } XCTAssertTrue(live!.isEmpty) XCTAssertFalse(frozen.isEmpty) } func testThawFromDifferentThread() { nonisolated(unsafe) let frozen = collection.freeze() XCTAssertTrue(frozen.isFrozen) dispatchSyncNewThread { let live = frozen.thaw() XCTAssertFalse(live!.isFrozen) let liveRealm = live!.realm! try! liveRealm.write { liveRealm.delete(live!) } XCTAssertTrue(live!.isEmpty) XCTAssertFalse(frozen.isEmpty) } } func testThawPreviousVersion() { let frozen = collection.freeze() XCTAssertTrue(frozen.isFrozen) XCTAssertEqual(collection.count, frozen.count) let realm = collection.realm! try! realm.write { realm.delete(collection) } XCTAssertNotEqual(frozen.count, collection.count, "Frozen collections should not change") let live = frozen.thaw() XCTAssertTrue(live!.isEmpty, "Thawed collection should reflect transactions since the original reference was frozen") XCTAssertFalse(frozen.isEmpty) XCTAssertEqual(live!.count, self.collection.count) } func testThawUpdatedOnDifferentThread() { let tsr = ThreadSafeReference(to: collection) nonisolated(unsafe) var frozen: Collection? nonisolated(unsafe) var frozenQuery: Results? XCTAssertEqual(collection.count, 2) // stringCol "1" and "2" XCTAssertEqual(collection.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(collection.where { $0.stringCol == "3" }.count, 0) let configuration = self.collection.realm!.configuration dispatchSyncNewThread { let realm = try! Realm(configuration: configuration) let collection = realm.resolve(tsr)! try! realm.write { collection.first!.stringCol = "3" } try! realm.write { realm.delete(collection.last!) } let query = collection.filter("stringCol == %@", "1") frozen = collection.freeze() // Results::Mode::TableView frozenQuery = query.freeze() // Results::Mode::Query } let thawed = frozen!.thaw() XCTAssertEqual(frozen!.count, 1) XCTAssertEqual(frozen!.first?.stringCol, "3") XCTAssertEqual(frozen!.filter("stringCol == %@", "1").count, 0) XCTAssertEqual(frozen!.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(frozen!.filter("stringCol == %@", "3").count, 1) XCTAssertEqual(frozen!.where { $0.stringCol == "1" }.count, 0) XCTAssertEqual(frozen!.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(frozen!.where { $0.stringCol == "3" }.count, 1) XCTAssertEqual(thawed!.count, 2) XCTAssertEqual(thawed!.first?.stringCol, "1") XCTAssertEqual(thawed!.filter("stringCol == %@", "1").count, 1) XCTAssertEqual(thawed!.filter("stringCol == %@", "2").count, 1) XCTAssertEqual(thawed!.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(thawed!.where { $0.stringCol == "1" }.count, 1) XCTAssertEqual(thawed!.where { $0.stringCol == "2" }.count, 1) XCTAssertEqual(thawed!.where { $0.stringCol == "3" }.count, 0) XCTAssertEqual(collection.count, 2) XCTAssertEqual(collection.first?.stringCol, "1") XCTAssertEqual(collection.filter("stringCol == %@", "1").count, 1) XCTAssertEqual(collection.filter("stringCol == %@", "2").count, 1) XCTAssertEqual(collection.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(collection.where { $0.stringCol == "1" }.count, 1) XCTAssertEqual(collection.where { $0.stringCol == "2" }.count, 1) XCTAssertEqual(collection.where { $0.stringCol == "3" }.count, 0) let thawedQuery = frozenQuery!.thaw() XCTAssertEqual(frozenQuery!.count, 0) XCTAssertEqual(frozenQuery!.first?.stringCol, nil) XCTAssertEqual(frozenQuery!.filter("stringCol == %@", "1").count, 0) XCTAssertEqual(frozenQuery!.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(frozenQuery!.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(frozenQuery!.where { $0.stringCol == "1" }.count, 0) XCTAssertEqual(frozenQuery!.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(frozenQuery!.where { $0.stringCol == "3" }.count, 0) XCTAssertEqual(thawedQuery!.count, 1) XCTAssertEqual(thawedQuery!.first?.stringCol, "1") XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "1").count, 1) XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "1" }.count, 1) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "3" }.count, 0) collection.realm!.refresh() XCTAssertEqual(thawed!.count, 1) XCTAssertEqual(thawed!.first?.stringCol, "3") XCTAssertEqual(thawed!.filter("stringCol == %@", "1").count, 0) XCTAssertEqual(thawed!.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(thawed!.filter("stringCol == %@", "3").count, 1) XCTAssertEqual(thawed!.where { $0.stringCol == "1" }.count, 0) XCTAssertEqual(thawed!.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(thawed!.where { $0.stringCol == "3" }.count, 1) XCTAssertEqual(thawedQuery!.count, 0) XCTAssertEqual(thawedQuery!.first?.stringCol, nil) XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "1").count, 0) XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(thawedQuery!.filter("stringCol == %@", "3").count, 0) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "1" }.count, 0) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(thawedQuery!.where { $0.stringCol == "3" }.count, 0) XCTAssertEqual(collection.count, 1) XCTAssertEqual(collection.first?.stringCol, "3") XCTAssertEqual(collection.filter("stringCol == %@", "1").count, 0) XCTAssertEqual(collection.filter("stringCol == %@", "2").count, 0) XCTAssertEqual(collection.filter("stringCol == %@", "3").count, 1) XCTAssertEqual(collection.where { $0.stringCol == "1" }.count, 0) XCTAssertEqual(collection.where { $0.stringCol == "2" }.count, 0) XCTAssertEqual(collection.where { $0.stringCol == "3" }.count, 1) } func testThawDeletedParent() { let frozenElement = collection.first!.freeze() XCTAssertTrue(frozenElement.isFrozen) let realm = collection.realm! try! realm.write { realm.delete(collection) } XCTAssertNil(collection.first) XCTAssertNotNil(frozenElement) let thawed = frozenElement.thaw() XCTAssertNil(thawed) } func testFreezeFromWrongThread() { nonisolated(unsafe) let unsafeSelf = self nonisolated(unsafe) let unsafeCollection = self.collection! dispatchSyncNewThread { unsafeSelf.assertThrows(unsafeCollection.freeze(), reason: "Realm accessed from incorrect thread") } } func testAccessFrozenCollectionFromDifferentThread() { nonisolated(unsafe) let frozen = collection.freeze() dispatchSyncNewThread { XCTAssertEqual(frozen[0].stringCol, "1") XCTAssertEqual(frozen[1].stringCol, "2") } } func testObserveFrozenCollection() { let frozen = collection.freeze() assertThrows(frozen.observe({ _ in }), reason: "Frozen Realms do not change and do not have change notifications.") } func testQueryFrozenCollection() { let frozen = collection.freeze() XCTAssertEqual(frozen.filter("stringCol = '1'").count, 1) XCTAssertEqual(frozen.filter("stringCol = '2'").count, 1) XCTAssertEqual(frozen.filter("stringCol = '3'").count, 0) XCTAssertTrue(frozen.filter("stringCol = '3'").isFrozen) XCTAssertEqual(frozen.where { $0.stringCol == "1" }.count, 1) XCTAssertEqual(frozen.where { $0.stringCol == "2" }.count, 1) XCTAssertEqual(frozen.where { $0.stringCol == "3" }.count, 0) XCTAssertTrue(frozen.where { $0.stringCol == "3" }.isFrozen) } func testFilterWithInt8Property() { _ = makeAggregateableObjects() var results = self.realm().objects(CTTAggregateObject.self).filter("int8Col = %d", Int8(0)) XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).filter("int8Col = %d", Int8(1)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int8Col = %d", Int8(2)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int8Col = %d", Int8(3)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int8Col == 0 } XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).where { $0.int8Col == 1 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int8Col == 2 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int8Col == 3 } XCTAssertEqual(results.count, 1) } func testFilterWithInt16Property() { _ = makeAggregateableObjects() var results = self.realm().objects(CTTAggregateObject.self).filter("int16Col = %d", Int16(0)) XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).filter("int16Col = %d", Int16(1)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int16Col = %d", Int16(2)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int16Col = %d", Int16(3)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int16Col == 0 } XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).where { $0.int16Col == 1 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int16Col == 2 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int16Col == 3 } XCTAssertEqual(results.count, 1) } func testFilterWithInt32Property() { _ = makeAggregateableObjects() var results = self.realm().objects(CTTAggregateObject.self).filter("int32Col = %d", Int32(0)) XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).filter("int32Col = %d", Int32(1)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int32Col = %d", Int32(2)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int32Col = %d", Int32(3)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int32Col == 0 } XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).where { $0.int32Col == 1 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int32Col == 2 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int32Col == 3 } XCTAssertEqual(results.count, 1) } func testFilterWithInt64Property() { _ = makeAggregateableObjects() var results = self.realm().objects(CTTAggregateObject.self).filter("int64Col = %d", Int64(0)) XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).filter("int64Col = %d", Int64(1)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int64Col = %d", Int64(2)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).filter("int64Col = %d", Int64(3)) XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int64Col == 0 } XCTAssertEqual(results.count, 0) results = self.realm().objects(CTTAggregateObject.self).where { $0.int64Col == 1 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int64Col == 2 } XCTAssertEqual(results.count, 1) results = self.realm().objects(CTTAggregateObject.self).where { $0.int64Col == 3 } XCTAssertEqual(results.count, 1) } } // MARK: Results class ResultsTests: RealmCollectionTests, Results> { override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(ResultsTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } func addObjectToResults() { let realm = self.realm() try! realm.write { realm.create(CTTNullableStringObjectWithLink.self, value: ["a"]) } } @MainActor func testNotificationBlockUpdating() { var theExpectation = expectation(description: "") var calls = 0 let token = collection.observe { (changes: RealmCollectionChange) in switch changes { case .initial(let results): XCTAssertEqual(results.count, calls + 2) XCTAssertEqual(results, self.collection) case .update(let results, _, _, _): XCTAssertEqual(results.count, calls + 2) XCTAssertEqual(results, self.collection) case .error: XCTFail("Shouldn't happen") } calls += 1 theExpectation.fulfill() } waitForExpectations(timeout: 1, handler: nil) theExpectation = expectation(description: "") addObjectToResults() waitForExpectations(timeout: 1, handler: nil) token.invalidate() } @MainActor func testNotificationBlockChangeIndices() { var theExpectation = expectation(description: "") var calls = 0 let token = collection.observe { (change: RealmCollectionChange) in switch change { case .initial(let results): XCTAssertEqual(calls, 0) XCTAssertEqual(results.count, 2) case .update(let results, let deletions, let insertions, let modifications): XCTAssertEqual(calls, 1) XCTAssertEqual(results.count, 3) XCTAssertEqual(deletions, []) XCTAssertEqual(insertions, [2]) XCTAssertEqual(modifications, []) case .error(let error): XCTFail(String(describing: error)) } calls += 1 theExpectation.fulfill() } waitForExpectations(timeout: 1, handler: nil) theExpectation = expectation(description: "") addObjectToResults() waitForExpectations(timeout: 1, handler: nil) token.invalidate() } } class ResultsWithCustomInitializerTests: TestCase { func testValueForKey() { let realm = realmWithTestPath() try! realm.write { realm.add(SwiftCustomInitializerObject(stringVal: "A")) } let collection = realm.objects(SwiftCustomInitializerObject.self) let expected = Array(collection.map { $0.stringCol }) let actual = collection.value(forKey: "stringCol") as! [String]? XCTAssertEqual(expected, actual!) assertEqual(Array(collection), collection.value(forKey: "self") as! [SwiftCustomInitializerObject]) } } class ResultsDistinctTests: TestCase { func testDistinctResultsUsingKeyPaths() { let realm = realmWithTestPath() let obj1 = CTTAggregateObject() obj1.intCol = 1 obj1.trueCol = true let obj2 = CTTAggregateObject() obj2.intCol = 1 obj2.trueCol = true let obj3 = CTTAggregateObject() obj3.intCol = 1 obj3.trueCol = false let obj4 = CTTAggregateObject() obj4.intCol = 2 obj4.trueCol = false let childObj1 = CTTIntegerObject() childObj1.intCol = 1 obj1.childIntCol = childObj1 let childObj2 = CTTIntegerObject() childObj2.intCol = 1 obj2.childIntCol = childObj2 let childObj3 = CTTIntegerObject() childObj3.intCol = 2 obj3.childIntCol = childObj3 try! realm.write { realm.add(obj1) realm.add(obj2) realm.add(obj3) realm.add(obj4) } let collection = realm.objects(CTTAggregateObject.self) var distinctResults = collection.distinct(by: ["intCol"]) var expected = [["int": 1], ["int": 2]] var actual = Array(distinctResults.map { ["int": $0.intCol] }) XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) distinctResults = collection.distinct(by: ["intCol", "trueCol"]) expected = [["int": 1, "true": 1], ["int": 1, "true": 0], ["int": 2, "true": 0]] actual = distinctResults.map { ["int": $0.intCol, "true": $0.trueCol ? 1 : 0] } XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) distinctResults = collection.distinct(by: ["childIntCol.intCol"]) expected = [["int": 1], ["int": 2]] actual = distinctResults.map { ["int": $0.childIntCol!.intCol] } XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) assertThrows(collection.distinct(by: ["childCol"])) assertThrows(collection.distinct(by: ["@sum.intCol"])) assertThrows(collection.distinct(by: ["stringListCol"])) } func testDistinctResultsUsingSwiftKeyPaths() { let realm = realmWithTestPath() let obj1 = CTTAggregateObject() obj1.intCol = 1 obj1.trueCol = true let obj2 = CTTAggregateObject() obj2.intCol = 1 obj2.trueCol = true let obj3 = CTTAggregateObject() obj3.intCol = 1 obj3.trueCol = false let obj4 = CTTAggregateObject() obj4.intCol = 2 obj4.trueCol = false let childObj1 = CTTIntegerObject() childObj1.intCol = 1 obj1.childIntCol = childObj1 let childObj2 = CTTIntegerObject() childObj2.intCol = 1 obj2.childIntCol = childObj2 let childObj3 = CTTIntegerObject() childObj3.intCol = 2 obj3.childIntCol = childObj3 try! realm.write { realm.add(obj1) realm.add(obj2) realm.add(obj3) realm.add(obj4) } let collection = realm.objects(CTTAggregateObject.self) var distinctResults = collection.distinct(by: [\CTTAggregateObject.intCol]) var expected = [["int": 1], ["int": 2]] var actual = Array(distinctResults.map { ["int": $0.intCol] }) XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) distinctResults = collection.distinct(by: [\CTTAggregateObject.intCol, \CTTAggregateObject.trueCol]) expected = [["int": 1, "true": 1], ["int": 1, "true": 0], ["int": 2, "true": 0]] actual = distinctResults.map { ["int": $0.intCol, "true": $0.trueCol ? 1 : 0] } XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) distinctResults = collection.distinct(by: [\CTTAggregateObject.childIntCol?.intCol]) expected = [["int": 1], ["int": 2]] actual = distinctResults.map { ["int": $0.childIntCol!.intCol] } XCTAssertEqual(expected as NSObject, actual as NSObject) assertEqual(distinctResults.map { $0 }, distinctResults.value(forKey: "self") as! [CTTAggregateObject]) } } class ResultsEquatabilityTests: TestCase { func testEquatability() { let realm = realmWithTestPath() try! realm.write { realm.add(SwiftCustomInitializerObject(stringVal: "A")) } let resultsA = realm.objects(SwiftCustomInitializerObject.self) let resultsB = realm.objects(SwiftCustomInitializerObject.self) XCTAssertNotEqual(resultsA, resultsB) XCTAssertEqual(resultsA, resultsA) XCTAssertEqual(resultsB, resultsB) } } class ResultsFromTableTests: ResultsTests { override func getCollection(_ realm: Realm) -> Results { return realm.objects(CTTNullableStringObjectWithLink.self) } override func getAggregateableCollectionInWrite(_ realm: Realm) -> Results { _ = makeAggregateableObjectsInWriteTransaction() return realm.objects(CTTAggregateObject.self) } } class ResultsFromTableViewTests: ResultsTests { override func getCollection(_ realm: Realm) -> Results { return realm.objects(CTTNullableStringObjectWithLink.self).filter("stringCol != ''") } override func getAggregateableCollectionInWrite(_ realm: Realm) -> Results { _ = makeAggregateableObjectsInWriteTransaction() return realm.objects(CTTAggregateObject.self).filter("trueCol == true") } } class ResultsFromLinkViewTests: ResultsTests { override func getCollection(_ realm: Realm) -> Results { let array = realm.create(CTTStringList.self, value: [[str1, str2]]) return array.array.filter(NSPredicate(value: true)) } override func getAggregateableCollectionInWrite(_ realm: Realm) -> Results { let list = CTTAggregateObjectList() realm.add(list) list.list.append(objectsIn: makeAggregateableObjectsInWriteTransaction()) return list.list.filter(NSPredicate(value: true)) } override func addObjectToResults() { let realm = self.realm() try! realm.write { let array = realm.objects(CTTStringList.self).last! array.array.append(realm.create(CTTNullableStringObjectWithLink.self, value: ["a"])) } } } // MARK: List class ListRealmCollectionTests: RealmCollectionTests, List> { override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(ListRealmCollectionTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } override func testDescription() { // swiftlint:disable:next line_length assertMatches(collection.description, "List <0x[0-9a-f]+> \\(\n\t\\[0\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 1;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\},\n\t\\[1\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 2;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\}\n\\)") } } class ListUnmanagedRealmCollectionTests: ListRealmCollectionTests { override func getCollection(_ realm: Realm) -> List { return CTTStringList(value: [[str1, str2]]).array } override func getAggregateableCollectionInWrite(_ realm: Realm) -> List { return CTTAggregateObjectList(value: [makeAggregateableObjectsInWriteTransaction()]).list } override func testRealm() { XCTAssertNil(collection.realm) } override func testCount() { XCTAssertEqual(2, collection.count) } override func testIndexOfObject() { XCTAssertEqual(0, collection.index(of: str1)!) XCTAssertEqual(1, collection.index(of: str2)!) } override func testSortWithDescriptor() { let collection = getAggregateableCollection() assertThrows(collection.sorted(by: [SortDescriptor(keyPath: "intCol", ascending: true)])) assertThrows(collection.sorted(by: [SortDescriptor(keyPath: "doubleCol", ascending: false), SortDescriptor(keyPath: "intCol", ascending: false)])) } override func testSortWithDescriptorBySwiftKeyPath() { let collection = getAggregateableCollection() assertThrows(collection.sorted(by: [SortDescriptor(keyPath: \CTTAggregateObject.intCol, ascending: true)])) assertThrows(collection.sorted(by: [SortDescriptor(keyPath: \CTTAggregateObject.doubleCol, ascending: false), SortDescriptor(keyPath: "intCol", ascending: false)])) } override func testFastEnumerationWithMutation() { // No standalone removal interface provided on RealmCollectionType } override func testFirst() { XCTAssertEqual(str1, collection.first!) } override func testLast() { XCTAssertEqual(str2, collection.last!) } // MARK: Things not implemented in standalone override func testSortWithProperty() { assertThrows(collection.sorted(byKeyPath: "stringCol", ascending: true)) assertThrows(collection.sorted(byKeyPath: "noSuchCol", ascending: true)) } override func testSortWithSwiftKeyPath() { assertThrows(collection.sorted(by: \.stringCol, ascending: true)) } override func testFilterFormat() { assertThrows(collection.filter("stringCol = '1'")) assertThrows(collection.filter("noSuchCol = '1'")) assertThrows(collection.where { $0.stringCol == "1" }) } override func testFilterPredicate() { let pred1 = NSPredicate(format: "stringCol = '1'") let pred2 = NSPredicate(format: "noSuchCol = '2'") assertThrows(collection.filter(pred1)) assertThrows(collection.filter(pred2)) } override func testFilterWithAnyVarags() { // Functionality not supported for standalone lists; don't test. } override func testArrayAggregateWithSwiftObjectDoesntThrow() { assertThrows(collection.filter("ANY stringListCol == %@", CTTNullableStringObjectWithLink())) } override func testObserve() { assertThrows(collection.observe { _ in }) } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testCancelTaskForObservationInit() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActor() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveInsideActor() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActorWithStringKeyPath() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActorWithKeyPath() async throws {} override func testObserveKeyPath() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathNoChange() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLink() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLinkNoChange() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLinkNoChangeList() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPath() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathNoChange() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathWithLink() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathWithLinkNoChangeList() { assertThrows(collection.observe { _ in }) } override func testObserveOnQueue() { assertThrows(collection.observe(on: DispatchQueue(label: "bg")) { _ in }) } func testFreeze() { assertThrows(collection.freeze(), reason: "This method may only be called on RLMArray instances retrieved from an RLMRealm") } override func testIsFrozen() { XCTAssertFalse(collection.isFrozen) } override func testThaw() {} override func testThawFromDifferentThread() {} override func testThawPreviousVersion() {} override func testThawDeletedParent() {} override func testThawUpdatedOnDifferentThread() {} override func testFreezeFromWrongThread() {} override func testAccessFrozenCollectionFromDifferentThread() {} override func testObserveFrozenCollection() {} override func testQueryFrozenCollection() {} } class ListNewlyAddedRealmCollectionTests: ListRealmCollectionTests { override func getCollection(_ realm: Realm) -> List { let array = CTTStringList(value: [[str1, str2]]) realm.add(array) return array.array } override func getAggregateableCollectionInWrite(_ realm: Realm) -> List { let list = CTTAggregateObjectList(value: [makeAggregateableObjectsInWriteTransaction()]) realm.add(list) return list.list } } class ListNewlyCreatedRealmCollectionTests: ListRealmCollectionTests { override func getCollection(_ realm: Realm) -> List { realm.create(CTTStringList.self, value: [[str1, str2]]).array } override func getAggregateableCollectionInWrite(_ realm: Realm) -> List { realm.create(CTTAggregateObjectList.self, value: [makeAggregateableObjectsInWriteTransaction()]).list } } class ListRetrievedRealmCollectionTests: ListRealmCollectionTests { override func getCollection(_ realm: Realm) -> List { _ = realm.create(CTTStringList.self, value: [[str1, str2]]) return realm.objects(CTTStringList.self).first!.array } override func getAggregateableCollectionInWrite(_ realm: Realm) -> List { _ = realm.create(CTTAggregateObjectList.self, value: [makeAggregateableObjectsInWriteTransaction()]) return realm.objects(CTTAggregateObjectList.self).first!.list } } // MARK: MutableSet class MutableSetRealmCollectionTests: RealmCollectionTests, MutableSet> { override class var defaultTestSuite: XCTestSuite { // Don't run tests for the base class if isEqual(MutableSetRealmCollectionTests.self) { return XCTestSuite(name: "empty") } return super.defaultTestSuite } // Tests which don't apply to Set override func testIndexOfObject() { } override func testIndexOfFormat() { } override func testIndexOfPredicate() { } override func testIndexOfQuery() {} // These can give any object in the Set override func testFirst() { let first = collection.first! XCTAssert(first.isSameObject(as: str1) || first.isSameObject(as: str2)) } override func testLast() { let last = collection.last! XCTAssert(last.isSameObject(as: str1) || last.isSameObject(as: str2)) } override func testValueForKey() { let expected = Set(collection.map { $0.stringCol }) let actual = collection.value(forKey: "stringCol") as Any? as! Set? XCTAssertEqual(expected, actual!) let actual2 = collection.value(forKey: "stringCol") as [AnyObject] as! [String] XCTAssertEqual(expected, Set(actual2)) // comparing value(forKey: "self") won't work because an NSSet will be produced, we don't know // the order of the objects and using [NSSet contains] won't work for a linked object. } override func testValueForKeyPath() { let collection = getAggregateableCollection() XCTAssertEqual(3, (collection.value(forKeyPath: "@count") as! NSNumber?)?.int64Value) XCTAssertEqual(3, (collection.value(forKeyPath: "@max.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(1, (collection.value(forKeyPath: "@min.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(6, (collection.value(forKeyPath: "@sum.intCol") as! NSNumber?)?.int64Value) XCTAssertEqual(2.0, (collection.value(forKeyPath: "@avg.intCol") as! NSNumber?)?.doubleValue) } override func testAccessFrozenCollectionFromDifferentThread() { nonisolated(unsafe) let frozen = collection.freeze() dispatchSyncNewThread { let o = frozen.map { $0.stringCol } XCTAssertTrue(o.contains("1")) XCTAssertTrue(o.contains("2")) } } override func testFastEnumeration() { var str = "" for obj in collection { str += obj.stringCol! } XCTAssertTrue((str == "12") || (str == "21")) } override func testDescription() { // ordering is not guaranteed, so handle that the objects could be in any position // swiftlint:disable:next line_length assertMatches(collection.description, "MutableSet <0x[0-9a-f]+> \\(\n\t\\[0\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = [0-9]+;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\},\n\t\\[1\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = [0-9]+;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\}\n\\)") } } class MutableSetUnmanagedRealmCollectionTests: MutableSetRealmCollectionTests { override func getCollection(_ realm: Realm) -> MutableSet { return CTTStringSet(value: [[str1, str2]]).set } override func getAggregateableCollectionInWrite(_ realm: Realm) -> MutableSet { return CTTAggregateObjectSet(value: [makeAggregateableObjectsInWriteTransaction()]).set } override func testRealm() { XCTAssertNil(collection.realm) } override func testCount() { XCTAssertEqual(2, collection.count) } override func testSortWithDescriptor() { let collection = getAggregateableCollection() assertThrows(collection.sorted(by: [SortDescriptor(keyPath: "intCol", ascending: true)])) assertThrows(collection.sorted(by: [SortDescriptor(keyPath: "doubleCol", ascending: false), SortDescriptor(keyPath: "intCol", ascending: false)])) } override func testSortWithDescriptorBySwiftKeyPath() { let collection = getAggregateableCollection() assertThrows(collection.sorted(by: [SortDescriptor(keyPath: \CTTAggregateObject.intCol, ascending: true)])) assertThrows(collection.sorted(by: [SortDescriptor(keyPath: \CTTAggregateObject.doubleCol, ascending: false), SortDescriptor(keyPath: \CTTAggregateObject.intCol, ascending: false)])) } override func testFastEnumerationWithMutation() { // No standalone removal interface provided on RealmCollectionType } // MARK: Things not implemented in standalone override func testSortWithProperty() { assertThrows(collection.sorted(byKeyPath: "stringCol", ascending: true)) assertThrows(collection.sorted(byKeyPath: "noSuchCol", ascending: true)) } override func testSortWithSwiftKeyPath() { assertThrows(collection.sorted(by: \.stringCol, ascending: true)) } override func testFilterFormat() { assertThrows(collection.filter("stringCol = '1'")) assertThrows(collection.filter("noSuchCol = '1'")) } override func testFilterPredicate() { let pred1 = NSPredicate(format: "stringCol = '1'") let pred2 = NSPredicate(format: "noSuchCol = '2'") assertThrows(collection.filter(pred1)) assertThrows(collection.filter(pred2)) } override func testFilterWithAnyVarags() { // Functionality not supported for standalone lists; don't test. } override func testArrayAggregateWithSwiftObjectDoesntThrow() { assertThrows(collection.filter("ANY stringListCol == %@", CTTNullableStringObjectWithLink())) } override func testObserve() { assertThrows(collection.observe { _ in }) } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testCancelTaskForObservationInit() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActor() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveInsideActor() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActorWithStringKeyPath() async throws {} @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) override func testObserveOnActorWithKeyPath() async throws {} override func testObserveOnQueue() { assertThrows(collection.observe(on: DispatchQueue(label: "bg")) { _ in }) } override func testObserveKeyPath() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathNoChange() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLink() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLinkNoChange() { assertThrows(collection.observe { _ in }) } override func testObserveKeyPathWithLinkNoChangeList() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPath() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathNoChange() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathWithLink() { assertThrows(collection.observe { _ in }) } override func testObservePartialKeyPathWithLinkNoChangeList() { assertThrows(collection.observe { _ in }) } func testFreeze() { assertThrows(collection.freeze(), reason: "This method may only be called on RLMSet instances retrieved from an RLMRealm") } override func testIsFrozen() { XCTAssertFalse(collection.isFrozen) } override func testThaw() {} override func testThawFromDifferentThread() {} override func testThawPreviousVersion() {} override func testThawDeletedParent() {} override func testThawUpdatedOnDifferentThread() {} override func testFreezeFromWrongThread() {} override func testAccessFrozenCollectionFromDifferentThread() {} override func testObserveFrozenCollection() {} override func testQueryFrozenCollection() {} } class MutableSetNewlyAddedRealmCollectionTests: MutableSetRealmCollectionTests { override func getCollection(_ realm: Realm) -> MutableSet { let set = CTTStringSet(value: [[str1, str2]]) realm.add(set) return set.set } override func getAggregateableCollectionInWrite(_ realm: Realm) -> MutableSet { let set = CTTAggregateObjectSet(value: [makeAggregateableObjectsInWriteTransaction()]) realm.add(set) return set.set } } class MutableSetNewlyCreatedRealmCollectionTests: MutableSetRealmCollectionTests { override func getCollection(_ realm: Realm) -> MutableSet { realm.create(CTTStringSet.self, value: [[str1, str2]]).set } override func getAggregateableCollectionInWrite(_ realm: Realm) -> MutableSet { realm.create(CTTAggregateObjectSet.self, value: [makeAggregateableObjectsInWriteTransaction()]).set } } class MutableSetRetrievedRealmCollectionTests: MutableSetRealmCollectionTests { override func getCollection(_ realm: Realm) -> MutableSet { _ = realm.create(CTTStringSet.self, value: [[str1, str2]]) return realm.objects(CTTStringSet.self).first!.set } override func getAggregateableCollectionInWrite(_ realm: Realm) -> MutableSet { _ = realm.create(CTTAggregateObjectSet.self, value: [makeAggregateableObjectsInWriteTransaction()]) return realm.objects(CTTAggregateObjectSet.self).first!.set } } class LinkingObjectsCollectionTypeTests: RealmCollectionTests, LinkingObjects> { override func getCollection(_ realm: Realm) -> LinkingObjects { let target = realm.create(CTTLinkTarget.self, value: [0]) for object in realm.objects(CTTNullableStringObjectWithLink.self) { object.linkCol = target } return target.stringObjects } override func getAggregateableCollectionInWrite(_ realm: Realm) -> LinkingObjects { let objects = makeAggregateableObjectsInWriteTransaction() let target = realm.create(CTTLinkTarget.self, value: [0]) for object in objects { object.linkCol = target } return target.aggregateObjects } override func testDescription() { // swiftlint:disable:next line_length assertMatches(collection.description, "LinkingObjects <0x[0-9a-f]+> \\(\n\t\\[0\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 1;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 0;\n\t\t\\};\n\t\\},\n\t\\[1\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 2;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 0;\n\t\t\\};\n\t\\}\n\\)") } } class LinkingObjectsEquatabilityTests: TestCase { func testEquatability() { let realm = realmWithTestPath() var parentA: CTTLinkTarget! var parentB: CTTLinkTarget! try! realm.write { parentA = realm.create(CTTLinkTarget.self, value: [0]) parentB = realm.create(CTTLinkTarget.self, value: [0]) let targetA = realm.create(CTTNullableStringObjectWithLink.self) let targetB = realm.create(CTTNullableStringObjectWithLink.self) targetA.linkCol = parentA targetB.linkCol = parentB } XCTAssertNotEqual(parentA.stringObjects, parentA.stringObjects) XCTAssertNotEqual(parentA.stringObjects, parentB.stringObjects) let ref = parentA.stringObjects XCTAssertEqual(ref, ref) } } class AnyRealmCollectionTests: RealmCollectionTests, AnyRealmCollection> { override func getCollection(_ realm: Realm) -> AnyRealmCollection { AnyRealmCollection(realm.create(CTTStringList.self, value: [[str1, str2]]).array) } override func getAggregateableCollectionInWrite(_ realm: Realm) -> AnyRealmCollection { AnyRealmCollection(realm.create(CTTAggregateObjectSet.self, value: [makeAggregateableObjectsInWriteTransaction()]).set) } override func testDescription() { // swiftlint:disable:next line_length assertMatches(collection.description, "AnyRealmCollection <0x[0-9a-f]+> \\(\n\t\\[0\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 1;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\},\n\t\\[1\\] CTTNullableStringObjectWithLink \\{\n\t\tstringCol = 2;\n\t\tlinkCol = CTTLinkTarget \\{\n\t\t\tid = 1;\n\t\t\\};\n\t\\}\n\\)") } } class AnyRealmCollectionEquatabilityTests: TestCase { func testEquatability() { let realm = realmWithTestPath() try! realm.write { realm.add(SwiftCustomInitializerObject(stringVal: "A")) realm.add(SwiftListObject(value: ["int": [1, 2, 3]])) realm.add(SwiftListObject(value: ["int": [1, 2, 3]])) } let resultsA = AnyRealmCollection(realm.objects(SwiftCustomInitializerObject.self)) let resultsB = AnyRealmCollection(realm.objects(SwiftCustomInitializerObject.self)) XCTAssertNotEqual(resultsA, resultsB) XCTAssertEqual(resultsA, resultsA) XCTAssertEqual(resultsB, resultsB) let listResultsA = realm.objects(SwiftListObject.self)[0] let listResultsB = realm.objects(SwiftListObject.self)[1] let listA = AnyRealmCollection(listResultsA.int) let listB = AnyRealmCollection(listResultsB.int) XCTAssertNotEqual(listA, listB) XCTAssertEqual(listA, listA) XCTAssertEqual(listB, listB) } } ================================================ FILE: RealmSwift/Tests/RealmConfigurationTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift import class Realm.Private.RLMRealmConfiguration class RealmConfigurationTests: TestCase { func testDefaultConfiguration() { let defaultConfiguration = Realm.Configuration.defaultConfiguration XCTAssertEqual(defaultConfiguration.fileURL, try! Realm().configuration.fileURL) XCTAssertNil(defaultConfiguration.inMemoryIdentifier) XCTAssertNil(defaultConfiguration.encryptionKey) XCTAssertFalse(defaultConfiguration.readOnly) XCTAssertEqual(defaultConfiguration.schemaVersion, 0) XCTAssert(defaultConfiguration.migrationBlock == nil) } func testSetDefaultConfiguration() { let fileURL = Realm.Configuration.defaultConfiguration.fileURL let configuration = Realm.Configuration(fileURL: URL(fileURLWithPath: "/dev/null")) Realm.Configuration.defaultConfiguration = configuration XCTAssertEqual(Realm.Configuration.defaultConfiguration.fileURL, URL(fileURLWithPath: "/dev/null")) Realm.Configuration.defaultConfiguration.fileURL = fileURL } func testCannotSetMutuallyExclusiveProperties() { var configuration = Realm.Configuration() configuration.readOnly = true configuration.deleteRealmIfMigrationNeeded = true assertThrows(try! Realm(configuration: configuration), reason: "Cannot set `deleteRealmIfMigrationNeeded` when `readOnly` is set.") } } ================================================ FILE: RealmSwift/Tests/RealmPropertyTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import Realm import RealmSwift class RealmPropertyObject: Object { var optionalIntValue = RealmProperty() var optionalInt8Value = RealmProperty() var optionalInt16Value = RealmProperty() var optionalInt32Value = RealmProperty() var optionalInt64Value = RealmProperty() var optionalFloatValue = RealmProperty() var optionalDoubleValue = RealmProperty() var optionalBoolValue = RealmProperty() // required for schema validation, but not used in tests. @objc dynamic var int = 0 } class RealmPropertyTests: TestCase { private func test(keyPath: KeyPath>, value: T?) { let o = RealmPropertyObject() o[keyPath: keyPath].value = value XCTAssertEqual(o[keyPath: keyPath].value, value) o[keyPath: keyPath].value = nil XCTAssertNil(o[keyPath: keyPath].value) let realm = realmWithTestPath() try! realm.write { realm.add(o) } XCTAssertNil(o[keyPath: keyPath].value) try! realm.write { o[keyPath: keyPath].value = value } XCTAssertEqual(o[keyPath: keyPath].value, value) } func testObject() { test(keyPath: \.optionalIntValue, value: 123456) test(keyPath: \.optionalInt8Value, value: 127 as Int8) test(keyPath: \.optionalInt16Value, value: 32766 as Int16) test(keyPath: \.optionalInt32Value, value: 2147483647 as Int32) test(keyPath: \.optionalInt64Value, value: 0x7FFFFFFFFFFFFFFF as Int64) test(keyPath: \.optionalFloatValue, value: 12345.6789 as Float) test(keyPath: \.optionalDoubleValue, value: 12345.6789 as Double) test(keyPath: \.optionalBoolValue, value: true) } } ================================================ FILE: RealmSwift/Tests/RealmSwiftTests-BridgingHeader.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "TestUtils.h" #import "RLMAssertions.h" #import "RLMTestCase.h" ================================================ FILE: RealmSwift/Tests/RealmTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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. // //////////////////////////////////////////////////////////////////////////// #if DEBUG @testable import RealmSwift #else import RealmSwift #endif import Foundation import Realm import XCTest #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif @available(*, deprecated) // Silence deprecation warnings for RealmOptional class RealmTests: TestCase { enum TestError: Error { case intentional } func testFileURL() { XCTAssertEqual(try! Realm(fileURL: testRealmURL()).configuration.fileURL, testRealmURL()) } func testReadOnly() { autoreleasepool { XCTAssertEqual(try! Realm().configuration.readOnly, false) try! Realm().write { try! Realm().create(SwiftIntObject.self, value: [100]) } } let config = Realm.Configuration(fileURL: defaultRealmURL(), readOnly: true) let readOnlyRealm = try! Realm(configuration: config) XCTAssertEqual(true, readOnlyRealm.configuration.readOnly) XCTAssertEqual(1, readOnlyRealm.objects(SwiftIntObject.self).count) assertThrows(try! Realm(), "Realm has different readOnly settings") } func testOpeningInvalidPathThrows() { let url = URL(fileURLWithPath: "/dev/null/foo") assertFails(.fileOperationFailed, url.appendingPathExtension("lock"), "Failed to open file at path '\(url.path).lock': parent path is not a directory") { try Realm(configuration: .init(fileURL: url)) } } func testReadOnlyFile() throws { autoreleasepool { let realm = try! Realm(fileURL: testRealmURL()) try! realm.write { realm.create(SwiftStringObject.self, value: ["a"]) } } let fileManager = FileManager.default try! fileManager.setAttributes([FileAttributeKey.immutable: true], ofItemAtPath: testRealmURL().path) // Should not be able to open read-write assertFails(.filePermissionDenied, testRealmURL(), "Failed to open Realm file at path '\(testRealmURL().path)': Operation not permitted. Please use a path where your app has read-write permissions.") { try Realm(fileURL: testRealmURL()) } assertSucceeds { let realm = try Realm(configuration: Realm.Configuration(fileURL: testRealmURL(), readOnly: true)) XCTAssertEqual(1, realm.objects(SwiftStringObject.self).count) } try! fileManager.setAttributes([FileAttributeKey.immutable: false], ofItemAtPath: testRealmURL().path) } func testReadOnlyRealmMustExist() { assertFails(.fileNotFound, defaultRealmURL(), "Failed to open Realm file at path '\(defaultRealmURL().path)': No such file or directory") { try Realm(configuration: Realm.Configuration(fileURL: defaultRealmURL(), readOnly: true)) } } func testFilePermissionDenied() { autoreleasepool { _ = try! Realm(fileURL: testRealmURL()) } // Make Realm at test path temporarily unreadable let fileManager = FileManager.default let permissions = try! fileManager .attributesOfItem(atPath: testRealmURL().path)[FileAttributeKey.posixPermissions] as! NSNumber try! fileManager.setAttributes([FileAttributeKey.posixPermissions: 0000], ofItemAtPath: testRealmURL().path) assertFails(.filePermissionDenied, testRealmURL(), "Failed to open Realm file at path '\(testRealmURL().path)': Permission denied. Please use a path where your app has read-write permissions.") { try Realm(fileURL: testRealmURL()) } try! fileManager.setAttributes([FileAttributeKey.posixPermissions: permissions], ofItemAtPath: testRealmURL().path) } #if !SWIFT_PACKAGE && DEBUG func testUnsupportedFileFormatVersion() { let config = Realm.Configuration.defaultConfiguration let bundledRealmPath = Bundle(for: RealmTests.self).path(forResource: "fileformat-pre-null.realm", ofType: nil)! try! FileManager.default.copyItem(atPath: bundledRealmPath, toPath: config.fileURL!.path) assertFails(.unsupportedFileFormatVersion, "Database has an unsupported version (2) and cannot be upgraded") { try Realm(configuration: config) } } func testFileFormatUpgradeRequiredButDisabled() { var config = Realm.Configuration.defaultConfiguration let bundledRealmPath = Bundle(for: RealmTests.self).path(forResource: "file-format-version-21.realm", ofType: nil)! try! FileManager.default.copyItem(atPath: bundledRealmPath, toPath: config.fileURL!.path) config.disableFormatUpgrade = true assertFails(.fileFormatUpgradeRequired, "Database upgrade required but prohibited.") { try Realm(configuration: config) } } #endif func testSchema() { let schema = try! Realm().schema XCTAssert(schema as AnyObject is Schema) XCTAssertEqual(1, schema.objectSchema.filter({ $0.className == "SwiftStringObject" }).count) } func testIsEmpty() { let realm = try! Realm() XCTAssert(realm.isEmpty, "Realm should be empty on creation.") realm.beginWrite() realm.create(SwiftStringObject.self, value: ["a"]) XCTAssertFalse(realm.isEmpty, "Realm should not be empty within a write transaction after adding an object.") realm.cancelWrite() XCTAssertTrue(realm.isEmpty, "Realm should be empty after canceling a write transaction that added an object.") realm.beginWrite() realm.create(SwiftStringObject.self, value: ["a"]) try! realm.commitWrite() XCTAssertFalse(realm.isEmpty, "Realm should not be empty after committing a write transaction that added an object.") } func testInit() { XCTAssertEqual(try! Realm(fileURL: testRealmURL()).configuration.fileURL, testRealmURL()) } func testInitFailable() { FileManager.default.createFile(atPath: defaultRealmURL().path, contents: "a".data(using: String.Encoding.utf8, allowLossyConversion: false), attributes: nil) assertFails(.invalidDatabase, defaultRealmURL(), "Failed to open Realm file at path '\(defaultRealmURL().path)': file is non-empty but too small (1 bytes) to be a valid Realm.") { _ = try Realm() } } func testInitInMemory() { autoreleasepool { let realm = inMemoryRealm("identifier") try! realm.write { realm.create(SwiftIntObject.self, value: [1]) return } } let realm = inMemoryRealm("identifier") XCTAssertEqual(realm.objects(SwiftIntObject.self).count, 0) try! realm.write { realm.create(SwiftIntObject.self, value: [1]) XCTAssertEqual(realm.objects(SwiftIntObject.self).count, 1) inMemoryRealm("identifier").create(SwiftIntObject.self, value: [1]) XCTAssertEqual(realm.objects(SwiftIntObject.self).count, 2) } let realm2 = inMemoryRealm("identifier2") XCTAssertEqual(realm2.objects(SwiftIntObject.self).count, 0) } func testInitCustomClassList() { let configuration = Realm.Configuration(fileURL: Realm.Configuration.defaultConfiguration.fileURL, objectTypes: [ EmbeddedTreeObject1.self, EmbeddedTreeObject2.self, EmbeddedTreeObject3.self, EmbeddedParentObject.self, SwiftStringObject.self ]) let sorted = configuration.objectTypes!.sorted { $0.className() < $1.className() } XCTAssertTrue(sorted[0] is EmbeddedParentObject.Type) XCTAssertTrue(sorted[1] is EmbeddedTreeObject1.Type) XCTAssertTrue(sorted[2] is EmbeddedTreeObject2.Type) XCTAssertTrue(sorted[3] is EmbeddedTreeObject3.Type) XCTAssertTrue(sorted[4] is SwiftStringObject.Type) let realm = try! Realm(configuration: configuration) XCTAssertEqual(["EmbeddedParentObject", "EmbeddedTreeObject1", "EmbeddedTreeObject2", "EmbeddedTreeObject3", "SwiftStringObject"], realm.schema.objectSchema.map { $0.className }.sorted()) } func testWrite() { try! Realm().write { self.assertThrows(try! Realm().beginWrite()) self.assertThrows(try! Realm().write { }) try! Realm().create(SwiftStringObject.self, value: ["1"]) XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) } XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) } func testDynamicWrite() { try! Realm().write { self.assertThrows(try! Realm().beginWrite()) self.assertThrows(try! Realm().write { }) try! Realm().dynamicCreate("SwiftStringObject", value: ["1"]) XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) } XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1) } func testWriteWithoutNotifying() { let realm = try! Realm() let token = realm.observe { _, _ in XCTFail("should not have been called") } try! realm.write(withoutNotifying: [token]) { realm.deleteAll() } // local realm notifications are called synchronously so no need to wait for anything token.invalidate() } func testDynamicWriteSubscripting() { let realm = try! Realm() realm.beginWrite() let object = realm.dynamicCreate("SwiftStringObject", value: ["1"]) try! realm.commitWrite() XCTAssertNotNil(object, "Dynamic Object Creation Failed") let stringVal = object["stringCol"] as! String XCTAssertEqual(stringVal, "1", "Object Subscripting Failed") } func testBeginWrite() { let realm = try! Realm() realm.beginWrite() assertThrows(realm.beginWrite()) realm.cancelWrite() realm.beginWrite() realm.create(SwiftStringObject.self, value: ["1"]) XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) } func testWriteReturning() { let realm = try! Realm() let object = try! realm.write { return realm.create(SwiftStringObject.self, value: ["1"]) } XCTAssertEqual(object.stringCol, "1") } func testCommitWrite() { let realm = try! Realm() realm.beginWrite() realm.create(SwiftStringObject.self, value: ["1"]) try! realm.commitWrite() XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) realm.beginWrite() } func testCancelWrite() { let realm = try! Realm() assertThrows(realm.cancelWrite()) realm.beginWrite() realm.create(SwiftStringObject.self, value: ["1"]) realm.cancelWrite() XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) try! realm.write { self.assertThrows(self.realmWithTestPath().cancelWrite()) let object = realm.create(SwiftStringObject.self) realm.cancelWrite() XCTAssertTrue(object.isInvalidated) XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } func testThrowsWrite() { assertFails(TestError.intentional) { try Realm().write { throw TestError.intentional } } assertFails(TestError.intentional) { try Realm().write { try! Realm().create(SwiftStringObject.self, value: ["1"]) throw TestError.intentional } } } func testInWriteTransaction() { let realm = try! Realm() XCTAssertFalse(realm.isInWriteTransaction) realm.beginWrite() XCTAssertTrue(realm.isInWriteTransaction) realm.cancelWrite() try! realm.write { XCTAssertTrue(realm.isInWriteTransaction) realm.cancelWrite() XCTAssertFalse(realm.isInWriteTransaction) } realm.beginWrite() realm.invalidate() XCTAssertFalse(realm.isInWriteTransaction) } func testAddSingleObject() { let realm = try! Realm() assertThrows(realm.add(SwiftObject())) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) var defaultRealmObject: SwiftObject! try! realm.write { defaultRealmObject = SwiftObject() realm.add(defaultRealmObject) XCTAssertEqual(1, realm.objects(SwiftObject.self).count) realm.add(defaultRealmObject) XCTAssertEqual(1, realm.objects(SwiftObject.self).count) } XCTAssertEqual(1, realm.objects(SwiftObject.self).count) let testRealm = realmWithTestPath() try! testRealm.write { self.assertThrows(testRealm.add(defaultRealmObject)) } } func testAddWithUpdateSingleObject() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftPrimaryStringObject.self).count) var defaultRealmObject: SwiftPrimaryStringObject! try! realm.write { defaultRealmObject = SwiftPrimaryStringObject() realm.add(defaultRealmObject, update: .all) XCTAssertEqual(1, realm.objects(SwiftPrimaryStringObject.self).count) realm.add(SwiftPrimaryStringObject(), update: .all) XCTAssertEqual(1, realm.objects(SwiftPrimaryStringObject.self).count) } XCTAssertEqual(1, realm.objects(SwiftPrimaryStringObject.self).count) let testRealm = realmWithTestPath() try! testRealm.write { self.assertThrows(testRealm.add(defaultRealmObject, update: .all)) } } func testAddMultipleObjects() { let realm = try! Realm() assertThrows(realm.add([SwiftObject(), SwiftObject()])) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) try! realm.write { let objs = [SwiftObject(), SwiftObject()] realm.add(objs) XCTAssertEqual(2, realm.objects(SwiftObject.self).count) } XCTAssertEqual(2, realm.objects(SwiftObject.self).count) let testRealm = realmWithTestPath() try! testRealm.write { self.assertThrows(testRealm.add(realm.objects(SwiftObject.self))) } } func testAddWithUpdateMultipleObjects() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftPrimaryStringObject.self).count) try! realm.write { let objs = [SwiftPrimaryStringObject(), SwiftPrimaryStringObject()] realm.add(objs, update: .all) XCTAssertEqual(1, realm.objects(SwiftPrimaryStringObject.self).count) } XCTAssertEqual(1, realm.objects(SwiftPrimaryStringObject.self).count) let testRealm = realmWithTestPath() try! testRealm.write { self.assertThrows(testRealm.add(realm.objects(SwiftPrimaryStringObject.self), update: .all)) } } // create() tests are in ObjectCreationTests.swift func testDeleteSingleObject() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftObject.self).count) assertThrows(realm.delete(SwiftObject())) var defaultRealmObject: SwiftObject! try! realm.write { defaultRealmObject = SwiftObject() self.assertThrows(realm.delete(defaultRealmObject)) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) realm.add(defaultRealmObject) XCTAssertEqual(1, realm.objects(SwiftObject.self).count) realm.delete(defaultRealmObject) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) } assertThrows(realm.delete(defaultRealmObject)) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) let testRealm = realmWithTestPath() assertThrows(testRealm.delete(defaultRealmObject)) try! testRealm.write { self.assertThrows(testRealm.delete(defaultRealmObject)) } } func testDeleteSequenceOfObjects() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftObject.self).count) var objs: [SwiftObject]! try! realm.write { objs = [SwiftObject(), SwiftObject()] realm.add(objs) XCTAssertEqual(2, realm.objects(SwiftObject.self).count) realm.delete(objs) XCTAssertEqual(0, realm.objects(SwiftObject.self).count) } XCTAssertEqual(0, realm.objects(SwiftObject.self).count) let testRealm = realmWithTestPath() assertThrows(testRealm.delete(objs)) try! testRealm.write { self.assertThrows(testRealm.delete(objs)) } } func testDeleteListOfObjects() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftCompanyObject.self).count) try! realm.write { let obj = SwiftCompanyObject() obj.employees.append(SwiftEmployeeObject()) realm.add(obj) XCTAssertEqual(1, realm.objects(SwiftEmployeeObject.self).count) realm.delete(obj.employees) XCTAssertEqual(0, obj.employees.count) XCTAssertEqual(0, realm.objects(SwiftEmployeeObject.self).count) } XCTAssertEqual(0, realm.objects(SwiftEmployeeObject.self).count) } func testDeleteMutableSetOfObjects() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftCompanyObject.self).count) try! realm.write { let obj = SwiftCompanyObject() obj.employeeSet.insert(SwiftEmployeeObject()) realm.add(obj) XCTAssertEqual(1, realm.objects(SwiftEmployeeObject.self).count) realm.delete(obj.employeeSet) XCTAssertEqual(0, obj.employeeSet.count) XCTAssertEqual(0, realm.objects(SwiftEmployeeObject.self).count) } XCTAssertEqual(0, realm.objects(SwiftEmployeeObject.self).count) } func testDeleteResults() { let realm = try! Realm(fileURL: testRealmURL()) XCTAssertEqual(0, realm.objects(SwiftCompanyObject.self).count) try! realm.write { realm.add(SwiftIntObject(value: [1])) realm.add(SwiftIntObject(value: [1])) realm.add(SwiftIntObject(value: [2])) XCTAssertEqual(3, realm.objects(SwiftIntObject.self).count) realm.delete(realm.objects(SwiftIntObject.self).filter("intCol = 1")) XCTAssertEqual(1, realm.objects(SwiftIntObject.self).count) } XCTAssertEqual(1, realm.objects(SwiftIntObject.self).count) } func testDeleteAll() { let realm = try! Realm() try! realm.write { realm.add(SwiftObject()) XCTAssertEqual(1, realm.objects(SwiftObject.self).count) realm.deleteAll() XCTAssertEqual(0, realm.objects(SwiftObject.self).count) } XCTAssertEqual(0, realm.objects(SwiftObject.self).count) } func testObjects() { try! Realm().write { try! Realm().create(SwiftIntObject.self, value: [100]) try! Realm().create(SwiftIntObject.self, value: [200]) try! Realm().create(SwiftIntObject.self, value: [300]) } XCTAssertEqual(0, try! Realm().objects(SwiftStringObject.self).count) XCTAssertEqual(3, try! Realm().objects(SwiftIntObject.self).count) assertThrows(try! Realm().objects(Object.self)) } func testDynamicObjects() { try! Realm().write { try! Realm().create(SwiftIntObject.self, value: [100]) try! Realm().create(SwiftIntObject.self, value: [200]) try! Realm().create(SwiftIntObject.self, value: [300]) } XCTAssertEqual(0, try! Realm().dynamicObjects("SwiftStringObject").count) XCTAssertEqual(3, try! Realm().dynamicObjects("SwiftIntObject").count) assertThrows(try! Realm().dynamicObjects("Object")) } func testDynamicObjectProperties() { try! Realm().write { try! Realm().create(SwiftObject.self) } let object = try! Realm().dynamicObjects("SwiftObject")[0] let dictionary = SwiftObject.defaultValues() XCTAssertEqual(object["boolCol"] as? NSNumber, dictionary["boolCol"] as! NSNumber?) XCTAssertEqual(object["intCol"] as? NSNumber, dictionary["intCol"] as! NSNumber?) XCTAssertEqual(object["int8Col"] as? NSNumber, dictionary["int8Col"] as! NSNumber?) XCTAssertEqual(object["int16Col"] as? NSNumber, dictionary["int16Col"] as! NSNumber?) XCTAssertEqual(object["int32Col"] as? NSNumber, dictionary["int32Col"] as! NSNumber?) XCTAssertEqual(object["int64Col"] as? NSNumber, dictionary["int64Col"] as! NSNumber?) XCTAssertEqual(object["floatCol"] as! Float, dictionary["floatCol"] as! Float, accuracy: 0.001) XCTAssertEqual(object["doubleCol"] as? NSNumber, dictionary["doubleCol"] as! NSNumber?) XCTAssertEqual(object["stringCol"] as! String?, dictionary["stringCol"] as! String?) XCTAssertEqual(object["binaryCol"] as! NSData?, dictionary["binaryCol"] as! NSData?) XCTAssertEqual(object["dateCol"] as! Date?, dictionary["dateCol"] as! Date?) XCTAssertEqual((object["objectCol"] as? SwiftBoolObject)?.boolCol, false) } func testDynamicObjectOptionalProperties() { try! Realm().write { try! Realm().create(SwiftOptionalDefaultValuesObject.self) } let object = try! Realm().dynamicObjects("SwiftOptionalDefaultValuesObject")[0] let dictionary = SwiftOptionalDefaultValuesObject.defaultValues() XCTAssertEqual(object["optIntCol"] as? NSNumber, dictionary["optIntCol"] as! NSNumber?) XCTAssertEqual(object["optInt8Col"] as? NSNumber, dictionary["optInt8Col"] as! NSNumber?) XCTAssertEqual(object["optInt16Col"] as? NSNumber, dictionary["optInt16Col"] as! NSNumber?) XCTAssertEqual(object["optInt32Col"] as? NSNumber, dictionary["optInt32Col"] as! NSNumber?) XCTAssertEqual(object["optInt64Col"] as? NSNumber, dictionary["optInt64Col"] as! NSNumber?) XCTAssertEqual(object["optFloatCol"] as? NSNumber, dictionary["optFloatCol"] as! NSNumber?) XCTAssertEqual(object["optDoubleCol"] as? NSNumber, dictionary["optDoubleCol"] as! NSNumber?) XCTAssertEqual(object["optStringCol"] as! String?, dictionary["optStringCol"] as! String?) XCTAssertEqual(object["optNSStringCol"] as! String?, dictionary["optNSStringCol"] as! String?) XCTAssertEqual(object["optBinaryCol"] as! NSData?, dictionary["optBinaryCol"] as! NSData?) XCTAssertEqual(object["optDateCol"] as! Date?, dictionary["optDateCol"] as! Date?) XCTAssertEqual(object["optDecimalCol"] as! Decimal128?, dictionary["optDecimalCol"] as! Decimal128?) XCTAssertEqual(object["optObjectIdCol"] as! ObjectId?, dictionary["optObjectIdCol"] as! ObjectId?) XCTAssertEqual((object["optObjectCol"] as? SwiftBoolObject)?.boolCol, true) XCTAssertEqual(object["optUuidCol"] as! UUID, dictionary["optUuidCol"] as! UUID) } func testIterateDynamicObjects() { try! Realm().write { for _ in 1..<3 { try! Realm().create(SwiftObject.self) } } let objects = try! Realm().dynamicObjects("SwiftObject") let dictionary = SwiftObject.defaultValues() for object in objects { XCTAssertEqual(object["boolCol"] as? NSNumber, dictionary["boolCol"] as! NSNumber?) XCTAssertEqual(object["intCol"] as? NSNumber, dictionary["intCol"] as! NSNumber?) XCTAssertEqual(object["int8Col"] as? NSNumber, dictionary["int8Col"] as! NSNumber?) XCTAssertEqual(object["int16Col"] as? NSNumber, dictionary["int16Col"] as! NSNumber?) XCTAssertEqual(object["int32Col"] as? NSNumber, dictionary["int32Col"] as! NSNumber?) XCTAssertEqual(object["int64Col"] as? NSNumber, dictionary["int64Col"] as! NSNumber?) XCTAssertEqual(object["floatCol"] as? NSNumber, dictionary["floatCol"] as! NSNumber?) XCTAssertEqual(object["doubleCol"] as? NSNumber, dictionary["doubleCol"] as! NSNumber?) XCTAssertEqual(object["stringCol"] as! String?, dictionary["stringCol"] as! String?) XCTAssertEqual(object["binaryCol"] as! NSData?, dictionary["binaryCol"] as! NSData?) XCTAssertEqual(object["dateCol"] as! Date?, dictionary["dateCol"] as! Date?) XCTAssertEqual((object["objectCol"] as? SwiftBoolObject)?.boolCol, false) } } func testDynamicObjectListProperties() { try! Realm().write { try! Realm().create(SwiftArrayPropertyObject.self, value: ["string", [["array"]], [[2]]]) } let object = try! Realm().dynamicObjects("SwiftArrayPropertyObject")[0] XCTAssertEqual(object["name"] as? String, "string") let array = object["array"] as! List XCTAssertEqual(array.first!["stringCol"] as? String, "array") XCTAssertEqual(array.last!["stringCol"] as? String, "array") for object in array { XCTAssertEqual(object["stringCol"] as? String, "array") } let intArray = object["intArray"] as! List XCTAssertEqual(intArray[0]["intCol"] as? Int, 2) XCTAssertEqual(intArray.first!["intCol"] as? Int, 2) XCTAssertEqual(intArray.last!["intCol"] as? Int, 2) for object in intArray { XCTAssertEqual(object["intCol"] as? Int, 2) } } func testDynamicObjectMutableSetProperties() { try! Realm().write { try! Realm().create(SwiftMutableSetPropertyObject.self, value: ["string", [["set"]], [[2]]]) } let object = try! Realm().dynamicObjects("SwiftMutableSetPropertyObject")[0] XCTAssertEqual(object["name"] as? String, "string") let set = object["set"] as! MutableSet XCTAssertEqual(set.first!["stringCol"] as? String, "set") XCTAssertEqual(set.last!["stringCol"] as? String, "set") for object in set { XCTAssertEqual(object["stringCol"] as? String, "set") } let intSet = object["intSet"] as! MutableSet XCTAssertEqual(intSet[0]["intCol"] as? Int, 2) XCTAssertEqual(intSet.first!["intCol"] as? Int, 2) XCTAssertEqual(intSet.last!["intCol"] as? Int, 2) for object in intSet { XCTAssertEqual(object["intCol"] as? Int, 2) } } func testIntPrimaryKey() { func testIntPrimaryKey(for type: O.Type) where O: SwiftPrimaryKeyObjectType, O.PrimaryKey: ExpressibleByIntegerLiteral { let realm = try! Realm() try! realm.write { realm.create(type, value: ["a", 1]) realm.create(type, value: ["b", 2]) } let object = realm.object(ofType: type, forPrimaryKey: 1 as O.PrimaryKey) XCTAssertNotNil(object) let missingObject = realm.object(ofType: type, forPrimaryKey: 0 as O.PrimaryKey) XCTAssertNil(missingObject) } testIntPrimaryKey(for: SwiftPrimaryIntObject.self) testIntPrimaryKey(for: SwiftPrimaryInt8Object.self) testIntPrimaryKey(for: SwiftPrimaryInt16Object.self) testIntPrimaryKey(for: SwiftPrimaryInt32Object.self) testIntPrimaryKey(for: SwiftPrimaryInt64Object.self) } func testOptionalIntPrimaryKey() { func testOptionalIntPrimaryKey(for type: O.Type, _ wrapped: Wrapped.Type) where Wrapped: ExpressibleByIntegerLiteral { let realm = try! Realm() try! realm.write { realm.create(type, value: ["a", NSNull()]) realm.create(type, value: ["b", 2]) } let object1a = realm.object(ofType: type, forPrimaryKey: NSNull()) XCTAssertNotNil(object1a) let object1b = realm.object(ofType: type, forPrimaryKey: nil as Wrapped?) XCTAssertNotNil(object1b) let object2 = realm.object(ofType: type, forPrimaryKey: 2 as Wrapped) XCTAssertNotNil(object2) let missingObject = realm.object(ofType: type, forPrimaryKey: 0 as Wrapped) XCTAssertNil(missingObject) } testOptionalIntPrimaryKey(for: SwiftPrimaryOptionalIntObject.self, Int.self) testOptionalIntPrimaryKey(for: SwiftPrimaryOptionalInt8Object.self, Int8.self) testOptionalIntPrimaryKey(for: SwiftPrimaryOptionalInt16Object.self, Int16.self) testOptionalIntPrimaryKey(for: SwiftPrimaryOptionalInt32Object.self, Int32.self) testOptionalIntPrimaryKey(for: SwiftPrimaryOptionalInt64Object.self, Int64.self) } func testStringPrimaryKey() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryStringObject.self, value: ["a", 1]) realm.create(SwiftPrimaryStringObject.self, value: ["b", 2]) } // When this is directly inside the XCTAssertNotNil, it doesn't work let object = realm.object(ofType: SwiftPrimaryStringObject.self, forPrimaryKey: "a") XCTAssertNotNil(object) // When this is directly inside the XCTAssertNil, it fails for some reason let missingObject = realm.object(ofType: SwiftPrimaryStringObject.self, forPrimaryKey: "z") XCTAssertNil(missingObject) } func testOptionalStringPrimaryKey() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryStringObject.self, value: ["a", 1]) realm.create(SwiftPrimaryStringObject.self, value: ["b", 2]) realm.create(SwiftPrimaryOptionalStringObject.self, value: [NSNull(), 1]) realm.create(SwiftPrimaryOptionalStringObject.self, value: ["b", 2]) } let object1 = realm.object(ofType: SwiftPrimaryOptionalStringObject.self, forPrimaryKey: NSNull()) XCTAssertNotNil(object1) let object2 = realm.object(ofType: SwiftPrimaryOptionalStringObject.self, forPrimaryKey: "b") XCTAssertNotNil(object2) let missingObject = realm.object(ofType: SwiftPrimaryOptionalStringObject.self, forPrimaryKey: "z") XCTAssertNil(missingObject) } func testUUIDPrimaryKey() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryUUIDObject.self, value: [UUID(uuidString: "8a12daba-8b23-11eb-8dcd-0242ac130003")!, "a"]) realm.create(SwiftPrimaryUUIDObject.self, value: [UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!, "b"]) } let object1 = realm.object(ofType: SwiftPrimaryUUIDObject.self, forPrimaryKey: UUID(uuidString: "8a12daba-8b23-11eb-8dcd-0242ac130003")!)! XCTAssertNotNil(object1) XCTAssertEqual(object1.stringCol, "a") let object2 = realm.object(ofType: SwiftPrimaryUUIDObject.self, forPrimaryKey: UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!)! XCTAssertNotNil(object2) XCTAssertEqual(object2.stringCol, "b") XCTAssertNil(realm.object(ofType: SwiftPrimaryUUIDObject.self, forPrimaryKey: UUID(uuidString: "4ee1fa48-8b23-11eb-8dcd-0242ac130003")!)) } func testObjectIdPrimaryKey() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryObjectIdObject.self, value: [ObjectId("1234567890ab1234567890aa"), 1]) realm.create(SwiftPrimaryObjectIdObject.self, value: [ObjectId("1234567890ab1234567890ab"), 2]) } let object1 = realm.object(ofType: SwiftPrimaryObjectIdObject.self, forPrimaryKey: ObjectId("1234567890ab1234567890aa"))! XCTAssertNotNil(object1) XCTAssertEqual(object1.intCol, 1) let object2 = realm.object(ofType: SwiftPrimaryObjectIdObject.self, forPrimaryKey: ObjectId("1234567890ab1234567890ab"))! XCTAssertNotNil(object2) XCTAssertEqual(object2.intCol, 2) XCTAssertNil(realm.object(ofType: SwiftPrimaryObjectIdObject.self, forPrimaryKey: ObjectId("1234567890ab1234567890ac"))) } func testDynamicObjectForPrimaryKey() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryStringObject.self, value: ["a", 1]) realm.create(SwiftPrimaryStringObject.self, value: ["b", 2]) } XCTAssertNotNil(realm.dynamicObject(ofType: "SwiftPrimaryStringObject", forPrimaryKey: "a")) XCTAssertNil(realm.dynamicObject(ofType: "SwiftPrimaryStringObject", forPrimaryKey: "z")) } func testDynamicObjectForPrimaryKeySubscripting() { let realm = try! Realm() try! realm.write { realm.create(SwiftPrimaryStringObject.self, value: ["a", 1]) } let object = realm.dynamicObject(ofType: "SwiftPrimaryStringObject", forPrimaryKey: "a") let stringVal = object!["stringCol"] as! String XCTAssertEqual(stringVal, "a", "Object Subscripting Failed!") } func testObserve() { let realm = try! Realm() var notificationCalled = false let token = realm.observe { _, realm in XCTAssertEqual(realm.configuration.fileURL, self.defaultRealmURL()) notificationCalled = true } XCTAssertFalse(notificationCalled) try! realm.write {} XCTAssertTrue(notificationCalled) token.invalidate() } func testRemoveNotification() { let realm = try! Realm() var notificationCalled = false let token = realm.observe { (_, realm) in XCTAssertEqual(realm.configuration.fileURL, self.defaultRealmURL()) notificationCalled = true } token.invalidate() try! realm.write {} XCTAssertFalse(notificationCalled) } @MainActor func testAutorefresh() { let realm = try! Realm() XCTAssertTrue(realm.autorefresh, "Autorefresh should default to true") realm.autorefresh = false XCTAssertFalse(realm.autorefresh) realm.autorefresh = true XCTAssertTrue(realm.autorefresh) // test that autoreresh is applied // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") let token = realm.observe { _, realm in XCTAssertNotNil(realm, "Realm should not be nil") notificationFired.fulfill() } dispatchSyncNewThread { @Sendable in let realm = try! Realm() try! realm.write { realm.create(SwiftStringObject.self, value: ["string"]) } } waitForExpectations(timeout: 1, handler: nil) token.invalidate() // get object let results = realm.objects(SwiftStringObject.self) XCTAssertEqual(results.count, Int(1), "There should be 1 object of type StringObject") XCTAssertEqual(results[0].stringCol, "string", "Value of first column should be 'string'") } @MainActor func testRefresh() { let realm = try! Realm() realm.autorefresh = false // test that autorefresh is not applied // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") var token: NotificationToken! token = realm.observe { _, realm in XCTAssertNotNil(realm, "Realm should not be nil") token.invalidate() notificationFired.fulfill() } let results = realm.objects(SwiftStringObject.self) XCTAssertEqual(results.count, Int(0), "There should be 1 object of type StringObject") dispatchSyncNewThread { @Sendable in try! Realm().write { _ = try! Realm().create(SwiftStringObject.self, value: ["string"]) } } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(results.count, Int(0), "There should be 1 object of type StringObject") // refresh realm.refresh() XCTAssertEqual(results.count, Int(1), "There should be 1 object of type StringObject") XCTAssertEqual(results[0].stringCol, "string", "Value of first column should be 'string'") } func testInvalidate() { let realm = try! Realm() let object = SwiftObject() try! realm.write { realm.add(object) return } realm.invalidate() XCTAssertEqual(object.isInvalidated, true) try! realm.write { realm.add(SwiftObject()) return } XCTAssertEqual(realm.objects(SwiftObject.self).count, 2) XCTAssertEqual(object.isInvalidated, true) } func testWriteCopyToPath() throws { let realm = try Realm() try realm.write { realm.add(SwiftObject()) } let fileURL = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("copy.realm") try realm.writeCopy(toFile: fileURL) try autoreleasepool { let copy = try Realm(fileURL: fileURL) XCTAssertEqual(1, copy.objects(SwiftObject.self).count) let frozenCopy = copy.freeze() XCTAssertEqual(1, frozenCopy.objects(SwiftObject.self).count) XCTAssertTrue(frozenCopy.isFrozen) XCTAssertTrue(frozenCopy.objects(SwiftObject.self).isFrozen) } try FileManager.default.removeItem(at: fileURL) } func testWriteCopyForConfiguration() throws { var localConfig = Realm.Configuration() localConfig.fileURL = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("original.realm") let realm = try Realm(configuration: localConfig) try realm.write { realm.add(SwiftBoolObject()) } XCTAssertEqual(realm.objects(SwiftBoolObject.self).count, 1) var destinationConfig = Realm.Configuration() destinationConfig.fileURL = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("destination.realm") try realm.writeCopy(configuration: destinationConfig) let destinationRealm = try Realm(configuration: destinationConfig) XCTAssertEqual(destinationRealm.objects(SwiftBoolObject.self).count, 1) try destinationRealm.write { destinationRealm.add(SwiftBoolObject()) } XCTAssertEqual(destinationRealm.objects(SwiftBoolObject.self).count, 2) let frozenRealm = destinationRealm.freeze() XCTAssertTrue(frozenRealm.isFrozen) XCTAssertTrue(frozenRealm.objects(SwiftBoolObject.self).isFrozen) try FileManager.default.removeItem(at: localConfig.fileURL!) try FileManager.default.removeItem(at: destinationConfig.fileURL!) } func testSeedFilePath() throws { var localConfig = Realm.Configuration() localConfig.fileURL = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("original.realm") try autoreleasepool { let realm = try Realm(configuration: localConfig) try realm.write { realm.add(SwiftBoolObject()) } XCTAssertEqual(realm.objects(SwiftBoolObject.self).count, 1) } var destinationConfig = Realm.Configuration() destinationConfig.fileURL = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("destination.realm") destinationConfig.seedFilePath = defaultRealmURL().deletingLastPathComponent().appendingPathComponent("original.realm") try autoreleasepool { // Should copy the seed file over before opening let destinationRealm = try Realm(configuration: destinationConfig) XCTAssertEqual(destinationRealm.objects(SwiftBoolObject.self).count, 1) try destinationRealm.write { destinationRealm.add(SwiftBoolObject()) } XCTAssertEqual(destinationRealm.objects(SwiftBoolObject.self).count, 2) } try autoreleasepool { let realm = try Realm(configuration: localConfig) try realm.write { realm.deleteAll() } XCTAssertEqual(realm.objects(SwiftBoolObject.self).count, 0) } try autoreleasepool { // Should not have copied the seed file as the Realm already exists let destinationRealm = try Realm(configuration: destinationConfig) XCTAssertEqual(destinationRealm.objects(SwiftBoolObject.self).count, 2) } } func testEquals() { nonisolated(unsafe) let realm = try! Realm() XCTAssertTrue(try! realm == Realm()) let testRealm = realmWithTestPath() XCTAssertFalse(realm == testRealm) dispatchSyncNewThread { let otherThreadRealm = try! Realm() XCTAssertFalse(realm == otherThreadRealm) } } func testCatchSpecificErrors() { do { _ = try Realm(configuration: Realm.Configuration(fileURL: URL(fileURLWithPath: "/dev/null/foo"))) XCTFail("Error should be thrown") } catch Realm.Error.fileOperationFailed { // Success to catch the error } catch { XCTFail("Unexpected error \(error)") } do { _ = try Realm(configuration: Realm.Configuration(fileURL: defaultRealmURL(), readOnly: true)) XCTFail("Error should be thrown") } catch Realm.Error.fileNotFound { // Success to catch the error } catch { XCTFail("Unexpected error \(error)") } } func testExists() { let config = Realm.Configuration() XCTAssertFalse(Realm.fileExists(for: config)) autoreleasepool { _ = try! Realm(configuration: config) } XCTAssertTrue(Realm.fileExists(for: config)) XCTAssertTrue(try! Realm.deleteFiles(for: config)) XCTAssertFalse(Realm.fileExists(for: config)) } func testThaw() { XCTAssertEqual(try! Realm().objects(SwiftBoolObject.self).count, 0) let realm = try! Realm() nonisolated(unsafe) let frozenRealm = realm.freeze() XCTAssert(frozenRealm.isFrozen) dispatchSyncNewThread { let thawedRealm = frozenRealm.thaw() XCTAssertFalse(thawedRealm.isFrozen) try! thawedRealm.write { try! Realm().create(SwiftBoolObject.self, value: ["boolCol": true]) } } XCTAssertEqual(try! Realm().objects(SwiftBoolObject.self).count, 0) realm.refresh() XCTAssertEqual(try! Realm().objects(SwiftBoolObject.self).count, 1) } // MARK: - Async Transactions @MainActor func testAsyncTransactionShouldWrite() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string"]) } onComplete: { _ in let object = realm.objects(SwiftStringObject.self).first XCTAssertEqual(object?.stringCol, "string") asyncComplete.fulfill() } waitForExpectations(timeout: 1, handler: nil) } @MainActor func testAsyncTransactionShouldWriteOnCommit() { let realm = try! Realm() let writeComplete = expectation(description: "async transaction complete") DispatchQueue.main.async { let realm = try! Realm() realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.commitAsyncWrite { _ in let object = realm.objects(SwiftStringObject.self).first XCTAssertEqual(object?.stringCol, "string") writeComplete.fulfill() } } } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) } @MainActor func testAsyncTransactionShouldCancel() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") asyncComplete.isInverted = true let asyncTransactionId = realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.commitAsyncWrite { _ in asyncComplete.fulfill() } } try! realm.cancelAsyncWrite(asyncTransactionId) waitForExpectations(timeout: 1, handler: nil) XCTAssertNil(realm.objects(SwiftStringObject.self).first) } @MainActor func testAsyncTransactionShouldCancelWithoutCommit() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") XCTAssertNil(realm.objects(SwiftStringObject.self).first) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) asyncComplete.fulfill() } waitForExpectations(timeout: 1, handler: nil) XCTAssertNil(realm.objects(SwiftStringObject.self).first) } @MainActor func testAsyncTransactionShouldNotAutoCommitOnCanceledTransaction() { let realm = try! Realm() let waitComplete = expectation(description: "async wait complete") let writeComplete = expectation(description: "async transaction complete") writeComplete.isInverted = true DispatchQueue.main.async { let realm = try! Realm() let transactionId = realm.writeAsync({ realm.create(SwiftStringObject.self, value: ["string"]) }, onComplete: { _ in writeComplete.fulfill() }) try! realm.cancelAsyncWrite(transactionId) waitComplete.fulfill() } waitForExpectations(timeout: 1, handler: nil) XCTAssertNil(realm.objects(SwiftStringObject.self).first) } @MainActor func testAsyncTransactionShouldAutorefresh() { let realm = try! Realm() realm.autorefresh = false // test that autoreresh is not applied // we have two notifications, one for opening the realm, and a second when performing our transaction let notificationFired = expectation(description: "notification fired") var token: NotificationToken! token = realm.observe { _, realm in XCTAssertNotNil(realm, "Realm should not be nil") token.invalidate() notificationFired.fulfill() } let results = realm.objects(SwiftStringObject.self) XCTAssertEqual(results.count, 0) realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string"]) } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(results.count, 1) // refresh realm.refresh() XCTAssertEqual(results.count, 1) XCTAssertEqual(results[0].stringCol, "string") } @MainActor func testAsyncTransactionSyncCommit() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.commitAsyncWrite(allowGrouping: true) { _ in asyncComplete.fulfill() } } realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string 2"]) realm.commitAsyncWrite() } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionSyncAfterAsyncWithoutCommit() { let realm = try! Realm() XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) let asyncComplete = expectation(description: "async transaction complete") realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) asyncComplete.fulfill() } try! realm.write { realm.create(SwiftStringObject.self, value: ["string 2"]) } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(1, realm.objects(SwiftStringObject.self).count) XCTAssertEqual("string 2", realm.objects(SwiftStringObject.self).first?.stringCol) } @MainActor func testAsyncTransactionWriteWithSync() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) try! realm.write { realm.create(SwiftStringObject.self, value: ["string"]) } realm.beginWrite() realm.create(SwiftStringObject.self, value: ["string 2"]) realm.commitAsyncWrite { _ in asyncComplete.fulfill() } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionMixedWithSync() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string"]) } realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string 2"]) } onComplete: { _ in asyncComplete.fulfill() } realm.beginWrite() realm.create(SwiftStringObject.self, value: ["string 3"]) try! realm.commitWrite() waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(3, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionMixedWithCancelledSync() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string"]) } realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string 2"]) } onComplete: { _ in asyncComplete.fulfill() } realm.beginWrite() realm.create(SwiftStringObject.self, value: ["string 3"]) realm.cancelWrite() waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionChangeNotification() { let realm = try! Realm() let asyncWriteComplete = expectation(description: "async write complete") asyncWriteComplete.expectedFulfillmentCount = 2 let updateComplete = expectation(description: "update complete") updateComplete.expectedFulfillmentCount = 2 let resultsUnderTest = realm.objects(SwiftStringObject.self) let token = resultsUnderTest.observe { change in switch change { case .initial: return // ignore case .update: updateComplete.fulfill() case .error: XCTFail("should not get here for this test") } } realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string 1"]) } onComplete: { _ in asyncWriteComplete.fulfill() } realm.writeAsync { realm.create(SwiftStringObject.self, value: ["string 2"]) } onComplete: { _ in asyncWriteComplete.fulfill() } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) token.invalidate() } @MainActor func testBeginAsyncTransactionInAsyncTransaction() { let realm = try! Realm() let transaction1 = expectation(description: "async transaction 1 complete") let transaction2 = expectation(description: "async transaction 2 complete") XCTAssertEqual(0, realm.objects(SwiftStringObject.self).count) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string 2"]) realm.commitAsyncWrite { _ in transaction1.fulfill() } } realm.commitAsyncWrite { _ in transaction2.fulfill() } } waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionFromSyncTransaction() { let realm = try! Realm() let transaction1 = expectation(description: "async transaction 1 complete") realm.beginWrite() realm.create(SwiftStringObject.self, value: ["string"]) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string 2"]) realm.commitAsyncWrite { _ in transaction1.fulfill() } } try! realm.commitWrite() waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionCancel() { let waitComplete = expectation(description: "async wait complete") let expectation = XCTestExpectation(description: "testAsyncTransactionCancel expectation") expectation.expectedFulfillmentCount = 3 let unexpectation = XCTestExpectation(description: "should not fulfill") unexpectation.isInverted = true DispatchQueue.main.async { @MainActor in let realm = try! Realm() realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) expectation.fulfill() } realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) realm.commitAsyncWrite() expectation.fulfill() } realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string"]) expectation.fulfill() realm.commitAsyncWrite() } let asyncTransactionIdB = realm.beginAsyncWrite { unexpectation.fulfill() } try! realm.cancelAsyncWrite(asyncTransactionIdB) self.wait(for: [expectation, unexpectation], timeout: 3) waitComplete.fulfill() } let realm = try! Realm() self.wait(for: [waitComplete], timeout: 4) XCTAssertEqual(2, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionCommit() { let realm = try! Realm() let changesAddedExpectation = expectation(description: "testAsyncTransactionCommit expectation") changesAddedExpectation.expectedFulfillmentCount = 2 realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["with 'commit' should commit"]) realm.commitAsyncWrite() changesAddedExpectation.fulfill() } realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["without 'commit' should not commit"]) changesAddedExpectation.fulfill() } let asyncTransactionId = realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["'cancel' after 'begin' should not commit"]) realm.commitAsyncWrite() changesAddedExpectation.fulfill() } try! realm.cancelAsyncWrite(asyncTransactionId) waitForExpectations(timeout: 1, handler: nil) XCTAssertEqual(1, realm.objects(SwiftStringObject.self).count) } @MainActor func testAsyncTransactionShouldWriteObjectFromOutsideOfTransaction() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") let objU = SwiftStringObject(value: ["string U"]) realm.beginAsyncWrite { realm.create(SwiftStringObject.self, value: ["string I"]) realm.add(objU) realm.commitAsyncWrite { _ in asyncComplete.fulfill() } } waitForExpectations(timeout: 1, handler: nil) XCTAssertNotNil(realm.objects(SwiftStringObject.self).first { $0.stringCol == "string U" }) XCTAssertNotNil(realm.objects(SwiftStringObject.self).first { $0.stringCol == "string I" }) } @MainActor func testAsyncTransactionShouldChangeExistingObject() { let realm = try! Realm() let asyncComplete = expectation(description: "async transaction complete") try! realm.write({ realm.create(SwiftStringObject.self, value: ["string A"]) }) let objA = realm.objects(SwiftStringObject.self).first(where: { $0.stringCol == "string A" })! realm.beginAsyncWrite { objA.stringCol = "string B" realm.commitAsyncWrite { _ in asyncComplete.fulfill() } } waitForExpectations(timeout: 1, handler: nil) XCTAssertNil(realm.objects(SwiftStringObject.self).first { $0.stringCol == "string A" }) XCTAssertNotNil(realm.objects(SwiftStringObject.self).first { $0.stringCol == "string B" }) } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @available(*, deprecated) // Silence deprecation warnings for RealmOptional extension RealmTests { // MARK: - Async Refresh func manuallyAdvancedRealm() throws -> (Realm, String) { let config = RLMRealmConfiguration.default() config.disableAutomaticChangeNotifications = true config.cache = false return (ObjectiveCSupport.convert(object: try RLMRealm(configuration: config)), config.pathOnDisk) } @MainActor func testAsyncRefresh() async throws { let realm = try await openRealm(actor: MainActor.shared) realm.autorefresh = false let results = realm.objects(SwiftStringObject.self) XCTAssertEqual(results.count, 0) var didRefresh = await realm.asyncRefresh() XCTAssertFalse(didRefresh) try await Task { @CustomGlobalActor in let realm = try await openRealm(actor: CustomGlobalActor.shared) try! realm.write { _ = realm.create(SwiftStringObject.self, value: ["string"]) } }.value XCTAssertEqual(results.count, 0) didRefresh = await realm.asyncRefresh() XCTAssertTrue(didRefresh) XCTAssertEqual(results.count, 1) } @MainActor func testAsyncRefreshWaitsForLatest() async throws { let (realm, path) = try manuallyAdvancedRealm() let results = realm.objects(SwiftStringObject.self) // Observe so that it has to wait and can't just advance immediately let token = results.observe { _ in } XCTAssertEqual(results.count, 0) let (realm2, _) = try manuallyAdvancedRealm() try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } RLMRunAsyncNotifiers(path) // Notifiers are now up to date for the above write, but the Realm hasn't // been refreshed try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } // Notifiers are now newer than the Realm, but still out of date Task { @MainActor in // This will run only when the parent task suspends as they're both // on the main actor RLMRunAsyncNotifiers(path) } XCTAssertEqual(results.count, 0) // Here notify() will advance to the version with one object, but we // need to wait for the version with two let didRefresh = await realm.asyncRefresh() XCTAssertTrue(didRefresh) XCTAssertEqual(results.count, 2) token.invalidate() } @MainActor func testAsyncRefreshWaitsForLatestAutorefreshOff() async throws { let (realm, path) = try manuallyAdvancedRealm() realm.autorefresh = false let results = realm.objects(SwiftStringObject.self) // Observe so that it has to wait and can't just advance immediately let token = results.observe { _ in } XCTAssertEqual(results.count, 0) let (realm2, _) = try manuallyAdvancedRealm() try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } RLMRunAsyncNotifiers(path) // Notifiers are now up to date for the above write, but the Realm hasn't // been refreshed try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } // Notifiers are now newer than the Realm, but still out of date Task { @MainActor in // This will run only when the parent task suspends as they're both // on the main actor RLMRunAsyncNotifiers(path) } XCTAssertEqual(results.count, 0) // Here notify() will advance to the version with one object, but we // need to wait for the version with two let didRefresh = await realm.asyncRefresh() XCTAssertTrue(didRefresh) XCTAssertEqual(results.count, 2) token.invalidate() } @MainActor func testAsyncRefreshWithMultipleWaiters() async throws { let (realm, path) = try manuallyAdvancedRealm() let results = realm.objects(SwiftStringObject.self) let token = results.observe { _ in } XCTAssertEqual(results.count, 0) // The order of execution here is weird. Each of the nested tasks can // run only when the outer task suspends, so this runs when we hit // the bottom asyncRefresh(), and then the task with RLMRunAsyncNotifiers // runs when we hit the inner asyncRefresh let task = Task { @MainActor in Task { @MainActor in RLMRunAsyncNotifiers(path) } XCTAssertEqual(results.count, 0) await realm.asyncRefresh() XCTAssertEqual(results.count, 1) } let (realm2, _) = try manuallyAdvancedRealm() try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } XCTAssertEqual(results.count, 0) await realm.asyncRefresh() XCTAssertEqual(results.count, 1) // Verify that both continuations were resumed _ = await task.value token.invalidate() } @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.4, *) func testAsyncRefreshOnQueueConfinedRealm() async throws { let realm = Locked(wrappedValue: nil) let queue = self.queue dispatchSyncNewThread { realm.wrappedValue = try! Realm(queue: queue) } // asyncRefresh() has to be called from a statically isolated context, // but the test as whole can't be isolated (or the dispatch async breaks), // and we have to hop to the actor before fork and not after or the child // crashes before we get to the precondition try await Task { @MainActor in try await assertPreconditionFailure("asyncRefresh() can only be called on main thread or actor-isolated Realms") { _ = await realm.wrappedValue!.asyncRefresh() } try await assertPreconditionFailure("asyncWrite() can only be called on main thread or actor-isolated Realms") { _ = try await realm.wrappedValue!.asyncWrite { } } }.value } @MainActor func testAsyncRefreshTaskCancellation() async throws { let (realm, _) = try manuallyAdvancedRealm() let results = realm.objects(SwiftStringObject.self) let token = results.observe { _ in } let (realm2, _) = try manuallyAdvancedRealm() try! realm2.write { _ = realm2.create(SwiftStringObject.self, value: ["string"]) } let task = Task { @MainActor in let didRefresh = await realm.asyncRefresh() XCTAssertFalse(didRefresh) } task.cancel() _ = await task.value token.invalidate() } // MARK: - Async Writes @MainActor func testAsyncWriteBasics() async throws { let realm = try await openRealm(actor: MainActor.shared) let obj = try await realm.asyncWrite { XCTAssertTrue(realm.isInWriteTransaction) XCTAssertTrue(realm.isPerformingAsynchronousWriteOperations) return realm.create(SwiftStringObject.self, value: ["foo"]) } XCTAssertFalse(realm.isInWriteTransaction) XCTAssertFalse(realm.isPerformingAsynchronousWriteOperations) XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) XCTAssertEqual(obj.stringCol, "foo") } @MainActor func testAsyncWriteCancel() async throws { let realm = try await openRealm(actor: MainActor.shared) try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) realm.cancelWrite() XCTAssertFalse(realm.isInWriteTransaction) } XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } @MainActor func testAsyncWriteBeginNewWriteAfterCancel() async throws { let realm = try await openRealm(actor: MainActor.shared) try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) realm.cancelWrite() realm.beginWrite() realm.create(SwiftStringObject.self, value: ["bar"]) } let objects = realm.objects(SwiftStringObject.self) XCTAssertEqual(objects.count, 1) XCTAssertEqual(try XCTUnwrap(objects.first).stringCol, "bar") } @MainActor func testAsyncWriteModifyExistingObject() async throws { let realm = try await openRealm(actor: MainActor.shared) let obj = try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) } try await realm.asyncWrite { obj.stringCol = "bar" } XCTAssertEqual(obj.stringCol, "bar") } @MainActor func testAsyncWriteCancelsOnThrow() async throws { let realm = try await openRealm(actor: MainActor.shared) await assertThrowsErrorAsync(try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) throw Realm.Error(.fail) }, Realm.Error(.fail)) await assertThrowsErrorAsync(try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) realm.cancelWrite() throw Realm.Error(.fail) }, Realm.Error(.fail)) XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 0) } @CustomGlobalActor func testAsyncWriteCustomGlobalActor() async throws { let realm = try await openRealm(actor: CustomGlobalActor.shared) let obj = try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) } XCTAssertEqual(realm.objects(SwiftStringObject.self).count, 1) XCTAssertEqual(obj.stringCol, "foo") try await realm.asyncWrite { obj.stringCol = "bar" } XCTAssertEqual(obj.stringCol, "bar") } func testAsyncWriteCustomActor() async throws { actor TestActor { var realm: Realm! var obj: SwiftStringObject? init() async throws { realm = try await openRealm(actor: self) } var count: Int { realm.objects(SwiftStringObject.self).count } var value: String? { obj?.stringCol } func create() async throws { obj = try await realm.asyncWrite { realm.create(SwiftStringObject.self, value: ["foo"]) } } func modify() async throws { try await realm.asyncWrite { obj?.stringCol = "bar" } } func close() { realm = nil obj = nil } } let actor = try await TestActor() var count = await actor.count XCTAssertEqual(count, 0) try await actor.create() count = await actor.count var value = await actor.value XCTAssertEqual(count, 1) XCTAssertEqual(value, "foo") try await actor.modify() count = await actor.count value = await actor.value XCTAssertEqual(count, 1) XCTAssertEqual(value, "bar") await actor.close() } @MainActor func testAsyncWriteTaskCancellation() async throws { let realm = try await openRealm(actor: MainActor.shared) realm.beginWrite() let ex = expectation(description: "Background thread ready") let task = Task { @CustomGlobalActor in let realm = try await openRealm(actor: CustomGlobalActor.shared) ex.fulfill() try await realm.asyncWrite { XCTFail("Should not have been called") } } await fulfillment(of: [ex], timeout: 2.0) Task { @CustomGlobalActor in // Cancel the task from within its actor so that we can be sure // that it has suspended with the task cancellation handler set task.cancel() } await assertThrowsErrorAsync(try await task.value, CancellationError()) realm.cancelWrite() } @MainActor func testAsyncWriteTaskCancelledBeforeWriteCalled() async throws { let realm = try await openRealm(actor: MainActor.shared) realm.beginWrite() let ex = expectation(description: "Background thread ready") let task = Task { @CustomGlobalActor in let realm = try await openRealm(actor: CustomGlobalActor.shared) ex.fulfill() // Block until cancelWrite() is called, ensuring that the Task is // cancelled before the call to asyncWrite realm.beginWrite() realm.cancelWrite() try await realm.asyncWrite { XCTFail("Should not have been called") } } await fulfillment(of: [ex], timeout: 2.0) task.cancel() realm.cancelWrite() await assertThrowsErrorAsync(try await task.value, CancellationError()) } // FIXME: deadlocks without https://github.com/realm/realm-core/pull/6413 @MainActor func skip_testAsyncWriteTaskCancellationTiming() async throws { let realm = try await openRealm(actor: MainActor.shared) realm.beginWrite() // Try to hit the timing windows which can't be deterministically tested // by just repeating it a bunch of times. This should trigger tsan errors // if the locking is incorrect. for _ in 0..<1000 { let ex = expectation(description: "Background thread ready") let task = Task { @CustomGlobalActor in let realm = try await openRealm(actor: CustomGlobalActor.shared) // Tearing down a Realm which is in the middle of async writes // is itself async, so we need to explicitly wait for that to // happen or we'll hit a data race when we try to close all // remaining open Realms in tearDown defer { realm.invalidate() } ex.fulfill() try await realm.asyncWrite { XCTFail("Should not have been called") } } await fulfillment(of: [ex], timeout: 2.0) task.cancel() await assertThrowsErrorAsync(try await task.value, CancellationError()) } realm.cancelWrite() } } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @globalActor actor CustomGlobalActor: GlobalActor { #if compiler(<6.2) static var shared = CustomGlobalActor() #else static let shared = CustomGlobalActor() #endif } #if compiler(<6) @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension CancellationError: Equatable { public static func == (lhs: CancellationError, rhs: CancellationError) -> Bool { true } } #else @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension CancellationError: @retroactive Equatable { public static func == (lhs: CancellationError, rhs: CancellationError) -> Bool { true } } #endif // Helper extension LogLevel { var logLevel: String { switch self { case .off: return "Off" case .fatal: return "Fatal" case .error: return "Error" case .warn: return "Warn" case .info: return "Info" case .detail: return "Details" case .debug: return "Debug" case .trace: return "Trace" case .all: return "All" default: return "unknown" } } } @available(macOS 12.0, watchOS 8.0, iOS 15.0, tvOS 15.0, macCatalyst 15.0, *) class LoggerTests: TestCase { var logger: Logger! override func setUp() { logger = Logger.shared } override func tearDown() { Logger.shared = logger } func testSetDefaultLogLevel() throws { nonisolated(unsafe) var logs: String = "" let logger = Logger(level: .off) { level, message in logs += "\(Date.now) \(level.logLevel) \(message)" } Logger.shared = logger try autoreleasepool { _ = try Realm() } XCTAssertTrue(logs.isEmpty) logger.level = .all try autoreleasepool { _ = try Realm() } // We should be getting logs after changing the log level XCTAssertEqual(Logger.shared.level, .all) XCTAssertTrue(logs.contains("Details DB:")) XCTAssertTrue(logs.contains("Trace DB:")) } func testDefaultLogger() throws { nonisolated(unsafe) var logs: String = "" let logger = Logger(level: .off) { level, message in logs += "\(Date.now) \(level.logLevel) \(message)" } Logger.shared = logger XCTAssertEqual(Logger.shared.level, .off) try autoreleasepool { _ = try Realm() } XCTAssertTrue(logs.isEmpty) // Info logger.level = .detail try autoreleasepool { _ = try Realm() } XCTAssertTrue(!logs.isEmpty) XCTAssertTrue(logs.contains("Details DB:")) // Trace logs = "" logger.level = .trace try autoreleasepool { _ = try Realm() } XCTAssertTrue(!logs.isEmpty) XCTAssertTrue(logs.contains("Trace DB:")) // Detail logs = "" logger.level = .detail try autoreleasepool { _ = try Realm() } XCTAssertTrue(!logs.isEmpty) XCTAssertTrue(logs.contains("Details DB:")) XCTAssertFalse(logs.contains("Trace DB:")) logs = "" Logger.shared = Logger(level: .trace) { level, message in logs += "\(Date.now) \(level.logLevel) \(message)" } XCTAssertEqual(Logger.shared.level, .trace) try autoreleasepool { _ = try Realm() } XCTAssertTrue(!logs.isEmpty) XCTAssertTrue(logs.contains("Details DB:")) XCTAssertTrue(logs.contains("Trace DB:")) } } ================================================ FILE: RealmSwift/Tests/SchemaTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift class SchemaTests: TestCase { var schema: Schema! override func setUp() { super.setUp() autoreleasepool { self.schema = try! Realm().schema } } func testObjectSchema() { let objectSchema = schema.objectSchema XCTAssertTrue(objectSchema.count > 0) } func testDescription() { XCTAssert(schema.description as Any is String) } func testSubscript() { XCTAssertEqual(schema["SwiftObject"]!.className, "SwiftObject") XCTAssertNil(schema["NoSuchClass"]) } func testEquals() { XCTAssertTrue(try! schema == Realm().schema) } func testNoSchemaForUnpersistedObjectClasses() { XCTAssertNil(schema["RLMObject"]) XCTAssertNil(schema["RLMObjectBase"]) XCTAssertNil(schema["RLMDynamicObject"]) XCTAssertNil(schema["Object"]) XCTAssertNil(schema["DynamicObject"]) XCTAssertNil(schema["MigrationObject"]) } func testValidNestedClass() throws { let privateSubclass = try XCTUnwrap(schema["PrivateObjectSubclass"]) XCTAssertEqual(privateSubclass.className, "PrivateObjectSubclass") let parent = try XCTUnwrap(schema["ObjectWithNestedEmbeddedObject"]) XCTAssertEqual(parent.properties[1].objectClassName, "ObjectWithNestedEmbeddedObject_NestedInnerClass") XCTAssertNotNil(schema["ObjectWithNestedEmbeddedObject_NestedInnerClass"]) } } ================================================ FILE: RealmSwift/Tests/SectionedResultsTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 import RealmSwift #if canImport(RealmTestSupport) import RealmTestSupport #endif class BaseSectionedResultsTests: RLMTestCaseBase { var realm: Realm? var obj: ModernAllTypesObject? override func tearDown() { obj = nil realm = nil } override func invokeTest() { autoreleasepool { super.invokeTest() } } } class BasePrimitiveSectionedResultsTests: RLMTestCaseBase { var realm: Realm? var obj: ModernAllTypesObject? override func setUp() { realm = try! Realm(configuration: .init(inMemoryIdentifier: "BasePrimitiveSectionedResultsTests", objectTypes: [ModernAllTypesObject.self])) obj = TestData.setupObject() try! realm?.write { realm?.add(obj!) } super.setUp() } override func tearDown() { obj = nil realm = nil } override func invokeTest() { autoreleasepool { super.invokeTest() } } private func assert(_ collection: T, ascending: Bool = true) where T.Element == TestData.Element { let sectionedResults = collection.sectioned(by: TestData.sectionBlock, ascending: ascending) var sectionCount = 0 var elementCount = 0 let keys = TestData.orderedKeys(ascending: ascending) for section in sectionedResults { XCTAssertEqual(section.key, keys[sectionCount]) sectionCount += 1 let expValues = ascending ? TestData.expectedSectionedValues[section.key] : TestData.expectedSectionedValues[section.key]!.reversed() for (i, value) in expValues!.enumerated() { XCTAssertEqual(section[i], value) elementCount += 1 } } XCTAssertEqual(sectionCount, TestData.expectedSectionedValues.keys.count) XCTAssertEqual(elementCount, TestData.expectedSectionedValues.values.flatMap { $0 }.count) } func testCreationFromResults() { if !TestData.skipResultsTests { let results = TestData.results(obj!) assert(results) assert(results, ascending: false) } } func testCreationFromList() { let list = TestData.list(obj!) assert(list) assert(list, ascending: false) } func testCreationFromMutableSet() { let set = TestData.mutableSet(obj!) assert(set) assert(set, ascending: false) } func testCreationFromAnyRealmCollection() { if !TestData.skipResultsTests { let collection = TestData.anyRealmCollection(obj!) assert(collection) assert(collection, ascending: false) } } func testFrozenFromParentCollection() { let collection = TestData.list(obj!) let sectionedResults = collection.sectioned(by: TestData.sectionBlock) XCTAssertFalse(sectionedResults.isFrozen) let frozenCollection = collection.freeze() let frozenSectionedResults = frozenCollection.sectioned(by: TestData.sectionBlock) XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, TestData.expectedSectionedValues.count) guard let thawedSectionedResults = frozenSectionedResults.thaw() else { return XCTFail("Could not thaw sectioned results.") } XCTAssertFalse(thawedSectionedResults.isFrozen) XCTAssertEqual(thawedSectionedResults.count, TestData.expectedSectionedValues.count) } func testFrozen() { let collection = TestData.list(obj!) let sectionedResults = collection.sectioned(by: TestData.sectionBlock) XCTAssertFalse(sectionedResults.isFrozen) let frozenSectionedResults = sectionedResults.freeze() XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, TestData.expectedSectionedValues.count) guard let thawedSectionedResults = frozenSectionedResults.thaw() else { return XCTFail("Could not thaw sectioned results.") } XCTAssertFalse(thawedSectionedResults.isFrozen) XCTAssertEqual(thawedSectionedResults.count, TestData.expectedSectionedValues.count) } } // swiftlint:disable:next type_name class BaseOptionalPrimitiveSectionedResultsTests: BaseSectionedResultsTests { override func setUp() { realm = try! Realm(configuration: .init(inMemoryIdentifier: "BaseOptionalPrimitiveSectionedResultsTests", objectTypes: [ModernAllTypesObject.self])) obj = TestData.setupObject() try! realm?.write { realm?.add(obj!) } super.setUp() } private func assertOptional(_ collection: T, asending: Bool = true) where T.Element == Optional { let sectionedResults = collection.sectioned(by: TestData.sectionBlock, ascending: asending) var sectionCount = 0 var elementCount = 0 let keys = TestData.orderedKeysOpt(ascending: asending) for section in sectionedResults { XCTAssertEqual(section.key, keys[sectionCount]) sectionCount += 1 let expValues = asending ? TestData.expectedSectionedValuesOpt[section.key] : TestData.expectedSectionedValuesOpt[section.key]!.reversed() for (i, value) in expValues!.enumerated() { XCTAssertEqual(section[i], value) elementCount += 1 } } XCTAssertEqual(sectionCount, TestData.expectedSectionedValuesOpt.keys.count) XCTAssertEqual(elementCount, TestData.expectedSectionedValuesOpt.values.flatMap { $0 }.count) } func testCreationFromResultsOptional() { if !TestData.skipResultsTests { let results = TestData.resultsOpt(obj!) assertOptional(results) assertOptional(results, asending: false) } } func testCreationFromListOptional() { let list = TestData.listOpt(obj!) assertOptional(list) assertOptional(list, asending: false) } func testCreationFromMutableSetOptional() { let set = TestData.mutableSetOpt(obj!) assertOptional(set) assertOptional(set, asending: false) } func testCreationFromAnyRealmCollectionOptional() { if !TestData.skipResultsTests { let collection = TestData.anyRealmCollectionOpt(obj!) assertOptional(collection) assertOptional(collection, asending: false) } } func testEquality() { let collection = TestData.listOpt(obj!) let sectionedResults = collection.sectioned(by: TestData.sectionBlock) let sectionedResults2 = collection.sectioned(by: TestData.sectionBlock) XCTAssertEqual(sectionedResults, sectionedResults) XCTAssertNotEqual(sectionedResults, sectionedResults2) } func testFrozenFromParentCollection() { let collection = TestData.listOpt(obj!) let sectionedResults = collection.sectioned(by: TestData.sectionBlock) XCTAssertFalse(sectionedResults.isFrozen) let frozenCollection = collection.freeze() let frozenSectionedResults = frozenCollection.sectioned(by: TestData.sectionBlock) XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, TestData.expectedSectionedValuesOpt.count) guard let thawedSectionedResults = frozenSectionedResults.thaw() else { return XCTFail("Could not thaw sectioned results.") } XCTAssertFalse(thawedSectionedResults.isFrozen) XCTAssertEqual(thawedSectionedResults.count, TestData.expectedSectionedValuesOpt.count) } func testFrozen() { let collection = TestData.listOpt(obj!) let sectionedResults = collection.sectioned(by: TestData.sectionBlock) XCTAssertFalse(sectionedResults.isFrozen) let frozenSectionedResults = sectionedResults.freeze() XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, TestData.expectedSectionedValuesOpt.count) guard let thawedSectionedResults = frozenSectionedResults.thaw() else { return XCTFail("Could not thaw sectioned results.") } XCTAssertFalse(thawedSectionedResults.isFrozen) XCTAssertEqual(thawedSectionedResults.count, TestData.expectedSectionedValuesOpt.count) } } class PrimitiveSectionedResultsTests: TestCase { override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(name: "Primitive SectionedResults Tests") BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BasePrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) BaseOptionalPrimitiveSectionedResultsTests.defaultTestSuite.tests.forEach(suite.addTest) return suite } } extension ModernAllTypesObject { var firstLetter: String? { stringCol.first.map { String($0) } } } extension ModernAllTypesProjection { var firstLetter: String? { stringCol.first.map { String($0) } } } class SectionedResultsTestsBase: RLMTestCaseBase { func createObjects(_ r: Realm? = nil) -> Realm { let o1 = ModernAllTypesObject() o1.stringCol = "banana" o1.arrayString.append(objectsIn: ["banana", "box", "apple", "chalk"]) o1.setString.insert(objectsIn: ["banana", "box", "apple", "chalk"]) let o2 = ModernAllTypesObject() o2.stringCol = "box" let o3 = ModernAllTypesObject() o3.stringCol = "apple" let o4 = ModernAllTypesObject() o4.stringCol = "chalk" let realm = try! r ?? Realm(configuration: .init(inMemoryIdentifier: "sectioned results test")) try! realm.write { realm.deleteAll() realm.add([o1, o2, o3, o4]) } return realm } } class SectionedResultsTests: SectionedResultsTestsBase { func testCreationFromResults() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = results.sectioned(by: \.firstLetter, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) let sectionedResults2 = results.sectioned(by: \.firstLetter, sortDescriptors: [SortDescriptor.init(keyPath: "stringCol", ascending: ascending)]) XCTAssertEqual(sectionedResults2.count, sectionCount) XCTAssertEqual(sectionedResults2.map { $0.key }, sectionKeys) let sectionedResults3 = results.sectioned(by: { String($0.stringCol.first!) }, sortDescriptors: [SortDescriptor.init(keyPath: "stringCol", ascending: ascending)]) XCTAssertEqual(sectionedResults3.count, sectionCount) XCTAssertEqual(sectionedResults3.map { $0.key }, sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testCreationFromList() { let realm = createObjects() let list = realm.objects(ModernAllTypesObject.self)[0].arrayString func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = list.sectioned(by: { String($0.first!) }, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testCreateFromMutableSet() { let realm = createObjects() let set = realm.objects(ModernAllTypesObject.self)[0].setString func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = set.sectioned(by: { String($0.first!) }, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testAllKeys() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) XCTAssertEqual(sectionedResults.allKeys, ["a", "b", "c"]) } func testSubscript() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let section = sectionedResults[0] XCTAssertEqual(section.count, 1) var obj = section[0] XCTAssertEqual(obj.stringCol, "apple") obj = sectionedResults[IndexPath(item: 0, section: 0)] XCTAssertEqual(obj.stringCol, "apple") } @MainActor func testObservation() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let ex = expectation(description: "initial notification") let token = sectionedResults.observe { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case .update: XCTFail("Shouldn't happen") } ex.fulfill() } waitForExpectations(timeout: 1, handler: nil) // add a second notification and wait for it var ex2 = expectation(description: "second initial notification") let token2 = sectionedResults.observe { _ in ex2.fulfill() } waitForExpectations(timeout: 1, handler: nil) // make a write and implicitly verify that only the unskipped // notification is called (the first would error on .update) ex2 = expectation(description: "change notification") try! realm.write(withoutNotifying: [token]) { realm.delete(results) } waitForExpectations(timeout: 1, handler: nil) token.invalidate() token2.invalidate() } @MainActor func testObserveWithKeyPathFilter() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let ex = expectation(description: "notifications") ex.expectedFulfillmentCount = 3 let token = sectionedResults.observe(keyPaths: [\.boolCol]) { _ in ex.fulfill() } let obj = results.where { $0.stringCol.starts(with: "a") }[0] // Expect notification as changing sections. try! realm.write { obj.stringCol = "box" } // Expect notification as the observed property is modified. try! realm.write { obj.boolCol = true } waitForExpectations(timeout: 1.0) let exRealm = expectation(description: "notifications") exRealm.expectedFulfillmentCount = 2 let realmToken = realm.observe { _, _ in exRealm.fulfill() } // Expect no notification as the object will not change sections. try! realm.write { obj.stringCol = "box" } try! realm.write { obj.stringCol = "box" } waitForExpectations(timeout: 1) token.invalidate() realmToken.invalidate() } @MainActor func testObserveWithKeyPathFilterOnSection() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true)[0] let ex = expectation(description: "notifications") ex.expectedFulfillmentCount = 2 let token = sectionedResults.observe(keyPaths: [\.boolCol]) { _ in ex.fulfill() } let obj = results.where { $0.stringCol.starts(with: "a") }[0] try! realm.write { obj.boolCol = false } waitForExpectations(timeout: 1.0) let exRealm = expectation(description: "notifications") exRealm.expectedFulfillmentCount = 2 let realmToken = realm.observe { _, _ in exRealm.fulfill() } // Expect no notification as the object will not change sections. try! realm.write { obj.intCol = 123 } try! realm.write { obj.intCol = 456 } waitForExpectations(timeout: 1) token.invalidate() realmToken.invalidate() } func testObserveOnQueue() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let sema = DispatchSemaphore(value: 0) let queue = DispatchQueue(label: "background") var firstRun = true let token = sectionedResults.observe(keyPaths: [\.stringCol], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 3) XCTAssertEqual(sectionsToDelete, [0]) XCTAssertEqual(sectionsToInsert, [2]) XCTAssertEqual(deletions.count, 2) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1), IndexPath(item: 1, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions.count, 2) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 0, section: 2)]) } else { XCTAssertEqual(collection.count, 3) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(insertions.isEmpty) XCTAssertEqual(modifications.count, 1) XCTAssertEqual(modifications, [IndexPath(item: 0, section: 0)]) } } XCTAssertFalse(Thread.isMainThread) sema.signal() } sema.wait() try! realm.write { realm.delete([results[2], results[0]]) // banana, apple results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "zebra" realm.add(o) } sema.wait() // Check modifications. firstRun = false try! realm.write { results[0].stringCol = "bogg" // previously: bog results[1].intCol = 1 // should be ignored. } sema.wait() token.invalidate() } func testObservationOnSection() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let section1 = sectionedResults[0] let section2 = sectionedResults[1] var firstRun = true // Only get notifications for key 'a'. let token1 = section1.observe(keyPaths: [\.stringCol]) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 1) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(sectionsToDelete, [0]) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 1) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0)]) XCTAssertEqual(sectionsToInsert, [0]) } } } // Only get notifications for key 'b'. let token2 = section2.observe(keyPaths: [\.stringCol]) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): if firstRun { XCTAssertEqual(collection.count, 2) } case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 1) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 2) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 0)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 1, section: 0)]) } } } try! realm.write { realm.delete([results[2], results[0]]) // banana, apple } try! realm.write {} firstRun = false try! realm.write { results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "bag" realm.add(o) } try! realm.write {} try! realm.write { let o = ModernAllTypesObject() o.stringCol = "app" realm.add(o) } try! realm.write {} token1.invalidate() token2.invalidate() } func testObservationOnSectionOnQueue() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let section1 = sectionedResults[0] let section2 = sectionedResults[1] let sema1 = DispatchSemaphore(value: 0) let sema2 = DispatchSemaphore(value: 0) let queue = DispatchQueue(label: "background") var firstRun = true // Only get notifications for key 'a'. let token1 = section1.observe(keyPaths: [\.stringCol], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 1) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(sectionsToDelete, [0]) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 1) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0)]) XCTAssertEqual(sectionsToInsert, [0]) } } XCTAssertFalse(Thread.isMainThread) sema1.signal() } sema1.wait() // Only get notifications for key 'b'. let token2 = section2.observe(keyPaths: [\.stringCol], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): if firstRun { XCTAssertEqual(collection.count, 2) } case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 1) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 2) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 0)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 1, section: 0)]) } } XCTAssertFalse(Thread.isMainThread) sema2.signal() } sema2.wait() try! realm.write { realm.delete([results[2], results[0]]) // banana, apple } sema1.wait() sema2.wait() firstRun = false try! realm.write { results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "bag" realm.add(o) } sema2.wait() try! realm.write { let o = ModernAllTypesObject() o.stringCol = "app" realm.add(o) } sema1.wait() token1.invalidate() token2.invalidate() } func testFrozenResults() { let realm = createObjects() let frozenResults = realm.objects(ModernAllTypesObject.self).freeze() XCTAssertTrue(frozenResults.isFrozen) try! realm.write { let o = ModernAllTypesObject() o.stringCol = "z" realm.add(o) } func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = frozenResults.sectioned(by: \.firstLetter, ascending: ascending) XCTAssertTrue(sectionedResults.isFrozen) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) guard let thawed = sectionedResults.thaw() else { return XCTFail("Could not produce thawed sectioned results") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, 4) XCTAssertEqual(thawed.map { $0.key }, ascending ? sectionKeys + ["z"] : ["z"] + sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testFrozen() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) XCTAssertFalse(results.isFrozen) func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String], newSection: String) { let frozenSectionedResults = results.sectioned(by: \.firstLetter, ascending: ascending).freeze() try! realm.write { let o = ModernAllTypesObject() o.stringCol = newSection realm.add(o) } XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, sectionCount) XCTAssertEqual(frozenSectionedResults.map { $0.key }, sectionKeys) guard let thawed = frozenSectionedResults.thaw() else { return XCTFail("Could not produce thawed sectioned results") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, sectionCount + 1) XCTAssertEqual(thawed.map { $0.key }, ascending ? sectionKeys + [newSection] : [newSection] + sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"], newSection: "d") assert(ascending: false, sectionCount: 4, sectionKeys: ["d", "c", "b", "a"], newSection: "e") } func testFrozenSection() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) XCTAssertFalse(results.isFrozen) func assert(ascending: Bool, beforeCount: Int, afterCount: Int, sectionKey: String) { let frozenSection = results.sectioned(by: \.firstLetter, ascending: ascending)[0].freeze() try! realm.write { let o = ModernAllTypesObject() o.stringCol = sectionKey realm.add(o) } XCTAssertTrue(frozenSection.isFrozen) XCTAssertEqual(frozenSection.count, 1) XCTAssertEqual(frozenSection.key, sectionKey) guard let thawed = frozenSection.thaw() else { return XCTFail("Could not produce thawed sectioned results") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, 2) XCTAssertEqual(thawed.key, sectionKey) } assert(ascending: true, beforeCount: 1, afterCount: 2, sectionKey: "a") assert(ascending: false, beforeCount: 1, afterCount: 2, sectionKey: "c") } } class SectionedResultsProjectionTests: SectionedResultsTestsBase { func testCreationFromResults() { let realm = createObjects() let results = realm.objects(ModernAllTypesProjection.self) func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = results.sectioned(by: \.firstLetter, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) var o: ModernAllTypesProjection = sectionedResults[0][0] XCTAssertEqual(o.stringCol, ascending ? "apple" : "chalk") let sectionedResults2 = results.sectioned(by: \.firstLetter, sortDescriptors: [SortDescriptor.init(keyPath: "stringCol", ascending: ascending)]) XCTAssertEqual(sectionedResults2.count, sectionCount) XCTAssertEqual(sectionedResults2.map { $0.key }, sectionKeys) o = sectionedResults2[0][0] XCTAssertEqual(o.stringCol, ascending ? "apple" : "chalk") let sectionedResults3 = results.sectioned(by: { String($0.stringCol.first!) }, sortDescriptors: [SortDescriptor.init(keyPath: "stringCol", ascending: ascending)]) XCTAssertEqual(sectionedResults3.count, sectionCount) XCTAssertEqual(sectionedResults3.map { $0.key }, sectionKeys) o = sectionedResults3[0][0] XCTAssertEqual(o.stringCol, ascending ? "apple" : "chalk") } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testCreationFromList() { let realm = createObjects() let list = realm.objects(ModernAllTypesProjection.self)[0].arrayString func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = list.sectioned(by: { String($0.first!) }, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testCreateFromMutableSet() { let realm = createObjects() let set = realm.objects(ModernAllTypesProjection.self)[0].setString func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = set.sectioned(by: { String($0.first!) }, ascending: ascending) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } @MainActor func testObservation() { let realm = createObjects() let results = realm.objects(ModernAllTypesProjection.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let ex = expectation(description: "initial notification") let token = sectionedResults.observe { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case .update: XCTFail("Shouldn't happen") } ex.fulfill() } waitForExpectations(timeout: 1, handler: nil) // add a second notification and wait for it var ex2 = expectation(description: "second initial notification") let token2 = sectionedResults.observe { _ in ex2.fulfill() } waitForExpectations(timeout: 1, handler: nil) // make a write and implicitly verify that only the unskipped // notification is called (the first would error on .update) ex2 = expectation(description: "change notification") try! realm.write(withoutNotifying: [token]) { realm.delete(realm.objects(ModernAllTypesObject.self)) } waitForExpectations(timeout: 1, handler: nil) token.invalidate() token2.invalidate() } func testObserveOnQueue() { let realm = createObjects() let results = realm.objects(ModernAllTypesProjection.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let sema = DispatchSemaphore(value: 0) let queue = DispatchQueue(label: "background") var firstRun = true let token = sectionedResults.observe(keyPaths: ["stringCol"], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 3) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 3) XCTAssertEqual(sectionsToDelete, [0]) XCTAssertEqual(sectionsToInsert, [2]) XCTAssertEqual(deletions.count, 2) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1), IndexPath(item: 1, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions.count, 2) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 0, section: 2)]) } else { XCTAssertEqual(collection.count, 3) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(insertions.isEmpty) XCTAssertEqual(modifications.count, 1) XCTAssertEqual(modifications, [IndexPath(item: 0, section: 0)]) } } XCTAssertFalse(Thread.isMainThread) sema.signal() } sema.wait() try! realm.write { realm.delete([results[2].rootObject, results[0].rootObject]) // banana, apple results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "zebra" realm.add(o) } sema.wait() // Check modifications. firstRun = false try! realm.write { results[0].stringCol = "bogg" // previously: bog results[1].intCol = 1 // should be ignored. } sema.wait() token.invalidate() } func testObservationOnSection() { let realm = createObjects() let results = realm.objects(ModernAllTypesProjection.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let section1 = sectionedResults[0] let section2 = sectionedResults[1] var firstRun = true // Only get notifications for key 'a'. let token1 = section1.observe(keyPaths: ["stringCol"]) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 1) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(sectionsToDelete, [0]) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 1) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0)]) XCTAssertEqual(sectionsToInsert, [0]) } } } // Only get notifications for key 'b'. let token2 = section2.observe(keyPaths: ["stringCol"]) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): if firstRun { XCTAssertEqual(collection.count, 2) } case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 1) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 2) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 0)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 1, section: 0)]) } } } try! realm.write { realm.delete([results[2].rootObject, results[0].rootObject]) // banana, apple } try! realm.write {} firstRun = false try! realm.write { results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "bag" realm.add(o) } try! realm.write {} try! realm.write { let o = ModernAllTypesObject() o.stringCol = "app" realm.add(o) } try! realm.write {} token1.invalidate() token2.invalidate() } func testObservationOnSectionOnQueue() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) let section1 = sectionedResults[0] let section2 = sectionedResults[1] let sema1 = DispatchSemaphore(value: 0) let sema2 = DispatchSemaphore(value: 0) let queue = DispatchQueue(label: "background") var firstRun = true // Only get notifications for key 'a'. let token1 = section1.observe(keyPaths: ["stringCol"], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): XCTAssertEqual(collection.count, 1) case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(sectionsToDelete, [0]) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertTrue(deletions.isEmpty) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 1) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0)]) XCTAssertEqual(sectionsToInsert, [0]) } } XCTAssertFalse(Thread.isMainThread) sema1.signal() } sema1.wait() // Only get notifications for key 'b'. let token2 = section2.observe(keyPaths: ["stringCol"], on: queue) { (changes: SectionedResultsChange) in switch changes { case .initial(let collection): if firstRun { XCTAssertEqual(collection.count, 2) } case let .update(collection, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): if firstRun { XCTAssertEqual(collection.count, 1) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 1)]) XCTAssertTrue(modifications.isEmpty) XCTAssertTrue(insertions.isEmpty) } else { XCTAssertEqual(collection.count, 2) XCTAssertTrue(sectionsToDelete.isEmpty) XCTAssertTrue(sectionsToInsert.isEmpty) XCTAssertEqual(deletions, [IndexPath(item: 0, section: 0)]) XCTAssertTrue(modifications.isEmpty) XCTAssertEqual(insertions, [IndexPath(item: 0, section: 0), IndexPath(item: 1, section: 0)]) } } XCTAssertFalse(Thread.isMainThread) sema2.signal() } sema2.wait() try! realm.write { realm.delete([results[2], results[0]]) // banana, apple } sema1.wait() sema2.wait() firstRun = false try! realm.write { results[0].stringCol = "bog" // previously: box let o = ModernAllTypesObject() o.stringCol = "bag" realm.add(o) } sema2.wait() try! realm.write { let o = ModernAllTypesObject() o.stringCol = "app" realm.add(o) } sema1.wait() token1.invalidate() token2.invalidate() } func testFrozenResults() { let realm = createObjects() let frozenResults = realm.objects(ModernAllTypesProjection.self).freeze() XCTAssertTrue(frozenResults.isFrozen) try! realm.write { let o = ModernAllTypesObject() o.stringCol = "z" realm.add(o) } func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String]) { let sectionedResults = frozenResults.sectioned(by: \.firstLetter, ascending: ascending) XCTAssertTrue(sectionedResults.isFrozen) XCTAssertEqual(sectionedResults.count, sectionCount) XCTAssertEqual(sectionedResults.map { $0.key }, sectionKeys) guard let thawed = sectionedResults.thaw() else { return XCTFail("Could not produce thawed sectioned results") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, 4) XCTAssertEqual(thawed.map { $0.key }, ascending ? sectionKeys + ["z"] : ["z"] + sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"]) assert(ascending: false, sectionCount: 3, sectionKeys: ["c", "b", "a"]) } func testFrozen() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) XCTAssertFalse(results.isFrozen) func assert(ascending: Bool, sectionCount: Int, sectionKeys: [String], newSection: String) { let frozenSectionedResults = results.sectioned(by: \.firstLetter, ascending: ascending).freeze() try! realm.write { let o = ModernAllTypesObject() o.stringCol = newSection realm.add(o) } XCTAssertTrue(frozenSectionedResults.isFrozen) XCTAssertEqual(frozenSectionedResults.count, sectionCount) XCTAssertEqual(frozenSectionedResults.map { $0.key }, sectionKeys) guard let thawed = frozenSectionedResults.thaw() else { return XCTFail("Could not produce thawed sectioned results") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, sectionCount + 1) XCTAssertEqual(thawed.map { $0.key }, ascending ? sectionKeys + [newSection] : [newSection] + sectionKeys) } assert(ascending: true, sectionCount: 3, sectionKeys: ["a", "b", "c"], newSection: "d") assert(ascending: false, sectionCount: 4, sectionKeys: ["d", "c", "b", "a"], newSection: "e") } func testFrozenSection() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) XCTAssertFalse(results.isFrozen) func assert(ascending: Bool, sectionKey: String, beforeCount: Int, afterCount: Int) { let frozenSection = results.sectioned(by: \.firstLetter, ascending: ascending)[0].freeze() try! realm.write { let o = ModernAllTypesObject() o.stringCol = sectionKey realm.add(o) } XCTAssertTrue(frozenSection.isFrozen) XCTAssertEqual(frozenSection.count, beforeCount) XCTAssertEqual(frozenSection.key, sectionKey) guard let thawed = frozenSection.thaw() else { return XCTFail("Could not produce thawed section.") } XCTAssertFalse(thawed.isFrozen) XCTAssertEqual(thawed.count, afterCount) XCTAssertEqual(thawed.key, sectionKey) } assert(ascending: true, sectionKey: "a", beforeCount: 1, afterCount: 2) assert(ascending: false, sectionKey: "c", beforeCount: 1, afterCount: 2) } func testFastEnumeration() { let realm = createObjects() let results = realm.objects(ModernAllTypesObject.self) let sectionedResults = results.sectioned(by: \.firstLetter, ascending: true) var keys = ["a", "b", "c"] var strs = ["apple", "banana", "box", "chalk"] for section in sectionedResults { XCTAssertEqual(section.key, keys.first!) for obj in section { XCTAssertEqual(obj.stringCol, strs.first!) strs = Array(strs.dropFirst()) } keys = Array(keys.dropFirst()) } XCTAssertTrue(keys.isEmpty) XCTAssertTrue(strs.isEmpty) } } protocol SectionedResultsTestData { associatedtype Key: _Persistable, Hashable associatedtype Element: RealmCollectionValue, _Persistable static var values: [Element] { get } static var expectedSectionedValues: [Key: [Element]] { get } static func orderedKeys(ascending: Bool) -> [Key] static func setupObject() -> ModernAllTypesObject static func list(_ obj: ModernAllTypesObject) -> List static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet static func results(_ obj: ModernAllTypesObject) -> Results static func anyRealmCollection(_ obj: ModernAllTypesObject) -> AnyRealmCollection static func sectionBlock(_ element: Element) -> Key static var skipResultsTests: Bool { get } } protocol OptionalSectionedResultsTestData { associatedtype Key: _Persistable, Hashable associatedtype Element: _RealmCollectionValueInsideOptional, _Persistable static var values: [Element] { get } static var expectedSectionedValuesOpt: [Key: [Element??]] { get } static func orderedKeysOpt(ascending: Bool) -> [Key] static func setupObject() -> ModernAllTypesObject static func listOpt(_ obj: ModernAllTypesObject) -> List static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet static func resultsOpt(_ obj: ModernAllTypesObject) -> Results static func anyRealmCollectionOpt(_ obj: ModernAllTypesObject) -> AnyRealmCollection static func sectionBlock(_ element: Element?) -> Key static var skipResultsTests: Bool { get } } extension SectionedResultsTestData { static func anyRealmCollection(_ obj: ModernAllTypesObject) -> AnyRealmCollection { AnyRealmCollection(results(obj)) } static var skipResultsTests: Bool { false } } extension OptionalSectionedResultsTestData { static func anyRealmCollectionOpt(_ obj: ModernAllTypesObject) -> AnyRealmCollection { AnyRealmCollection(resultsOpt(obj)) } static var skipResultsTests: Bool { false } } struct SectionedResultsTestDataInt: SectionedResultsTestData { static var values: [Int] { [5, 4, 3, 2, 1] } static var expectedSectionedValues: [Int: [Int]] { [1: [1, 3, 5], 0: [2, 4]] } static func orderedKeys(ascending: Bool) -> [Int] { return [1, 0] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayInt.append(objectsIn: values) object.setInt.insert(objectsIn: values) object.arrayOptInt.append(objectsIn: values + [nil]) object.setOptInt.insert(objectsIn: values + [nil]) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayInt } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setInt } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayInt.sorted(ascending: true) } static func sectionBlock(_ element: Int) -> Int { element % 2 } } struct SectionedResultsTestDataOptionalInt: OptionalSectionedResultsTestData { static var values: [Int] { [5, 4, 3, 2, 1] } static var expectedSectionedValuesOpt: [Int?: [Int??]] { return [1: [1, 3, 5], 0: [2, 4], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [Int?] { return ascending ? [nil, 1, 0] : [1, 0, nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptInt.append(objectsIn: values + [nil]) object.setOptInt.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptInt } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptInt } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptInt.sorted(ascending: true) } static func sectionBlock(_ element: Int?) -> Int? { guard let element = element else { return nil } return element % 2 } } struct SectionedResultsTestDataFloat: SectionedResultsTestData { static var values: [Float] { [5.5, 4.4, 3.3, 2.2, 1.1] } static var expectedSectionedValues: [String: [Float]] { ["small": [1.1, 2.2, 3.3, 4.4], "large": [5.5]] } static func orderedKeys(ascending: Bool) -> [String] { return ascending ? ["small", "large"] : ["large", "small"] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayFloat.append(objectsIn: values) object.setFloat.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayFloat } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setFloat } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayFloat.sorted(ascending: true) } static func sectionBlock(_ element: Float) -> String { return (element >= 5.0) ? "large" : "small" } } struct SectionedResultsTestDataOptionalFloat: OptionalSectionedResultsTestData { static var values: [Float] { [5.5, 4.4, 3.3, 2.2, 1.1] } static var expectedSectionedValuesOpt: [Float?: [Float??]] { return [0.0: [1.1, 2.2, 3.3, 4.4], 1.0: [5.5], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [Float?] { return ascending ? [nil, 0.0, 1.0] : [1.0, 0.0, nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptFloat.append(objectsIn: values + [nil]) object.setOptFloat.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptFloat } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptFloat } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptFloat.sorted(ascending: true) } static func sectionBlock(_ element: Float?) -> Float? { guard let element = element else { return nil } return (element >= 5.0) ? 1.0 : 0.0 } } struct SectionedResultsTestDataDouble: SectionedResultsTestData { static var values: [Double] { [5.5, 4.4, 3.3, 2.2, 1.1] } static var expectedSectionedValues: [String: [Double]] { ["small": [1.1, 2.2, 3.3, 4.4], "large": [5.5]] } static func orderedKeys(ascending: Bool) -> [String] { return ascending ? ["small", "large"] : ["large", "small"] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayDouble.append(objectsIn: values) object.setDouble.insert(objectsIn: values) object.arrayOptDouble.append(objectsIn: values + [nil]) object.setOptDouble.insert(objectsIn: values + [nil]) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayDouble } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setDouble } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayDouble.sorted(ascending: true) } static func sectionBlock(_ element: Double) -> String { return (element >= 5.0) ? "large" : "small" } } struct SectionedResultsTestDataOptionalDouble: OptionalSectionedResultsTestData { static var values: [Double] { [5.5, 4.4, 3.3, 2.2, 1.1] } static var expectedSectionedValuesOpt: [Double?: [Double??]] { return [0.0: [1.1, 2.2, 3.3, 4.4], 1.0: [5.5], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [Double?] { return ascending ? [nil, 0.0, 1.0] : [1.0, 0.0, nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayDouble.append(objectsIn: values) object.setDouble.insert(objectsIn: values) object.arrayOptDouble.append(objectsIn: values + [nil]) object.setOptDouble.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptDouble } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptDouble } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptDouble.sorted(ascending: true) } static func sectionBlock(_ element: Double?) -> Double? { guard let element = element else { return nil } return (element >= 5.0) ? 1.0 : 0.0 } } struct SectionedResultsTestDataString: SectionedResultsTestData { static var values: [String] { ["apple", "banana", "any", "phone", "door"] } static var expectedSectionedValues: [String: [String]] { ["a": ["any", "apple"], "b": ["banana"], "d": ["door"], "p": ["phone"]] } static func orderedKeys(ascending: Bool) -> [String] { return ascending ? ["a", "b", "d", "p"] : ["p", "d", "b", "a"] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayString.append(objectsIn: values) object.setString.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayString } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setString } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayString.sorted(ascending: true) } static func sectionBlock(_ element: String) -> String { String(element.first!) } } struct SectionedResultsTestDataOptionalString: OptionalSectionedResultsTestData { static var values: [String] { ["apple", "banana", "any", "phone", "door"] } static var expectedSectionedValuesOpt: [String?: [String??]] { return ["a": ["any", "apple"], "b": ["banana"], "d": ["door"], "p": ["phone"], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [String?] { return ascending ? [nil, "a", "b", "d", "p"] : ["p", "d", "b", "a", nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptString.append(objectsIn: values + [nil]) object.setOptString.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptString } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptString } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptString.sorted(ascending: true) } static func sectionBlock(_ element: String?) -> String? { guard let element = element else { return nil } return String(element.first!) } } struct SectionedResultsTestDataAnyRealmValue: SectionedResultsTestData { static var values: [AnyRealmValue] { [.string("apple"), .int(2), .bool(true), .data(.init(repeating: 1, count: 1)), .decimal128(123.456)] } static var expectedSectionedValues: [String: [AnyRealmValue]] { ["alphanumeric": [.bool(true), .int(2), .decimal128(123.456), .string("apple")], "data": [.data(.init(repeating: 1, count: 1))]] } static func orderedKeys(ascending: Bool) -> [String] { return ascending ? ["alphanumeric", "data"] : ["data", "alphanumeric"] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayAny.append(objectsIn: values) object.setAny.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayAny } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setAny } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayAny.sorted(ascending: true) } static func sectionBlock(_ element: AnyRealmValue) -> String { switch element { case .int: return "alphanumeric" case .bool: return "alphanumeric" case .string: return "alphanumeric" case .data: return "data" case .decimal128: return "alphanumeric" default: XCTFail("Element not supported") return "" } } } struct SectionedResultsTestDataBinary: SectionedResultsTestData { static var values: [Data] { [Data(base64Encoded: "more")!, Data(base64Encoded: "door")!, Data(base64Encoded: "absolute")!, Data(base64Encoded: "abstract")!] } static var expectedSectionedValues: [String: [Data]] { ["short": [Data(base64Encoded: "door")!, Data(base64Encoded: "more")!], "long": [Data(base64Encoded: "absolute")!, Data(base64Encoded: "abstract")!]] } static func orderedKeys(ascending: Bool) -> [String] { ascending ? ["long", "short"] : ["short", "long"] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayBinary.append(objectsIn: values) object.setBinary.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayBinary } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setBinary } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayBinary.sorted(ascending: true) } static func sectionBlock(_ element: Data) -> String { return element.base64EncodedString().count == 4 ? "short" : "long" } } struct SectionedResultsTestDataOptionalBinary: OptionalSectionedResultsTestData { static var values: [Data] { [Data(base64Encoded: "more")!, Data(base64Encoded: "door")!, Data(base64Encoded: "absolute")!, Data(base64Encoded: "abstract")!] } static var expectedSectionedValuesOpt: [String?: [Data??]] { ["short": [Data(base64Encoded: "door")!, Data(base64Encoded: "more")!], "long": [Data(base64Encoded: "absolute")!, Data(base64Encoded: "abstract")!], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [String?] { ascending ? [nil, "long", "short"] : ["short", "long", nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptBinary.append(objectsIn: values + [nil]) object.setOptBinary.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptBinary } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptBinary } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptBinary.sorted(ascending: true) } static func sectionBlock(_ element: Data?) -> String? { guard let element = element else { return nil } return element.base64EncodedString().count == 4 ? "short" : "long" } } struct SectionedResultsTestDataDate: SectionedResultsTestData { static var values: [Date] { [Date(timeIntervalSince1970: 1656547200), // 6-30-22 Date(timeIntervalSince1970: 1653868800), // 5-30-22 Date(timeIntervalSince1970: 1651276800), // 4-30-22 Date(timeIntervalSince1970: 1650412800)] // 4-20-22 } static var expectedSectionedValues: [Date: [Date]] { [Date(timeIntervalSince1970: 1653955200): [Date(timeIntervalSince1970: 1656547200)], // June Date(timeIntervalSince1970: 1651276800): [Date(timeIntervalSince1970: 1653868800)], // May Date(timeIntervalSince1970: 1648684800): [Date(timeIntervalSince1970: 1650412800), // April Date(timeIntervalSince1970: 1651276800)]] } static func orderedKeys(ascending: Bool) -> [Date] { let keys = [Date(timeIntervalSince1970: 1648684800), Date(timeIntervalSince1970: 1651276800), Date(timeIntervalSince1970: 1653955200)] return ascending ? keys : keys.reversed() } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayDate.append(objectsIn: values) object.setDate.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayDate } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setDate } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayDate.sorted(ascending: true) } static func sectionBlock(_ element: Date) -> Date { var cal = Calendar(identifier: .gregorian) cal.timeZone = TimeZone(secondsFromGMT: 0)! let comps = cal.dateComponents([.month, .year], from: element) return cal.date(from: DateComponents(year: comps.year, month: comps.month, day: 0, hour: 0, minute: 0, second: 0))! } } struct SectionedResultsTestDataOptionalDate: OptionalSectionedResultsTestData { static var values: [Date] { [Date(timeIntervalSince1970: 1656547200), // 6-30-22 Date(timeIntervalSince1970: 1653868800), // 5-30-22 Date(timeIntervalSince1970: 1651276800), // 4-30-22 Date(timeIntervalSince1970: 1650412800)] // 4-20-22 } static var expectedSectionedValuesOpt: [Date?: [Date??]] { [nil: [.some(.none)], Date(timeIntervalSince1970: 1653955200): [Date(timeIntervalSince1970: 1656547200)], // June Date(timeIntervalSince1970: 1651276800): [Date(timeIntervalSince1970: 1653868800)], // May Date(timeIntervalSince1970: 1648684800): [Date(timeIntervalSince1970: 1650412800), // April Date(timeIntervalSince1970: 1651276800)]] } static func orderedKeysOpt(ascending: Bool) -> [Date?] { let keys = [nil, Date(timeIntervalSince1970: 1648684800), Date(timeIntervalSince1970: 1651276800), Date(timeIntervalSince1970: 1653955200)] return ascending ? keys : keys.reversed() } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptDate.append(objectsIn: values + [nil]) object.setOptDate.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptDate } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptDate } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptDate.sorted(ascending: true) } static func sectionBlock(_ element: Date?) -> Date? { guard let date = element else { return nil } var cal = Calendar(identifier: .gregorian) cal.timeZone = TimeZone(secondsFromGMT: 0)! let comps = cal.dateComponents([.month, .year], from: date) return cal.date(from: DateComponents(year: comps.year, month: comps.month, day: 0, hour: 0, minute: 0, second: 0))! } } struct SectionedResultsTestDataDecimal128: SectionedResultsTestData { static var values: [Decimal128] { [Decimal128(1.0), Decimal128(3.0), Decimal128(2.0), Decimal128(4.0)] } static var expectedSectionedValues: [Decimal128: [Decimal128]] { [Decimal128(1.0): [Decimal128(1.0), Decimal128(3.0)], Decimal128(2.0): [Decimal128(2.0), Decimal128(4.0)]] } static func orderedKeys(ascending: Bool) -> [Decimal128] { let keys = [Decimal128(1.0), Decimal128(2.0)] return ascending ? keys : keys.reversed() } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayDecimal.append(objectsIn: values) object.setDecimal.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayDecimal } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setDecimal } static func results(_ obj: ModernAllTypesObject) -> Results { obj.arrayDecimal.sorted(ascending: true) } static func sectionBlock(_ element: Decimal128) -> Decimal128 { return element.doubleValue.truncatingRemainder(dividingBy: 2.0) == 0 ? Decimal128(2.0) : Decimal128(1.0) } } // swiftlint:disable:next type_name struct SectionedResultsTestDataOptionalDecimal128: OptionalSectionedResultsTestData { static var values: [Decimal128] { [Decimal128(1.0), Decimal128(3.0), Decimal128(2.0), Decimal128(4.000)] } static var expectedSectionedValuesOpt: [Decimal128?: [Decimal128??]] { [Decimal128(1.0): [Decimal128(1.0), Decimal128(3.0)], Decimal128(2.0): [Decimal128(2.0), Decimal128(4.0)], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [Decimal128?] { let keys: [Decimal128?] = [nil, Decimal128(1.0), Decimal128(2.0)] return ascending ? keys : keys.reversed() } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptDecimal.append(objectsIn: values + [nil]) object.setOptDecimal.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptDecimal } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptDecimal } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { obj.arrayOptDecimal.sorted(ascending: true) } static func sectionBlock(_ element: Decimal128?) -> Decimal128? { guard let decimal = element else { return nil } return decimal.doubleValue.truncatingRemainder(dividingBy: 2.0) == 0 ? Decimal128(2.0) : Decimal128(1.0) } } struct SectionedResultsTestDataBool: SectionedResultsTestData { static var values: [Bool] { [true, false] } static var expectedSectionedValues: [Bool: [Bool]] { [false: [false], true: [true]] } static func orderedKeys(ascending: Bool) -> [Bool] { return ascending ? [false, true] : [true, false] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayBool.append(objectsIn: values) object.setBool.insert(objectsIn: values) return object } static func list(_ obj: ModernAllTypesObject) -> List { obj.arrayBool } static func mutableSet(_ obj: ModernAllTypesObject) -> MutableSet { obj.setBool } static func results(_ obj: ModernAllTypesObject) -> Results { fatalError("Not implemented") } static func sectionBlock(_ element: Bool) -> Bool { element } static var skipResultsTests: Bool { true } } struct SectionedResultsTestDataOptionalBool: OptionalSectionedResultsTestData { static var values: [Bool] { [true, false] } static var expectedSectionedValuesOpt: [Bool?: [Bool??]] { return [false: [false], true: [true], nil: [.some(.none)]] } static func orderedKeysOpt(ascending: Bool) -> [Bool?] { return ascending ? [nil, false, true] : [true, false, nil] } static func setupObject() -> ModernAllTypesObject { let object = ModernAllTypesObject() object.arrayOptBool.append(objectsIn: values + [nil]) object.setOptBool.insert(objectsIn: values + [nil]) return object } static func listOpt(_ obj: ModernAllTypesObject) -> List { obj.arrayOptBool } static func mutableSetOpt(_ obj: ModernAllTypesObject) -> MutableSet { obj.setOptBool } static func resultsOpt(_ obj: ModernAllTypesObject) -> Results { fatalError("Not implemented") } static func sectionBlock(_ element: Bool?) -> Bool? { guard let element = element else { return nil } return element } static var skipResultsTests: Bool { true } } // Missing: // Sectioning on Enum ================================================ FILE: RealmSwift/Tests/SortDescriptorTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 import RealmSwift class SortDescriptorTests: TestCase { let sortDescriptor = SortDescriptor(keyPath: "property") func testAscendingDefaultsToTrue() { XCTAssertTrue(sortDescriptor.ascending) } func testReversedReturnsReversedDescriptor() { let reversed = sortDescriptor.reversed() XCTAssertEqual(reversed.keyPath, sortDescriptor.keyPath, "Key path should stay the same when reversed.") XCTAssertFalse(reversed.ascending) XCTAssertTrue(reversed.reversed().ascending) } func testDescription() { XCTAssertEqual(sortDescriptor.description, "SortDescriptor(keyPath: property, direction: ascending)") } func testStringLiteralConvertible() { let literalSortDescriptor: RealmSwift.SortDescriptor = "property" XCTAssertEqual(sortDescriptor, literalSortDescriptor, "SortDescriptor should conform to StringLiteralConvertible") } func testComparison() { let sortDescriptor1 = SortDescriptor(keyPath: "property1", ascending: true) let sortDescriptor2 = SortDescriptor(keyPath: "property1", ascending: false) let sortDescriptor3 = SortDescriptor(keyPath: "property2", ascending: true) let sortDescriptor4 = SortDescriptor(keyPath: "property2", ascending: false) // validate different XCTAssertNotEqual(sortDescriptor1, sortDescriptor2, "Should not match") XCTAssertNotEqual(sortDescriptor1, sortDescriptor3, "Should not match") XCTAssertNotEqual(sortDescriptor1, sortDescriptor4, "Should not match") XCTAssertNotEqual(sortDescriptor2, sortDescriptor3, "Should not match") XCTAssertNotEqual(sortDescriptor2, sortDescriptor4, "Should not match") XCTAssertNotEqual(sortDescriptor3, sortDescriptor4, "Should not match") let sortDescriptor5 = SortDescriptor(keyPath: "property1", ascending: true) let sortDescriptor6 = SortDescriptor(keyPath: "property2", ascending: true) // validate same XCTAssertEqual(sortDescriptor1, sortDescriptor5, "Should match") XCTAssertEqual(sortDescriptor3, sortDescriptor6, "Should match") } } ================================================ FILE: RealmSwift/Tests/SwiftLinkTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import RealmSwift class SwiftLinkTests: TestCase { func testBasicLink() { let realm = realmWithTestPath() let owner = SwiftOwnerObject() owner.name = "Tim" owner.dog = SwiftDogObject() owner.dog!.dogName = "Harvie" try! realm.write { realm.add(owner) } let owners = realm.objects(SwiftOwnerObject.self) let dogs = realm.objects(SwiftDogObject.self) XCTAssertEqual(owners.count, Int(1), "Expecting 1 owner") XCTAssertEqual(dogs.count, Int(1), "Expecting 1 dog") XCTAssertEqual(owners[0].name, "Tim", "Tim is named Tim") XCTAssertEqual(dogs[0].dogName, "Harvie", "Harvie is named Harvie") XCTAssertEqual(owners[0].dog!.dogName, "Harvie", "Tim's dog should be Harvie") } func testMultipleOwnerLink() { let realm = realmWithTestPath() let owner = SwiftOwnerObject() owner.name = "Tim" owner.dog = SwiftDogObject() owner.dog!.dogName = "Harvie" try! realm.write { realm.add(owner) } XCTAssertEqual(realm.objects(SwiftOwnerObject.self).count, Int(1), "Expecting 1 owner") XCTAssertEqual(realm.objects(SwiftDogObject.self).count, Int(1), "Expecting 1 dog") realm.beginWrite() let fiel = realm.create(SwiftOwnerObject.self, value: ["Fiel", NSNull()]) fiel.dog = owner.dog try! realm.commitWrite() XCTAssertEqual(realm.objects(SwiftOwnerObject.self).count, Int(2), "Expecting 2 owners") XCTAssertEqual(realm.objects(SwiftDogObject.self).count, Int(1), "Expecting 1 dog") } func testLinkRemoval() { let realm = realmWithTestPath() let owner = SwiftOwnerObject() owner.name = "Tim" owner.dog = SwiftDogObject() owner.dog!.dogName = "Harvie" try! realm.write { realm.add(owner) } XCTAssertEqual(realm.objects(SwiftOwnerObject.self).count, Int(1), "Expecting 1 owner") XCTAssertEqual(realm.objects(SwiftDogObject.self).count, Int(1), "Expecting 1 dog") try! realm.write { realm.delete(owner.dog!) } XCTAssertNil(owner.dog, "Dog should be nullified when deleted") // refresh owner and check let owner2 = realm.objects(SwiftOwnerObject.self).first! XCTAssertNotNil(owner2, "Should have 1 owner") XCTAssertNil(owner2.dog, "Dog should be nullified when deleted") XCTAssertEqual(realm.objects(SwiftDogObject.self).count, Int(0), "Expecting 0 dogs") } func testLinkingObjects() { let realm = realmWithTestPath() let owner = SwiftOwnerObject() owner.name = "Tim" owner.dog = SwiftDogObject() owner.dog!.dogName = "Harvie" XCTAssertEqual(0, owner.dog!.owners.count, "Linking objects are not available until the object is persisted") try! realm.write { realm.add(owner) } let owners = owner.dog!.owners XCTAssertEqual(1, owners.count) XCTAssertEqual(owner.name, owners.first!.name) try! realm.write { owner.dog = nil } XCTAssertEqual(0, owners.count) } func testLinkingObjectsWithNoPersistedProps() { let realm = realmWithTestPath() let target = OnlyComputedProps() let source1 = LinkToOnlyComputed() source1.value = 1 source1.link = target XCTAssertEqual(target.backlinks.count, 0, "Linking objects are not available until the object is persisted") try! realm.write { realm.add(source1) } XCTAssertEqual(target.backlinks.count, 1) XCTAssertEqual(target.backlinks.first!.value, source1.value) let source2 = LinkToOnlyComputed() source2.value = 2 source2.link = target XCTAssertEqual(target.backlinks.count, 1, "Linking objects to an unpersisted object are not available") try! realm.write { realm.add(source2) } XCTAssertEqual(target.backlinks.count, 2) XCTAssertTrue(target.backlinks.contains(where: { $0.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(targetWithNoLinks.backlinks.count, 0, "No object is linking to targetWithNoLinks") } } ================================================ FILE: RealmSwift/Tests/SwiftTestObjects.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import RealmSwift import Realm final class SwiftStringObject: Object { @objc dynamic var stringCol = "" convenience init(stringCol: String) { self.init() self.stringCol = stringCol } } class ModernSwiftStringObject: Object { @Persisted var stringCol = "" } class ModernSwiftStringProjection: Projection { @Projected(\ModernSwiftStringObject.stringCol) var string } class SwiftBoolObject: Object, Identifiable { @objc dynamic var boolCol = false } class SwiftIntObject: Object { @objc dynamic var intCol = 0 } class SwiftInt8Object: Object { @objc dynamic var int8Col = 0 } class SwiftInt16Object: Object { @objc dynamic var int16Col = 0 } class SwiftInt32Object: Object { @objc dynamic var int32Col = 0 } class SwiftInt64Object: Object { @objc dynamic var int64Col = 0 } class SwiftLongObject: Object { @objc dynamic var longCol: Int64 = 0 } @objc enum IntEnum: Int, RealmEnum, Codable { case value1 = 1 case value2 = 3 } class SwiftObject: Object { @objc dynamic var boolCol = false @objc dynamic var intCol = 123 @objc dynamic var int8Col: Int8 = 123 @objc dynamic var int16Col: Int16 = 123 @objc dynamic var int32Col: Int32 = 123 @objc dynamic var int64Col: Int64 = 123 @objc dynamic var intEnumCol = IntEnum.value1 @objc dynamic var floatCol = 1.23 as Float @objc dynamic var doubleCol = 12.3 @objc dynamic var stringCol = "a" @objc dynamic var binaryCol = Data("a".utf8) @objc dynamic var dateCol = Date(timeIntervalSince1970: 1) @objc dynamic var decimalCol = Decimal128("123e4") @objc dynamic var objectIdCol = ObjectId("1234567890ab1234567890ab") @objc dynamic var objectCol: SwiftBoolObject? = SwiftBoolObject() @objc dynamic var uuidCol: UUID = UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! let anyCol = RealmProperty() let arrayCol = List() let setCol = MutableSet() let mapCol = Map() class func defaultValues() -> [String: Any] { return [ "boolCol": false, "intCol": 123, "int8Col": 123 as Int8, "int16Col": 123 as Int16, "int32Col": 123 as Int32, "int64Col": 123 as Int64, "floatCol": 1.23 as Float, "doubleCol": 12.3, "stringCol": "a", "binaryCol": Data("a".utf8), "dateCol": Date(timeIntervalSince1970: 1), "decimalCol": Decimal128("123e4"), "objectIdCol": ObjectId("1234567890ab1234567890ab"), "objectCol": [false], "uuidCol": UUID(uuidString: "137decc8-b300-4954-a233-f89909f4fd89")! ] } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftOptionalObject: Object { @objc dynamic var optNSStringCol: NSString? @objc dynamic var optStringCol: String? @objc dynamic var optBinaryCol: Data? @objc dynamic var optDateCol: Date? @objc dynamic var optDecimalCol: Decimal128? @objc dynamic var optObjectIdCol: ObjectId? @objc dynamic var optUuidCol: UUID? let optIntCol = RealmOptional() let optInt8Col = RealmOptional() let optInt16Col = RealmOptional() let optInt32Col = RealmOptional() let optInt64Col = RealmOptional() let optFloatCol = RealmOptional() let optDoubleCol = RealmOptional() let optBoolCol = RealmOptional() let optEnumCol = RealmOptional() let otherIntCol = RealmProperty() @objc dynamic var optObjectCol: SwiftBoolObject? } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftOptionalPrimaryObject: SwiftOptionalObject { let id = RealmOptional() override class func primaryKey() -> String? { return "id" } } class SwiftListObject: Object { let int = List() let int8 = List() let int16 = List() let int32 = List() let int64 = List() let float = List() let double = List() let string = List() let data = List() let date = List() let decimal = List() let objectId = List() let uuid = List() let any = List() let intOpt = List() let int8Opt = List() let int16Opt = List() let int32Opt = List() let int64Opt = List() let floatOpt = List() let doubleOpt = List() let stringOpt = List() let dataOpt = List() let dateOpt = List() let decimalOpt = List() let objectIdOpt = List() let uuidOpt = List() } class SwiftMutableSetObject: Object { let int = MutableSet() let int8 = MutableSet() let int16 = MutableSet() let int32 = MutableSet() let int64 = MutableSet() let float = MutableSet() let double = MutableSet() let string = MutableSet() let data = MutableSet() let date = MutableSet() let decimal = MutableSet() let objectId = MutableSet() let uuid = MutableSet() let any = MutableSet() let intOpt = MutableSet() let int8Opt = MutableSet() let int16Opt = MutableSet() let int32Opt = MutableSet() let int64Opt = MutableSet() let floatOpt = MutableSet() let doubleOpt = MutableSet() let stringOpt = MutableSet() let dataOpt = MutableSet() let dateOpt = MutableSet() let decimalOpt = MutableSet() let objectIdOpt = MutableSet() let uuidOpt = MutableSet() } class SwiftMapObject: Object { let int = Map() let int8 = Map() let int16 = Map() let int32 = Map() let int64 = Map() let float = Map() let double = Map() let bool = Map() let string = Map() let data = Map() let date = Map() let decimal = Map() let objectId = Map() let uuid = Map() let object = Map() let any = Map() let intOpt = Map() let int8Opt = Map() let int16Opt = Map() let int32Opt = Map() let int64Opt = Map() let floatOpt = Map() let doubleOpt = Map() let boolOpt = Map() let stringOpt = Map() let dataOpt = Map() let dateOpt = Map() let decimalOpt = Map() let objectIdOpt = Map() let uuidOpt = Map() } class SwiftImplicitlyUnwrappedOptionalObject: Object { @objc dynamic var optNSStringCol: NSString! @objc dynamic var optStringCol: String! @objc dynamic var optBinaryCol: Data! @objc dynamic var optDateCol: Date! @objc dynamic var optDecimalCol: Decimal128! @objc dynamic var optObjectIdCol: ObjectId! @objc dynamic var optObjectCol: SwiftBoolObject! @objc dynamic var optUuidCol: UUID! } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftOptionalDefaultValuesObject: Object { @objc dynamic var optNSStringCol: NSString? = "A" @objc dynamic var optStringCol: String? = "B" @objc dynamic var optBinaryCol: Data? = Data("C".utf8) @objc dynamic var optDateCol: Date? = Date(timeIntervalSince1970: 10) @objc dynamic var optDecimalCol: Decimal128? = "123" @objc dynamic var optObjectIdCol: ObjectId? = ObjectId("1234567890ab1234567890ab") @objc dynamic var optUuidCol: UUID? = UUID(uuidString: "00000000-0000-0000-0000-000000000000") let optIntCol = RealmOptional(1) let optInt8Col = RealmOptional(1) let optInt16Col = RealmOptional(1) let optInt32Col = RealmOptional(1) let optInt64Col = RealmOptional(1) let optFloatCol = RealmOptional(2.2) let optDoubleCol = RealmOptional(3.3) let optBoolCol = RealmOptional(true) @objc dynamic var optObjectCol: SwiftBoolObject? = SwiftBoolObject(value: [true]) class func defaultValues() -> [String: Any] { return [ "optNSStringCol": "A", "optStringCol": "B", "optBinaryCol": Data("C".utf8), "optDateCol": Date(timeIntervalSince1970: 10), "optDecimalCol": Decimal128("123"), "optObjectIdCol": ObjectId("1234567890ab1234567890ab"), "optIntCol": 1, "optInt8Col": Int8(1), "optInt16Col": Int16(1), "optInt32Col": Int32(1), "optInt64Col": Int64(1), "optFloatCol": 2.2 as Float, "optDoubleCol": 3.3, "optBoolCol": true, "optUuidCol": UUID(uuidString: "00000000-0000-0000-0000-000000000000")! ] } } class SwiftOptionalIgnoredPropertiesObject: Object { @objc dynamic var id = 0 @objc dynamic var optNSStringCol: NSString? = "A" @objc dynamic var optStringCol: String? = "B" @objc dynamic var optBinaryCol: Data? = Data("C".utf8) @objc dynamic var optDateCol: Date? = Date(timeIntervalSince1970: 10) @objc dynamic var optDecimalCol: Decimal128? = "123" @objc dynamic var optObjectIdCol: ObjectId? = ObjectId("1234567890ab1234567890ab") @objc dynamic var optObjectCol: SwiftBoolObject? = SwiftBoolObject(value: [true]) override class func ignoredProperties() -> [String] { return [ "optNSStringCol", "optStringCol", "optBinaryCol", "optDateCol", "optDecimalCol", "optObjectIdCol", "optObjectCol" ] } } class SwiftDogObject: Object { @objc dynamic var dogName = "" let owners = LinkingObjects(fromType: SwiftOwnerObject.self, property: "dog") } class SwiftOwnerObject: Object { @objc dynamic var name = "" @objc dynamic var dog: SwiftDogObject? = SwiftDogObject() } class SwiftAggregateObject: Object { @objc dynamic var intCol = 0 @objc dynamic var int8Col: Int8 = 0 @objc dynamic var int16Col: Int16 = 0 @objc dynamic var int32Col: Int32 = 0 @objc dynamic var int64Col: Int64 = 0 @objc dynamic var floatCol = 0 as Float @objc dynamic var doubleCol = 0.0 @objc dynamic var decimalCol = 0.0 as Decimal128 @objc dynamic var boolCol = false @objc dynamic var dateCol = Date() @objc dynamic var trueCol = true let stringListCol = List() } class SwiftAllIntSizesObject: Object { @objc dynamic var int8: Int8 = 0 @objc dynamic var int16: Int16 = 0 @objc dynamic var int32: Int32 = 0 @objc dynamic var int64: Int64 = 0 } class SwiftEmployeeObject: Object { @objc dynamic var name = "" @objc dynamic var age = 0 @objc dynamic var hired = false } class SwiftCompanyObject: Object { @objc dynamic var name = "" let employees = List() let employeeSet = MutableSet() let employeeMap = Map() } class SwiftArrayPropertyObject: Object { @objc dynamic var name = "" let array = List() let intArray = List() let swiftObjArray = List() } class SwiftMutableSetPropertyObject: Object { @objc dynamic var name = "" let set = MutableSet() let intSet = MutableSet() let swiftObjSet = MutableSet() } class SwiftMapPropertyObject: Object { @objc dynamic var name = "" let map = Map() let intMap = Map() let swiftObjectMap = Map() let dogMap = Map() } class SwiftDoubleListOfSwiftObject: Object { let array = List() } class SwiftListOfSwiftObject: Object { let array = List() } class SwiftMutableSetOfSwiftObject: Object { let set = MutableSet() } class SwiftMapOfSwiftObject: Object { let map = Map() } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftMapOfSwiftOptionalObject: Object { let map = Map() } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftListOfSwiftOptionalObject: Object { let array = List() } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftMutableSetOfSwiftOptionalObject: Object { let set = MutableSet() } class SwiftArrayPropertySubclassObject: SwiftArrayPropertyObject { let boolArray = List() } class SwiftLinkToPrimaryStringObject: Object { @objc dynamic var pk = "" @objc dynamic var object: SwiftPrimaryStringObject? let objects = List() override class func primaryKey() -> String? { return "pk" } } class SwiftUTF8Object: Object { // swiftlint:disable:next identifier_name @objc dynamic var 柱колоéнǢкƱаم👍 = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" } class SwiftIgnoredPropertiesObject: Object { @objc dynamic var name = "" @objc dynamic var age = 0 @objc dynamic var runtimeProperty: AnyObject? @objc dynamic var runtimeDefaultProperty = "property" @objc dynamic var readOnlyProperty: Int { return 0 } override class func ignoredProperties() -> [String] { return ["runtimeProperty", "runtimeDefaultProperty"] } } class SwiftRecursiveObject: Object { let objects = List() let objectSet = MutableSet() } protocol SwiftPrimaryKeyObjectType { associatedtype PrimaryKey static func primaryKey() -> String? } class SwiftPrimaryStringObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var intCol = 0 typealias PrimaryKey = String override class func primaryKey() -> String? { return "stringCol" } } class SwiftPrimaryOptionalStringObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol: String? = "" @objc dynamic var intCol = 0 typealias PrimaryKey = String? override class func primaryKey() -> String? { return "stringCol" } } class SwiftPrimaryIntObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var intCol = 0 typealias PrimaryKey = Int override class func primaryKey() -> String? { return "intCol" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryOptionalIntObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" let intCol = RealmOptional() typealias PrimaryKey = RealmOptional override class func primaryKey() -> String? { return "intCol" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryInt8Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var int8Col: Int8 = 0 typealias PrimaryKey = Int8 override class func primaryKey() -> String? { return "int8Col" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryOptionalInt8Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" let int8Col = RealmOptional() typealias PrimaryKey = RealmOptional override class func primaryKey() -> String? { return "int8Col" } } class SwiftPrimaryInt16Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var int16Col: Int16 = 0 typealias PrimaryKey = Int16 override class func primaryKey() -> String? { return "int16Col" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryOptionalInt16Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" let int16Col = RealmOptional() typealias PrimaryKey = RealmOptional override class func primaryKey() -> String? { return "int16Col" } } class SwiftPrimaryInt32Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var int32Col: Int32 = 0 typealias PrimaryKey = Int32 override class func primaryKey() -> String? { return "int32Col" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryOptionalInt32Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" let int32Col = RealmOptional() typealias PrimaryKey = RealmOptional override class func primaryKey() -> String? { return "int32Col" } } class SwiftPrimaryInt64Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" @objc dynamic var int64Col: Int64 = 0 typealias PrimaryKey = Int64 override class func primaryKey() -> String? { return "int64Col" } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftPrimaryOptionalInt64Object: Object, SwiftPrimaryKeyObjectType { @objc dynamic var stringCol = "" let int64Col = RealmOptional() typealias PrimaryKey = RealmOptional override class func primaryKey() -> String? { return "int64Col" } } class SwiftPrimaryUUIDObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var uuidCol: UUID = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! @objc dynamic var stringCol = "" typealias PrimaryKey = Int64 override class func primaryKey() -> String? { return "uuidCol" } } class SwiftPrimaryObjectIdObject: Object, SwiftPrimaryKeyObjectType { @objc dynamic var objectIdCol: ObjectId = ObjectId.generate() @objc dynamic var intCol = 0 typealias PrimaryKey = Int64 override class func primaryKey() -> String? { return "objectIdCol" } } class SwiftIndexedPropertiesObject: Object { @objc dynamic var stringCol = "" @objc dynamic var intCol = 0 @objc dynamic var int8Col: Int8 = 0 @objc dynamic var int16Col: Int16 = 0 @objc dynamic var int32Col: Int32 = 0 @objc dynamic var int64Col: Int64 = 0 @objc dynamic var boolCol = false @objc dynamic var dateCol = Date() @objc dynamic var uuidCol = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")! @objc dynamic var floatCol: Float = 0.0 @objc dynamic var doubleCol: Double = 0.0 @objc dynamic var dataCol = Data() let anyCol = RealmProperty() override class func indexedProperties() -> [String] { return ["stringCol", "intCol", "int8Col", "int16Col", "int32Col", "int64Col", "boolCol", "dateCol", "anyCol", "uuidCol"] } } @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftIndexedOptionalPropertiesObject: Object { @objc dynamic var optionalStringCol: String? = "" let optionalIntCol = RealmOptional() let optionalInt8Col = RealmOptional() let optionalInt16Col = RealmOptional() let optionalInt32Col = RealmOptional() let optionalInt64Col = RealmOptional() let optionalBoolCol = RealmOptional() @objc dynamic var optionalDateCol: Date? = Date() @objc dynamic var optionalUUIDCol: UUID? = UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e") let optionalFloatCol = RealmOptional() let optionalDoubleCol = RealmOptional() @objc dynamic var optionalDataCol: Data? = Data() override class func indexedProperties() -> [String] { return ["optionalStringCol", "optionalIntCol", "optionalInt8Col", "optionalInt16Col", "optionalInt32Col", "optionalInt64Col", "optionalBoolCol", "optionalDateCol", "optionalUUIDCol"] } } class SwiftCustomInitializerObject: Object { @objc dynamic var stringCol: String init(stringVal: String) { stringCol = stringVal super.init() } required override init() { stringCol = "" super.init() } } class SwiftConvenienceInitializerObject: Object { @objc dynamic var stringCol = "" convenience init(stringCol: String) { self.init() self.stringCol = stringCol } } class SwiftObjectiveCTypesObject: Object { @objc dynamic var stringCol: NSString? @objc dynamic var dateCol: NSDate? @objc dynamic var dataCol: NSData? } class SwiftComputedPropertyNotIgnoredObject: Object { @objc dynamic var _urlBacking = "" // Dynamic; no ivar @objc dynamic var dynamicURL: URL? { get { return URL(string: _urlBacking) } set { _urlBacking = newValue?.absoluteString ?? "" } } // Non-dynamic; no ivar var url: URL? { get { return URL(string: _urlBacking) } set { _urlBacking = newValue?.absoluteString ?? "" } } } @objc(SwiftObjcRenamedObject) class SwiftObjcRenamedObject: Object { @objc dynamic var stringCol = "" } @objc(SwiftObjcRenamedObjectWithTotallyDifferentName) class SwiftObjcArbitrarilyRenamedObject: Object { @objc dynamic var boolCol = false } class SwiftCircleObject: Object { @objc dynamic var obj: SwiftCircleObject? let array = List() } // Exists to serve as a superclass to `SwiftGenericPropsOrderingObject` class SwiftGenericPropsOrderingParent: Object { var implicitlyIgnoredComputedProperty: Int { return 0 } let implicitlyIgnoredReadOnlyProperty: Int = 1 let parentFirstList = List() let parentFirstSet = MutableSet() @objc dynamic var parentFirstNumber = 0 func parentFunction() -> Int { return parentFirstNumber + 1 } @objc dynamic var parentSecondNumber = 1 var parentComputedProp: String { return "hello world" } } // Used to verify that Swift properties (generic and otherwise) are detected properly and // added to the schema in the correct order. @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftGenericPropsOrderingObject: SwiftGenericPropsOrderingParent { func myFunction() -> Int { return firstNumber + secondNumber + thirdNumber } @objc dynamic var dynamicComputed: Int { return 999 } var firstIgnored = 999 @objc dynamic var dynamicIgnored = 999 @objc dynamic var firstNumber = 0 // Managed property class func myClassFunction(x: Int, y: Int) -> Int { return x + y } var secondIgnored = 999 lazy var lazyIgnored = 999 let firstArray = List() // Managed property let firstSet = MutableSet() // Managed property @objc dynamic var secondNumber = 0 // Managed property var computedProp: String { return "\(firstNumber), \(secondNumber), and \(thirdNumber)" } let secondArray = List() // Managed property let secondSet = MutableSet() // Managed property override class func ignoredProperties() -> [String] { return ["firstIgnored", "dynamicIgnored", "secondIgnored", "thirdIgnored", "lazyIgnored", "dynamicLazyIgnored"] } let firstOptionalNumber = RealmOptional() // Managed property var thirdIgnored = 999 @objc dynamic lazy var dynamicLazyIgnored = 999 let firstLinking = LinkingObjects(fromType: SwiftGenericPropsOrderingHelper.self, property: "first") let secondLinking = LinkingObjects(fromType: SwiftGenericPropsOrderingHelper.self, property: "second") @objc dynamic var thirdNumber = 0 // Managed property let secondOptionalNumber = RealmOptional() // Managed property } // Only exists to allow linking object properties on `SwiftGenericPropsNotLastObject`. @available(*, deprecated) // Silence deprecation warnings for RealmOptional class SwiftGenericPropsOrderingHelper: Object { @objc dynamic var first: SwiftGenericPropsOrderingObject? @objc dynamic var second: SwiftGenericPropsOrderingObject? } class SwiftRenamedProperties1: Object { @objc dynamic var propA = 0 @objc dynamic var propB = "" let linking1 = LinkingObjects(fromType: LinkToSwiftRenamedProperties1.self, property: "linkA") let linking2 = LinkingObjects(fromType: LinkToSwiftRenamedProperties2.self, property: "linkD") override class func _realmObjectName() -> String { return "Swift Renamed Properties" } override class func propertiesMapping() -> [String: String] { return ["propA": "prop 1", "propB": "prop 2"] } } class SwiftRenamedProperties2: Object { @objc dynamic var propC = 0 @objc dynamic var propD = "" let linking1 = LinkingObjects(fromType: LinkToSwiftRenamedProperties1.self, property: "linkA") let linking2 = LinkingObjects(fromType: LinkToSwiftRenamedProperties2.self, property: "linkD") override class func _realmObjectName() -> String { return "Swift Renamed Properties" } override class func propertiesMapping() -> [String: String] { return ["propC": "prop 1", "propD": "prop 2"] } } class LinkToSwiftRenamedProperties1: Object { @objc dynamic var linkA: SwiftRenamedProperties1? @objc dynamic var linkB: SwiftRenamedProperties2? let array1 = List() let set1 = MutableSet() override class func _realmObjectName() -> String { return "Link To Swift Renamed Properties" } override class func propertiesMapping() -> [String: String] { return ["linkA": "link 1", "linkB": "link 2", "array1": "array", "set1": "set"] } } class LinkToSwiftRenamedProperties2: Object { @objc dynamic var linkC: SwiftRenamedProperties1? @objc dynamic var linkD: SwiftRenamedProperties2? let array2 = List() let set2 = MutableSet() override class func _realmObjectName() -> String { return "Link To Swift Renamed Properties" } override class func propertiesMapping() -> [String: String] { return ["linkC": "link 1", "linkD": "link 2", "array2": "array", "set2": "set"] } } class EmbeddedParentObject: Object { @objc dynamic var object: EmbeddedTreeObject1? let array = List() let map = Map() } class EmbeddedPrimaryParentObject: Object { @objc dynamic var pk: Int = 0 @objc dynamic var object: EmbeddedTreeObject1? let array = List() override class func primaryKey() -> String? { return "pk" } } protocol EmbeddedTreeObject: EmbeddedObject { var value: Int { get set } } class EmbeddedTreeObject1: EmbeddedObject, EmbeddedTreeObject { @objc dynamic var value = 0 @objc dynamic var child: EmbeddedTreeObject2? let children = List() let parent1 = LinkingObjects(fromType: EmbeddedParentObject.self, property: "object") let parent2 = LinkingObjects(fromType: EmbeddedParentObject.self, property: "array") } class EmbeddedTreeObject2: EmbeddedObject, EmbeddedTreeObject { @objc dynamic var value = 0 @objc dynamic var child: EmbeddedTreeObject3? let children = List() let parent3 = LinkingObjects(fromType: EmbeddedTreeObject1.self, property: "child") let parent4 = LinkingObjects(fromType: EmbeddedTreeObject1.self, property: "children") } class EmbeddedTreeObject3: EmbeddedObject, EmbeddedTreeObject { @objc dynamic var value = 0 let parent3 = LinkingObjects(fromType: EmbeddedTreeObject2.self, property: "child") let parent4 = LinkingObjects(fromType: EmbeddedTreeObject2.self, property: "children") } class ObjectWithNestedEmbeddedObject: Object { @objc dynamic var value = 0 @objc dynamic var inner: NestedInnerClass? @objc(ObjectWithNestedEmbeddedObject_NestedInnerClass) class NestedInnerClass: EmbeddedObject { @objc dynamic var value = 0 } } @objc(PrivateObjectSubclass) 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 } ================================================ FILE: RealmSwift/Tests/SwiftUITests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 import RealmSwift import SwiftUI import Combine class SwiftUIObject: Object, ObjectKeyIdentifiable { @Persisted var list: RealmSwift.List @Persisted var stringList: RealmSwift.List @Persisted var set: RealmSwift.MutableSet @Persisted var map: Map @Persisted var primitiveList: RealmSwift.List @Persisted var primitiveSet: RealmSwift.MutableSet @Persisted var primitiveMap: Map @Persisted var str = "foo" @Persisted var int = 0 convenience init(str: String = "foo") { self.init() self.str = str } } class UIElementsProjection: Projection, ObjectKeyIdentifiable { @Projected(\SwiftUIObject.str) var label @Projected(\SwiftUIObject.int) var counter } class EmbeddedTreeSwiftUIObject1: EmbeddedObject, EmbeddedTreeObject, ObjectKeyIdentifiable { @objc dynamic var value = 0 @objc dynamic var child: EmbeddedTreeObject2? let children = RealmSwift.List() } private let inMemoryIdentifier = "swiftui-tests" func hasSwiftUI() -> Bool { if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { return true } return false } @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) class SwiftUITests: TestCase { override class var defaultTestSuite: XCTestSuite { if hasSwiftUI() { return super.defaultTestSuite } return XCTestSuite(name: "\(type(of: self))") } // MARK: - List Operations @MainActor func testManagedUnmanagedListAppendPrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveList XCTAssertEqual(state.count, 0) $state.append(1) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.append(2) XCTAssertEqual(state.count, 2) } @MainActor func testManagedUnmanagedListAppendUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.list XCTAssertEqual(state.count, 0) $state.append(SwiftBoolObject()) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.append(SwiftBoolObject()) XCTAssertEqual(state.count, 2) } @MainActor func testManagedListAppendUnmanagedObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.list XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() $state.append(SwiftBoolObject()) XCTAssertEqual(state.count, 1) } @MainActor func testManagedListAppendFrozenObject() throws { let listObj = SwiftUIObject() @StateRealmObject var state = listObj.list XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) let obj = SwiftBoolObject() try realm.write { realm.add(listObj) realm.add(obj) } let frozen = obj.freeze() _state.update() $state.append(frozen) XCTAssertEqual(state.count, 1) } @MainActor func testManagedUnmanagedListRemovePrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveList XCTAssertEqual(state.count, 0) $state.append(1) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.append(2) XCTAssertEqual(state.count, 2) $state.remove(at: 0) XCTAssertEqual(state[0], 2) XCTAssertEqual(state.count, 1) } @MainActor func testManagedUnmanagedListRemoveUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.list XCTAssertEqual(state.count, 0) $state.append(SwiftBoolObject()) XCTAssertEqual(state.count, 1) $state.remove(at: 0) XCTAssertEqual(state.count, 0) } @MainActor func testManagedListAppendRemoveObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.list XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() $state.append(SwiftBoolObject()) XCTAssertEqual(state.count, 1) $state.remove(at: 0) XCTAssertEqual(state.count, 0) } // MARK: - MutableSet Operations @MainActor func testManagedUnmanagedMutableSetInsertPrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveSet XCTAssertEqual(state.count, 0) $state.insert(1) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.insert(2) XCTAssertEqual(state.count, 2) } @MainActor func testManagedUnmanagedMutableSetInsertUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) $state.insert(SwiftBoolObject()) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.insert(SwiftBoolObject()) XCTAssertEqual(state.count, 2) } @MainActor func testManagedMutableSetInsertUnmanagedObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() $state.insert(SwiftBoolObject()) XCTAssertEqual(state.count, 1) } @MainActor func testManagedMutableSetInsertFrozenObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) let obj = SwiftBoolObject() try realm.write { realm.add(object) realm.add(obj) } let frozen = obj.freeze() _state.update() $state.insert(frozen) XCTAssertEqual(state.count, 1) } @MainActor func testMutableSetRemovePrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveSet XCTAssertEqual(state.count, 0) $state.insert(1) XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.insert(2) XCTAssertEqual(state.count, 2) $state.remove(1) XCTAssertEqual(state.count, 1) } @MainActor func testUnmanagedMutableSetRemoveUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) let obj = SwiftBoolObject() $state.insert(obj) XCTAssertEqual(state.count, 1) $state.remove(obj) XCTAssertEqual(state.count, 0) } @MainActor func testManagedMutableSetRemoveUnmanagedObject() throws { let object = SwiftUIObject() let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) let obj = SwiftBoolObject() $state.insert(obj) XCTAssertEqual(state.count, 1) XCTAssertNotNil(obj.realm) $state.remove(obj) XCTAssertEqual(state.count, 0) } @MainActor func testManagedMutableSetRemoveObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.set XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() let obj = SwiftBoolObject() let objState = StateRealmObject(wrappedValue: obj) var hit = 0 // This will append an observer to SwiftUIKVO let cancellable = objState._publisher .sink { _ in } receiveValue: { _ in hit += 1 } objState.wrappedValue.boolCol = true XCTAssertEqual(hit, 1) $state.insert(objState.wrappedValue) XCTAssertEqual(state.count, 1) $state.remove(objState.wrappedValue) XCTAssertEqual(state.count, 0) cancellable.cancel() } // MARK: - Map Operations @MainActor func testManagedUnmanagedMapAppendPrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveMap XCTAssertEqual(state.count, 0) $state.set(object: 1, for: "one") XCTAssertEqual(state.count, 1) XCTAssertEqual($state["one"], 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.set(object: 2, for: "two") $state.set(object: 3, for: "two") XCTAssertEqual(state.count, 2) XCTAssertEqual($state["two"], 3) } @MainActor func testManagedUnmanagedMapAppendUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.map XCTAssertEqual(state.count, 0) $state.set(object: SwiftBoolObject(), for: "one") XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.set(object: SwiftBoolObject(), for: "two") XCTAssertEqual(state.count, 2) } @MainActor func testManagedMapAppendUnmanagedObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.map XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() $state.set(object: SwiftBoolObject(), for: "one") XCTAssertEqual(state.count, 1) } @MainActor func testManagedUnmanagedMapRemovePrimitive() throws { let object = SwiftUIObject() @StateRealmObject var state = object.primitiveMap XCTAssertEqual(state.count, 0) $state.set(object: 1, for: "one") XCTAssertEqual(state.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } $state.set(object: 2, for: "two") XCTAssertEqual(state.count, 2) $state.set(object: nil, for: "one") XCTAssertEqual(state.count, 1) XCTAssertEqual(state.keys, ["two"]) } @MainActor func testManagedUnmanagedMapRemoveUnmanagedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.map XCTAssertEqual(state.count, 0) $state.set(object: SwiftBoolObject(), for: "one") XCTAssertEqual(state.count, 1) $state.set(object: nil, for: "one") XCTAssertEqual(state.count, 0) } @MainActor func testManagedMapAppendRemoveObservedObject() throws { let object = SwiftUIObject() @StateRealmObject var state = object.map XCTAssertEqual(state.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(object) } _state.update() $state.set(object: SwiftBoolObject(), for: "one") XCTAssertEqual(state.count, 1) $state.set(object: nil, for: "one") XCTAssertEqual(state.count, 0) } // MARK: - ObservedResults Operations @MainActor func testResultsAppendUnmanagedObject() throws { let object = SwiftUIObject() let fullResults = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(fullResults.wrappedValue.count, 0) fullResults.projectedValue.append(object) XCTAssertEqual(fullResults.wrappedValue.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) realm.beginWrite() object.str = "abc" object.int = 1 // add another default inited object for filter comparison realm.add(SwiftUIObject()) try realm.commitWrite() let filteredResults = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration, filter: NSPredicate(format: "str = %@", "abc")) XCTAssertEqual(fullResults.wrappedValue.count, 2) XCTAssertEqual(filteredResults.wrappedValue.count, 1) var sortedResults = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration, filter: NSPredicate(format: "int >= 0"), sortDescriptor: SortDescriptor(keyPath: "int", ascending: true)) XCTAssertEqual(sortedResults.wrappedValue.count, 2) XCTAssertEqual(sortedResults.wrappedValue[0].int, 0) XCTAssertEqual(sortedResults.wrappedValue[1].int, 1) sortedResults = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration, filter: NSPredicate(format: "int >= 0"), sortDescriptor: SortDescriptor(keyPath: "int", ascending: false)) XCTAssertEqual(sortedResults.wrappedValue.count, 2) XCTAssertEqual(sortedResults.wrappedValue[0].int, 1) XCTAssertEqual(sortedResults.wrappedValue[1].int, 0) } @MainActor func testResultsAppendManagedObject() throws { @ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state let object = SwiftUIObject() XCTAssertEqual(state.count, 0) $state.append(object) XCTAssertEqual(state.count, 1) $state.append(object) XCTAssertEqual(state.count, 1) } @MainActor func testResultsRemoveUnmanagedObject() throws { @ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state let object = SwiftUIObject() XCTAssertEqual(state.count, 0) assertThrows($state.remove(object)) XCTAssertEqual(state.count, 0) } @MainActor func testResultsRemoveManagedObject() throws { @ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state let object = SwiftUIObject() XCTAssertEqual(state.count, 0) $state.append(object) XCTAssertEqual(state.count, 1) $state.remove(object) XCTAssertEqual(state.count, 0) } @MainActor func testResultsMoveUnmanagedObject() throws { @ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state let object = SwiftUIObject() XCTAssertEqual(state.count, 0) object.stringList.append(SwiftStringObject(stringCol: "Tom")) object.stringList.append(SwiftStringObject(stringCol: "Sam")) object.stringList.append(SwiftStringObject(stringCol: "Dan")) object.stringList.append(SwiftStringObject(stringCol: "Paul")) let binding = object.bind(\.stringList) XCTAssertEqual(object.stringList.first!.stringCol, "Tom") XCTAssertEqual(object.stringList[1].stringCol, "Sam") XCTAssertEqual(object.stringList[2].stringCol, "Dan") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") binding.move(fromOffsets: IndexSet([0]), toOffset: 3) XCTAssertEqual(object.stringList.first!.stringCol, "Sam") XCTAssertEqual(object.stringList[1].stringCol, "Dan") XCTAssertEqual(object.stringList[2].stringCol, "Tom") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") binding.move(fromOffsets: IndexSet([2]), toOffset: 4) XCTAssertEqual(object.stringList.first!.stringCol, "Sam") XCTAssertEqual(object.stringList[1].stringCol, "Dan") XCTAssertEqual(object.stringList[2].stringCol, "Paul") XCTAssertEqual(object.stringList.last!.stringCol, "Tom") binding.move(fromOffsets: IndexSet([3]), toOffset: 0) XCTAssertEqual(object.stringList.first!.stringCol, "Tom") XCTAssertEqual(object.stringList[1].stringCol, "Sam") XCTAssertEqual(object.stringList[2].stringCol, "Dan") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") XCTAssertEqual(state.count, 0) } @MainActor func testResultsMoveManagedObject() throws { @ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state let object = SwiftUIObject() XCTAssertEqual(state.count, 0) object.stringList.append(SwiftStringObject(stringCol: "Tom")) object.stringList.append(SwiftStringObject(stringCol: "Sam")) object.stringList.append(SwiftStringObject(stringCol: "Dan")) object.stringList.append(SwiftStringObject(stringCol: "Paul")) $state.append(object) let binding = object.bind(\.stringList) XCTAssertEqual(object.stringList.first!.stringCol, "Tom") XCTAssertEqual(object.stringList[1].stringCol, "Sam") XCTAssertEqual(object.stringList[2].stringCol, "Dan") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") binding.move(fromOffsets: IndexSet([0]), toOffset: 3) XCTAssertEqual(object.stringList.first!.stringCol, "Sam") XCTAssertEqual(object.stringList[1].stringCol, "Dan") XCTAssertEqual(object.stringList[2].stringCol, "Tom") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") binding.move(fromOffsets: IndexSet([2]), toOffset: 4) XCTAssertEqual(object.stringList.first!.stringCol, "Sam") XCTAssertEqual(object.stringList[1].stringCol, "Dan") XCTAssertEqual(object.stringList[2].stringCol, "Paul") XCTAssertEqual(object.stringList.last!.stringCol, "Tom") binding.move(fromOffsets: IndexSet([3]), toOffset: 0) XCTAssertEqual(object.stringList.first!.stringCol, "Tom") XCTAssertEqual(object.stringList[1].stringCol, "Sam") XCTAssertEqual(object.stringList[2].stringCol, "Dan") XCTAssertEqual(object.stringList.last!.stringCol, "Paul") XCTAssertEqual(state.count, 1) } @MainActor func testSwiftQuerySyntax() throws { let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { realm.add(SwiftUIObject(value: ["str": "apple"])) realm.add(SwiftUIObject(value: ["str": "antenna"])) realm.add(SwiftUIObject(value: ["str": "baz"])) } let filteredResults = ObservedResults(SwiftUIObject.self, configuration: realm.configuration, where: { $0.str.starts(with: "a") }, sortDescriptor: SortDescriptor.init(keyPath: \SwiftUIObject.str, ascending: true)) XCTAssertEqual(filteredResults.wrappedValue.count, 2) XCTAssertEqual(filteredResults.wrappedValue[0].str, "antenna") } @MainActor func testResultsAppendFrozenObject() throws { let state1 = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) let object1 = SwiftUIObject() XCTAssertEqual(state1.wrappedValue.count, 0) state1.projectedValue.append(object1) XCTAssertEqual(state1.wrappedValue.count, 1) state1.projectedValue.append(object1) XCTAssertEqual(state1.wrappedValue.count, 1) let state2 = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) for item in state1.wrappedValue { XCTAssert(item.isFrozen) state2.append(item) } XCTAssertEqual(state1.wrappedValue.count, 1) let realm = inMemoryRealm(inMemoryIdentifier) let object2 = SwiftUIObject() try! realm.write { realm.add(object2) } let frozenObj = object2.freeze() state2.append(frozenObj) XCTAssertEqual(state1.wrappedValue.count, 2) XCTAssertEqual(state2.wrappedValue.count, 2) } // MARK: Object Operations @MainActor func testUnmanagedObjectModification() throws { @StateRealmObject var state = SwiftUIObject() state.str = "bar" XCTAssertEqual(state.str, "bar") XCTAssertEqual($state.wrappedValue.str, "bar") } @MainActor func testManagedObjectModification() throws { @StateRealmObject var state = SwiftUIObject() ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) .projectedValue.append(state) assertThrows(state.str = "bar") $state.str.wrappedValue = "bar" XCTAssertEqual($state.wrappedValue.str, "bar") } @MainActor func testManagedObjectDelete() throws { let results = ObservedResults(SwiftUIObject.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) @StateRealmObject var state = SwiftUIObject() XCTAssertEqual(results.wrappedValue.count, 0) $state.delete() XCTAssertEqual(results.wrappedValue.count, 0) results.projectedValue.append(state) XCTAssertEqual(results.wrappedValue.count, 1) $state.delete() } // MARK: Bind @MainActor func testUnmanagedManagedObjectBind() { let object = SwiftUIObject() let binding = object.bind(\.str) XCTAssertEqual(object.str, "foo") XCTAssertEqual(binding.wrappedValue, "foo") binding.wrappedValue = "bar" XCTAssertEqual(binding.wrappedValue, "bar") let realm = inMemoryRealm(inMemoryIdentifier) try? realm.write { realm.add(object) } let managedBinding = object.bind(\.str) XCTAssertEqual(object.str, "bar") XCTAssertEqual(binding.wrappedValue, "bar") managedBinding.wrappedValue = "baz" XCTAssertEqual(object.str, "baz") XCTAssertEqual(binding.wrappedValue, "baz") } @MainActor func testStateRealmObjectKVO() throws { @StateRealmObject var object = SwiftUIObject() var hit = 0 let cancellable = _object._publisher .sink { _ in } receiveValue: { _ in hit += 1 } XCTAssertEqual(hit, 0) object.int += 1 XCTAssertEqual(hit, 1) XCTAssertNotNil(object.observationInfo) let realm = try Realm() try realm.write { realm.add(object) } XCTAssertEqual(hit, 1) XCTAssertNil(object.observationInfo) try realm.write { object.thaw()!.int += 1 } XCTAssertEqual(hit, 2) cancellable.cancel() XCTAssertEqual(hit, 2) } // MARK: - Projection ObservedResults Operations @MainActor func testResultsAppendProjection() throws { let realm = inMemoryRealm(inMemoryIdentifier) @ObservedResults(UIElementsProjection.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state XCTAssertEqual(state.count, 0) try! realm.write { realm.create(SwiftUIObject.self) } XCTAssertEqual(state.count, 1) } @MainActor func testResultsRemoveProjection() throws { let realm = inMemoryRealm(inMemoryIdentifier) @ObservedResults(UIElementsProjection.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) var state var object: SwiftUIObject! try! realm.write { object = realm.create(SwiftUIObject.self) } XCTAssertEqual(state.count, 1) try! realm.write { realm.delete(object) } XCTAssertEqual(state.count, 0) } @MainActor func testProjectionStateRealmObjectKVO() throws { @StateRealmObject var projection = UIElementsProjection(projecting: SwiftUIObject()) var hit = 0 let cancellable = _projection._publisher .sink { _ in } receiveValue: { _ in hit += 1 } XCTAssertEqual(hit, 0) projection.counter += 1 XCTAssertEqual(hit, 1) XCTAssertNotNil(projection.rootObject.observationInfo) let realm = try Realm() try realm.write { realm.add(projection.rootObject) } XCTAssertEqual(hit, 1) XCTAssertNil(projection.rootObject.observationInfo) try realm.write { projection.thaw()!.counter += 1 } XCTAssertEqual(hit, 2) cancellable.cancel() XCTAssertEqual(hit, 2) } @MainActor func testProjectionDelete() throws { let results = ObservedResults(UIElementsProjection.self, configuration: inMemoryRealm(inMemoryIdentifier).configuration) let projection = UIElementsProjection(projecting: SwiftUIObject()) @StateRealmObject var state = projection XCTAssertEqual(results.wrappedValue.count, 0) $state.delete() XCTAssertEqual(results.wrappedValue.count, 0) results.projectedValue.append(state) XCTAssertEqual(results.wrappedValue.count, 1) $state.delete() } // MARK: - Projection Bind @MainActor func testProjectionBind() { let projection = UIElementsProjection(projecting: SwiftUIObject()) let binding = projection.bind(\.label) XCTAssertEqual(projection.label, "foo") XCTAssertEqual(binding.wrappedValue, "foo") binding.wrappedValue = "bar" XCTAssertEqual(binding.wrappedValue, "bar") let realm = inMemoryRealm(inMemoryIdentifier) try? realm.write { realm.add(projection.rootObject) } let managedBinding = projection.bind(\.label) XCTAssertEqual(projection.label, "bar") XCTAssertEqual(binding.wrappedValue, "bar") managedBinding.wrappedValue = "baz" XCTAssertEqual(projection.label, "baz") XCTAssertEqual(binding.wrappedValue, "baz") } // MARK: - ObservedSectionedResults @MainActor func testObservedSectionedResults() throws { let fullResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(fullResults.wrappedValue.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { let object = SwiftUIObject() object.str = "abc" object.int = 1 // add another default inited object for filter comparison realm.add(object) } realm.refresh() XCTAssertEqual(fullResults.wrappedValue.count, 1) XCTAssertEqual(fullResults.wrappedValue[0].key, "abc") try realm.write { let object = SwiftUIObject() object.str = "def" object.int = 1 // add another default inited object for filter comparison realm.add(object) } var filteredResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, filter: NSPredicate(format: "str = %@", "def"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(filteredResults.wrappedValue.count, 1) XCTAssertEqual(filteredResults.wrappedValue[0].key, "def") filteredResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, where: { $0.str == "def" }, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(filteredResults.wrappedValue.count, 1) XCTAssertEqual(filteredResults.wrappedValue[0].key, "def") fullResults.where = { $0.str == "def" } XCTAssertEqual(fullResults.wrappedValue.count, 1) XCTAssertEqual(fullResults.wrappedValue[0].key, "def") fullResults.filter = NSPredicate(format: "str != %@", "def") XCTAssertEqual(fullResults.wrappedValue.count, 1) XCTAssertEqual(fullResults.wrappedValue[0].key, "abc") } @MainActor func testObservedSectionedResultsWithProjection() throws { let fullResults = ObservedSectionedResults(UIElementsProjection.self, sectionKeyPath: \.label, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(fullResults.wrappedValue.count, 0) let realm = inMemoryRealm(inMemoryIdentifier) try realm.write { let object = SwiftUIObject() object.str = "abc" object.int = 1 // add another default inited object for filter comparison realm.add(object) } realm.refresh() XCTAssertEqual(fullResults.wrappedValue.count, 1) XCTAssertEqual(fullResults.wrappedValue[0].key, "abc") try realm.write { let object = SwiftUIObject() object.str = "def" object.int = 1 // add another default inited object for filter comparison realm.add(object) } let filteredResults = ObservedSectionedResults(UIElementsProjection.self, sectionKeyPath: \.label, filter: NSPredicate(format: "str = %@", "def"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(filteredResults.wrappedValue.count, 1) XCTAssertEqual(filteredResults.wrappedValue[0].key, "def") } @MainActor func testAllObservedSectionedResultsConstructors() throws { let realm = inMemoryRealm(inMemoryIdentifier) let object1 = SwiftUIObject() let object2 = SwiftUIObject() try realm.write { object1.str = "foo" realm.add(object1) object2.str = "bar" realm.add(object2) } // Projections with `sectionKeyPath` var projectionSectionedResults = ObservedSectionedResults(UIElementsProjection.self, sectionKeyPath: \.label, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(projectionSectionedResults.wrappedValue.count, 2) XCTAssertEqual(projectionSectionedResults.wrappedValue.allKeys, ["bar", "foo"]) XCTAssertEqual(projectionSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[0][0].label, "bar") XCTAssertEqual(projectionSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[1][0].label, "foo") projectionSectionedResults = ObservedSectionedResults(UIElementsProjection.self, sectionKeyPath: \.label, sortDescriptors: [SortDescriptor.init(keyPath: "str", ascending: false)], configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(projectionSectionedResults.wrappedValue.count, 2) XCTAssertEqual(projectionSectionedResults.wrappedValue.allKeys, ["foo", "bar"]) XCTAssertEqual(projectionSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[0][0].label, "foo") XCTAssertEqual(projectionSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[1][0].label, "bar") projectionSectionedResults = ObservedSectionedResults(UIElementsProjection.self, sectionKeyPath: \.label, sortDescriptors: [SortDescriptor.init(keyPath: "str")], filter: NSPredicate(format: "str == 'foo'"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(projectionSectionedResults.wrappedValue.count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue.allKeys, ["foo"]) XCTAssertEqual(projectionSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[0][0].label, "foo") // Projections with `sectionBlock` projectionSectionedResults = ObservedSectionedResults(UIElementsProjection.self, sectionBlock: { $0.label.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor.init(keyPath: "str")], configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(projectionSectionedResults.wrappedValue.count, 2) XCTAssertEqual(projectionSectionedResults.wrappedValue.allKeys, ["b", "f"]) XCTAssertEqual(projectionSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[0][0].label, "bar") XCTAssertEqual(projectionSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[1][0].label, "foo") projectionSectionedResults = ObservedSectionedResults(UIElementsProjection.self, sectionBlock: { $0.label.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor.init(keyPath: "str")], filter: NSPredicate(format: "str == 'foo'"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(projectionSectionedResults.wrappedValue.count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue.allKeys, ["f"]) XCTAssertEqual(projectionSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(projectionSectionedResults.wrappedValue[0][0].label, "foo") // Objects with `sectionKeyPath` var objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 2) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["bar", "foo"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "bar") XCTAssertEqual(objectSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[1][0].str, "foo") objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, sortDescriptors: [SortDescriptor.init(keyPath: "str", ascending: false)], configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 2) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["foo", "bar"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "foo") XCTAssertEqual(objectSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[1][0].str, "bar") objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, sortDescriptors: [SortDescriptor.init(keyPath: "str")], where: { $0.str == "foo" }, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["foo"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "foo") objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionKeyPath: \.str, sortDescriptors: [SortDescriptor.init(keyPath: "str")], filter: NSPredicate(format: "str == 'foo'"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["foo"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "foo") // Objects with `sectionBlock` objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionBlock: { $0.str.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor.init(keyPath: "str")], configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 2) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["b", "f"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "bar") XCTAssertEqual(objectSectionedResults.wrappedValue[1].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[1][0].str, "foo") objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionBlock: { $0.str.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor.init(keyPath: "str")], filter: NSPredicate(format: "str == 'foo'"), configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["f"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "foo") objectSectionedResults = ObservedSectionedResults(SwiftUIObject.self, sectionBlock: { $0.str.first.map(String.init(_:)) ?? "" }, sortDescriptors: [SortDescriptor.init(keyPath: "str")], where: { $0.str == "foo" }, configuration: inMemoryRealm(inMemoryIdentifier).configuration) XCTAssertEqual(objectSectionedResults.wrappedValue.count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue.allKeys, ["f"]) XCTAssertEqual(objectSectionedResults.wrappedValue[0].count, 1) XCTAssertEqual(objectSectionedResults.wrappedValue[0][0].str, "foo") } } ================================================ FILE: RealmSwift/Tests/SwiftUnicodeTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif let utf8TestString = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" class SwiftUnicodeTests: TestCase { func testUTF8StringContents() { let realm = realmWithTestPath() try! realm.write { realm.create(SwiftStringObject.self, value: [utf8TestString]) return } let obj1 = realm.objects(SwiftStringObject.self).first! XCTAssertEqual(obj1.stringCol, utf8TestString) let obj2 = realm.objects(SwiftStringObject.self).filter("stringCol == %@", utf8TestString).first! assertEqual(obj1, obj2) XCTAssertEqual(obj2.stringCol, utf8TestString) XCTAssertEqual(Int(0), realm.objects(SwiftStringObject.self).filter("stringCol != %@", utf8TestString).count) } func testUTF8PropertyWithUTF8StringContents() { let realm = realmWithTestPath() try! realm.write { realm.create(SwiftUTF8Object.self, value: [utf8TestString]) return } let obj1 = realm.objects(SwiftUTF8Object.self).first! XCTAssertEqual(obj1.柱колоéнǢкƱаم👍, utf8TestString, "Storing and retrieving a string with UTF8 content should work") let obj2 = realm.objects(SwiftUTF8Object.self).filter("%K == %@", "柱колоéнǢкƱаم👍", utf8TestString).first! assertEqual(obj1, obj2) } } ================================================ FILE: RealmSwift/Tests/TestCase.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import Realm import Realm.Dynamic import RealmSwift import XCTest #if canImport(RealmTestSupport) import RealmTestSupport import RealmSwiftTestSupport #endif func inMemoryRealm(_ inMememoryIdentifier: String) -> Realm { return try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: inMememoryIdentifier)) } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) func openRealm(configuration: Realm.Configuration = .defaultConfiguration, actor: isolated any Actor) async throws -> Realm { #if compiler(<6) try await Realm(configuration: configuration, actor: actor) #else try await Realm.open(configuration: configuration) #endif } class TestCase: RLMTestCaseBase { @Locked var exceptionThrown = false var testDir: String! = nil let queue = DispatchQueue(label: "background") func configurationWithTestPath(configuration: Realm.Configuration = Realm.Configuration()) -> Realm.Configuration { var configuration = configuration configuration.fileURL = testRealmURL() return configuration } @discardableResult func realmWithTestPath(configuration: Realm.Configuration = Realm.Configuration()) -> Realm { var configuration = configuration configuration.fileURL = testRealmURL() return try! Realm(configuration: configuration) } override class func tearDown() { RLMRealm.resetRealmState() super.tearDown() } override func invokeTest() { testDir = RLMRealmPathForFile(realmFilePrefix()) do { try FileManager.default.removeItem(atPath: testDir) } catch { // The directory shouldn't actually already exist, so not an error } try! FileManager.default.createDirectory(at: URL(fileURLWithPath: testDir, isDirectory: true), withIntermediateDirectories: true, attributes: nil) let config = Realm.Configuration(fileURL: defaultRealmURL()) Realm.Configuration.defaultConfiguration = config exceptionThrown = false autoreleasepool { super.invokeTest() } queue.sync { } if !exceptionThrown { XCTAssertFalse(RLMHasCachedRealmForPath(defaultRealmURL().path)) XCTAssertFalse(RLMHasCachedRealmForPath(testRealmURL().path)) } resetRealmState() do { try FileManager.default.removeItem(atPath: testDir) } catch { XCTFail("Unable to delete realm files") } // Verify that there are no remaining realm files after the test let parentDir = (testDir as NSString).deletingLastPathComponent for url in FileManager.default.enumerator(atPath: parentDir)! { let url = url as! NSString XCTAssertNotEqual(url.pathExtension, "realm", "Lingering realm file at \(parentDir)/\(url)") assert(url.pathExtension != "realm") } } func dispatchSyncNewThread(block: @Sendable @escaping () -> Void) { queue.async { autoreleasepool { block() } } queue.sync { } } func dispatchSyncBackground(block: @Sendable @escaping (TestCase) -> Void) { nonisolated(unsafe) let unsafeSelf = self queue.async { autoreleasepool { block(unsafeSelf) } } queue.sync { } } func assertThrows(_ block: @autoclosure () -> T, named: String? = RLMExceptionName, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { exceptionThrown = true RLMAssertThrowsWithName(self, { _ = block() }, named, message, fileName, lineNumber) } func assertThrows(_ block: @autoclosure () -> T, reason: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { exceptionThrown = true RLMAssertThrowsWithReason(self, { _ = block() }, reason, message, fileName, lineNumber) } func assertThrows(_ block: @autoclosure () -> T, reasonMatching regexString: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { exceptionThrown = true RLMAssertThrowsWithReasonMatching(self, { _ = block() }, regexString, message, fileName, lineNumber) } private func realmFilePrefix() -> String { let name: String? = self.name return name!.trimmingCharacters(in: CharacterSet(charactersIn: "-[]")) } internal func testRealmURL() -> URL { return realmURLForFile("test.realm") } internal func defaultRealmURL() -> URL { return realmURLForFile("default.realm") } private func realmURLForFile(_ fileName: String) -> URL { let directory = URL(fileURLWithPath: testDir, isDirectory: true) return directory.appendingPathComponent(fileName, isDirectory: false) } } extension Realm { @discardableResult public func create(_ type: T.Type, value: [String: Any], update: UpdatePolicy = .error) -> T { return create(type, value: value as Any, update: update) } @discardableResult public func create(_ type: T.Type, value: [Any], update: UpdatePolicy = .error) -> T { return create(type, value: value as Any, update: update) } } extension Object { public convenience init(value: [String: Any]) { self.init(value: value as Any) } public convenience init(value: [Any]) { self.init(value: value as Any) } } ================================================ FILE: RealmSwift/Tests/TestUtils.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2022 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 Foundation import RealmSwift import XCTest #if canImport(RealmTestSupport) import RealmTestSupport #endif // Wrap a sendable value in a lock to enable sharing a mutable variable between // threads. // // When guarding a member variable this can be used as a property wrapper to // simplify use. Due to a bug in the Swift compiler // (https://github.com/apple/swift/issues/61358), this current doesn't work for // local variables. @propertyWrapper public class Locked: @unchecked Sendable { private var _value: T private let lock: os_unfair_lock_t = .allocate(capacity: 1) public init(_ value: T) { _value = value lock.initialize(to: os_unfair_lock()) } public var value: T { get { withLock {$0 } } set { withLock { $0 = newValue } } // Accessor for modify operations (e.g. += and mutating functions on structs) // which eliminates race conditions which would otherwise happen if multiple // threads mutated the value at the same time. _modify { os_unfair_lock_lock(lock) yield &_value os_unfair_lock_unlock(lock) } } // Invoke a closure while holding the lock. This can be used to safely // perform logic more complicated than a simple assignment or read of the // value. public func withLock(_ fn: (inout T) -> U) -> U { os_unfair_lock_lock(lock) let ret = fn(&_value) os_unfair_lock_unlock(lock) return ret } // Property wrapper implementation public convenience init(wrappedValue: T) { self.init(wrappedValue) } public var wrappedValue: T { get { value } set { value = newValue } } } /// Check whether two test objects are equal (refer to the same row in the same Realm), even if their models /// don't define a primary key. public func assertEqual(_ o1: O?, _ o2: O?, fileName: StaticString = #filePath, lineNumber: UInt = #line) { if o1 == nil && o2 == nil { return } if let o1 = o1, let o2 = o2, o1.isSameObject(as: o2) { return } XCTFail("Objects expected to be equal, but weren't. First: \(String(describing: o1)), " + "second: \(String(describing: o2))", file: (fileName), line: lineNumber) } /// Check whether two collections containing Realm objects are equal. public func assertEqual(_ c1: C, _ c2: C, fileName: StaticString = #filePath, lineNumber: UInt = #line) where C.Iterator.Element: Object { XCTAssertEqual(c1.count, c2.count, "Collection counts were incorrect", file: (fileName), line: lineNumber) for (o1, o2) in zip(c1, c2) { assertEqual(o1, o2, fileName: fileName, lineNumber: lineNumber) } } public func assertEqual(_ expected: [T?], _ actual: [T?], file: StaticString = #file, line: UInt = #line) { if expected.count != actual.count { XCTFail("assertEqual failed: (\"\(expected)\") is not equal to (\"\(actual)\")", file: (file), line: line) return } XCTAssertEqual(expected.count, actual.count, "Collection counts were incorrect", file: (file), line: line) for (e, a) in zip(expected, actual) where e != a { XCTFail("assertEqual failed: (\"\(expected)\") is not equal to (\"\(actual)\")", file: (file), line: line) return } } public func assertSucceeds(message: String? = nil, fileName: StaticString = #filePath, lineNumber: UInt = #line, block: () throws -> Void) { do { try block() } catch { XCTFail("Expected no error, but instead caught <\(error)>.", file: (fileName), line: lineNumber) } } public func assertFails(_ expectedError: Realm.Error.Code, _ message: String? = nil, fileName: StaticString = #filePath, lineNumber: UInt = #line, block: () throws -> T) { do { _ = try autoreleasepool(invoking: block) XCTFail("Expected to catch <\(expectedError)>, but no error was thrown.", file: fileName, line: lineNumber) } catch let e as Realm.Error where e.code == expectedError { if message != nil { XCTAssertEqual(e.localizedDescription, message, file: fileName, line: lineNumber) } } catch { XCTFail("Expected to catch <\(expectedError)>, but instead caught <\(error)>.", file: fileName, line: lineNumber) } } public func assertFails(_ expectedError: Realm.Error.Code, _ file: URL, _ message: String, fileName: StaticString = #filePath, lineNumber: UInt = #line, block: () throws -> T) { do { _ = try autoreleasepool(invoking: block) XCTFail("Expected to catch <\(expectedError)>, but no error was thrown.", file: fileName, line: lineNumber) } catch let e as Realm.Error where e.code == expectedError { XCTAssertEqual(e.localizedDescription, message, file: fileName, line: lineNumber) XCTAssertEqual(e.fileURL, file, file: fileName, line: lineNumber) } catch { XCTFail("Expected to catch <\(expectedError)>, but instead caught <\(error)>.", file: fileName, line: lineNumber) } } public func assertFails(_ expectedError: Error, _ message: String? = nil, fileName: StaticString = #filePath, lineNumber: UInt = #line, block: () throws -> T) { do { _ = try autoreleasepool(invoking: block) XCTFail("Expected to catch <\(expectedError)>, but no error was thrown.", file: fileName, line: lineNumber) } catch let e where e._code == expectedError._code { // Success! } catch { XCTFail("Expected to catch <\(expectedError)>, but instead caught <\(error)>.", file: fileName, line: lineNumber) } } public func assertNil(block: @autoclosure() -> T?, _ message: String? = nil, fileName: StaticString = #filePath, lineNumber: UInt = #line) { XCTAssert(block() == nil, message ?? "", file: (fileName), line: lineNumber) } public extension XCTestCase { func assertMatches(_ block: @autoclosure () -> String, _ regexString: String, _ message: String? = nil, fileName: String = #file, lineNumber: UInt = #line) { RLMAssertMatches(self, block, regexString, message, fileName, lineNumber) } } /// Check that a `MutableSet` contains all expected elements. public func assertSetContains(_ set: MutableSet, keyPath: KeyPath, items: [U]) where U: Hashable { var itemMap = Dictionary(uniqueKeysWithValues: items.map { ($0, false)}) set.map { $0[keyPath: keyPath]}.forEach { itemMap[$0] = items.contains($0) } // ensure all items are present in the set. XCTAssertFalse(itemMap.values.contains(false)) } /// Check that an `AnyRealmCollection` contains all expected elements. public func assertAnyRealmCollectionContains(_ set: AnyRealmCollection, keyPath: KeyPath, items: [U]) where U: Hashable { var itemMap = Dictionary(uniqueKeysWithValues: items.map { ($0, false) }) set.map { $0[keyPath: keyPath]}.forEach { itemMap[$0] = items.contains($0) } // ensure all items are present in the set. XCTAssertFalse(itemMap.values.contains(false)) } #if compiler(<6) @_unsafeInheritExecutor @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func assertThrowsErrorAsync( _ expression: @autoclosure () async throws -> T, _ expectedError: E, file: StaticString = #filePath, line: UInt = #line) async { do { _ = try await expression() XCTFail("Expected expression to throw error \(expectedError)", file: file, line: line) } catch let error as E { XCTAssertEqual(error, expectedError, file: file, line: line) } catch { XCTFail("Expected expression to throw error \(expectedError) but got \(error)", file: file, line: line) } } // Fork, call an expression which should hit a precondition failure in the child // process, and then verify that the expected failure message was printed. Note // that Swift and Foundation do not support fork(), so anything which does more // than a very limited amount of work before the precondition failure is very // likely to break. @_unsafeInheritExecutor @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.4, *) public func assertPreconditionFailure(_ message: String, _ expression: () async throws -> T, file: StaticString = #filePath, line: UInt = #line) async throws { // We can't perform these tests on tvOS, watchOS, or on devices guard RLMCanFork() else { return } let pipe = Pipe() let pid = RLMFork() if pid == -1 { return XCTFail("Failed to fork for test", file: file, line: line) } if pid == 0 { // In child process // Point stdout and stderr at our pipe let fd = pipe.fileHandleForWriting.fileDescriptor while dup2(fd, STDOUT_FILENO) == -1 && errno == EINTR {} while dup2(fd, STDERR_FILENO) == -1 && errno == EINTR {} _ = try await expression() exit(0) } try pipe.fileHandleForWriting.close() while true { var status: Int32 = 0 let ret = waitpid(pid, &status, 0) if ret == -1 && errno == EINTR { continue } guard ret > 0 else { return XCTFail("Failed to wait for child process to exit? errno: \(errno)", file: file, line: line) } guard status != 0 else { return XCTFail("Expected child process to crash with message \"\(message)\", but it exited cleanly", file: file, line: line) } break } guard let data = try pipe.fileHandleForReading.readToEnd() else { return XCTFail("Expected child process to crash with message \"\(message)\", but it exited without printing anything", file: file, line: line) } guard let str = String(data: data, encoding: .utf8) else { return XCTFail("Expected child process to crash with message \"\(message)\", but it did not print valid utf-8", file: file, line: line) } if !str.contains("Precondition failed: \(message)") && !str.contains("Fatal error: \(message)") { XCTFail("Expected \"\(str)\" to contain \"\(message)\")", file: file, line: line) } } #else @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func assertThrowsErrorAsync( _ expression: @autoclosure () async throws -> T, _ expectedError: E, _isolation: isolated (any Actor)? = #isolation, file: StaticString = #filePath, line: UInt = #line ) async { do { _ = try await expression() XCTFail("Expected expression to throw error \(expectedError)", file: file, line: line) } catch let error as E { XCTAssertEqual(error, expectedError, file: file, line: line) } catch { XCTFail("Expected expression to throw error \(expectedError) but got \(error)", file: file, line: line) } } // Fork, call an expression which should hit a precondition failure in the child // process, and then verify that the expected failure message was printed. Note // that Swift and Foundation do not support fork(), so anything which does more // than a very limited amount of work before the precondition failure is very // likely to break. @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.4, *) public func assertPreconditionFailure( _ message: String, _ expression: () async throws -> T, _isolation: isolated (any Actor)? = #isolation, file: StaticString = #filePath, line: UInt = #line ) async throws { // We can't perform these tests on tvOS, watchOS, or on devices guard RLMCanFork() else { return } let pipe = Pipe() let pid = RLMFork() if pid == -1 { return XCTFail("Failed to fork for test", file: file, line: line) } if pid == 0 { // In child process // Point stdout and stderr at our pipe let fd = pipe.fileHandleForWriting.fileDescriptor while dup2(fd, STDOUT_FILENO) == -1 && errno == EINTR {} while dup2(fd, STDERR_FILENO) == -1 && errno == EINTR {} _ = try await expression() exit(0) } try pipe.fileHandleForWriting.close() while true { var status: Int32 = 0 let ret = waitpid(pid, &status, 0) if ret == -1 && errno == EINTR { continue } guard ret > 0 else { return XCTFail("Failed to wait for child process to exit? errno: \(errno)", file: file, line: line) } guard status != 0 else { return XCTFail("Expected child process to crash with message \"\(message)\", but it exited cleanly", file: file, line: line) } break } guard let data = try pipe.fileHandleForReading.readToEnd() else { return XCTFail("Expected child process to crash with message \"\(message)\", but it exited without printing anything", file: file, line: line) } guard let str = String(data: data, encoding: .utf8) else { return XCTFail("Expected child process to crash with message \"\(message)\", but it did not print valid utf-8", file: file, line: line) } if !str.contains("Precondition failed: \(message)") && !str.contains("Fatal error: \(message)") { XCTFail("Expected \"\(str)\" to contain \"\(message)\")", file: file, line: line) } } #endif ================================================ FILE: RealmSwift/Tests/TestValueFactory.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import RealmSwift // MARK: - ObjectFactory protocol ObjectFactory { static func get() -> T } struct ManagedObjectFactory: ObjectFactory { static func get() -> T { let config = Realm.Configuration(inMemoryIdentifier: "test", objectTypes: [ModernAllTypesObject.self, ModernCollectionsOfEnums.self, ModernEmbeddedObject.self, CustomPersistableCollections.self, SwiftStringObject.self]) let realm = try! Realm(configuration: config) if !realm.isInWriteTransaction { realm.beginWrite() } let obj = T() realm.add(obj) return obj } } struct UnmanagedObjectFactory: ObjectFactory { static func get() -> T { return T() } } // MARK: ValueFactory protocol ValueFactory where Self: RealmCollectionValue { associatedtype Wrapped: RealmCollectionValue = Self static func values() -> [Self] static func min() -> Self static func max() -> Self } extension ValueFactory { static func min() -> Self { values().first! } static func max() -> Self { values().last! } } extension ValueFactory where Self: PersistableEnum { static func values() -> [Self] { return Array(Self.allCases) } } protocol NumericValueFactory: ValueFactory { associatedtype AverageType: RealmCollectionValue = Double where AverageType.PersistedType: AddableType static func sum(_ values: [Self]) -> Double static func average() -> Double static func doubleValue(_ value: Self) -> Double static var zero: Self { get } } extension NumericValueFactory { static func sum() -> Double { return sum(values()) } static func average() -> Double { return sum() / 3 } } extension NumericValueFactory where Self: AdditiveArithmetic { static func sum(_ values: [Self]) -> Double { return doubleValue(values.reduce(.zero, +)) } } extension NumericValueFactory where PersistedType == Self { static func doubleValue(_ value: Self) -> Double { return (value as! NSNumber).doubleValue } } extension NumericValueFactory where Self: PersistableEnum { static func doubleValue(_ value: Self) -> Double { return (value as! NSNumber).doubleValue } } extension NumericValueFactory where Self: CustomPersistable, PersistedType: NumericValueFactory { static func sum(_ values: [Self]) -> Double { return PersistedType.sum(values.map { $0.persistableValue }) } } protocol ListValueFactory: ValueFactory { associatedtype ListRoot: Object static var array: KeyPath> { get } } protocol SetValueFactory: ValueFactory { associatedtype SetRoot: Object static var mutableSet: KeyPath> { get } } protocol MapValueFactory: ValueFactory { associatedtype MapRoot: Object static var map: KeyPath> { get } } protocol ListValueFactoryOptional: ListValueFactory where Self: _RealmCollectionValueInsideOptional { static var optArray: KeyPath> { get } } protocol SetValueFactoryOptional: SetValueFactory where Self: _RealmCollectionValueInsideOptional { static var optMutableSet: KeyPath> { get } } protocol MapValueFactoryOptional: MapValueFactory where Self: _RealmCollectionValueInsideOptional { static var optMap: KeyPath> { get } } // MARK: Optional extension Optional: ValueFactory where Wrapped: ValueFactory & _RealmCollectionValueInsideOptional { static func values() -> [Self] { var v = Array(Wrapped.values()) v.remove(at: 1) v.insert(nil, at: 0) return v } static func min() -> Self { Self.some(Wrapped.min()) } } extension Optional: NumericValueFactory where Wrapped: NumericValueFactory & _RealmCollectionValueInsideOptional { typealias AverageType = Wrapped.AverageType static func doubleValue(_ value: Self) -> Double { return Wrapped.doubleValue(value!) } static func min() -> Self { Self.some(Wrapped.min()) } static func sum(_ values: [Self]) -> Double { return Wrapped.sum(values.compactMap { $0 }) } static func average() -> Double { return sum() / 2 } static var zero: Optional { Wrapped.zero } } extension Optional: ListValueFactory where Wrapped: ListValueFactoryOptional { static var array: KeyPath> { Wrapped.optArray } } extension Optional: SetValueFactory where Wrapped: SetValueFactoryOptional { static var mutableSet: KeyPath> { Wrapped.optMutableSet } } extension Optional: MapValueFactory where Wrapped: MapValueFactoryOptional { static var map: KeyPath> { Wrapped.optMap } } // MARK: - Bool extension Bool: ValueFactory { private static let _values: [Bool] = [true, false, true] static func values() -> [Bool] { return _values } } extension Bool: ListValueFactoryOptional { static var array: KeyPath> { \.arrayBool } static var optArray: KeyPath> { \.arrayOptBool } } extension Bool: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setBool } static var optMutableSet: KeyPath> { \.setOptBool } } extension Bool: MapValueFactoryOptional { static var map: KeyPath> { \.mapBool } static var optMap: KeyPath> { \.mapOptBool } } // MARK: - Int extension Int: NumericValueFactory { private static let _values: [Int] = [1, 2, 3] static func values() -> [Int] { return _values } } extension Int: ListValueFactoryOptional { static var array: KeyPath> { \.arrayInt } static var optArray: KeyPath> { \.arrayOptInt } } extension Int: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt } static var optMutableSet: KeyPath> { \.setOptInt } } extension Int: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt } static var optMap: KeyPath> { \.mapOptInt } } // MARK: - Int8 extension Int8: NumericValueFactory { private static let _values: [Int8] = [1, 2, 3] static func values() -> [Int8] { return _values } } extension Int8: ListValueFactoryOptional { static var array: KeyPath> { \.arrayInt8 } static var optArray: KeyPath> { \.arrayOptInt8 } } extension Int8: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt8 } static var optMutableSet: KeyPath> { \.setOptInt8 } } extension Int8: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt8 } static var optMap: KeyPath> { \.mapOptInt8 } } // MARK: - Int16 extension Int16: NumericValueFactory { private static let _values: [Int16] = [1, 2, 3] static func values() -> [Int16] { return _values } } extension Int16: ListValueFactoryOptional { static var array: KeyPath> { \.arrayInt16 } static var optArray: KeyPath> { \.arrayOptInt16 } } extension Int16: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt16 } static var optMutableSet: KeyPath> { \.setOptInt16 } } extension Int16: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt16 } static var optMap: KeyPath> { \.mapOptInt16 } } // MARK: - Int32 extension Int32: NumericValueFactory { private static let _values: [Int32] = [1, 2, 3] static func values() -> [Int32] { return _values } } extension Int32: ListValueFactoryOptional { static var array: KeyPath> { \.arrayInt32 } static var optArray: KeyPath> { \.arrayOptInt32 } } extension Int32: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt32 } static var optMutableSet: KeyPath> { \.setOptInt32 } } extension Int32: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt32 } static var optMap: KeyPath> { \.mapOptInt32 } } // MARK: - Int64 extension Int64: NumericValueFactory { private static let _values: [Int64] = [1, 2, 3] static func values() -> [Int64] { return _values } } extension Int64: ListValueFactoryOptional { static var array: KeyPath> { \.arrayInt64 } static var optArray: KeyPath> { \.arrayOptInt64 } } extension Int64: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt64 } static var optMutableSet: KeyPath> { \.setOptInt64 } } extension Int64: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt64 } static var optMap: KeyPath> { \.mapOptInt64 } } // MARK: - Float extension Float: NumericValueFactory { private static let _values: [Float] = [1.1, 2.2, 3.3] static func values() -> [Float] { return _values } } extension Float: ListValueFactoryOptional { static var array: KeyPath> { \.arrayFloat } static var optArray: KeyPath> { \.arrayOptFloat } } extension Float: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setFloat } static var optMutableSet: KeyPath> { \.setOptFloat } } extension Float: MapValueFactoryOptional { static var map: KeyPath> { \.mapFloat } static var optMap: KeyPath> { \.mapOptFloat } } // MARK: - Double extension Double: NumericValueFactory { private static let _values: [Double] = [1.1, 2.2, 3.3] static func values() -> [Double] { return _values } } extension Double: ListValueFactoryOptional { static var array: KeyPath> { \.arrayDouble } static var optArray: KeyPath> { \.arrayOptDouble } } extension Double: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setDouble } static var optMutableSet: KeyPath> { \.setOptDouble } } extension Double: MapValueFactoryOptional { static var map: KeyPath> { \.mapDouble } static var optMap: KeyPath> { \.mapOptDouble } } // MARK: - String extension String: ValueFactory { private static let _values: [String] = ["a", "b", "c"] static func values() -> [String] { return _values } } extension String: ListValueFactoryOptional { static var array: KeyPath> { \.arrayString } static var optArray: KeyPath> { \.arrayOptString } } extension String: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setString } static var optMutableSet: KeyPath> { \.setOptString } } extension String: MapValueFactoryOptional { static var map: KeyPath> { \.mapString } static var optMap: KeyPath> { \.mapOptString } } // MARK: - Data extension Data: ValueFactory { private static let _values: [Data] = [Data("a".utf8), Data("b".utf8), Data("c".utf8)] static func values() -> [Data] { return _values } } extension Data: ListValueFactoryOptional { static var array: KeyPath> { \.arrayBinary } static var optArray: KeyPath> { \.arrayOptBinary } } extension Data: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setBinary } static var optMutableSet: KeyPath> { \.setOptBinary } } extension Data: MapValueFactoryOptional { static var map: KeyPath> { \.mapBinary } static var optMap: KeyPath> { \.mapOptBinary } } // MARK: - Date extension Date: ValueFactory { private static let _values: [Date] = [Date(), Date().addingTimeInterval(10), Date().addingTimeInterval(20)] static func values() -> [Date] { return _values } } extension Date: ListValueFactoryOptional { static var array: KeyPath> { \.arrayDate } static var optArray: KeyPath> { \.arrayOptDate } } extension Date: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setDate } static var optMutableSet: KeyPath> { \.setOptDate } } extension Date: MapValueFactoryOptional { static var map: KeyPath> { \.mapDate } static var optMap: KeyPath> { \.mapOptDate } } // MARK: - Decimal128 extension Decimal128: NumericValueFactory { private static let _values: [Decimal128] = [Decimal128(number: 1), Decimal128(number: 2), Decimal128(number: 3)] static func values() -> [Decimal128] { return _values } static func doubleValue(_ value: Decimal128) -> Double { return value.doubleValue } } extension Decimal128: ListValueFactoryOptional { static var array: KeyPath> { \.arrayDecimal } static var optArray: KeyPath> { \.arrayOptDecimal } } extension Decimal128: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setDecimal } static var optMutableSet: KeyPath> { \.setOptDecimal } } extension Decimal128: MapValueFactoryOptional { static var map: KeyPath> { \.mapDecimal } static var optMap: KeyPath> { \.mapOptDecimal } } // MARK: - ObjectId extension ObjectId: ValueFactory { private static let _values: [ObjectId] = [ObjectId.generate(), ObjectId.generate(), ObjectId.generate()] static func values() -> [ObjectId] { return _values } } extension ObjectId: ListValueFactoryOptional { static var array: KeyPath> { \.arrayObjectId } static var optArray: KeyPath> { \.arrayOptObjectId } } extension ObjectId: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setObjectId } static var optMutableSet: KeyPath> { \.setOptObjectId } } extension ObjectId: MapValueFactoryOptional { static var map: KeyPath> { \.mapObjectId } static var optMap: KeyPath> { \.mapOptObjectId } } // MARK: - UUID extension UUID: ValueFactory { private static let _values: [UUID] = [UUID(), UUID(), UUID()] static func values() -> [UUID] { return _values } } extension UUID: ListValueFactoryOptional { static var array: KeyPath> { \.arrayUuid } static var optArray: KeyPath> { \.arrayOptUuid } } extension UUID: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setUuid } static var optMutableSet: KeyPath> { \.setOptUuid } } extension UUID: MapValueFactoryOptional { static var map: KeyPath> { \.mapUuid } static var optMap: KeyPath> { \.mapOptUuid } } // MARK: - EnumInt enum EnumInt: Int, PersistableEnum { case value1 = 1 case value2 = 2 case value3 = 3 } extension EnumInt: ValueFactory { static func values() -> [EnumInt] { return EnumInt.allCases } } extension EnumInt: ListValueFactoryOptional { static var array: KeyPath> { \.listInt } static var optArray: KeyPath> { \.listIntOpt } } extension EnumInt: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt } static var optMutableSet: KeyPath> { \.setIntOpt } } extension EnumInt: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt } static var optMap: KeyPath> { \.mapIntOpt } } // MARK: - EnumInt8 enum EnumInt8: Int8, PersistableEnum { case value1 = 1 case value2 = 2 case value3 = 3 } extension EnumInt8: ValueFactory { static func values() -> [EnumInt8] { return EnumInt8.allCases } } extension EnumInt8: ListValueFactoryOptional { static var array: KeyPath> { \.listInt8 } static var optArray: KeyPath> { \.listInt8Opt } } extension EnumInt8: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt8 } static var optMutableSet: KeyPath> { \.setInt8Opt } } extension EnumInt8: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt8 } static var optMap: KeyPath> { \.mapInt8Opt } } // MARK: - EnumInt16 enum EnumInt16: Int16, PersistableEnum { case value1 = 1 case value2 = 2 case value3 = 3 } extension EnumInt16: ValueFactory { static func values() -> [EnumInt16] { return EnumInt16.allCases } } extension EnumInt16: ListValueFactoryOptional { static var array: KeyPath> { \.listInt16 } static var optArray: KeyPath> { \.listInt16Opt } } extension EnumInt16: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt16 } static var optMutableSet: KeyPath> { \.setInt16Opt } } extension EnumInt16: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt16 } static var optMap: KeyPath> { \.mapInt16Opt } } // MARK: - EnumInt32 enum EnumInt32: Int32, PersistableEnum { case value1 = 1 case value2 = 2 case value3 = 3 } extension EnumInt32: ValueFactory { static func values() -> [EnumInt32] { return EnumInt32.allCases } } extension EnumInt32: ListValueFactoryOptional { static var array: KeyPath> { \.listInt32 } static var optArray: KeyPath> { \.listInt32Opt } } extension EnumInt32: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt32 } static var optMutableSet: KeyPath> { \.setInt32Opt } } extension EnumInt32: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt32 } static var optMap: KeyPath> { \.mapInt32Opt } } // MARK: - EnumInt64 enum EnumInt64: Int64, PersistableEnum { case value1 = 1 case value2 = 2 case value3 = 3 } extension EnumInt64: ValueFactory { static func values() -> [EnumInt64] { return EnumInt64.allCases } } extension EnumInt64: ListValueFactoryOptional { static var array: KeyPath> { \.listInt64 } static var optArray: KeyPath> { \.listInt64Opt } } extension EnumInt64: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setInt64 } static var optMutableSet: KeyPath> { \.setInt64Opt } } extension EnumInt64: MapValueFactoryOptional { static var map: KeyPath> { \.mapInt64 } static var optMap: KeyPath> { \.mapInt64Opt } } // MARK: - EnumFloat enum EnumFloat: Float, PersistableEnum { case value1 = 1.1 case value2 = 2.2 case value3 = 3.3 } extension EnumFloat: ValueFactory { static func values() -> [EnumFloat] { return EnumFloat.allCases } } extension EnumFloat: ListValueFactoryOptional { static var array: KeyPath> { \.listFloat } static var optArray: KeyPath> { \.listFloatOpt } } extension EnumFloat: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setFloat } static var optMutableSet: KeyPath> { \.setFloatOpt } } extension EnumFloat: MapValueFactoryOptional { static var map: KeyPath> { \.mapFloat } static var optMap: KeyPath> { \.mapFloatOpt } } // MARK: - EnumDouble enum EnumDouble: Double, PersistableEnum { case value1 = 1.1 case value2 = 2.2 case value3 = 3.3 } extension EnumDouble: ValueFactory { static func values() -> [EnumDouble] { return EnumDouble.allCases } } extension EnumDouble: ListValueFactoryOptional { static var array: KeyPath> { \.listDouble } static var optArray: KeyPath> { \.listDoubleOpt } } extension EnumDouble: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setDouble } static var optMutableSet: KeyPath> { \.setDoubleOpt } } extension EnumDouble: MapValueFactoryOptional { static var map: KeyPath> { \.mapDouble } static var optMap: KeyPath> { \.mapDoubleOpt } } // MARK: - EnumString enum EnumString: String, PersistableEnum { case value1 = "a" case value2 = "b" case value3 = "c" } extension EnumString: ValueFactory { static func values() -> [EnumString] { return EnumString.allCases } } extension EnumString: ListValueFactoryOptional { static var array: KeyPath> { \.listString } static var optArray: KeyPath> { \.listStringOpt } } extension EnumString: SetValueFactoryOptional { static var mutableSet: KeyPath> { \.setString } static var optMutableSet: KeyPath> { \.setStringOpt } } extension EnumString: MapValueFactoryOptional { static var map: KeyPath> { \.mapString } static var optMap: KeyPath> { \.mapStringOpt } } // MARK: - Custom Persistable extension CustomPersistable where PersistedType: ValueFactory { typealias Wrapped = Self static func values() -> [Self] { PersistedType.values().map(Self.init) } } extension CustomPersistable where PersistedType: NumericValueFactory { typealias AverageType = PersistedType.AverageType static func doubleValue(_ value: Self) -> Double { PersistedType.doubleValue(value.persistableValue) } static var zero: Self { Self(persistedValue: PersistedType.zero) } } extension BoolWrapper: ValueFactory {} extension BoolWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listBool } static var optArray: KeyPath> { \.listOptBool } } extension IntWrapper: NumericValueFactory {} extension IntWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listInt } static var optArray: KeyPath> { \.listOptInt } } extension Int8Wrapper: NumericValueFactory {} extension Int8Wrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listInt8 } static var optArray: KeyPath> { \.listOptInt8 } } extension Int16Wrapper: NumericValueFactory {} extension Int16Wrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listInt16 } static var optArray: KeyPath> { \.listOptInt16 } } extension Int32Wrapper: NumericValueFactory {} extension Int32Wrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listInt32 } static var optArray: KeyPath> { \.listOptInt32 } } extension Int64Wrapper: NumericValueFactory {} extension Int64Wrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listInt64 } static var optArray: KeyPath> { \.listOptInt64 } } extension FloatWrapper: NumericValueFactory {} extension FloatWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listFloat } static var optArray: KeyPath> { \.listOptFloat } } extension DoubleWrapper: NumericValueFactory {} extension DoubleWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listDouble } static var optArray: KeyPath> { \.listOptDouble } } extension StringWrapper: ValueFactory {} extension StringWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listString } static var optArray: KeyPath> { \.listOptString } } extension DataWrapper: ValueFactory {} extension DataWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listBinary } static var optArray: KeyPath> { \.listOptBinary } } extension DateWrapper: ValueFactory {} extension DateWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listDate } static var optArray: KeyPath> { \.listOptDate } } extension Decimal128Wrapper: NumericValueFactory { static func doubleValue(_ value: Decimal128Wrapper) -> Double { return value.persistableValue.doubleValue } } extension Decimal128Wrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listDecimal } static var optArray: KeyPath> { \.listOptDecimal } } extension ObjectIdWrapper: ValueFactory {} extension ObjectIdWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listObjectId } static var optArray: KeyPath> { \.listOptObjectId } } extension UUIDWrapper: ValueFactory {} extension UUIDWrapper: ListValueFactoryOptional { static var array: KeyPath> { \.listUuid } static var optArray: KeyPath> { \.listOptUuid } } extension EmbeddedObjectWrapper: ValueFactory { static func values() -> [EmbeddedObjectWrapper] { Int.values().map { EmbeddedObjectWrapper(value: $0) } } } extension EmbeddedObjectWrapper: ListValueFactory { static var array: KeyPath> { \.listObject } } ================================================ FILE: RealmSwift/Tests/ThreadSafeReferenceTests.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 import RealmSwift #if canImport(RealmSwiftTestSupport) import RealmSwiftTestSupport #endif class ThreadSafeReferenceTests: TestCase { /// Resolve a thread-safe reference confirming that you can't resolve it a second time. func assertResolve(_ realm: Realm, _ reference: ThreadSafeReference) -> T? { XCTAssertFalse(reference.isInvalidated) let object = realm.resolve(reference) XCTAssert(reference.isInvalidated) assertThrows(realm.resolve(reference), reason: "Can only resolve a thread safe reference once") return object } func testInvalidThreadSafeReferenceConstruction() { let stringObject = SwiftStringObject() let arrayParent = SwiftArrayPropertyObject(value: ["arrayObject", [["a"]]]) let arrayObject = arrayParent.array let setParent = SwiftMutableSetPropertyObject(value: ["setObject", [["a"]]]) let setObject = setParent.set assertThrows(ThreadSafeReference(to: stringObject), reason: "Cannot construct reference to unmanaged object") assertThrows(ThreadSafeReference(to: arrayObject), reason: "Cannot construct reference to unmanaged object") assertThrows(ThreadSafeReference(to: setObject), reason: "Cannot construct reference to unmanaged object") let realm = try! Realm() realm.beginWrite() realm.add(stringObject) realm.add(arrayParent) realm.add(setParent) realm.deleteAll() try! realm.commitWrite() assertThrows(ThreadSafeReference(to: stringObject), reason: "Cannot construct reference to invalidated object") assertThrows(ThreadSafeReference(to: arrayObject), reason: "Cannot construct reference to invalidated object") assertThrows(ThreadSafeReference(to: setObject), reason: "Cannot construct reference to invalidated object") } func testInvalidThreadSafeReferenceUsage() { let realm = try! Realm() realm.beginWrite() let stringObject = realm.create(SwiftStringObject.self, value: ["stringCol": "hello"]) let ref1 = ThreadSafeReference(to: stringObject) try! realm.commitWrite() let ref2 = ThreadSafeReference(to: stringObject) let ref3 = ThreadSafeReference(to: stringObject) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { XCTAssertNil(unsafeSelf.realmWithTestPath().resolve(ref1)) let realm = try! Realm() _ = realm.resolve(ref2) unsafeSelf.assertThrows(realm.resolve(ref2), reason: "Can only resolve a thread safe reference once") // Assert that we can resolve a different reference to the same object. XCTAssertEqual(unsafeSelf.assertResolve(realm, ref3)!.stringCol, "hello") } } func testPassThreadSafeReferenceToDeletedObject() { let realm = try! Realm() let intObject = SwiftIntObject() try! realm.write { realm.add(intObject) } let ref1 = ThreadSafeReference(to: intObject) let ref2 = ThreadSafeReference(to: intObject) XCTAssertEqual(0, intObject.intCol) dispatchSyncNewThread { let realm = try! Realm() try! realm.write { realm.deleteAll() } } nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() XCTAssertEqual(unsafeSelf.assertResolve(realm, ref1)!.intCol, 0) realm.refresh() XCTAssertNil(unsafeSelf.assertResolve(realm, ref2)) } } func testPassThreadSafeReferencesToMultipleObjects() { let realm = try! Realm() let (stringObject, intObject) = (SwiftStringObject(), SwiftIntObject()) try! realm.write { realm.add(stringObject) realm.add(intObject) } let stringObjectRef = ThreadSafeReference(to: stringObject) let intObjectRef = ThreadSafeReference(to: intObject) XCTAssertEqual("", stringObject.stringCol) XCTAssertEqual(0, intObject.intCol) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let stringObject = unsafeSelf.assertResolve(realm, stringObjectRef)! let intObject = unsafeSelf.assertResolve(realm, intObjectRef)! try! realm.write { stringObject.stringCol = "the meaning of life" intObject.intCol = 42 } } XCTAssertEqual("", stringObject.stringCol) XCTAssertEqual(0, intObject.intCol) realm.refresh() XCTAssertEqual("the meaning of life", stringObject.stringCol) XCTAssertEqual(42, intObject.intCol) } func testPassThreadSafeReferenceToList() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "jg"])) } XCTAssertEqual(1, company.employees.count) XCTAssertEqual("jg", company.employees[0].name) let listRef = ThreadSafeReference(to: company.employees) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let employees = unsafeSelf.assertResolve(realm, listRef)! XCTAssertEqual(1, employees.count) XCTAssertEqual("jg", employees[0].name) try! realm.write { employees.removeAll() employees.append(SwiftEmployeeObject(value: ["name": "jp"])) employees.append(SwiftEmployeeObject(value: ["name": "az"])) } XCTAssertEqual(2, employees.count) XCTAssertEqual("jp", employees[0].name) XCTAssertEqual("az", employees[1].name) } XCTAssertEqual(1, company.employees.count) XCTAssertEqual("jg", company.employees[0].name) realm.refresh() XCTAssertEqual(2, company.employees.count) XCTAssertEqual("jp", company.employees[0].name) XCTAssertEqual("az", company.employees[1].name) } func testPassThreadSafeReferenceToMutableSet() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "jg"])) } XCTAssertEqual(1, company.employeeSet.count) XCTAssertEqual("jg", company.employeeSet[0].name) let setRef = ThreadSafeReference(to: company.employeeSet) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let employeeSet = unsafeSelf.assertResolve(realm, setRef)! XCTAssertEqual(1, employeeSet.count) XCTAssertEqual("jg", employeeSet[0].name) try! realm.write { employeeSet.removeAll() employeeSet.insert(SwiftEmployeeObject(value: ["name": "jp"])) employeeSet.insert(SwiftEmployeeObject(value: ["name": "az"])) } XCTAssertEqual(2, employeeSet.count) assertSetContains(employeeSet, keyPath: \.name, items: ["jp", "az"]) } XCTAssertEqual(1, company.employeeSet.count) XCTAssertEqual("jg", company.employeeSet[0].name) realm.refresh() XCTAssertEqual(2, company.employeeSet.count) assertSetContains(company.employeeSet, keyPath: \.name, items: ["jp", "az"]) } func testPassThreadSafeReferenceToResults() { let realm = try! Realm() let allObjects = realm.objects(SwiftStringObject.self) let results = allObjects .filter("stringCol != 'C'") .sorted(byKeyPath: "stringCol", ascending: false) let resultsRef = ThreadSafeReference(to: results) try! realm.write { realm.create(SwiftStringObject.self, value: ["A"]) realm.create(SwiftStringObject.self, value: ["B"]) realm.create(SwiftStringObject.self, value: ["C"]) realm.create(SwiftStringObject.self, value: ["D"]) } XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let results = unsafeSelf.assertResolve(realm, resultsRef)! let allObjects = realm.objects(SwiftStringObject.self) XCTAssertEqual(0, allObjects.count) XCTAssertEqual(0, results.count) realm.refresh() XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) try! realm.write { realm.delete(results[2]) realm.delete(results[0]) realm.create(SwiftStringObject.self, value: ["E"]) } XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, results.count) XCTAssertEqual("E", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) } XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, results.count) XCTAssertEqual("D", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) XCTAssertEqual("A", results[2].stringCol) realm.refresh() XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, results.count) XCTAssertEqual("E", results[0].stringCol) XCTAssertEqual("B", results[1].stringCol) } func testPassThreadSafeReferenceToLinkingObjects() { let realm = try! Realm() let dogA = SwiftDogObject(value: ["dogName": "Cookie", "age": 10]) let unaccessedDogB = SwiftDogObject(value: ["dogName": "Skipper", "age": 7]) // Ensures that a `LinkingObjects` without cached results can be handed over try! realm.write { realm.add(SwiftOwnerObject(value: ["name": "Andrea", "dog": dogA])) realm.add(SwiftOwnerObject(value: ["name": "Mike", "dog": unaccessedDogB])) } XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Andrea", dogA.owners[0].name) let ownersARef = ThreadSafeReference(to: dogA.owners) let ownersBRef = ThreadSafeReference(to: unaccessedDogB.owners) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let ownersA = unsafeSelf.assertResolve(realm, ownersARef)! let ownersB = unsafeSelf.assertResolve(realm, ownersBRef)! XCTAssertEqual(1, ownersA.count) XCTAssertEqual("Andrea", ownersA[0].name) XCTAssertEqual(1, ownersB.count) XCTAssertEqual("Mike", ownersB[0].name) try! realm.write { (ownersA[0].dog, ownersB[0].dog) = (ownersB[0].dog, ownersA[0].dog) } XCTAssertEqual(1, ownersA.count) XCTAssertEqual("Mike", ownersA[0].name) XCTAssertEqual(1, ownersB.count) XCTAssertEqual("Andrea", ownersB[0].name) } XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Andrea", dogA.owners[0].name) XCTAssertEqual(1, unaccessedDogB.owners.count) XCTAssertEqual("Mike", unaccessedDogB.owners[0].name) realm.refresh() XCTAssertEqual(1, dogA.owners.count) XCTAssertEqual("Mike", dogA.owners[0].name) XCTAssertEqual(1, unaccessedDogB.owners.count) XCTAssertEqual("Andrea", unaccessedDogB.owners[0].name) } func testPassThreadSafeReferenceToAnyRealmCollection() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "A"])) company.employees.append(SwiftEmployeeObject(value: ["name": "B"])) company.employees.append(SwiftEmployeeObject(value: ["name": "C"])) company.employees.append(SwiftEmployeeObject(value: ["name": "D"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "A"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "B"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "C"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "D"])) } let results = AnyRealmCollection(realm.objects(SwiftEmployeeObject.self) .filter("name != 'C'") .sorted(byKeyPath: "name", ascending: false)) let list = AnyRealmCollection(company.employees) let set = AnyRealmCollection(company.employeeSet) XCTAssertEqual(6, results.count) XCTAssertEqual("D", results[0].name) XCTAssertEqual("D", results[1].name) XCTAssertEqual("B", results[2].name) XCTAssertEqual(4, list.count) XCTAssertEqual("A", list[0].name) XCTAssertEqual("B", list[1].name) XCTAssertEqual("C", list[2].name) XCTAssertEqual("D", list[3].name) XCTAssertEqual(4, set.count) assertAnyRealmCollectionContains(set, keyPath: \.name, items: ["A", "B", "C", "D"]) let resultsRef = ThreadSafeReference(to: results) let listRef = ThreadSafeReference(to: list) let setRef = ThreadSafeReference(to: set) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() let results = unsafeSelf.assertResolve(realm, resultsRef)! let list = unsafeSelf.assertResolve(realm, listRef)! let set = unsafeSelf.assertResolve(realm, setRef)! XCTAssertEqual(6, results.count) XCTAssertEqual("D", results[0].name) XCTAssertEqual("D", results[1].name) XCTAssertEqual("B", results[2].name) XCTAssertEqual(4, list.count) XCTAssertEqual("A", list[0].name) XCTAssertEqual("B", list[1].name) XCTAssertEqual("C", list[2].name) XCTAssertEqual("D", list[3].name) XCTAssertEqual(4, set.count) assertAnyRealmCollectionContains(set, keyPath: \.name, items: ["A", "B", "C", "D"]) } } } // MARK: TestThreadSafeWrappersStruct struct TestThreadSafeWrapperStruct { @ThreadSafe var stringObject: SwiftStringObject? @ThreadSafe var intObject: SwiftIntObject? @ThreadSafe var employees: List? @ThreadSafe var stringMap: Map? @ThreadSafe var employeeSet: MutableSet? @ThreadSafe var owners: LinkingObjects? @ThreadSafe var results: Results? @ThreadSafe var arcResults: AnyRealmCollection? @ThreadSafe var arcList: AnyRealmCollection? @ThreadSafe var arcSet: AnyRealmCollection? } // MARK: ThreadSafeWrapperTests class ThreadSafeWrapperTests: ThreadSafeReferenceTests { func wrapperStruct() -> TestThreadSafeWrapperStruct { let realm = try! Realm() var stringObj: SwiftStringObject?, intObj: SwiftIntObject? try! realm.write({ stringObj = realm.create(SwiftStringObject.self, value: ["stringCol": "before"]) intObj = realm.create(SwiftIntObject.self, value: ["intCol": 1]) }) return TestThreadSafeWrapperStruct(stringObject: stringObj, intObject: intObj) } func testThreadSafeWrapperInvalidConstruction() { let unmanagedObj = SwiftStringObject(value: ["stringCol": "before"]) assertThrows(TestThreadSafeWrapperStruct(stringObject: unmanagedObj), reason: "Only managed objects may be wrapped as thread safe.") } func testThreadSafeWrapper() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) dispatchSyncNewThread { try! Realm().write({ testStruct.stringObject!.stringCol = "after" testStruct.intObject!.intCol = 2 }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after") XCTAssertEqual(testStruct.intObject!.intCol, 2) // Edit value again to test the same thread safe reference isn't resolved twice dispatchSyncNewThread { try! Realm().write({ testStruct.stringObject!.stringCol = "after, again" testStruct.intObject!.intCol = 3 }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after, again") XCTAssertEqual(testStruct.intObject!.intCol, 3) } func testThreadSafeWrapperDeleteObject() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) dispatchSyncNewThread { let realm = try! Realm() try! realm.write({ realm.delete(testStruct.stringObject!) realm.delete(testStruct.intObject!) }) } XCTAssertNil(testStruct.stringObject) XCTAssertNil(testStruct.intObject) } func testThreadSafeWrapperReassign() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) dispatchSyncNewThread { let realm = try! Realm() try! realm.write({ let stringObj = realm.create(SwiftStringObject.self, value: ["stringCol": "after"]) let intObj = realm.create(SwiftIntObject.self, value: ["intCol": 2]) testStruct.stringObject = stringObj testStruct.intObject = intObj }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after") XCTAssertEqual(testStruct.intObject!.intCol, 2) } func testThreadSafeWrapperReassignToNil() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) dispatchSyncNewThread { let realm = try! Realm() try! realm.write({ testStruct.stringObject = nil testStruct.intObject = nil }) } XCTAssertNil(testStruct.stringObject) XCTAssertNil(testStruct.intObject) dispatchSyncNewThread { let realm = try! Realm() try! realm.write({ testStruct.stringObject = realm.create(SwiftStringObject.self, value: ["stringCol": "after, again"]) testStruct.intObject = realm.create(SwiftIntObject.self, value: ["intCol": 3]) }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after, again") XCTAssertEqual(testStruct.intObject!.intCol, 3) } func testThreadSafeWrapperNilConstruction() { let testStruct = TestThreadSafeWrapperStruct(stringObject: nil, intObject: nil) XCTAssertEqual(testStruct.stringObject, nil) XCTAssertEqual(testStruct.intObject, nil) let config = { var config = Realm.Configuration.defaultConfiguration config.objectTypes = [SwiftStringObject.self, SwiftIntObject.self] return config }() dispatchSyncNewThread { let realm = try! Realm(configuration: config) try! realm.write({ testStruct.stringObject = realm.create(SwiftStringObject.self, value: ["stringCol": "after"]) testStruct.intObject = realm.create(SwiftIntObject.self, value: ["intCol": 2]) }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after") XCTAssertEqual(testStruct.intObject!.intCol, 2) // Edit value again to test the same thread safe reference isn't resolved twice dispatchSyncNewThread { let realm = try! Realm(configuration: config) try! realm.write({ testStruct.stringObject!.stringCol = "after, again" testStruct.intObject!.intCol = 3 }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after, again") XCTAssertEqual(testStruct.intObject!.intCol, 3) } func testThreadSafeWrapperDifferentConfig() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) dispatchSyncNewThread { var config = Realm.Configuration.defaultConfiguration config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("newpath.realm") config.objectTypes = [SwiftEmployeeObject.self, SwiftStringObject.self, SwiftIntObject.self] let realm = try! Realm(configuration: config) // Different realm config than original try! realm.write({ let stringObj = realm.create(SwiftStringObject.self, value: ["stringCol": "after"]) let intObj = realm.create(SwiftIntObject.self, value: ["intCol": 2]) testStruct.stringObject = stringObj testStruct.intObject = intObj }) } XCTAssertEqual(testStruct.stringObject!.stringCol, "after") XCTAssertEqual(testStruct.intObject!.intCol, 2) } func testThreadSafeWrapperInvalidReassign() { let testStruct = wrapperStruct() XCTAssertEqual(testStruct.stringObject!.stringCol, "before") XCTAssertEqual(testStruct.intObject!.intCol, 1) nonisolated(unsafe) let unsafeSelf = self dispatchSyncNewThread { let realm = try! Realm() try! realm.write { unsafeSelf.assertThrows(testStruct.stringObject = SwiftStringObject(), reason: "Only managed objects may be wrapped as thread safe.") } } } func testThreadSafeWrapperToList() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "jg"])) } XCTAssertEqual(1, company.employees.count) XCTAssertEqual("jg", company.employees[0].name) let testStruct = TestThreadSafeWrapperStruct(employees: company.employees) dispatchSyncNewThread { let realm = try! Realm() XCTAssertEqual(1, testStruct.employees!.count) XCTAssertEqual("jg", testStruct.employees![0].name) try! realm.write { testStruct.employees!.removeAll() testStruct.employees!.append(SwiftEmployeeObject(value: ["name": "jp"])) testStruct.employees!.append(SwiftEmployeeObject(value: ["name": "az"])) } XCTAssertEqual(2, testStruct.employees!.count) XCTAssertEqual("jp", testStruct.employees![0].name) XCTAssertEqual("az", testStruct.employees![1].name) } XCTAssertEqual(2, testStruct.employees!.count) XCTAssertEqual("jp", testStruct.employees![0].name) XCTAssertEqual("az", testStruct.employees![1].name) } func testThreadSafeWrapperToMap() { let testStruct = TestThreadSafeWrapperStruct() let realm = try! Realm() try! realm.write { let mapObject = SwiftMapObject() mapObject.object["before"] = realm.create(SwiftStringObject.self, value: ["stringCol": "first"]) realm.add(mapObject) testStruct.stringMap = mapObject.object } dispatchSyncNewThread { XCTAssertEqual(testStruct.stringMap?.count, 1) XCTAssertEqual(testStruct.stringMap?["before"]??.stringCol, "first") let realm = try! Realm() try! realm.write { let swiftStringObject = realm.create(SwiftStringObject.self, value: ["stringCol": "second"]) testStruct.stringMap!["after"] = swiftStringObject } } XCTAssertEqual(testStruct.stringMap?.count, 2) XCTAssertEqual(testStruct.stringMap?["before"]??.stringCol, "first") XCTAssertEqual(testStruct.stringMap?["after"]??.stringCol, "second") } func testThreadSafeWrapperToMutableSet() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "jg"])) } XCTAssertEqual(1, company.employeeSet.count) XCTAssertEqual("jg", company.employeeSet[0].name) let testStruct = TestThreadSafeWrapperStruct(employeeSet: company.employeeSet) dispatchSyncNewThread { let realm = try! Realm() XCTAssertEqual(1, testStruct.employeeSet!.count) XCTAssertEqual("jg", testStruct.employeeSet![0].name) try! realm.write { testStruct.employeeSet!.removeAll() testStruct.employeeSet!.insert(SwiftEmployeeObject(value: ["name": "jp"])) testStruct.employeeSet!.insert(SwiftEmployeeObject(value: ["name": "az"])) } XCTAssertEqual(2, testStruct.employeeSet!.count) assertSetContains(testStruct.employeeSet!, keyPath: \.name, items: ["jp", "az"]) } XCTAssertEqual(2, testStruct.employeeSet!.count) assertSetContains(testStruct.employeeSet!, keyPath: \.name, items: ["jp", "az"]) } func testThreadSafeWrapperToResults() { let realm = try! Realm() let allObjects = realm.objects(SwiftStringObject.self) let results = allObjects .filter("stringCol != 'C'") .sorted(byKeyPath: "stringCol", ascending: false) let resultsStruct = TestThreadSafeWrapperStruct(results: results) try! realm.write { realm.create(SwiftStringObject.self, value: ["A"]) realm.create(SwiftStringObject.self, value: ["B"]) realm.create(SwiftStringObject.self, value: ["C"]) realm.create(SwiftStringObject.self, value: ["D"]) } XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, resultsStruct.results!.count) XCTAssertEqual("D", resultsStruct.results![0].stringCol) XCTAssertEqual("B", resultsStruct.results![1].stringCol) XCTAssertEqual("A", resultsStruct.results![2].stringCol) dispatchSyncNewThread { let realm = try! Realm() let allObjects = realm.objects(SwiftStringObject.self) XCTAssertEqual(4, allObjects.count) XCTAssertEqual(3, resultsStruct.results!.count) XCTAssertEqual("D", resultsStruct.results![0].stringCol) XCTAssertEqual("B", resultsStruct.results![1].stringCol) XCTAssertEqual("A", resultsStruct.results![2].stringCol) try! realm.write { realm.delete(resultsStruct.results![2]) realm.delete(resultsStruct.results![0]) realm.create(SwiftStringObject.self, value: ["E"]) } XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, resultsStruct.results!.count) XCTAssertEqual("E", resultsStruct.results![0].stringCol) XCTAssertEqual("B", resultsStruct.results![1].stringCol) } realm.refresh() XCTAssertEqual(3, allObjects.count) XCTAssertEqual(2, resultsStruct.results!.count) XCTAssertEqual("E", resultsStruct.results![0].stringCol) XCTAssertEqual("B", resultsStruct.results![1].stringCol) } func testThreadSafeWrapperLinkingObjects() { let realm = try! Realm() let dog = SwiftDogObject(value: ["dogName": "Cookie", "age": 10]) try! realm.write { realm.add(SwiftOwnerObject(value: ["name": "Andrea", "dog": dog])) } let linkingObjectsStruct = TestThreadSafeWrapperStruct(owners: dog.owners) XCTAssertEqual(1, linkingObjectsStruct.owners!.count) XCTAssertEqual("Andrea", linkingObjectsStruct.owners![0].name) dispatchSyncNewThread { let realm = try! Realm() XCTAssertEqual(1, linkingObjectsStruct.owners!.count) XCTAssertEqual("Andrea", linkingObjectsStruct.owners![0].name) try! realm.write { linkingObjectsStruct.owners![0].name = "Mike" } XCTAssertEqual(1, linkingObjectsStruct.owners!.count) XCTAssertEqual("Mike", linkingObjectsStruct.owners![0].name) } XCTAssertEqual(1, linkingObjectsStruct.owners!.count) XCTAssertEqual("Mike", linkingObjectsStruct.owners![0].name) } func testThreadSafeWrapperToAnyRealmCollection() { let realm = try! Realm() let company = SwiftCompanyObject() try! realm.write { realm.add(company) company.employees.append(SwiftEmployeeObject(value: ["name": "A"])) company.employees.append(SwiftEmployeeObject(value: ["name": "B"])) company.employees.append(SwiftEmployeeObject(value: ["name": "C"])) company.employees.append(SwiftEmployeeObject(value: ["name": "D"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "A"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "B"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "C"])) company.employeeSet.insert(SwiftEmployeeObject(value: ["name": "D"])) } let testStruct = TestThreadSafeWrapperStruct(arcResults: AnyRealmCollection(realm.objects(SwiftEmployeeObject.self) .filter("name != 'C'") .sorted(byKeyPath: "name", ascending: false)), arcList: AnyRealmCollection(company.employees), arcSet: AnyRealmCollection(company.employeeSet)) XCTAssertEqual(6, testStruct.arcResults!.count) XCTAssertEqual("D", testStruct.arcResults![0].name) XCTAssertEqual("D", testStruct.arcResults![1].name) XCTAssertEqual("B", testStruct.arcResults![2].name) XCTAssertEqual(4, testStruct.arcList!.count) XCTAssertEqual("A", testStruct.arcList![0].name) XCTAssertEqual("B", testStruct.arcList![1].name) XCTAssertEqual("C", testStruct.arcList![2].name) XCTAssertEqual("D", testStruct.arcList![3].name) XCTAssertEqual(4, testStruct.arcSet!.count) assertAnyRealmCollectionContains(testStruct.arcSet!, keyPath: \.name, items: ["A", "B", "C", "D"]) dispatchSyncNewThread { XCTAssertEqual(6, testStruct.arcResults!.count) XCTAssertEqual("D", testStruct.arcResults![0].name) XCTAssertEqual("D", testStruct.arcResults![1].name) XCTAssertEqual("B", testStruct.arcResults![2].name) XCTAssertEqual(4, testStruct.arcList!.count) XCTAssertEqual("A", testStruct.arcList![0].name) XCTAssertEqual("B", testStruct.arcList![1].name) XCTAssertEqual("C", testStruct.arcList![2].name) XCTAssertEqual("D", testStruct.arcList![3].name) XCTAssertEqual(4, testStruct.arcSet!.count) assertAnyRealmCollectionContains(testStruct.arcSet!, keyPath: \.name, items: ["A", "B", "C", "D"]) } } #if swift(<6) // Swift 6 has not implemented sendability checking for property wrappers func testThreadSafeWrapperInline() throws { let values = ["A", "B", "C", "D"] try autoreleasepool { let realm = try Realm() try realm.write { realm.create(SwiftStringObject.self, value: ["A"]) realm.create(SwiftStringObject.self, value: ["B"]) realm.create(SwiftStringObject.self, value: ["C"]) realm.create(SwiftStringObject.self, value: ["D"]) } } let realm = try! Realm() @ThreadSafe var results = realm.objects(SwiftStringObject.self) dispatchSyncNewThread { guard let results else { return XCTFail("no results") } results.indices.forEach { idx in XCTAssertEqual(results[idx].stringCol, values[idx]) } } @ThreadSafe var swiftStringObject = results!.first dispatchSyncNewThread { guard let swiftStringObject else { return XCTFail("no results") } XCTAssertEqual(swiftStringObject.stringCol, "A") } } func mutateStringCol(@ThreadSafe stringObj: SwiftStringObject?) { dispatchSyncNewThread { let realm = try! Realm() try! realm.write { stringObj?.stringCol = "after" } } } func testThreadSafeFunctionArgument() { let realm = try! Realm() @ThreadSafe var stringObj = try! realm.write { realm.create(SwiftStringObject.self, value: ["stringCol": "before"]) } XCTAssertEqual(stringObj!.stringCol, "before") self.mutateStringCol(stringObj: stringObj) realm.refresh() XCTAssertEqual(stringObj!.stringCol, "after") } func testThreadSafeUnmanagedArgument() { nonisolated(unsafe) let stringObj = SwiftStringObject(value: ["stringCol": "before"]) dispatchSyncNewThread { self.assertThrows(self.mutateStringCol(stringObj: stringObj), reason: "Only managed objects may be wrapped as thread safe.") } } func testThreadSafeMultipleDispatch() { let realm = try! Realm() @ThreadSafe var obj = try! realm.write { realm.create(SwiftStringObject.self, value: ["stringCol": "before"]) } let ex = expectation(description: "executes first block") DispatchQueue.concurrentPerform(iterations: 100) { i in try! obj?.realm?.write { obj?.stringCol = "middle" } if i == 99 { ex.fulfill() } } waitForExpectations(timeout: 5, handler: nil) XCTAssertNotEqual(obj?.stringCol, "before") } #endif } ================================================ FILE: RealmSwift/ThreadSafeReference.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 Realm /** Objects of types which conform to `ThreadConfined` can be managed by a Realm, which will make them bound to a thread-specific `Realm` instance. Managed objects must be explicitly exported and imported to be passed between threads. Managed instances of objects conforming to this protocol can be converted to a thread-safe reference for transport between threads by passing to the `ThreadSafeReference(to:)` constructor. Note that only types defined by Realm can meaningfully conform to this protocol, and defining new classes which attempt to conform to it will not make them work with `ThreadSafeReference`. */ public protocol ThreadConfined { /** The Realm which manages the object, or `nil` if the object is unmanaged. Unmanaged objects are not confined to a thread and cannot be passed to methods expecting a `ThreadConfined` object. */ var realm: Realm? { get } /// Indicates if the object can no longer be accessed because it is now invalid. var isInvalidated: Bool { get } /** Indicates if the object is frozen. Frozen objects are not confined to their source thread. Forming a `ThreadSafeReference` to a frozen object is allowed, but is unlikely to be useful. */ var isFrozen: Bool { get } /** Returns a frozen snapshot of this object. Unlike normal Realm live objects, the frozen copy can be read from any thread, and the values read will never update to reflect new writes to the Realm. Frozen collections can be queried like any other Realm collection. Frozen objects cannot be mutated, and cannot be observed for change notifications. Unmanaged Realm objects cannot be frozen. - warning: Holding onto a frozen object for an extended period while performing write transaction on the Realm may result in the Realm file growing to large sizes. See `Realm.Configuration.maximumNumberOfActiveVersions` for more information. */ func freeze() -> Self /** Returns a live (mutable) reference of this object. Will return self if called on an already live object. */ func thaw() -> Self? } /** An object intended to be passed between threads containing a thread-safe reference to its thread-confined object. To resolve a thread-safe reference on a target Realm on a different thread, pass to `Realm.resolve(_:)`. - warning: A `ThreadSafeReference` object must be resolved at most once. Failing to resolve a `ThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. - note: Prefer short-lived `ThreadSafeReference`s as the data for the version of the source Realm will be retained until all references have been resolved or deallocated. - see: `ThreadConfined` - see: `Realm.resolve(_:)` */ @frozen public struct ThreadSafeReference { /** Indicates if the reference can no longer be resolved because an attempt to resolve it has already occurred. References can only be resolved once. */ public var isInvalidated: Bool { return objectiveCReference.isInvalidated } private let objectiveCReference: RLMThreadSafeReference /** Create a thread-safe reference to the thread-confined object. - parameter threadConfined: The thread-confined object to create a thread-safe reference to. - note: You may continue to use and access the thread-confined object after passing it to this constructor. */ public init(to threadConfined: Confined) { objectiveCReference = RLMThreadSafeReference(threadConfined: (threadConfined as! _ObjcBridgeable)._rlmObjcValue as! RLMThreadConfined) } internal func resolve(in realm: Realm) -> Confined? { guard let resolved = realm.rlmRealm.__resolve(objectiveCReference) as? RLMThreadConfined else { return nil } return (Confined.self as! _ObjcBridgeable.Type)._rlmFromObjc(resolved).flatMap { $0 as? Confined } } } // MARK: ThreadSafe propertyWrapper /** A property wrapper type that may be passed between threads. A `@ThreadSafe` property contains a thread-safe reference to the underlying wrapped value. This reference is resolved to the thread on which the wrapped value is accessed. A new thread safe reference is created each time the property is accessed. - warning: This property wrapper should not be used for properties on long lived objects. `@ThreadSafe` properties contain a `ThreadSafeReference` which can pin the source version of the Realm in use. This means that this property wrapper is **better suited for function arguments and local variables** **that get captured by an aynchronously dispatched block.** - see: `ThreadSafeReference` - see: `ThreadConfined` */ @propertyWrapper public final class ThreadSafe { private var threadSafeReference: ThreadSafeReference? private var rlmConfiguration: RLMRealmConfiguration? private let lock = NSLock() /// :nodoc: public var wrappedValue: T? { get { lock.lock() guard let threadSafeReference = threadSafeReference, let rlmConfig = rlmConfiguration else { lock.unlock() return nil } do { let rlmRealm = try RLMRealm(configuration: rlmConfig) let realm = Realm(rlmRealm) guard let value = threadSafeReference.resolve(in: realm) else { self.threadSafeReference = nil lock.unlock() return nil } self.threadSafeReference = ThreadSafeReference(to: value) lock.unlock() return value // FIXME: wrappedValue should throw // As of Swift 5.5 property wrappers can't have throwing accessors. } catch let error as NSError { lock.unlock() throwRealmException(error.localizedDescription) } } set { lock.lock() guard let newValue = newValue else { threadSafeReference = nil lock.unlock() return } guard let rlmConfiguration = newValue.realm?.rlmRealm.configuration else { lock.unlock() throwRealmException("Only managed objects may be wrapped as thread safe.") } self.rlmConfiguration = rlmConfiguration threadSafeReference = ThreadSafeReference(to: newValue) lock.unlock() } } /// :nodoc: public init(wrappedValue: T?) { self.wrappedValue = wrappedValue } } extension Realm { // MARK: Thread Safe Reference /** Returns the same object as the one referenced when the `ThreadSafeReference` was first created, but resolved for the current Realm for this thread. Returns `nil` if this object was deleted after the reference was created. - parameter reference: The thread-safe reference to the thread-confined object to resolve in this Realm. - warning: A `ThreadSafeReference` object must be resolved at most once. Failing to resolve a `ThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. An exception will be thrown if a reference is resolved more than once. - warning: Cannot call within a write transaction. - note: Will refresh this Realm if the source Realm was at a later version than this one. - see: `ThreadSafeReference(to:)` */ public func resolve(_ reference: ThreadSafeReference) -> Confined? { return reference.resolve(in: self) } } extension ThreadSafeReference: Sendable { } extension RLMThreadSafeReference: @unchecked Sendable { } extension ThreadSafe: @unchecked Sendable { } ================================================ FILE: RealmSwift/Util.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 Foundation import Realm import os.log #if BUILDING_REALM_SWIFT_TESTS import RealmSwift #endif // MARK: Internal Helpers // Swift 3.1 provides fixits for some of our uses of unsafeBitCast // to use unsafeDowncast instead, but the bitcast is required. internal func noWarnUnsafeBitCast(_ x: T, to type: U.Type) -> U { return unsafeBitCast(x, to: type) } /// Given a list of `Any`-typed varargs, unwrap any optionals and /// replace them with the underlying value or NSNull. internal func unwrapOptionals(in varargs: [Any]) -> [Any] { return varargs.map { arg in if let someArg = arg as Any? { return someArg } return NSNull() } } internal func notFoundToNil(index: UInt) -> Int? { if index == UInt(NSNotFound) { return nil } return Int(index) } internal func throwRealmException(_ message: String, userInfo: [AnyHashable: Any]? = nil) -> Never { NSException(name: NSExceptionName(rawValue: RLMExceptionName), reason: message, userInfo: userInfo).raise() fatalError() // unreachable } internal func throwForNegativeIndex(_ int: Int, parameterName: String = "index") { if int < 0 { throwRealmException("Cannot pass a negative value for '\(parameterName)'.") } } internal func gsub(pattern: String, template: String, string: String, error: NSErrorPointer = nil) -> String? { let regex = try? NSRegularExpression(pattern: pattern, options: []) return regex?.stringByReplacingMatches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count), withTemplate: template) } extension ObjectBase { // Must *only* be used to call Realm Objective-C APIs that are exposed on `RLMObject` // but actually operate on `RLMObjectBase`. Do not expose cast value to user. internal func unsafeCastToRLMObject() -> RLMObject { return noWarnUnsafeBitCast(self, to: RLMObject.self) } } internal func coerceToNil(_ value: Any) -> Any? { if value is NSNull { return nil } // nil in Any is bridged to obj-c as NSNull. In the obj-c code we usually // convert NSNull back to nil, which ends up as Optional.none if case Optional.none = value { return nil } return value } // MARK: CustomObjectiveCBridgeable internal extension _ObjcBridgeable { static func _rlmFromObjc(_ value: Any) -> Self? { _rlmFromObjc(value, insideOptional: false) } } /// :nodoc: public func dynamicBridgeCast(fromObjectiveC x: Any) -> T { if let bridged = failableDynamicBridgeCast(fromObjectiveC: x) as T? { return bridged } fatalError("Could not convert value '\(x)' to type '\(T.self)'") } /// :nodoc: @usableFromInline internal func failableDynamicBridgeCast(fromObjectiveC x: Any) -> T? { if let bridgeableType = T.self as? _ObjcBridgeable.Type { return bridgeableType._rlmFromObjc(x).flatMap { $0 as? T } } if let value = x as? T { return value } return nil } /// :nodoc: public func dynamicBridgeCast(fromSwift x: T) -> Any { if let x = x as? _ObjcBridgeable { return x._rlmObjcValue } return x } @usableFromInline internal func staticBridgeCast(fromSwift x: T) -> Any { return x._rlmObjcValue } @usableFromInline internal func staticBridgeCast(fromObjectiveC x: Any) -> T { if let value = T._rlmFromObjc(x) { return value } throwRealmException("Could not convert value '\(x)' to type '\(T.self)'.") } @usableFromInline internal func failableStaticBridgeCast(fromObjectiveC x: Any) -> T? { return T._rlmFromObjc(x) } internal func logRuntimeIssue(_ message: StaticString) { if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) { // Reporting a runtime issue to Xcode requires pretending to be // one of the system libraries which are allowed to do so. We do // this by looking up a symbol defined by SwiftUI, getting the // dso information from that, and passing that to os_log() to // claim that we're SwiftUI. As this is obviously not a particularly legal thing to do, we only do it in debug and simulator builds. var dso = #dsohandle #if DEBUG || targetEnvironment(simulator) let sym = dlsym(dlopen(nil, RTLD_LAZY), "$s7SwiftUI3AppMp") var info = Dl_info() dladdr(sym, &info) if let base = info.dli_fbase { dso = UnsafeRawPointer(base) } #endif let log = OSLog(subsystem: "com.apple.runtime-issues", category: "Realm") os_log(.fault, dso: dso, log: log, message) } else { print(message) } } @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) extension Actor { @_unavailableFromAsync internal func invokeIsolated(_ operation: (isolated Self, Arg) throws -> Ret, _ arg: Arg, file: StaticString = #fileID, line: UInt = #line ) rethrows -> Ret { preconditionIsolated(file: file, line: line) return try withoutActuallyEscaping(operation) { fn in try unsafeBitCast(fn, to: ((Self, Arg) throws -> Ret).self)(self, arg) } } } ================================================ FILE: RealmSwift.podspec ================================================ # coding: utf-8 Pod::Spec.new do |s| s.name = 'RealmSwift' version = `sh build.sh get-version` s.version = version s.summary = 'Realm Swift is a modern data framework & database for iOS, macOS, tvOS & watchOS.' s.description = <<-DESC The Realm Database, for Swift. (If you want to use Realm from Objective-C, see the “Realm” pod.) Realm is a fast, easy-to-use replacement for Core Data & SQLite. Works on iOS, macOS, tvOS & watchOS. Learn more and get help at https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/. DESC s.homepage = "https://realm.io" s.source = { :git => 'https://github.com/realm/realm-swift.git', :tag => "v#{s.version}" } s.author = { 'Realm' => 'realm-help@mongodb.com' } s.requires_arc = true s.license = { :type => 'Apache 2.0', :file => 'LICENSE' } s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.13' s.watchos.deployment_target = '4.0' s.tvos.deployment_target = '12.0' s.preserve_paths = %w(build.sh) s.swift_version = '5' s.weak_frameworks = 'SwiftUI' s.dependency 'Realm', "= #{s.version}" s.source_files = 'RealmSwift/*.swift', 'RealmSwift/Impl/*.swift', 'Realm/Swift/*.swift' s.resource_bundles = {'realm_swift_privacy' => ['RealmSwift/PrivacyInfo.xcprivacy']} s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES', 'IPHONEOS_DEPLOYMENT_TARGET_1500' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET_1600' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET_2600' => '12.0', 'IPHONEOS_DEPLOYMENT_TARGET' => '$(IPHONEOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'MACOSX_DEPLOYMENT_TARGET_1500' => '10.13', 'MACOSX_DEPLOYMENT_TARGET_1600' => '10.13', 'MACOSX_DEPLOYMENT_TARGET_2600' => '10.13', 'MACOSX_DEPLOYMENT_TARGET' => '$(MACOSX_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'WATCHOS_DEPLOYMENT_TARGET_1500' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET_1600' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET_2600' => '4.0', 'WATCHOS_DEPLOYMENT_TARGET' => '$(WATCHOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', 'TVOS_DEPLOYMENT_TARGET_1500' => '12.0', 'TVOS_DEPLOYMENT_TARGET_1600' => '12.0', 'TVOS_DEPLOYMENT_TARGET_2600' => '12.0', 'TVOS_DEPLOYMENT_TARGET' => '$(TVOS_DEPLOYMENT_TARGET_$(XCODE_VERSION_MAJOR))', } end ================================================ FILE: SUPPORT.md ================================================ # Support The Realm team is here to help you with your Realm-related issues! ## Documentation Before asking questions, please familiarize yourself with our [Realm Swift](https://www.mongodb.com/docs/atlas/device-sdks/sdk/swift/) documentation. We also have a number of [Tech Notes](https://www.mongodb.com/developer/products/realm/) which cover various topics that may be of interest. ## Stack Overflow If you have questions about configuring or using Realm you can ask them on Stack Overflow. We continually monitor the [`realm` tag](https://stackoverflow.com/tags/realm). Please also tag your question with `ios`, `swift`, or other tags as appropriate. When asking questions on Stack Overflow, please keep in mind Stack Overflow's [question guidelines](https://stackoverflow.com/help/how-to-ask), and please use their search functionality to see if your question has been asked before. ## GitHub Issues If you are running into issues with Realm, including potential bugs or feature requests, we encourage you to file an issue on our [GitHub issue tracker](https://github.com/realm/realm-swift/issues). Please check out our [Contribution Guidelines](CONTRIBUTING.md) for information on how to properly file an issue. We greatly appreciate demonstration projects that we can run for ourselves in order to see issues or potential bugs; we prioritize clearly-written tickets that include reproduction cases. You may attach these to the ticket; let us know if you need to share them confidentially, and we’ll provide instructions on how to do so. ================================================ FILE: build.sh ================================================ #!/bin/bash ################################################################################## # Custom build tool for Realm Objective-C binding. # # (C) Copyright 2011-2022 by realm.io. ################################################################################## set -eo pipefail readonly source_root="$(dirname "$0")" : "${REALM_CORE_VERSION:=$(sed -n 's/^REALM_CORE_VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")}" # set to "current" to always use the current build # Provide a fallback value for TMPDIR, relevant for Xcode Bots : "${TMPDIR:=$(getconf DARWIN_USER_TEMP_DIR)}" PATH=/usr/libexec:$PATH if [ -n "${CI}" ]; then CODESIGN_PARAMS=(CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO) fi if [ -n "${GITHUB_WORKSPACE}" ]; then DERIVED_DATA="$GITHUB_WORKSPACE/build/DerivedData" ROOT_WORKSPACE="$GITHUB_WORKSPACE" BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF}}" else ROOT_WORKSPACE="$(pwd)" DERIVED_DATA="$ROOT_WORKSPACE/build/DerivedData" BRANCH="$(git branch --show-current)" fi usage() { cat < /dev/null } iphone_name() { if (( $(xcode_version_major) < 16 )); then echo 'iPhone 15' elif (( $(xcode_version_major) < 26 )); then echo 'iPhone 16' else echo 'iPhone 17' fi } ###################################### # Device Test Helper ###################################### test_devices() { local serial_numbers=() local awk_script=" /^ +Vendor ID: / { is_apple = 0; } /^ +Vendor ID: 0x05[aA][cC] / { is_apple = 1; } /^ +Serial Number: / { if (is_apple) { match(\$0, /^ +Serial Number: /); print substr(\$0, RLENGTH + 1); } } " local serial_numbers_text=$(/usr/sbin/system_profiler SPUSBDataType | /usr/bin/awk "$awk_script") while read -r number; do if [[ "$number" != "" ]]; then serial_numbers+=("$number") fi done <<< "$serial_numbers_text" if [[ ${#serial_numbers[@]} == 0 ]]; then echo "At least one iOS/tvOS device must be connected to this computer to run device tests" if [ -z "${JENKINS_HOME}" ]; then # Don't fail if running locally and there's no device exit 0 fi exit 1 fi local sdk="$1" local scheme="$2" local configuration="$3" local failed=0 for device in "${serial_numbers[@]}"; do xc -scheme "$scheme" -configuration "$configuration" -destination "id=$device" -sdk "$sdk" test || failed=1 done return $failed } ###################################### # Docs ###################################### build_docs() { local language="$1" local version=$(sh build.sh get-version) local xcodebuild_arguments="--objc,Realm/Realm.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path),-I,$(pwd)" local module="Realm" local objc="--objc" if [[ "$language" == "swift" ]]; then xcodebuild_arguments="-scheme,RealmSwift" module="RealmSwift" objc="" fi jazzy \ "${objc}" \ --clean \ --author Realm \ --author_url https://docs.mongodb.com/realm-sdks \ --github_url https://github.com/realm/realm-swift \ --github-file-prefix "https://github.com/realm/realm-swift/tree/v${version}" \ --module-version "${version}" \ --xcodebuild-arguments "${xcodebuild_arguments}" \ --module "${module}" \ --root-url "https://docs.mongodb.com/realm-sdks/${language}/${version}/" \ --output "docs/${language}_output" \ --head "$(cat docs/custom_head.html)" \ --exclude 'RealmSwift/Impl/*' } ###################################### # Input Validation ###################################### if [ "$#" -eq 0 ] || [ "$#" -gt 3 ]; then usage exit 1 fi ###################################### # Variables ###################################### COMMAND="$1" LINKAGE="dynamic" # Use Debug config if command ends with -debug, otherwise default to Release case "$COMMAND" in *-debug) COMMAND="${COMMAND%-debug}" CONFIGURATION="Debug" ;; *-static) COMMAND="${COMMAND%-static}" LINKAGE="static" CONFIGURATION="Static" ;; esac export CONFIGURATION=${CONFIGURATION:-Release} # Pre-choose Xcode version for those operations that do not override it REALM_XCODE_VERSION=${xcode_version:-$REALM_XCODE_VERSION} source "${source_root}/scripts/swift-version.sh" set_xcode_version ###################################### # Commands ###################################### case "$COMMAND" in ###################################### # Clean ###################################### "clean") find . -type d -name build -exec rm -r "{}" + exit 0 ;; ###################################### # Dependencies ###################################### "download-core") sh scripts/download-core.sh exit 0 ;; ###################################### # Building ###################################### "build") sh build.sh xcframework exit 0 ;; "ios") build_combined Realm ios exit 0 ;; "ios-swift") build_combined Realm ios build_combined RealmSwift ios exit 0 ;; "watchos") build_combined Realm watchos exit 0 ;; "watchos-swift") build_combined Realm watchos build_combined RealmSwift watchos exit 0 ;; "tvos") build_combined Realm tvos exit 0 ;; "tvos-swift") build_combined Realm tvos build_combined RealmSwift tvos exit 0 ;; "osx") build_combined Realm osx exit 0 ;; "osx-swift") build_combined Realm osx build_combined RealmSwift osx exit 0 ;; "catalyst") build_combined Realm catalyst ;; "catalyst-swift") build_combined Realm catalyst build_combined RealmSwift catalyst ;; "visionos") build_combined Realm visionos ;; "visionos-swift") build_combined Realm visionos build_combined RealmSwift visionos ;; "xcframework") # Build all of the requested frameworks shift PLATFORMS="${*:-osx ios watchos tvos catalyst visionos}" for platform in $PLATFORMS; do sh build.sh "$platform-swift" done # Assemble them into xcframeworks rm -rf "$DERIVED_DATA/Realm/Build/Products"*.xcframework find "$DERIVED_DATA/Realm/Build/Products" -name 'Realm.framework' \ | sed 's/.*/-framework &/' \ | xargs xcodebuild -create-xcframework -allow-internal-distribution -output "build/$CONFIGURATION/Realm.xcframework" find "$DERIVED_DATA/Realm/Build/Products" -name 'RealmSwift.framework' \ | sed 's/.*/-framework &/' \ | xargs xcodebuild -create-xcframework -allow-internal-distribution -output "build/$CONFIGURATION/RealmSwift.xcframework" # Because we have a module named Realm and a type named Realm we need to manually resolve the naming # collisions that are happening. These collisions create a red herring which tells the user the xcframework # was compiled with an older Swift version and is not compatible with the current compiler. find "build/$CONFIGURATION/RealmSwift.xcframework" -name "*.swiftinterface" \ -exec sed -i '' 's/Realm\.//g' {} \; \ -exec sed -i '' 's/import Private/import Realm.Private\nimport Realm.Swift/g' {} \; \ -exec sed -i '' 's/RealmSwift.Configuration/RealmSwift.Realm.Configuration/g' {} \; \ -exec sed -i '' 's/extension Configuration/extension Realm.Configuration/g' {} \; \ -exec sed -i '' 's/RealmSwift.Error[[:>:]]/RealmSwift.Realm.Error/g' {} \; \ -exec sed -i '' 's/extension Error/extension Realm.Error/g' {} \; \ -exec sed -i '' 's/RealmSwift.AsyncOpenTask/RealmSwift.Realm.AsyncOpenTask/g' {} \; \ -exec sed -i '' 's/RealmSwift.UpdatePolicy/RealmSwift.Realm.UpdatePolicy/g' {} \; \ -exec sed -i '' 's/RealmSwift.Notification[[:>:]]/RealmSwift.Realm.Notification/g' {} \; \ -exec sed -i '' 's/τ_1_0/V/g' {} \; # Generics will use τ_1_0 which needs to be changed to the correct type name. exit 0 ;; ###################################### # Analysis ###################################### "analyze-osx") xc -scheme Realm -configuration "$CONFIGURATION" analyze exit 0 ;; ###################################### # Testing ###################################### "test") set +e # Run both sets of tests even if the first fails failed=0 sh build.sh test-ios || failed=1 sh build.sh test-ios-swift || failed=1 sh build.sh test-ios-devices || failed=1 sh build.sh test-tvos-devices || failed=1 sh build.sh test-osx || failed=1 sh build.sh test-osx-swift || failed=1 sh build.sh test-catalyst || failed=1 sh build.sh test-catalyst-swift || failed=1 exit $failed ;; "test-all") set +e failed=0 sh build.sh test || failed=1 sh build.sh test-debug || failed=1 exit $failed ;; "test-ios") xctest Realm -configuration "$CONFIGURATION" -sdk iphonesimulator -destination "name=$(iphone_name)" exit 0 ;; "test-ios-swift") xctest RealmSwift -configuration "$CONFIGURATION" -sdk iphonesimulator -destination "name=$(iphone_name)" exit 0 ;; "test-ios-devices") failed=0 trap "failed=1" ERR sh build.sh test-ios-devices-objc sh build.sh test-ios-devices-swift exit $failed ;; "test-ios-devices-objc") test_devices iphoneos "Realm" "$CONFIGURATION" exit $? ;; "test-ios-devices-swift") test_devices iphoneos "RealmSwift" "$CONFIGURATION" exit $? ;; "test-tvos") destination="Apple TV" xctest Realm -configuration "$CONFIGURATION" -sdk appletvsimulator -destination "name=$destination" exit $? ;; "test-tvos-swift") destination="Apple TV" xctest RealmSwift -configuration "$CONFIGURATION" -sdk appletvsimulator -destination "name=$destination" exit $? ;; "test-tvos-devices") test_devices appletvos TestHost "$CONFIGURATION" ;; "test-osx") xctest Realm -configuration "$CONFIGURATION" -destination "platform=macOS,arch=$(uname -m)" exit 0 ;; "test-osx-swift") xctest RealmSwift -configuration "$CONFIGURATION" -destination "platform=macOS,arch=$(uname -m)" exit 0 ;; test-swiftpm*) SANITIZER=$(echo "$COMMAND" | cut -d - -f 3) # FIXME: throwing an exception from a property getter corrupts Swift's # runtime exclusivity checking state. Unfortunately, this is something # we do a lot in tests. SWIFT_TEST_FLAGS=(-Xcc -g0 -Xswiftc -enforce-exclusivity=none) if [ -n "$SANITIZER" ]; then SWIFT_TEST_FLAGS+=(--sanitize "$SANITIZER") export ASAN_OPTIONS='check_initialization_order=true:detect_stack_use_after_return=true' fi xcrun swift package resolve xcrun swift test --configuration "$(echo "$CONFIGURATION" | tr "[:upper:]" "[:lower:]")" "${SWIFT_TEST_FLAGS[@]}" exit 0 ;; "test-ios-swiftui") xctest 'SwiftUITestHost' -configuration "$CONFIGURATION" -sdk iphonesimulator -destination "name=$(iphone_name)" exit 0 ;; "test-catalyst") xctest Realm -configuration "$CONFIGURATION" -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' exit 0 ;; "test-catalyst-swift") xctest RealmSwift -configuration "$CONFIGURATION" -destination 'platform=macOS,variant=Mac Catalyst' CODE_SIGN_IDENTITY='' exit 0 ;; "test-visionos") xctest Realm -configuration "$CONFIGURATION" -sdk xrsimulator -destination 'platform=visionOS Simulator,name=Apple Vision Pro' CODE_SIGN_IDENTITY='' exit 0 ;; "test-visionos-swift") xctest RealmSwift -configuration "$CONFIGURATION" -sdk xrsimulator -destination 'platform=visionOS Simulator,name=Apple Vision Pro' CODE_SIGN_IDENTITY='' exit 0 ;; ###################################### # Full verification ###################################### "verify") sh build.sh verify-cocoapods sh build.sh verify-docs sh build.sh verify-spm-ios sh build.sh verify-swiftlint sh build.sh verify-swiftpm sh build.sh verify-watchos sh buils.sh verify-xcframework sh build.sh verify-osx sh build.sh verify-osx-debug sh build.sh verify-osx-swift sh build.sh verify-osx-swift-debug sh build.sh verify-ios-static sh build.sh verify-ios-static-debug sh build.sh verify-ios-dynamic sh build.sh verify-ios-dynamic-debug sh build.sh verify-ios-swift sh build.sh verify-ios-swift-debug sh build.sh verify-ios-device-objc sh build.sh verify-ios-device-swift sh build.sh verify-tvos sh build.sh verify-tvos-debug sh build.sh verify-tvos-device sh build.sh verify-catalyst sh build.sh verify-catalyst-swift sh build.sh verify-ios-swiftui ;; "verify-cocoapods") export REALM_TEST_BRANCH="$BRANCH" if [[ -d .git ]]; then # Verify the current branch, unless one was already specified in the sha environment variable. if [[ -z $BRANCH ]]; then export REALM_TEST_BRANCH=$(git rev-parse --abbrev-ref HEAD) fi if [[ $(git log -1 '@{push}..') != "" ]] || ! git diff-index --quiet HEAD; then echo "WARNING: verify-cocoapods will test the latest revision of $BRANCH found on GitHub." echo " Any unpushed local changes will not be tested." echo "" sleep 1 fi fi cd examples/installation ./build.rb ios cocoapods static ./build.rb ios cocoapods dynamic ./build.rb osx cocoapods ./build.rb tvos cocoapods ./build.rb watchos cocoapods ./build.rb catalyst cocoapods ;; verify-cocoapods-*) PLATFORM=$(echo "$COMMAND" | cut -d - -f 3) cd examples/installation REALM_TEST_BRANCH="$BRANCH" ./build.rb "$PLATFORM" cocoapods "$LINKAGE" ;; "verify-docs") sh build.sh docs for lang in swift objc; do undocumented="docs/${lang}_output/undocumented.json" if ruby -rjson -e "j = JSON.parse(File.read('docs/${lang}_output/undocumented.json')); exit j['warnings'].length != 0"; then echo "Undocumented Realm $lang declarations:" cat "$undocumented" exit 1 fi done exit 0 ;; "verify-spm") export REALM_TEST_BRANCH="$BRANCH" if [[ -d .git ]]; then # Verify the current branch, unless one was already specified in the sha environment variable. if [[ -z $BRANCH ]]; then export REALM_TEST_BRANCH=$(git rev-parse --abbrev-ref HEAD) fi if [[ $(git log -1 '@{push}..') != "" ]] || ! git diff-index --quiet HEAD; then echo "WARNING: verify-spm will test the latest revision of $BRANCH found on GitHub." echo " Any unpushed local changes will not be tested." echo "" sleep 1 fi fi cd examples/installation ./build.rb ios spm static ./build.rb ios spm dynamic ./build.rb osx spm ./build.rb watchos spm ./build.rb tvos spm ./build.rb catalyst spm exit 0 ;; verify-spm-*) PLATFORM=$(echo "$COMMAND" | cut -d - -f 3) cd examples/installation REALM_TEST_BRANCH="$BRANCH" ./build.rb "$PLATFORM" spm "$LINKAGE" exit 0 ;; "verify-swiftlint") swiftlint lint --strict exit 0 ;; verify-swiftpm*) sh build.sh "test-$(echo "$COMMAND" | cut -d - -f 2-)" exit 0 ;; "verify-watchos") sh build.sh watchos-swift exit 0 ;; "verify-xcframework") sh build.sh xcframework osx exit 0 ;; "verify-osx-encryption") REALM_ENCRYPT_ALL=YES sh build.sh test-osx exit 0 ;; "verify-osx") REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/osx/objc/RealmExamples.xcworkspace" \ sh build.sh test-osx sh build.sh examples-osx ( cd examples/osx/objc/build/DerivedData/RealmExamples/Build/Products/Release DYLD_FRAMEWORK_PATH=. ./JSONImport >/dev/null ) exit 0 ;; "verify-osx-swift-evolution") export REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS REALM_BUILD_LIBRARY_FOR_DISTRIBUTION=YES" sh build.sh test-osx-swift exit 0 ;; "verify-ios") REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/ios/objc/RealmExamples.xcworkspace" \ sh build.sh test-ios sh build.sh examples-ios ;; "verify-ios-swift") REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/ios/swift/RealmExamples.xcworkspace" \ sh build.sh test-ios-swift sh build.sh examples-ios-swift ;; "verify-ios-swift-evolution") export REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS REALM_BUILD_LIBRARY_FOR_DISTRIBUTION=YES" sh build.sh test-ios-swift exit 0 ;; "verify-tvos") REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/tvos/objc/RealmExamples.xcworkspace" \ sh build.sh test-tvos sh build.sh examples-tvos exit 0 ;; "verify-tvos-swift") REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS -workspace examples/tvos/swift/RealmExamples.xcworkspace" \ sh build.sh test-tvos-swift sh build.sh examples-tvos-swift exit 0 ;; "verify-tvos-swift-evolution") export REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS REALM_BUILD_LIBRARY_FOR_DISTRIBUTION=YES" sh build.sh test-tvos-swift exit 0 ;; "verify-xcframework-evolution-mode") export REALM_EXTRA_BUILD_ARGUMENTS="$REALM_EXTRA_BUILD_ARGUMENTS REALM_BUILD_LIBRARY_FOR_DISTRIBUTION=YES" unset REALM_SWIFT_VERSION # Build with the oldest supported Xcode version REALM_XCODE_VERSION=$REALM_XCODE_OLDEST_VERSION sh build.sh xcframework osx # Try to import the built framework using the newest supported version cd examples/installation REALM_XCODE_VERSION=$REALM_XCODE_LATEST_VERSION ./build.rb osx xcframework exit 0 ;; verify-*) sh build.sh "test-$(echo "$COMMAND" | cut -d - -f 2-)" exit 0 ;; ###################################### # Docs ###################################### "docs") build_docs objc build_docs swift exit 0 ;; ###################################### # Examples ###################################### "examples") sh build.sh clean sh build.sh examples-ios sh build.sh examples-ios-swift sh build.sh examples-osx sh build.sh examples-tvos sh build.sh examples-tvos-swift exit 0 ;; "examples-ios") workspace="examples/ios/objc/RealmExamples.xcworkspace" examples="Simple TableView Migration Backlink GroupedTableView Encryption" versions="0 1 2 3 4 5" for example in $examples; do if [ "$example" = "Migration" ]; then # The migration example needs to be built for each schema version to ensure each compiles. for version in $versions; do xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk iphonesimulator "${CODESIGN_PARAMS[@]}" GCC_PREPROCESSOR_DEFINITIONS="\$(GCC_PREPROCESSOR_DEFINITIONS) SCHEMA_VERSION_$version" done else xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk iphonesimulator "${CODESIGN_PARAMS[@]}" fi done if [ -n "$CI" ]; then xc -workspace "$workspace" -scheme Extension -configuration "$CONFIGURATION" -sdk iphonesimulator "${CODESIGN_PARAMS[@]}" fi exit 0 ;; "examples-ios-swift") workspace="examples/ios/swift/RealmExamples.xcworkspace" if [[ ! -d "$workspace" ]]; then workspace="${workspace/swift/swift-$REALM_XCODE_VERSION}" fi examples="Simple TableView Migration Backlink GroupedTableView Encryption AppClip AppClipParent" versions="0 1 2 3 4 5" for example in $examples; do if [ "$example" = "Migration" ]; then # The migration example needs to be built for each schema version to ensure each compiles. for version in $versions; do xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk iphonesimulator "${CODESIGN_PARAMS[@]}" OTHER_SWIFT_FLAGS="\$(OTHER_SWIFT_FLAGS) -DSCHEMA_VERSION_$version" done else xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk iphonesimulator "${CODESIGN_PARAMS[@]}" fi done exit 0 ;; "examples-osx") workspace="examples/osx/objc/RealmExamples.xcworkspace" xc -workspace "$workspace" \ -scheme JSONImport -configuration "${CONFIGURATION}" \ -destination "platform=macOS,arch=$(uname -m)" \ build "${CODESIGN_PARAMS[@]}" ;; "examples-tvos") workspace="examples/tvos/objc/RealmExamples.xcworkspace" examples="DownloadCache PreloadedData" for example in $examples; do xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk appletvsimulator "${CODESIGN_PARAMS[@]}" done exit 0 ;; "examples-tvos-swift") workspace="examples/tvos/swift/RealmExamples.xcworkspace" if [[ ! -d "$workspace" ]]; then workspace="${workspace/swift/swift-$REALM_XCODE_VERSION}" fi examples="DownloadCache PreloadedData" for example in $examples; do xc -workspace "$workspace" -scheme "$example" -configuration "$CONFIGURATION" -sdk appletvsimulator "${CODESIGN_PARAMS[@]}" done exit 0 ;; ###################################### # Versioning ###################################### "get-version") plist_get 'Realm/Realm-Info.plist' 'CFBundleShortVersionString' exit 0 ;; "set-version") realm_version="$2" version_files="Realm/Realm-Info.plist" if [ -z "$realm_version" ]; then echo "You must specify a version." exit 1 fi # The bundle version can contain only three groups of digits separated by periods, # so strip off any -beta.x tag from the end of the version string. bundle_version=$(echo "$realm_version" | cut -d - -f 1) for version_file in $version_files; do PlistBuddy -c "Set :CFBundleVersion $bundle_version" "$version_file" PlistBuddy -c "Set :CFBundleShortVersionString $realm_version" "$version_file" done sed -i '' "s/^VERSION=.*/VERSION=$realm_version/" dependencies.list sed -i '' "s/^let cocoaVersion =.*/let cocoaVersion = Version(\"$realm_version\")/" Package.swift sed -i '' "s/x.y.z Release notes (yyyy-MM-dd)/$realm_version Release notes ($(date '+%Y-%m-%d'))/" CHANGELOG.md exit 0 ;; "set-core-version") new_version="$2" old_version="$(sed -n 's/^REALM_CORE_VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" sed -i '' "s/^REALM_CORE_VERSION=.*/REALM_CORE_VERSION=v$new_version/" dependencies.list sed -i '' "s/^let coreVersion =.*/let coreVersion = Version(\"$new_version\")/" Package.swift sed -i '' "s/Upgraded realm-core from ? to ?/Upgraded realm-core from $old_version to $new_version/" CHANGELOG.md exit 0 ;; ###################################### # Continuous Integration PR ###################################### "ci-pr") echo "Building with Xcode Version $(xcodebuild -version)" export REALM_EXTRA_BUILD_ARGUMENTS='GCC_GENERATE_DEBUGGING_SYMBOLS=NO -allowProvisioningUpdates' target="$2" sh build.sh install-xcode-platform "$target" sh build.sh "verify-$target" ;; "install-xcode-platform") target="$2" # If there are already simulators installed on the GHA VM we happen to # run on, downloadPlatform sometimes fails due to it being in the middle # of updating caches and timing out. Calling simctl list first waits for # this to happen. If there are no simulators already installed, simctl # also won't be installed yet. xcrun simctl list > /dev/null || true case "$target" in ios*) xcodebuild -downloadPlatform iOS ;; tvos*) xcodebuild -downloadPlatform tvOS ;; visionos*) xcodebuild -downloadPlatform visionOS ;; watchos*) xcodebuild -downloadPlatform watchOS ;; esac ;; ###################################### # Release packaging ###################################### "release-package-examples") ./scripts/package_examples.rb zip --symlinks -r realm-examples.zip examples -x "examples/installation/*" ;; "release-package-docs") sh build.sh docs zip -r docs/realm-docs.zip docs/objc_output docs/swift_output ;; "release-package") version="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" find . -regex './build-[a-z]*-[12].*' -maxdepth 1 \ | sed 's@./build-[a-z]*-\(.*\)-.*@\1@' \ | sort -u --version-sort \ | xargs ./scripts/create-release-package.rb "${ROOT_WORKSPACE}/pkg" "${version}" ;; "release-test-examples") VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" filename="realm-swift-${VERSION}" unzip "${filename}" cp "$0" "${filename}" cp -r "${source_root}/scripts" "${filename}" cp "dependencies.list" "${filename}" cd "${filename}" sh build.sh examples-ios sh build.sh examples-tvos sh build.sh examples-osx sh build.sh examples-ios-swift sh build.sh examples-tvos-swift cd .. rm -rf "${filename}" exit 0 ;; "install-apple-certificates") # create variables CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db # import certificate and provisioning profile from secrets echo "$DEVELOPMENT_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH # create temporary keychain security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # import certificate to keychain security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH exit 0 ;; ###################################### # Release tests ###################################### "test-package-examples") VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" dir="realm-swift-${VERSION}" # Unzip it unzip "${dir}.zip" # Copy the build.sh file into the downloaded directory cp "$0" "${dir}" # Copy the scripts into the downloaded directory cp -r "${ROOT_WORKSPACE}/scripts" "${dir}" # Copy dependencies.list cp -r "${ROOT_WORKSPACE}/dependencies.list" "${dir}" cd "${dir}" # Test Examples sh build.sh examples-ios sh build.sh examples-tvos sh build.sh examples-osx sh build.sh examples-ios-swift sh build.sh examples-tvos-swift ;; ###################################### # Publish ###################################### "publish-github") sha="$2" VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" ./scripts/github_release.rb download-artifacts release-package "${sha}" unzip release-package.zip -d release-package ./scripts/github_release.rb create-release "$VERSION" exit 0 ;; "publish-docs") sha="$2" ./scripts/github_release.rb download-artifacts realm-docs "${sha}" unzip_artifact realm-docs.zip unzip realm-docs.zip VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" PRERELEASE_REGEX='alpha|beta|rc|preview' if [[ $VERSION =~ $PRERELEASE_REGEX ]]; then echo "Pre-release version" exit 0 fi s3cmd put --no-mime-magic --guess-mime-type --recursive --acl-public docs/swift_output/ s3://realm-sdks/docs/realm-sdks/swift/${VERSION}/ s3cmd put --no-mime-magic --guess-mime-type --recursive --acl-public docs/swift_output/ s3://realm-sdks/docs/realm-sdks/swift/latest/ s3cmd put --no-mime-magic --guess-mime-type --recursive --acl-public docs/objc_output/ s3://realm-sdks/docs/realm-sdks/objc/${VERSION}/ s3cmd put --no-mime-magic --guess-mime-type --recursive --acl-public docs/objc_output/ s3://realm-sdks/docs/realm-sdks/objc/latest/ ;; "publish-cocoapods") cd "${ROOT_WORKSPACE}" pod trunk push Realm.podspec --verbose --allow-warnings pod trunk push RealmSwift.podspec --verbose --allow-warnings --synchronous exit 0 ;; "prepare-publish-changelog") VERSION="$(sed -n 's/^VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")" ./scripts/github_release.rb package-release-notes "$VERSION" exit 0 ;; "add-empty-changelog") empty_section=$(cat < ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) * None. ### Compatibility * Carthage release for Swift is built with Xcode 26.3. * CocoaPods: 1.10 or later. * Xcode: 26.1-26.4 beta 1 ### Internal * Upgraded realm-core from ? to ? EOS) changelog=$(cat CHANGELOG.md) echo "$empty_section" > CHANGELOG.md echo >> CHANGELOG.md echo "$changelog" >> CHANGELOG.md ;; *) echo "Unknown command '$COMMAND'" usage exit 1 ;; esac ================================================ FILE: contrib/Development.md ================================================ # Developing Realm ## Building Realm There are three ways to build Realm 1. \[Recommended] Using Xcode, open Package.swift. With this approach you can build either against a released Core version or a custom branch. 1. Using Xcode, open Realm.xcodeproj. This will download the version of Core specified in `dependencies.list/REALM_CORE_VERSION` and build the Swift SDK against it. 1. From the command line, run `./build.sh build`. Similarly to 2., this also downloads Core and builds against it. ### Building against a custom branch of Core To build Realm against a custom Core branch, update `Package.swift` by updating the Realm Core dependency from `exact` to `branch`: ```diff dependencies: [ - .package(url: "https://github.com/realm/realm-core.git", exact: coreVersion) + .package(url: "https://github.com/realm/realm-core.git", branch: "*your-custom-branch*") ], ================================================ FILE: contrib/ReleaseProcess.md ================================================ ## Releasing from master Follow these steps to release a new version of the Realm Swift SDK. 1. Open Github actions [Prepare-release](https://github.com/realm/realm-swift/actions/workflows/master-push.yml) page and cancel any workflow runs. This is not mandatory. 2. Update the version number and set the release date in the changelog: `sh build.sh set-version X.Y.Z` 3. Take another look over `CHANGELOG.md` to make sure it looks sensible. Delete any remaining placeholders for things which didn't happen in this release (e.g. the breaking changes section). 4. Commit and push to `master`. This automatically kicks off a build that current takes around 1 hours. 5. Once the [Prepare-release](https://github.com/realm/realm-swift/actions/workflows/master-push.yml) job completes, run the [Publish-release](https://github.com/realm/realm-swift/actions/workflows/publish-release.yml) workflow manually to publish the release. This tags the version, creates a release on github, pushes to CocoaPods, and updates the website, and then runs another set of tests to validate that the published release can be installed. This process takes 1-2 hours. ## Releasing alpha/beta/preview/rc version from other branches Follow these steps when we have long-lived branches that we are making alpha/beta releases from. 1. Update the version number and set the release date in the changelog: `sh build.sh set-version X.Y.Z-preview`. Note that the presence of `alpha`, `beta`, `preview` or `rc` in the version number is semantically significant and makes the release job not mark the version as the latest release on the web site. 2. Take another look over `CHANGELOG.md` to make sure it looks sensible. Delete any remaining placeholders for things which didn't happen in this release (e.g. the breaking changes section). 3. Commit and push to the branch. 4. Run the [Prepare-release](https://github.com/realm/realm-swift/actions/workflows/master-push.yml) workflow manually using the desired branch (it only automatically runs for pushes to `master`). 5. Run the [Publish-release](https://github.com/realm/realm-swift/actions/workflows/publish-release.yml) job to publish the release, selecting the desired branch. This tags the version, creates a release on github, pushes to CocoaPods, and updates the website, and then runs another set of tests to validate that the published release can be installed. This process takes 1-2 hours. ## Releasing a backported fix Follow these steps when there are changes in `master` that shouldn't be included in the release. 1. Check out the base release which is being hotfixed (e.g. `git checkout v0.96.0`). 2. Run `sh build.sh add-empty-changelog`and commit the result. 3. Cherry-pick the commit(s) you want to include in the release. 4. Move the changelog entries from the cherry-picked commit(s) to the section for the version being released (they are likely to end up in the wrong place from the automatic merge). 5. Set version: `sh build.sh set-version X.Y.Z` 6. Push to a new branch of the format `release/0.96.1` or similar. 7. Open a draft PR for the release branch to run the PR CI job on it. 8. Once the PR job passes, run [Prepare-release](https://github.com/realm/realm-swift/actions/workflows/master-push.yml) workflow for the release branch. 8. Run the [release-cocoa](https://ci.realm.io/job/release-cocoa/) job selecting your branch branch name as a parameter and confirm that it succeeded. 10. Close the draft PR for the release branch without merging it. ================================================ FILE: contrib/SignXCFramework.md ================================================ ## Signing the XCFramework By Apple's requirements, we should sign our release binaries so Xcode can validate it was signed by the same developer on every new version. Follow these steps to update the signing certificate in case of change or after the current used certificate has been revoke. 1. Create an Apple Distribution or Apple Development certificate from XCode's Settings/Accounts menu or from Apple's developer portal. 2. Export the given certificate with a distintic password, edit github's secret variable `P12_PASSWORD` with the new password. https://help.apple.com/xcode/mac/current/#/dev154b28f09 3. Generate a Base64 string from the exported certificate using ``` base64 -i BUILD_CERTIFICATE.p12 | pbcopy ``` 4. Edit github's secret variable `DEVELOPMENT_CERTIFICATE_BASE64` with the copied value. 5. Edit the current github's secret variable `SIGNING_IDENTITY` to the new identity associated to the exported certificate. ================================================ FILE: contrib/UpgradingXcode.md ================================================ Check https://developer.apple.com/documentation/xcode-release-notes to see new Xcode releases and https://developer.apple.com/xcode-cloud/release-notes/ for Xcode cloud release notes. Xcode cloud doesn't update Xcode versions immediately after release, this may take from a few a hours to some days. # Update Xcode cloud workflow's Xcode version(s) ## https://github.com/realm/realm-swift 1. Update `pr-ci-matrix.rb`. Add or remove version(s) from XCODE_VERSIONS. 2. Run `ruby ./scripts/xcode_cloud_helper.rb -t {APP_STORE_CONNECT_TOKEN} synchronize-workflows` and select `create` if you want just to create new workflows, `delete` to remove unused workflows and `both` if you want both create and clean. 2. You can also run the `update-xcode-cloud-workflows` Github action manually for step 2. 3. Enable manually the new created workflows. 4. If needed, add environment values to the newly created workflows. 5. Update version(s) from xcode_versions in `scripts/package-examples.rb`. 6. Update XCODE_VERSION in `.github/workflows/master-push.yml` and `.github/workflows/publish-release.yml` and check if DOC_VERSION, RELEASE_VERSION and TEST_VERSION needs to be updated. 7. Search for `#if swift` and see if there's any we can remove. 8. Update the Carthage version in CHANGELOG.md (and add a changelog entry). 9. If there's new project settings migrations, open each of the Xcode projects and apply/skip them as applicable. Note that we generally do *not* want to use the Swift version migrations as we support multiple Swift versions at once. ## Notes * New workflows which includes an environment value should update the environment values manually, e.g. targets with server test. `App Store Connect API` doesn't have allow to set environment values for workflows in the current API. ================================================ FILE: dependencies.list ================================================ VERSION=20.0.4 REALM_CORE_VERSION=v20.1.4 ================================================ FILE: docs/README.md ================================================ # Realm SDK for Swift Use the Realm SDK for Swift to develop iOS, macOS, watchOS and tvOS apps in Swift and Objective-C. ## Get Started with the Swift SDK These docs contain minimal-explanation code examples of how to work with the Swift SDK. To get started with SwiftUI, see: [SwiftUI Quick Start](swiftui-tutorial.md) ### Install the Swift SDK Use Swift Package Manager, CocoaPods, or Carthage to Install the SDK for iOS, macOS, tvOS, and watchOS in your project. Import `RealmSwift` in your project files to get started. ### Define an Object Schema Use Swift to idiomatically define an object schema. ### Open a Database The SDK's database - Realm - stores objects in files on your device. Or you can open an in-memory database which does not create a file. Configure and open a database to specify the options for your database file. ### Read and Write Data - Create, read, update, and delete objects from the device database. - Filter data using the SDK's type-safe .where syntax, or construct an NSPredicate. ### React to Changes Live objects mean that your data is always up-to-date. You can register a notification handler to watch for changes and perform some logic, such as updating your UI. Or in SwiftUI, use the Swift property wrappers to update Views when data changes. ## Realm SwiftUI The Swift SDK offers property wrappers and convenience features designed to make it easier to work with SwiftUI. For example View code that demonstrates common SwiftUI patterns, check out the SwiftUI documentation. ```swift struct SearchableDogsView: View { @ObservedResults(Dog.self) var dogs @State private var searchFilter = "" var body: some View { NavigationView { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) } } .searchable(text: $searchFilter, collection: $dogs, keyPath: \.name) { ForEach(dogs) { dogsFiltered in Text(dogsFiltered.name).searchCompletion(dogsFiltered.name) } } } } } ``` ## Generating API Reference Docs You can generate the API docs locally by running `sh build.sh docs` from the root of this repository. This requires installation of [jazzy](https://github.com/realm/jazzy/). You will find the output in `docs/swift_output/` and `docs/objc_output/`. ================================================ FILE: docs/custom_head.html ================================================ ================================================ FILE: docs/example-projects.md ================================================ # Realm SDK Example Projects Explore engineering and expert-provided example projects to learn best practices and common development patterns for the Swift SDK. |Project Name|Description|Source Code| | --- | --- | --- | |Integrating In-App Purchases|Use [DELETE ME]'s efficient data management and synchronization capabilities to build a recipes library with in-app purchases (IAP) using StoreKit.|[Swift](https://github.com/realm/realm-swift-samples/tree/main/InAppPurchasesAtlasAppServices)| |RTicket|Build a simple issue ticket system with Realm and SwiftUI.|[Swift](https://github.com/mongodb-developer/RTicket)| |RCurrency|Use Realm to cache data retrieved from an API and access the data offline.|[Swift](https://github.com/realm/RCurrency)| |RChat|Build a simple chat app with SwiftUI and Realm.|[Swift](https://github.com/realm/RChat)| ================================================ FILE: docs/guides/crud/create.md ================================================ # CRUD - Create - Swift SDK ## Create a New Object ### About The Examples On This Page The examples on this page use the following models: #### Objective-C ```objectivec // DogToy.h @interface DogToy : RLMObject @property NSString *name; @end // Dog.h @interface Dog : RLMObject @property NSString *name; @property int age; @property NSString *color; // To-one relationship @property DogToy *favoriteToy; @end // Enable Dog for use in RLMArray RLM_COLLECTION_TYPE(Dog) // Person.h // A person has a primary key ID, a collection of dogs, and can be a member of multiple clubs. @interface Person : RLMObject @property int _id; @property NSString *name; // To-many relationship - a person can have many dogs @property RLMArray *dogs; // Inverse relationship - a person can be a member of many clubs @property (readonly) RLMLinkingObjects *clubs; @end RLM_COLLECTION_TYPE(Person) // DogClub.h @interface DogClub : RLMObject @property NSString *name; @property RLMArray *members; @end // Dog.m @implementation Dog @end // DogToy.m @implementation DogToy @end // Person.m @implementation Person // Define the primary key for the class + (NSString *)primaryKey { return @"_id"; } // Define the inverse relationship to dog clubs + (NSDictionary *)linkingObjectsProperties { return @{ @"clubs": [RLMPropertyDescriptor descriptorWithClass:DogClub.class propertyName:@"members"], }; } @end // DogClub.m @implementation DogClub @end ``` #### Swift ```swift class DogToy: Object { @Persisted var name = "" } class Dog: Object { @Persisted var name = "" @Persisted var age = 0 @Persisted var color = "" @Persisted var currentCity = "" @Persisted var citiesVisited: MutableSet @Persisted var companion: AnyRealmValue // To-one relationship @Persisted var favoriteToy: DogToy? // Map of city name -> favorite park in that city @Persisted var favoriteParksByCity: Map } class Person: Object { @Persisted(primaryKey: true) var id = 0 @Persisted var name = "" // To-many relationship - a person can have many dogs @Persisted var dogs: List // Embed a single object. // Embedded object properties must be marked optional. @Persisted var address: Address? convenience init(name: String, address: Address) { self.init() self.name = name self.address = address } } class Address: EmbeddedObject { @Persisted var street: String? @Persisted var city: String? @Persisted var country: String? @Persisted var postalCode: String? } ``` ### Create an Object #### Objective-C To add an object to a realm, instantiate it as you would any other object and then pass it to `-[RLMRealm addObject:]` inside of a write transaction. ```objectivec // Get the default realm. // You only need to do this once per thread. RLMRealm *realm = [RLMRealm defaultRealm]; // Instantiate the class. Dog *dog = [[Dog alloc] init]; dog.name = @"Max"; dog.age = 5; // Open a thread-safe transaction. [realm transactionWithBlock:^() { // Add the instance to the realm. [realm addObject:dog]; }]; ``` #### Swift To add an object to a realm, instantiate it as you would any other object and then pass it to `Realm.add(_:update:)` inside of a write transaction. ```swift // Instantiate the class and set its values. let dog = Dog() dog.name = "Rex" dog.age = 10 // Get the default realm. You only need to do this once per thread. let realm = try! Realm() // Open a thread-safe transaction. try! realm.write { // Add the instance to the realm. realm.add(dog) } ``` ### Initialize Objects with a Value You can initialize an object by passing an initializer value to `Object.init(value:)`. The initializer value can be a [key-value coding](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/) compliant object, a dictionary, or an array containing one element for each managed property. > Note: > When using an array as an initializer value, you must include all properties in the same order as they are defined in the model. > #### Objective-C ```objectivec // (1) Create a Dog object from a dictionary Dog *myDog = [[Dog alloc] initWithValue:@{@"name" : @"Pluto", @"age" : @3}]; // (2) Create a Dog object from an array Dog *myOtherDog = [[Dog alloc] initWithValue:@[@"Pluto", @3]]; RLMRealm *realm = [RLMRealm defaultRealm]; // Add to the realm with transaction [realm transactionWithBlock:^() { [realm addObject:myDog]; [realm addObject:myOtherDog]; }]; ``` #### Swift ```swift // (1) Create a Dog object from a dictionary let myDog = Dog(value: ["name": "Pluto", "age": 3]) // (2) Create a Dog object from an array let myOtherDog = Dog(value: ["Fido", 5]) let realm = try! Realm() // Add to the realm inside a transaction try! realm.write { realm.add([myDog, myOtherDog]) } ``` You can even initialize related or embedded objects by nesting initializer values: #### Objective-C ```objectivec // Instead of using pre-existing dogs... Person *aPerson = [[Person alloc] initWithValue:@[@123, @"Jane", @[aDog, anotherDog]]]; // ...we can create them inline Person *anotherPerson = [[Person alloc] initWithValue:@[@123, @"Jane", @[@[@"Buster", @5], @[@"Buddy", @6]]]]; ``` #### Swift ```swift // Instead of using pre-existing dogs... let aPerson = Person(value: [123, "Jane", [aDog, anotherDog]]) // ...we can create them inline let anotherPerson = Person(value: [123, "Jane", [["Buster", 5], ["Buddy", 6]]]) ``` #### Some Property Types are Only Mutable in a Write Transaction Some property types are only mutable in a write transaction. For example, you can instantiate an object with a MutableSet property, but you can only set that property's value in a write transaction. You cannot initialize the object with a value for that property unless you do so inside a write transaction. ### Create an Object with JSON Realm does not directly support JSON, but you can use [JSONSerialization.jsonObject(with:options:)](https://developer.apple.com/documentation/foundation/jsonserialization/1415493-jsonobject) to convert JSON into a value that you can pass to `Realm.create(_:value:update:)`. #### Objective-C ```objectivec // Specify a dog toy in JSON NSData *data = [@"{\"name\": \"Tennis ball\"}" dataUsingEncoding: NSUTF8StringEncoding]; RLMRealm *realm = [RLMRealm defaultRealm]; // Insert from NSData containing JSON [realm transactionWithBlock:^{ id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; [DogToy createInRealm:realm withValue:json]; }]; ``` #### Swift ```swift // Specify a dog toy in JSON let data = "{\"name\": \"Tennis ball\"}".data(using: .utf8)! let realm = try! Realm() // Insert from data containing JSON try! realm.write { let json = try! JSONSerialization.jsonObject(with: data, options: []) realm.create(DogToy.self, value: json) } ``` Nested objects or arrays in the JSON map to to-one or to-many relationships. The JSON property names and types must match the destination object schema exactly. For example: - `float` properties must be initialized with float-backed `NSNumbers`. - `Date` and `Data` properties cannot be inferred from strings. Convert them to the appropriate type before passing to `Realm.create(_:value:update:)`. - Required properties cannot be `null` or missing in the JSON. Realm ignores any properties in the JSON not defined in the object schema. > Tip: > If your JSON schema doesn't exactly align with your Realm objects, consider using a third-party framework to transform your JSON. There are many model mapping frameworks that work with Realm. See a [partial list in the realm-swift repository](https://github.com/realm/realm-swift/issues/694#issuecomment-144785299). > ### Create an Embedded Object To create an embedded object, assign an instance of the embedded object to a parent object's property: #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ Address *address = [[Address alloc] init]; address.street = @"123 Fake St."; address.city = @"Springfield"; address.country = @"USA"; address.postalCode = @"90710"; Contact *contact = [Contact contactWithName:@"Nick Riviera"]; // Assign the embedded object property contact.address = address; [realm addObject:contact]; NSLog(@"Added contact: %@", contact); }]; ``` #### Swift ```swift // Open the default realm let realm = try! Realm() try! realm.write { let address = Address() address.street = "123 Fake St" address.city = "Springfield" address.country = "USA" address.postalCode = "90710" let contact = Person(name: "Nick Riviera", address: address) realm.add(contact) } ``` ### Create an Object with a Map Property When you create an object that has a `map property`, you can set the values for keys in a few ways: - Set keys and values on the object and then add the object to the realm - Set the object's keys and values directly inside a write transaction - Use key-value coding to set or update keys and values inside a write transaction ```swift let realm = try! Realm() // Record a dog's name and current city let dog = Dog() dog.name = "Wolfie" dog.currentCity = "New York" // Set map values dog.favoriteParksByCity["New York"] = "Domino Park" // Store the data in a realm try! realm.write { realm.add(dog) // You can also set map values inside a write transaction dog.favoriteParksByCity["Chicago"] = "Wrigley Field" dog.favoriteParksByCity.setValue("Bush Park", forKey: "Ottawa") } ``` Realm disallows the use of `.` or `$` characters in map keys. You can use percent encoding and decoding to store a map key that contains one of these disallowed characters. ``` // Percent encode . or $ characters to use them in map keys let mapKey = "New York.Brooklyn" let encodedMapKey = "New York%2EBrooklyn" ``` ### Create an Object with a MutableSet Property You can create objects that contain `MutableSet` properties as you would any Realm object, but you can only mutate a MutableSet within a write transaction. This means you can only set the value(s) of a mutable set property within a write transaction. ```swift let realm = try! Realm() // Record a dog's name and current city let dog = Dog() dog.name = "Maui" dog.currentCity = "New York" // Store the data in a realm. Add the dog's current city // to the citiesVisited MutableSet try! realm.write { realm.add(dog) // You can only mutate the MutableSet in a write transaction. // This means you can't set values at initialization, but must do it during a write. dog.citiesVisited.insert(dog.currentCity) } // You can also add multiple items to the set. try! realm.write { dog.citiesVisited.insert(objectsIn: ["Boston", "Chicago"]) } print("\(dog.name) has visited: \(dog.citiesVisited)") ``` ### Create an Object with an AnyRealmValue Property When you create an object with an AnyRealmValue property, you must specify the type of the value you store in the property. The Realm Swift SDK provides an `AnyRealmValue enum` that iterates through all of the types the AnyRealmValue can store. Later, when you read an AnyRealmValue, you must check the type before you do anything with the value. ```swift // Create a Dog object and then set its properties let myDog = Dog() myDog.name = "Rex" // This dog has no companion. // You can set the field's type to "none", which represents `nil` myDog.companion = .none // Create another Dog whose companion is a cat. // We don't have a Cat object, so we'll use a string to describe the companion. let theirDog = Dog() theirDog.name = "Wolfie" theirDog.companion = .string("Fluffy the Cat") // Another dog might have a dog as a companion. // We do have an object that can represent that, so we can specify the // type is a Dog object, and even set the object's value. let anotherDog = Dog() anotherDog.name = "Fido" // Note: this sets Spot as a companion of Fido, but does not set // Fido as a companion of Spot. Spot has no companion in this instance. anotherDog.companion = .object(Dog(value: ["name": "Spot"])) // Add the dogs to the realm let realm = try! Realm() try! realm.write { realm.add([myDog, theirDog, anotherDog]) } // After adding these dogs to the realm, we now have 4 dog objects. let dogs = realm.objects(Dog.self) XCTAssertEqual(dogs.count, 4) ``` ## Create an Object Asynchronously You can use Swift concurrency features to write asynchronously to an actor-isolated realm. This function from the example `RealmActor` defined on the Use Realm with Actors page shows how you might write to an actor-isolated realm: ```swift func createTodo(name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": name, "owner": owner, "status": status ]) } } ``` And you might perform this write using Swift's async syntax: ```swift func createObject() async throws { // Because this function is not isolated to this actor, // you must await operations completed on the actor try await actor.createTodo(name: "Take the ring to Mount Doom", owner: "Frodo", status: "In Progress") let taskCount = await actor.count print("The actor currently has \(taskCount) tasks") } let actor = try await RealmActor() try await createObject() ``` This operation does not block or perform I/O on the calling thread. For more information about writing to realm using Swift concurrency features, refer to Use Realm with Actors - Swift SDK. ## Copy an Object to Another Realm #### Objective-C To copy an object from one realm to another, pass the original object to `+[RLMObject createInRealm:withValue:]`: ```objectivec RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.inMemoryIdentifier = @"first realm"; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Wolfie"; dog.age = 1; [realm addObject:dog]; }]; // Later, fetch the instance we want to copy Dog *wolfie = [[Dog objectsInRealm:realm where:@"name == 'Wolfie'"] firstObject]; // Open the other realm RLMRealmConfiguration *otherConfiguration = [RLMRealmConfiguration defaultConfiguration]; otherConfiguration.inMemoryIdentifier = @"second realm"; RLMRealm *otherRealm = [RLMRealm realmWithConfiguration:otherConfiguration error:nil]; [otherRealm transactionWithBlock:^{ // Copy to the other realm Dog *wolfieCopy = [[wolfie class] createInRealm:otherRealm withValue:wolfie]; wolfieCopy.age = 2; // Verify that the copy is separate from the original XCTAssertNotEqual(wolfie.age, wolfieCopy.age); }]; ``` #### Swift To copy an object from one realm to another, pass the original object to `Realm.create(_:value:update:):`: ```swift let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "first realm")) try! realm.write { let dog = Dog() dog.name = "Wolfie" dog.age = 1 realm.add(dog) } // Later, fetch the instance we want to copy let wolfie = realm.objects(Dog.self).first(where: { $0.name == "Wolfie" })! // Open the other realm let otherRealm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "second realm")) try! otherRealm.write { // Copy to the other realm let wolfieCopy = otherRealm.create(type(of: wolfie), value: wolfie) wolfieCopy.age = 2 // Verify that the copy is separate from the original XCTAssertNotEqual(wolfie.age, wolfieCopy.age) } ``` > Important: > The `create` methods do not support handling cyclical object graphs. Do not pass in an object containing relationships involving objects that refer back to their parents, either directly or indirectly. > ================================================ FILE: docs/guides/crud/crud.md ================================================ # CRUD - Swift SDK ## Write Transactions Realm uses a highly efficient storage engine to persist objects. You can **create** objects in a realm, **update** objects in a realm, and eventually **delete** objects from a realm. Because these operations modify the state of the realm, we call them writes. Realm handles writes in terms of **transactions**. A transaction is a list of read and write operations that Realm treats as a single indivisible operation. In other words, a transaction is *all or nothing*: either all of the operations in the transaction succeed or none of the operations in the transaction take effect. All writes must happen in a transaction. A realm allows only one open transaction at a time. Realm blocks other writes on other threads until the open transaction is complete. Consequently, there is no race condition when reading values from the realm within a transaction. When you are done with your transaction, Realm either **commits** it or **cancels** it: - When Realm **commits** a transaction, Realm writes all changes to disk. - When Realm **cancels** a write transaction or an operation in the transaction causes an error, all changes are discarded (or "rolled back"). ### Run a Transaction The Swift SDK represents each transaction as a callback function that contains zero or more read and write operations. To run a transaction, define a transaction callback and pass it to the realm's `write` method. Within this callback, you are free to create, read, update, and delete on the realm. If the code in the callback throws an exception when Realm runs it, Realm cancels the transaction. Otherwise, Realm commits the transaction immediately after the callback. > Important: > Since transactions block each other, it is best to avoid opening transactions on both the UI thread and a background thread. If a background transaction blocks your UI thread's transaction, your app may appear unresponsive. > > Example: > The following code shows how to run a transaction with the realm's write method. If the code in the callback throws an exception, Realm cancels the transaction. Otherwise, Realm commits the transaction. > > #### Objective-C > > ```objectivec > // Open the default realm. > RLMRealm *realm = [RLMRealm defaultRealm]; > > // Open a thread-safe transaction. > [realm transactionWithBlock:^() { > // ... Make changes ... > // Realm automatically cancels the transaction in case of exception. > // Otherwise, Realm automatically commits the transaction at the > // end of the code block. > }]; > > ``` > > > #### Swift > > ```swift > // Open the default realm. > let realm = try! Realm() > > // Prepare to handle exceptions. > do { > // Open a thread-safe transaction. > try realm.write { > // Make any writes within this code block. > // Realm automatically cancels the transaction > // if this code throws an exception. Otherwise, > // Realm automatically commits the transaction > // after the end of this code block. > } > } catch let error as NSError { > // Failed to write to realm. > // ... Handle error ... > } > > ``` > > ## Interface-Driven Writes Realm always delivers notifications asynchronously, so they never block the UI thread. However, there are situations when the UI must reflect changes instantly. If you update the UI directly at the same time as the write, the eventual notification could double that update. This could lead to your app crashing due to inconsistent state between the UI and the backing data store. To avoid this, you can write without sending a notification to a specific handler. We call this type of transaction an **interface-driven write**. > Example: > Say we decide to manage a table view's data source manually, because our app design requires an instantaneous response to UI-driven table updates. As soon as a user adds an item to the table view, we insert it to our data source, which writes to the realm but also immediately kicks off the animation. However, when Realm delivers the change notification for this insertion a little later, it indicates that an object has been added. But we already updated the table view for it! Rather than writing complicated code to handle this case, we can use interface-driven writes to prevent a specific notification handler from firing for that specific write. > Interface-driven writes, also known as silent writes, are especially useful when using fine-grained collection notifications. While you use interface-driven writes for the current user's updates and update the UI immediately, the sync process can use standard notifications to update the UI. ================================================ FILE: docs/guides/crud/delete.md ================================================ # CRUD - Delete - Swift SDK ## Delete Realm Objects Deleting Realm Objects must occur within write transactions. For more information about write transactions, see: Transactions. If you want to delete the Realm file itself, see: Delete a Realm. > Important: > You cannot access or modify an object after you have deleted it from a realm. If you try to use a deleted object, Realm throws an error. > ### About The Examples On This Page The examples on this page use the following models: #### Objective-C ```objectivec // DogToy.h @interface DogToy : RLMObject @property NSString *name; @end // Dog.h @interface Dog : RLMObject @property NSString *name; @property int age; @property NSString *color; // To-one relationship @property DogToy *favoriteToy; @end // Enable Dog for use in RLMArray RLM_COLLECTION_TYPE(Dog) // Person.h // A person has a primary key ID, a collection of dogs, and can be a member of multiple clubs. @interface Person : RLMObject @property int _id; @property NSString *name; // To-many relationship - a person can have many dogs @property RLMArray *dogs; // Inverse relationship - a person can be a member of many clubs @property (readonly) RLMLinkingObjects *clubs; @end RLM_COLLECTION_TYPE(Person) // DogClub.h @interface DogClub : RLMObject @property NSString *name; @property RLMArray *members; @end // Dog.m @implementation Dog @end // DogToy.m @implementation DogToy @end // Person.m @implementation Person // Define the primary key for the class + (NSString *)primaryKey { return @"_id"; } // Define the inverse relationship to dog clubs + (NSDictionary *)linkingObjectsProperties { return @{ @"clubs": [RLMPropertyDescriptor descriptorWithClass:DogClub.class propertyName:@"members"], }; } @end // DogClub.m @implementation DogClub @end ``` #### Swift ```swift class Dog: Object { @Persisted var name = "" @Persisted var age = 0 @Persisted var color = "" @Persisted var currentCity = "" @Persisted var citiesVisited: MutableSet @Persisted var companion: AnyRealmValue // Map of city name -> favorite park in that city @Persisted var favoriteParksByCity: Map } ``` ### Delete an Object #### Objective-C To delete an object from a realm, pass the object to `-[RLMRealm deleteObject:]` inside of a write transaction. ```objectivec [realm transactionWithBlock:^() { // Delete the instance from the realm. [realm deleteObject:dog]; }]; ``` #### Swift To delete an object from a realm, pass the object to `Realm.delete(_:)` inside of a write transaction. ```swift // Previously, we've added a dog object to the realm. let dog = Dog(value: ["name": "Max", "age": 5]) let realm = try! Realm() try! realm.write { realm.add(dog) } // Delete the instance from the realm. try! realm.write { realm.delete(dog) } ``` ### Delete Multiple Objects #### Swift > Version added: 10.19.0 To delete a collection of objects from a realm, pass the collection to `Realm.delete(_:)` inside of a write transaction. ```swift let realm = try! Realm() try! realm.write { // Find dogs younger than 2 years old. let puppies = realm.objects(Dog.self).where { $0.age < 2 } // Delete the objects in the collection from the realm. realm.delete(puppies) } ``` #### Swift Nspredicate To delete a collection of objects from a realm, pass the collection to `Realm.delete(_:)` inside of a write transaction. ```swift let realm = try! Realm() try! realm.write { // Find dogs younger than 2 years old. let puppies = realm.objects(Dog.self).filter("age < 2") // Delete the objects in the collection from the realm. realm.delete(puppies) } ``` #### Objective-C To delete a collection of objects from a realm, pass the collection to `-[Realm deleteObjects:]` inside of a write transaction. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^() { // Find dogs younger than 2 years old. RLMResults *puppies = [Dog objectsInRealm:realm where:@"age < 2"]; // Delete all objects in the collection from the realm. [realm deleteObjects:puppies]; }]; ``` ### Delete an Object and Its Related Objects Sometimes, you want to delete related objects when you delete the parent object. We call this a **chaining delete**. Realm does not delete the related objects for you. If you do not delete the objects yourself, they remain orphaned in your realm. Whether or not this is a problem depends on your application's needs. The best way to delete dependent objects is to iterate through the dependencies and delete them before deleting the parent object. #### Objective-C ```objectivec [realm transactionWithBlock:^() { // Delete Ali's dogs. [realm deleteObjects:[ali dogs]]; // Delete Ali. [realm deleteObject:ali]; }]; ``` #### Swift ```swift let person = realm.object(ofType: Person.self, forPrimaryKey: 1)! try! realm.write { // Delete the related collection realm.delete(person.dogs) realm.delete(person) } ``` ### Delete All Objects of a Specific Type #### Objective-C To delete all objects of a given object type from a realm, pass the result of [+[YourRealmObjectClass allObjectsInRealm:]] to `-[Realm deleteObjects:]` inside of a write transaction. Replace `YourRealmObjectClass` with your Realm object class name. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^() { // Delete all instances of Dog from the realm. RLMResults *allDogs = [Dog allObjectsInRealm:realm]; [realm deleteObjects:allDogs]; }]; ``` #### Swift To delete all objects of a given object type from a realm, pass the result of `Realm.objects(_)` for the type you wish to delete to `Realm.delete(_:)` inside of a write transaction. ```swift let realm = try! Realm() try! realm.write { // Delete all instances of Dog from the realm. let allDogs = realm.objects(Dog.self) realm.delete(allDogs) } ``` ### Delete All Objects in a Realm #### Objective-C To delete all objects from the realm, call [-[RLMRealm deleteAllObjects]] inside of a write transaction. This clears the realm of all object instances but does not affect the realm's schema. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^() { // Delete all objects from the realm. [realm deleteAllObjects]; }]; ``` #### Swift To delete all objects from the realm, call `Realm.deleteAll()` inside of a write transaction. This clears the realm of all object instances but does not affect the realm's schema. ```swift let realm = try! Realm() try! realm.write { // Delete all objects from the realm. realm.deleteAll() } ``` ### Delete Map Keys/Values You can delete `map` entries in a few ways: - Use `removeObject(for:)` to remove the key and the value - If the dictionary's value is optional, you can set the value of the key to `nil` to keep the key. ```swift let realm = try! Realm() // Find the dog we want to update let wolfie = realm.objects(Dog.self).where { $0.name == "Wolfie" }.first! // Delete an entry try! realm.write { // Use removeObject(for:) wolfie.favoriteParksByCity.removeObject(for: "New York") // Or assign `nil` to delete non-optional values. // If the value type were optional (e.g. Map) // this would assign `nil` to that entry rather than deleting it. wolfie.favoriteParksByCity["New York"] = nil } XCTAssertNil(wolfie.favoriteParksByCity["New York"]) ``` ### Delete MutableSet Elements You can delete specific elements from a `MutableSet`, or clear all of the elements from the set. If you are working with multiple sets, you can also remove elements in one set from the other set; see: Update a MutableSet Property. ```swift let realm = try! Realm() // Record a dog's name and list of cities he has visited. let dog = Dog() dog.name = "Maui" let dogCitiesVisited = ["New York", "Boston", "Toronto"] try! realm.write { realm.add(dog) dog.citiesVisited.insert(objectsIn: dogCitiesVisited) } XCTAssertEqual(dog.citiesVisited.count, 3) // Later... we decide the dog didn't really visit Toronto // since the plane just stopped there for a layover. // Remove the element from the set. try! realm.write { dog.citiesVisited.remove("Toronto") } XCTAssertEqual(dog.citiesVisited.count, 2) // Or, in the case where the person entered the data for // the wrong dog, remove all elements from the set. try! realm.write { dog.citiesVisited.removeAll() } XCTAssertEqual(dog.citiesVisited.count, 0) ``` ### Delete the Value of an AnyRealmValue To delete the value of an AnyRealmValue, set it to `.none`. ```swift let realm = try! Realm() // Wolfie's companion is "Fluffy the Cat", represented by a string. // Fluffy has gone to visit friends for the summer, so Wolfie has no companion. let wolfie = realm.objects(Dog.self).where { $0.name == "Wolfie" }.first! try! realm.write { // You cannot set an AnyRealmValue to nil; you must set it to `.none`, instead. wolfie.companion = .none } ``` ## Delete an Object Asynchronously You can use Swift concurrency features to asynchronously delete objects using an actor-isolated realm. This function from the example `RealmActor` defined on the Use Realm with Actors page shows how you might delete an object in an actor-isolated realm: ```swift func deleteTodo(id: ObjectId) async throws { try await realm.asyncWrite { let todoToDelete = realm.object(ofType: Todo.self, forPrimaryKey: id) realm.delete(todoToDelete!) } } ``` And you might perform this deletion using Swift's async syntax: ```swift let actor = try await RealmActor() let todoId = await actor.getObjectId(forTodoNamed: "Keep Mr. Frodo safe from that Gollum") try await actor.deleteTodo(id: todoId) let updatedTodoCount = await actor.count if updatedTodoCount == todoCount - 1 { print("Successfully deleted the todo") } ``` This operation does not block or perform I/O on the calling thread. For more information about writing to realm using Swift concurrency features, refer to Use Realm with Actors. ================================================ FILE: docs/guides/crud/filter-data.md ================================================ # Filter Data - Swift SDK ## Overview To filter data in your realm, you can leverage Realm's query engine. > Version added: 10.19.0 > The Realm Swift Query API offers an idiomatic way for Swift developers to query data. Use Swift-style syntax to query a realm with the benefits of auto-completion and type safety. The Realm Swift Query API does not replace the NSPredicate Query API in newer SDK versions; instead, you can use either. For SDK versions prior to 10.19.0, or for Objective-C developers, Realm's query engine supports NSPredicate Query. ## About the Examples on This Page The examples in this page use a simple data set for a task list app. The two Realm object types are `Project` and `Task`. A `Task` has a name, assignee's name, and completed flag. There is also an arbitrary number for priority -- higher is more important -- and a count of minutes spent working on it. Finally, a `Task` can have one or more string `labels` and one or more integer `ratings`. A `Project` has zero or more `Tasks`. See the schema for these two classes, `Project` and `Task`, below: #### Objective-C ```objectivec // Task.h @interface Task : RLMObject @property NSString *name; @property bool isComplete; @property NSString *assignee; @property int priority; @property int progressMinutes; @end RLM_COLLECTION_TYPE(Task) // Task.m @implementation Task @end // Project.h @interface Project : RLMObject @property NSString *name; @property RLMArray *tasks; @end // Project.m @implementation Project @end ``` #### Swift ```swift class Task: Object { @Persisted var name = "" @Persisted var isComplete = false @Persisted var assignee: String? @Persisted var priority = 0 @Persisted var progressMinutes = 0 @Persisted var labels: MutableSet @Persisted var ratings: MutableSet } class Project: Object { @Persisted var name = "" @Persisted var tasks: List } ``` You can set up the realm for these examples with the following code: #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^() { // Add projects and tasks here }]; RLMResults *tasks = [Task allObjectsInRealm:realm]; RLMResults *projects = [Project allObjectsInRealm:realm]; ``` #### Swift ```swift let realm = try! Realm() try! realm.write { // Add tasks and projects here. let project = Project() project.name = "New Project" let task = Task() task.assignee = "Alex" task.priority = 5 project.tasks.append(task) realm.add(project) // ... } let tasks = realm.objects(Task.self) let projects = realm.objects(Project.self) ``` ## Realm Swift Query API > Version added: 10.19.0 > For SDK versions older than 10.19.0, use the NSPredicate query API. > You can build a filter with Swift-style syntax using the `.where` `Realm Swift query API`: ```swift let realmSwiftQuery = projects.where { ($0.tasks.progressMinutes > 1) && ($0.tasks.assignee == "Ali") } ``` This query API constructs an NSPredicate to perform the query. It gives developers a type-safe idiomatic API to use directly, and abstracts away the NSPredicate construction. The `.where` API takes a callback that evaluates to true or false. The callback receives an instance of the type being queried, and you can leverage the compiler to statically check that you are creating valid queries that reference valid properties. In the examples on this page, we use the `$0` shorthand to reference the variable passed into the callback. ### Operators There are several types of operators available to query a Realm collection. Queries work by **evaluating** an operator expression for every object in the collection being queried. If the expression resolves to `true`, Realm Database includes the object in the results collection. #### Comparison Operators You can use Swift comparison operators with the Realm Swift Query API (`==`, `!=`, `>`, `>=`, `<`, `<=`). > Example: > The following example uses the query engine's comparison operators to: > > - Find high priority tasks by comparing the value of the `priority` property value with a threshold number, above which priority can be considered high. > - Find long-running tasks by seeing if the `progressMinutes` property is at or above a certain value. > - Find unassigned tasks by finding tasks where the `assignee` property is equal to `null`. > > ```swift > let highPriorityTasks = tasks.where { > $0.priority > 5 > } > print("High-priority tasks: \(highPriorityTasks.count)") > > let longRunningTasks = tasks.where { > $0.progressMinutes >= 120 > } > print("Long running tasks: \(longRunningTasks.count)") > > let unassignedTasks = tasks.where { > $0.assignee == nil > } > print("Unassigned tasks: \(unassignedTasks.count)") > > ``` > #### Collections You can query for values within a collection using the `.contains` operators. You can search for individual values by element, or search within a range. |Operator|Description| | --- | --- | |.in(_ collection:)|Evaluates to `true` if the property referenced by the expression contains an element in the given array.| |.contains(_ element:)|Equivalent to the `IN` operator. Evaluates to `true` if the property referenced by the expression contains the value.| |`.contains(_ range:)`|Equivalent to the `BETWEEN` operator. Evaluates to `true` if the property referenced by the expression contains a value that is within the range.| |`.containsAny(in: )`|Equivalent to the `IN` operator combined with the `ANY` operator. Evaluates to `true` if any elements contained in the given array are present in the collection.| > Example: > - Find tasks where the `labels` MutableSet collection property contains "quick win". > - Find tasks where the `progressMinutes` property is within a given range of minutes. > > ```swift > let quickWinTasks = tasks.where { > $0.labels.contains("quick win") > } > print("Tasks labeled 'quick win': \(quickWinTasks.count)") > > let progressBetween30and60 = tasks.where { > $0.progressMinutes.contains(30...60) > } > print("Tasks with progress between 30 and 60 minutes: \(progressBetween30and60.count)") > > ``` > > Find tasks where the `labels` MutableSet collection property contains any of the elements in the given array: "quick win" or "bug". > > ```swift > let quickWinOrBugTasks = tasks.where { > $0.labels.containsAny(in: ["quick win", "bug"]) > } > print("Tasks labeled 'quick win' or 'bug': \(quickWinOrBugTasks.count)") > > ``` > > Version added: 10.23.0 > :The `IN` operator > The Realm Swift Query API now supports the `IN` operator. Evaluates to `true` if the property referenced by the expression contains the value. > Example: > Find tasks assigned to specific teammates Ali or Jamie by seeing if the `assignee` property is in a list of names. > > ```swift > let taskAssigneeInAliOrJamie = tasks.where { > let assigneeNames = ["Ali", "Jamie"] > return $0.assignee.in(assigneeNames) > } > print("Tasks IN Ali or Jamie: \(taskAssigneeInAliOrJamie.count)") > > ``` > #### Logical Operators You can make compound queries using Swift logical operators (`&&`, `!`, `||`). > Example: > We can use the query language's logical operators to find all of Ali's completed tasks. That is, we find all tasks where the `assignee` property value is equal to 'Ali' AND the `isComplete` property value is `true`: > > ```swift > let aliComplete = tasks.where { > ($0.assignee == "Ali") && ($0.isComplete == true) > } > print("Ali's complete tasks: \(aliComplete.count)") > > ``` > #### String Operators You can compare string values using these string operators. Regex-like wildcards allow more flexibility in search. > Note: > You can use the following options with string operators: > > - `.caseInsensitive` for case insensitivity. `$0.name.contains("f", options: .caseInsensitive)` > - `.diacriticInsensitive` for diacritic insensitivity: Realm treats special characters as the base character (e.g. `é` -> `e`). `$0.name.contains("e", options: .diacriticInsensitive)` > |Operator|Description| | --- | --- | |.starts(with value: String)|Evaluates to `true` if the collection contains an element whose value begins with the specified string value.| |.contains(_ value: String)|Evaluates to `true` if the left-hand string expression is found anywhere in the right-hand string expression.| |.ends(with value: String)|Evaluates to `true` if the collection contains an element whose value ends with the specified string value.| |.like(_ value: String)|Evaluates to `true` if the left-hand string expression matches the right-hand string wildcard string expression. A wildcard string expression is a string that uses normal characters with two special wildcard characters: The `*` wildcard matches zero or more of any character The `?` wildcard matches any character. For example, the wildcard string "d?g" matches "dog", "dig", and "dug", but not "ding", "dg", or "a dog".| |==|Evaluates to `true` if the left-hand string is lexicographically equal to the right-hand string.| |!=|Evaluates to `true` if the left-hand string is not lexicographically equal to the right-hand string.| > Example: > The following example uses the query engine's string operators to find: > > - Projects with a name starting with the letter 'e' > - Projects with names that contain 'ie' > - Projects with an `assignee` property whose value is similar to `Al?x` > - Projects that contain e-like characters with diacritic insensitivity > > ```swift > // Use the .caseInsensitive option for case-insensitivity. > let startWithE = projects.where { > $0.name.starts(with: "e", options: .caseInsensitive) > } > print("Projects that start with 'e': \(startWithE.count)") > > let containIe = projects.where { > $0.name.contains("ie") > } > print("Projects that contain 'ie': \(containIe.count)") > > let likeWildcard = tasks.where { > $0.assignee.like("Al?x") > } > print("Tasks with assignees like Al?x: \(likeWildcard.count)") > > // Use the .diacriticInsensitive option for diacritic insensitivity: contains 'e', 'E', 'é', etc. > let containElike = projects.where { > $0.name.contains("e", options: .diacriticInsensitive) > } > print("Projects that contain 'e', 'E', 'é', etc.: \(containElike.count)") > > ``` > > Note: > String sorting and case-insensitive queries are only supported for character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', and 'Latin Extended B' (UTF-8 range 0-591). > #### Geospatial Operators > Version added: 10.47.0 Use the `geoWithin` operator to query geospatial data with one of the SDK's provided shapes: - `GeoCircle` - `GeoBox` - `GeoPolygon` This operator evaluates to `true` if: - An object has a geospatial data "shape" containing a `String` property with the value of Point and a `List` containing a longitude/latitude pair. - The longitude/latitude of the persisted object falls within the geospatial query shape. ```swift let companiesInSmallCircle = realm.objects(Geospatial_Company.self).where { $0.location.geoWithin(smallCircle!) } print("Number of companies in small circle: \(companiesInSmallCircle.count)") ``` For more information about querying geospatial data, refer to Query Geospatial Data. #### Aggregate Operators You can apply an aggregate operator to a collection property of a Realm object. Aggregate operators traverse a collection and reduce it to a single value. |Operator|Description| | --- | --- | |.avg|Evaluates to the average value of a given numerical property across a collection.| |.count|Evaluates to the number of objects in the given collection. This is currently only supported on to-many relationship collections and not on lists of primitives. In order to use `.count` on a list of primitives, consider wrapping the primitives in a Realm object.| |.max|Evaluates to the highest value of a given numerical property across a collection.| |.min|Evaluates to the lowest value of a given numerical property across a collection.| |.sum|Evaluates to the sum of a given numerical property across a collection.| > Example: > We create a couple of filters to show different facets of the data: > > - Projects with average tasks priority above 5. > - Projects that contain only low-priority tasks below 5. > - Projects where all tasks are high-priority above 5. > - Projects that contain more than 5 tasks. > - Long running projects. > > ```swift > let averageTaskPriorityAbove5 = projects.where { > $0.tasks.priority.avg > 5 > } > print("Projects with average task priority above 5: \(averageTaskPriorityAbove5.count)") > > let allTasksLowerPriority = projects.where { > $0.tasks.priority.max < 5 > } > print("Projects where all tasks are lower priority: \(allTasksLowerPriority.count)") > > let allTasksHighPriority = projects.where { > $0.tasks.priority.min > 5 > } > print("Projects where all tasks are high priority: \(allTasksHighPriority.count)") > > let moreThan5Tasks = projects.where { > $0.tasks.count > 5 > } > print("Projects with more than 5 tasks: \(moreThan5Tasks.count)") > > let longRunningProjects = projects.where { > $0.tasks.progressMinutes.sum > 100 > } > print("Long running projects: \(longRunningProjects.count)") > > ``` > #### Set Operators A **set operator** uses specific rules to determine whether to pass each input collection object to the output collection by applying a given query expression to every element of a given list property of the object. > Example: > Running the following queries in `projects` collections returns: > > - Projects where a set of string `labels` contains any of "quick win", "bug". > - Projects where any element in a set of integer `ratings` is greater than 3. > > ```swift > let projectsWithGivenLabels = projects.where { > $0.tasks.labels.containsAny(in: ["quick win", "bug"]) > } > print("Projects with quick wins: \(projectsWithGivenLabels.count)") > > let projectsWithRatingsOver3 = projects.where { > $0.tasks.ratings > 3 > } > print("Projects with any ratings over 3: \(projectsWithRatingsOver3.count)") > > ``` > ### Subqueries You can iterate through a collection property with another query using a subquery. To form a subquery, you must wrap the expression in parentheses and immediately follow it with the `.count` aggregator. ```swift ().count > n ``` If the expression does not produce a valid subquery, you'll get an exception at runtime. > Example: > Running the following query on a `projects` collection returns projects with tasks that have not been completed by a user named Alex. > > ```swift > let subquery = projects.where { > ($0.tasks.isComplete == false && $0.tasks.assignee == "Alex").count > 0 > } > print("Projects with incomplete tasks assigned to Alex: \(subquery.count)") > > ``` > ## NSPredicate Queries You can build a filter with NSPredicate: #### Objective-C ```objectivec NSPredicate *predicate = [NSPredicate predicateWithFormat:@"progressMinutes > %@ AND name == %@", @1, @"Ali"]; ``` #### Swift ```swift let predicate = NSPredicate(format: "progressMinutes > 1 AND name == %@", "Ali") ``` ### Expressions Filters consist of **expressions** in an NSPredicate. An expression consists of one of the following: - The name (keypath) of a property of the object currently being evaluated. - An operator and up to two argument expression(s). - A value, such as a string (`'hello'`) or a number (`5`). ### Dot Notation When referring to an object property, you can use **dot notation** to refer to child properties of that object. You can even refer to the properties of embedded objects and relationships with dot notation. For example, consider a query on an object with a `workplace` property that refers to a Workplace object. The Workplace object has an embedded object property, `address`. You can chain dot notations to refer to the zipcode property of that address: ```objective-c workplace.address.zipcode == 10012 ``` ### Substitutions You can use the following substitutions in your predicate format strings: - `%@` to specify values - `%K` to specify [keypaths](https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#grammar_key-path-expression) #### Objective-C ```objectivec [NSPredicate predicateWithFormat:@"%K > %@ AND %K == %@", @"progressMinutes", @1, @"name", @"Ali"]; ``` #### Swift ```swift NSPredicate(format: "%K > %@ AND %K == %@", "progressMinutes", NSNumber(1), "name", "Ali") ``` ### Operators There are several types of operators available to filter a Realm collection. Filters work by **evaluating** an operator expression for every object in the collection being filtered. If the expression resolves to `true`, Realm Database includes the object in the results collection. #### Comparison Operators The most straightforward operation in a search is to compare values. > Important: > The type on both sides of the operator must be equivalent. For example, comparing an ObjectId with string will result in a precondition failure with a message like: > > ``` > "Expected object of type object id for property 'id' on object of type > 'User', but received: 11223344556677889900aabb (Invalid value)" > ``` > > You can compare any numeric type with any other numeric type. > |Operator|Description| | --- | --- | |`between`|Evaluates to `true` if the left-hand numerical or date expression is between or equal to the right-hand range. For dates, this evaluates to `true` if the left-hand date is within the right-hand date range.| |== , =|Evaluates to `true` if the left-hand expression is equal to the right-hand expression.| |>|Evaluates to `true` if the left-hand numerical or date expression is greater than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than the right-hand date.| |>=|Evaluates to `true` if the left-hand numerical or date expression is greater than or equal to the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is later than or the same as the right-hand date.| |`in`|Evaluates to `true` if the left-hand expression is in the right-hand list or string.| |<|Evaluates to `true` if the left-hand numerical or date expression is less than the right-hand numerical or date expression. For dates, this evaluates to `true` if the left-hand date is earlier than the right-hand date.| |<=|Evaluates to `true` if the left-hand numeric expression is less than or equal to the right-hand numeric expression. For dates, this evaluates to `true` if the left-hand date is earlier than or the same as the right-hand date.| |!= , <>|Evaluates to `true` if the left-hand expression is not equal to the right-hand expression.| > Example: > The following example uses the query engine's comparison operators to: > > - Find high priority tasks by comparing the value of the `priority` property value with a threshold number, above which priority can be considered high. > - Find long-running tasks by seeing if the `progressMinutes` property is at or above a certain value. > - Find unassigned tasks by finding tasks where the `assignee` property is equal to `null`. > - Find tasks assigned to specific teammates Ali or Jamie by seeing if the `assignee` property is in a list of names. > > #### Objective-C > > ```objectivec > NSLog(@"High priority tasks: %lu", > [[tasks objectsWithPredicate:[NSPredicate predicateWithFormat:@"priority > %@", @5]] count]); > > NSLog(@"Short running tasks: %lu", > [[tasks objectsWhere:@"progressMinutes between {1, 15}"] count]); > > NSLog(@"Unassigned tasks: %lu", > [[tasks objectsWhere:@"assignee == nil"] count]); > > NSLog(@"Ali or Jamie's tasks: %lu", > [[tasks objectsWhere:@"assignee IN {'Ali', 'Jamie'}"] count]); > > NSLog(@"Tasks with progress between 30 and 60 minutes: %lu", > [[tasks objectsWhere:@"progressMinutes BETWEEN {30, 60}"] count]); > > > ``` > > > #### Swift > > ```swift > let highPriorityTasks = tasks.filter("priority > 5") > print("High priority tasks: \(highPriorityTasks.count)") > > let longRunningTasks = tasks.filter("progressMinutes > 120") > print("Long running tasks: \(longRunningTasks.count)") > > let unassignedTasks = tasks.filter("assignee == nil") > print("Unassigned tasks: \(unassignedTasks.count)") > > let aliOrJamiesTasks = tasks.filter("assignee IN {'Ali', 'Jamie'}") > print("Ali or Jamie's tasks: \(aliOrJamiesTasks.count)") > > let progressBetween30and60 = tasks.filter("progressMinutes BETWEEN {30, 60}") > print("Tasks with progress between 30 and 60 minutes: \(progressBetween30and60.count)") > > ``` > > #### Logical Operators You can make compound predicates using logical operators. |Operator|Description| | --- | --- | |and &&|Evaluates to `true` if both left-hand and right-hand expressions are `true`.| |not !|Negates the result of the given expression.| |or \\|\\||Evaluates to `true` if either expression returns `true`.| > Example: > We can use the query language's logical operators to find all of Ali's completed tasks. That is, we find all tasks where the `assignee` property value is equal to 'Ali' AND the `isComplete` property value is `true`: > > #### Objective-C > > ```objectivec > NSLog(@"Ali's complete tasks: %lu", > [[tasks objectsWhere:@"assignee == 'Ali' AND isComplete == true"] count]); > > ``` > > > #### Swift > > ```swift > let aliComplete = tasks.filter("assignee == 'Ali' AND isComplete == true") > print("Ali's complete tasks: \(aliComplete.count)") > > ``` > > #### String Operators You can compare string values using these string operators. Regex-like wildcards allow more flexibility in search. > Note: > You can use the following modifiers with the string operators: > > - `[c]` for case insensitivity. `[NSPredicate predicateWithFormat: @"name CONTAINS[c] 'f'"]``NSPredicate(format: "name CONTAINS[c] 'f'")` > - `[d]` for diacritic insensitivity: Realm treats special characters as the base character (e.g. `é` -> `e`). `[NSPredicate predicateWithFormat: @"name CONTAINS[d] 'e'"]``NSPredicate(format: "name CONTAINS[d] 'e'")` > |Operator|Description| | --- | --- | |beginsWith|Evaluates to `true` if the left-hand string expression begins with the right-hand string expression. This is similar to `contains`, but only matches if the right-hand string expression is found at the beginning of the left-hand string expression.| |contains , in|Evaluates to `true` if the left-hand string expression is found anywhere in the right-hand string expression.| |endsWith|Evaluates to `true` if the left-hand string expression ends with the right-hand string expression. This is similar to `contains`, but only matches if the left-hand string expression is found at the very end of the right-hand string expression.| |like|Evaluates to `true` if the left-hand string expression matches the right-hand string wildcard string expression. A wildcard string expression is a string that uses normal characters with two special wildcard characters: The `*` wildcard matches zero or more of any character The `?` wildcard matches any character. For example, the wildcard string "d?g" matches "dog", "dig", and "dug", but not "ding", "dg", or "a dog".| |== , =|Evaluates to `true` if the left-hand string is lexicographically equal to the right-hand string.| |!= , <>|Evaluates to `true` if the left-hand string is not lexicographically equal to the right-hand string.| > Example: > We use the query engine's string operators to find projects with a name starting with the letter 'e' and projects with names that contain 'ie': > > #### Objective-C > > ```objectivec > // Use [c] for case-insensitivity. > NSLog(@"Projects that start with 'e': %lu", > [[projects objectsWhere:@"name BEGINSWITH[c] 'e'"] count]); > > NSLog(@"Projects that contain 'ie': %lu", > [[projects objectsWhere:@"name CONTAINS 'ie'"] count]); > > ``` > > > #### Swift > > ```swift > // Use [c] for case-insensitivity. > let startWithE = projects.filter("name BEGINSWITH[c] 'e'") > print("Projects that start with 'e': \(startWithE.count)") > > let containIe = projects.filter("name CONTAINS 'ie'") > print("Projects that contain 'ie': \(containIe.count)") > > // [d] for diacritic insensitivty: contains 'e', 'E', 'é', etc. > let containElike = projects.filter("name CONTAINS[cd] 'e'") > print("Projects that contain 'e', 'E', 'é', etc.: \(containElike.count)") > > ``` > > > Note: > String sorting and case-insensitive queries are only supported for character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', and 'Latin Extended B' (UTF-8 range 0-591). > #### Geospatial Operators > Version added: 10.47.0 You can perform a geospatial query using the `IN` operator with one of the SDK's provided shapes: - `GeoCircle` - `GeoBox` - `GeoPolygon` This operator evaluates to `true` if: - An object has a geospatial data "shape" containing a `String` property with the value of Point and a `List` containing a longitude/latitude pair. - The longitude/latitude of the persisted object falls within the geospatial query shape. ```swift let filterArguments = NSMutableArray() filterArguments.add(largeBox) let companiesInLargeBox = realm.objects(Geospatial_Company.self) .filter(NSPredicate(format: "location IN %@", argumentArray: filterArguments as? [Any])) print("Number of companies in large box: \(companiesInLargeBox.count)") ``` For more information about querying geospatial data, refer to Query Geospatial Data. #### Aggregate Operators You can apply an aggregate operator to a collection property of a Realm object. Aggregate operators traverse a collection and reduce it to a single value. |Operator|Description| | --- | --- | |@avg|Evaluates to the average value of a given numerical property across a collection.| |@count|Evaluates to the number of objects in the given collection. This is currently only supported on to-many relationship collections and not on lists of primitives. In order to use `@count` on a list of primitives, consider wrapping the primitives in a Realm object.| |@max|Evaluates to the highest value of a given numerical property across a collection.| |@min|Evaluates to the lowest value of a given numerical property across a collection.| |@sum|Evaluates to the sum of a given numerical property across a collection.| > Example: > We create a couple of filters to show different facets of the data: > > - Projects with average tasks priority above 5. > - Long running projects. > > #### Objective-C > > ```objectivec > NSLog(@"Projects with average tasks priority above 5: %lu", > [[projects objectsWhere:@"tasks.@avg.priority > 5"] count]); > > NSLog(@"Projects where all tasks are lower priority: %lu", > [[projects objectsWhere:@"tasks.@max.priority < 5"] count]); > > NSLog(@"Projects where all tasks are high priority: %lu", > [[projects objectsWhere:@"tasks.@min.priority > 5"] count]); > > NSLog(@"Projects with more than 5 tasks: %lu", > [[projects objectsWhere:@"tasks.@count > 5"] count]); > > NSLog(@"Long running projects: %lu", > [[projects objectsWhere:@"tasks.@sum.progressMinutes > 100"] count]); > > ``` > > > #### Swift > > ```swift > let averageTaskPriorityAbove5 = projects.filter("tasks.@avg.priority > 5") > print("Projects with average task priority above 5: \(averageTaskPriorityAbove5.count)") > > let allTasksLowerPriority = projects.filter("tasks.@max.priority < 5") > print("Projects where all tasks are lower priority: \(allTasksLowerPriority.count)") > > let allTasksHighPriority = projects.filter("tasks.@min.priority > 5") > print("Projects where all tasks are high priority: \(allTasksHighPriority.count)") > > let moreThan5Tasks = projects.filter("tasks.@count > 5") > print("Projects with more than 5 tasks: \(moreThan5Tasks.count)") > > let longRunningProjects = projects.filter("tasks.@sum.progressMinutes > 100") > print("Long running projects: \(longRunningProjects.count)") > > ``` > > #### Set Operators A **set operator** uses specific rules to determine whether to pass each input collection object to the output collection by applying a given predicate to every element of a given list property of the object. |Operator|Description| | --- | --- | |`ALL`|Returns objects where the predicate evaluates to `true` for all objects in the collection.| |`ANY`, `SOME`|Returns objects where the predicate evaluates to `true` for any objects in the collection.| |`NONE`|Returns objects where the predicate evaluates to false for all objects in the collection.| > Example: > We use the query engine's set operators to find: > > - Projects with no complete tasks. > - Projects with any top priority tasks. > > #### Objective-C > > ```objectivec > NSLog(@"Projects with no complete tasks: %lu", > [[projects objectsWhere:@"NONE tasks.isComplete == true"] count]); > > NSLog(@"Projects with any top priority tasks: %lu", > [[projects objectsWhere:@"ANY tasks.priority == 10"] count]); > > ``` > > > #### Swift > > ```swift > let noCompleteTasks = projects.filter("NONE tasks.isComplete == true") > print("Projects with no complete tasks: \(noCompleteTasks.count)") > > let anyTopPriorityTasks = projects.filter("ANY tasks.priority == 10") > print("Projects with any top priority tasks: \(anyTopPriorityTasks.count)") > > ``` > > ### Subqueries You can iterate through a collection property with another query using the `SUBQUERY()` predicate function. `SUBQUERY()` has the following signature: ```objective-c SUBQUERY(, , ) ``` - `collection`: the name of the list property to iterate through - `variableName`: a variable name of the current element to use in the subquery - `predicate`: a string that contains the subquery predicate. You can use the variable name specified by `variableName` to refer to the currently iterated element. > Example: > Running the following filter on a `projects` collection returns projects with tasks that have not been completed by a user named Alex. > > #### Objective-C > > ```objectivec > NSPredicate *predicate = [NSPredicate predicateWithFormat: > @"SUBQUERY(tasks, $task, $task.isComplete == %@ AND $task.assignee == %@).@count > 0", > @NO, > @"Alex"]; > NSLog(@"Projects with incomplete tasks assigned to Alex: %lu", > [[projects objectsWithPredicate:predicate] count]); > > ``` > > > #### Swift > > ```swift > let predicate = NSPredicate( > format: "SUBQUERY(tasks, $task, $task.isComplete == false AND $task.assignee == %@).@count > 0", "Alex") > print("Projects with incomplete tasks assigned to Alex: \(projects.filter(predicate).count)") > > ``` > > ================================================ FILE: docs/guides/crud/react-to-changes.md ================================================ # React to Changes - Swift SDK All Realm objects are **live objects**, which means they automatically update whenever they're modified. Realm emits a notification event whenever any property changes. You can register a notification handler to listen for these notification events, and update your UI with the latest data. This page shows how to manually register notification listeners in Swift. Realm SDK for Swift offers SwiftUI property wrappers to make it easy to automatically update the UI when data changes. For more about how to use the SwiftUI property wrappers to react to changes, refer to Observe an Object. ## Register a Realm Change Listener You can register a notification handler on an entire realm. Realm calls the notification handler whenever any write transaction involving that Realm is committed. The handler receives no information about the change. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Observe realm notifications. Keep a strong reference to the notification token // or the observation will stop. RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) { // `notification` is an enum specifying what kind of notification was emitted. // ... update UI ... }]; // ... // Later, explicitly stop observing. [token invalidate]; ``` #### Swift ```swift let realm = try! Realm() // Observe realm notifications. Keep a strong reference to the notification token // or the observation will stop. let token = realm.observe { notification, realm in // `notification` is an enum specifying what kind of notification was emitted viewController.updateUI() } // ... // Later, explicitly stop observing. token.invalidate() ``` ## Register a Collection Change Listener You can register a notification handler on a collection within a realm. Realm notifies your handler: - After first retrieving the collection. - Whenever a write transaction adds, changes, or removes objects in the collection. Notifications describe the changes since the prior notification with three lists of indices: the indices of the objects that were deleted, inserted, and modified. > Important: > In collection notification handlers, always apply changes in the following order: deletions, insertions, then modifications. Handling insertions before deletions may result in unexpected behavior. > Collection notifications provide a `change` parameter that reports which objects are deleted, added, or modified during the write transaction. This `RealmCollectionChange` resolves to an array of index paths that you can pass to a `UITableView`'s batch update methods. > Important: > This example of a collection change listener does not support high-frequency updates. Under an intense workload, this collection change listener may cause the app to throw an exception. > #### Objective-C ```objectivec @interface CollectionNotificationExampleViewController : UITableViewController @end @implementation CollectionNotificationExampleViewController { RLMNotificationToken *_notificationToken; } - (void)viewDidLoad { [super viewDidLoad]; // Observe RLMResults Notifications __weak typeof(self) weakSelf = self; _notificationToken = [[Dog objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *error) { if (error) { NSLog(@"Failed to open realm on background worker: %@", error); return; } UITableView *tableView = weakSelf.tableView; // Initial run of the query will pass nil for the change information if (!changes) { [tableView reloadData]; return; } // Query results have changed, so apply them to the UITableView [tableView performBatchUpdates:^{ // Always apply updates in the following order: deletions, insertions, then modifications. // Handling insertions before deletions may result in unexpected behavior. [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; } completion:^(BOOL finished) { // ... }]; }]; } @end ``` #### Swift ```swift class CollectionNotificationExampleViewController: UITableViewController { var notificationToken: NotificationToken? override func viewDidLoad() { super.viewDidLoad() let realm = try! Realm() let results = realm.objects(Dog.self) // Observe collection notifications. Keep a strong // reference to the notification token or the // observation will stop. notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in guard let tableView = self?.tableView else { return } switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI tableView.reloadData() case .update(_, let deletions, let insertions, let modifications): // Query results have changed, so apply them to the UITableView tableView.performBatchUpdates({ // Always apply updates in the following order: deletions, insertions, then modifications. // Handling insertions before deletions may result in unexpected behavior. tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .automatic) tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .automatic) tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .automatic) }, completion: { finished in // ... }) case .error(let error): // An error occurred while opening the Realm file on the background worker thread fatalError("\(error)") } } } } ``` ## Register an Object Change Listener You can register a notification handler on a specific object within a realm. Realm notifies your handler: - When the object is deleted. - When any of the object's properties change. The handler receives information about what fields changed and whether the object was deleted. #### Objective-C ```objectivec @interface Dog : RLMObject @property NSString *name; @property int age; @end @implementation Dog @end RLMNotificationToken *objectNotificationToken = nil; void objectNotificationExample() { Dog *dog = [[Dog alloc] init]; dog.name = @"Max"; dog.age = 3; // Open the default realm RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:dog]; }]; // Observe object notifications. Keep a strong reference to the notification token // or the observation will stop. Invalidate the token when done observing. objectNotificationToken = [dog addNotificationBlock:^(BOOL deleted, NSArray * _Nullable changes, NSError * _Nullable error) { if (error != nil) { NSLog(@"An error occurred: %@", [error localizedDescription]); return; } if (deleted) { NSLog(@"The object was deleted."); return; } NSLog(@"Property %@ changed to '%@'", changes[0].name, changes[0].value); }]; // Now update to trigger the notification [realm transactionWithBlock:^{ dog.name = @"Wolfie"; }]; } ``` #### Swift ```swift // Define the dog class. class Dog: Object { @Persisted var name = "" } var objectNotificationToken: NotificationToken? func objectNotificationExample() { let dog = Dog() dog.name = "Max" // Open the default realm. let realm = try! Realm() try! realm.write { realm.add(dog) } // Observe object notifications. Keep a strong reference to the notification token // or the observation will stop. Invalidate the token when done observing. objectNotificationToken = dog.observe { change in switch change { case .change(let object, let properties): for property in properties { print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'") } case .error(let error): print("An error occurred: \(error)") case .deleted: print("The object was deleted.") } } // Now update to trigger the notification try! realm.write { dog.name = "Wolfie" } } ``` ## Register a Key Path Change Listener > Version added: 10.12.0 In addition to registering a notification handler on an `object` or `collection`, you can pass an optional string `keyPaths` parameter to specify the key path or key paths to watch. > Example: > ```swift > // Define the dog class. > class Dog: Object { > @Persisted var name = "" > @Persisted var favoriteToy = "" > @Persisted var age: Int? > } > > var objectNotificationToken: NotificationToken? > > func objectNotificationExample() { > let dog = Dog() > dog.name = "Max" > dog.favoriteToy = "Ball" > dog.age = 2 > > // Open the default realm. > let realm = try! Realm() > try! realm.write { > realm.add(dog) > } > // Observe notifications on some of the object's key paths. Keep a strong > // reference to the notification token or the observation will stop. > // Invalidate the token when done observing. > objectNotificationToken = dog.observe(keyPaths: ["favoriteToy", "age"], { change in > switch change { > case .change(let object, let properties): > for property in properties { > print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'") > } > case .error(let error): > print("An error occurred: \(error)") > case .deleted: > print("The object was deleted.") > } > }) > > // Now update to trigger the notification > try! realm.write { > dog.favoriteToy = "Frisbee" > } > // When you specify one or more key paths, changes to other properties > // do not trigger notifications. In this example, changing the "name" > // property does not trigger a notification. > try! realm.write { > dog.name = "Maxamillion" > } > } > ``` > > Version added: 10.14.0 You can `observe` a partially type-erased [PartialKeyPath](https://developer.apple.com/documentation/swift/partialkeypath) on `Objects` or `RealmCollections`. ```swift objectNotificationToken = dog.observe(keyPaths: [\Dog.favoriteToy, \Dog.age], { change in ``` When you specify `keyPaths`, *only* changes to those `keyPaths` trigger notification blocks. Any other changes do not trigger notification blocks. > Example: > Consider a `Dog` object where one of its properties is a list of `siblings`: > > ```swift > class Dog: Object { > @Persisted var name = "" > @Persisted var siblings: List > @Persisted var age: Int? > } > ``` > > If you pass `siblings` as a `keyPath` to observe, any insertion, deletion, or modification to the `siblings` list would trigger a notification. However, a change to `someSibling.name` would not trigger a notification, unless you explicitly observed `["siblings.name"]`. > > Note: > Multiple notification tokens on the same object which filter for separate key paths *do not* filter exclusively. If one key path change is satisfied for one notification token, then all notification token blocks for that object will execute. > ### Realm Collections When you observe key paths on the various collection types, expect these behaviors: - `LinkingObjects`: Observing a property of the LinkingObject triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the list or the object that the list is on trigger a notification. - `Lists`: Observing a property of the list's object will triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the list or the object that the list is on trigger a notification. - `Map`: Observing a property of the map's object triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the Map or the object that the map is on trigger a notification. The `change` parameter reports, in the form of keys within the map, which key-value pairs are added, removed, or modified during each write transaction. - `MutableSet`: Observing a property of a MutableSet's object triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the MutableSet or the object that the MutableSet is on trigger a notification. - `Results`: Observing a property of the Result triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the Result trigger a notification. ## Write Silently You can write to a realm *without* sending a notification to a specific observer by passing the observer's notification token in an array to `realm.write(withoutNotifying:_)`: #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Observe realm notifications RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) { // ... handle update }]; // Later, pass the token in an array to the realm's `-transactionWithoutNotifying:block:` method. // Realm will _not_ notify the handler after this write. [realm transactionWithoutNotifying:@[token] block:^{ // ... write to realm }]; // Finally [token invalidate]; ``` #### Swift ```swift let realm = try! Realm() // Observe realm notifications let token = realm.observe { notification, realm in // ... handle update } // Later, pass the token in an array to the realm.write(withoutNotifying:) // method to write without sending a notification to that observer. try! realm.write(withoutNotifying: [token]) { // ... write to realm } // Finally token.invalidate() ``` ## Stop Watching for Changes Observation stops when the token returned by an `observe` call becomes invalid. You can explicitly invalidate a token by calling its `invalidate()` method. > Important: > Notifications stop if the token is in a local variable that goes out of scope. > #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Observe and obtain token RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) { /* ... */ }]; // Stop observing [token invalidate]; ``` #### Swift ```swift let realm = try! Realm() // Observe and obtain token let token = realm.observe { notification, realm in /* ... */ } // Stop observing token.invalidate() ``` ## Key-value Observation ### Key-value Observation Compliance Realm objects are [key-value observing (KVO) compliant](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift) for most properties: - Almost all managed (non-ignored) properties on `Object` subclasses - The `invalidated` property on `Object` and `List` You cannot observe `LinkingObjects` properties via Key-value observation. > Important: > You cannot add an object to a realm (with `realm.add(obj)` or similar methods) while it has any registered observers. > ### Managed vs. Unmanaged KVO Considerations Observing the properties of unmanaged instances of `Object` subclasses works like any other dynamic property. Observing the properties of managed objects works differently. With realm-managed objects, the value of a property may change when: - You assign to it - The realm is refreshed, either manually with `realm.refresh()` or automatically on a runloop thread - You begin a write transaction after changes on another thread Realm applies changes made in the write transaction(s) on other threads at once. Observers see Key-value observation notifications at once. Intermediate steps do not trigger KVO notifications. > Example: > Say your app performs a write transaction that increments a property from 1 to 10. On the main thread, you get a single notification of a change directly from 1 to 10. You won't get notifications for every incremental change between 1 and 10. > Avoid modifying managed Realm objects from within `observeValueForKeyPath(_:ofObject:change:context:)`. Property values can change when not in a write transaction, or as part of beginning a write transaction. ### Observing Realm Lists Observing changes made to Realm `List` properties is simpler than `NSMutableArray` properties: - You don't have to mark `List` properties as dynamic to observe them. - You can call modification methods on `List` directly. Anyone observing the property that stores it gets a notification. You don't need to use `mutableArrayValueForKey(_:)`, although realm does support this for code compatibility. > Seealso: > Examples of using Realm with [ReactiveCocoa from Objective-C](https://github.com/realm/realm-swift/tree/master/examples/ios/objc/TableView), and [ReactKit from Swift](https://github.com/realm/realm-swift/tree/v2.3.0/examples/ios/swift-2.2/ReactKit). > ## React to Changes on a Different Actor You can observe notifications on a different actor. Calling `await object.observe(on: Actor)` or `await collection.observe(on: Actor)` registers a block to be called each time the object or collection changes. ```swift // Create a simple actor actor BackgroundActor { public func deleteTodo(tsrToTodo tsr: ThreadSafeReference) throws { let realm = try! Realm() try realm.write { // Resolve the thread safe reference on the Actor where you want to use it. // Then, do something with the object. let todoOnActor = realm.resolve(tsr) realm.delete(todoOnActor!) } } } // Execute some code on a different actor - in this case, the MainActor @MainActor func mainThreadFunction() async throws { let backgroundActor = BackgroundActor() let realm = try! await Realm() // Create a todo item so there is something to observe try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": "Arrive safely in Bree", "owner": "Merry", "status": "In Progress" ]) } // Get the collection of todos on the current actor let todoCollection = realm.objects(Todo.self) // Register a notification token, providing the actor where you want to observe changes. // This is only required if you want to observe on a different actor. let token = await todoCollection.observe(on: backgroundActor, { actor, changes in print("A change occurred on actor: \(actor)") switch changes { case .initial: print("The initial value of the changed object was: \(changes)") case .update(_, let deletions, let insertions, let modifications): if !deletions.isEmpty { print("An object was deleted: \(changes)") } else if !insertions.isEmpty { print("An object was inserted: \(changes)") } else if !modifications.isEmpty { print("An object was modified: \(changes)") } case .error(let error): print("An error occurred: \(error.localizedDescription)") } }) // Update an object to trigger the notification. // This example triggers a notification that the object is deleted. // We can pass a thread-safe reference to an object to update it on a different actor. let todo = todoCollection.where { $0.name == "Arrive safely in Bree" }.first! let threadSafeReferenceToTodo = ThreadSafeReference(to: todo) try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo) // Invalidate the token when done observing token.invalidate() } ``` For more information about change notifications on another actor, refer to Observe Notifications on a Different Actor. ## React to Changes to a Class Projection Like other realm objects, you can react to changes to a class projection. When you register a class projection change listener, you see notifications for changes made through the class projection object directly. You also see notifications for changes to the underlying object's properties that project through the class projection object. Properties on the underlying object that are not `@Projected` in the class projection do not generate notifications. This notification block fires for changes in: - `Person.firstName` property of the class projection's underlying `Person` object, but not changes to `Person.lastName` or `Person.friends`. - `PersonProjection.firstName` property, but not another class projection that uses the same underlying object's property. ```swift let realm = try! Realm() let projectedPerson = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })! let token = projectedPerson.observe(keyPaths: ["firstName"], { change in switch change { case .change(let object, let properties): for property in properties { print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'") } case .error(let error): print("An error occurred: \(error)") case .deleted: print("The object was deleted.") } }) // Now update to trigger the notification try! realm.write { projectedPerson.firstName = "David" } ``` ## Notification Delivery Notification delivery can vary depending on: - Whether or not the notification occurs within a write transaction - The relative threads of the write and the observation When your application relies on the timing of notification delivery, such as when you use notifications to update a `UITableView`, it's important to understand the specific behaviors for your application code's context. ### Perform Writes Only on a Different Thread than the Observing Thread Reading an observed collection or object from inside a change notification always accurately tells you what has changed in the collection passed to the callback since the last time the callback was invoked. Reading collections or objects outside of change notifications always gives you the exact same values you saw in the most recent change notification for that object. Reading objects other than the observed one *inside* a change notification may see a different value prior to the notification for that change being delivered. Realm `refresh` brings the entire realm from 'old version' to 'latest version' in one operation. However, there might have been multiple change notifications fired between 'old version' and 'latest version'. Inside a callback, you may see changes that have pending notifications. Writes on different threads eventually become visible on the observing thread. Explicitly calling `refresh()` blocks until the writes made on other threads are visible and the appropriate notifications have been sent. If you call `refresh()` within a notification callback, it's a no-op. ### Perform Writes on the Observing Thread, Outside of Notifications At the start of the write transaction all behaviors above apply to this context. Additionally, you can expect to always see the latest version of the data. Inside a write transaction, the only changes you see are those you've made so far within the write transaction. Between committing a write transaction and the next set of change notifications being sent, you can see the changes you made in the write transaction, but no other changes. Writes made on different threads do not become visible until you receive the next set of notifications. Performing another write on the same thread sends notifications for the previous write first. ### Perform Writes Inside of Notifications When you perform writes within notifications, you see many of the same behaviors above, with a few exceptions. Callbacks invoked before the one that performed a write behave normally. While Realm invokes change callbacks in a stable order, this is not strictly the order in which you added the observations. If beginning the write refreshes the realm, which can happen if another thread is making writes, this triggers recursive notifications. These nested notifications report the changes made since the last call to the callback. For callbacks before the one making the write, this means the inner notification reports only the changes made after the ones already reported in the outer notification. If the callback making the write tries to write again in the inner notification, Realm throws an exception. The callbacks after the one making the write get a single notification for both sets of changes. After the callback completes the write and returns, Realm does not invoke any of the subsequent callbacks as they no longer have any changes to report. Realm provides a notification later for the write as if the write had happened outside of a notification. If beginning the write doesn't refresh the realm, the write happens as usual. However, Realm invokes the subsequent callbacks in an inconsistent state. They continue to report the original change information, but the observed object/collection now includes the changes from the write made in the previous callback. If you try to perform manual checks and write handling to get more fine-grained notifications from within a write transaction, you can get notifications nested more than two levels deep. An example of a manual write handling is checking `realm.isInWriteTransaction`, and if so making changes, calling `realm.commitWrite()` and then `realm.beginWrite()`. The nested notifications and potential for error make this manual manipulation error-prone and difficult to debug. You can use the writeAsync API to sidestep complexity if you don't need fine-grained change information from inside your write block. Observing an async write similar to this provides notifications even if the notification happens to be delivered inside a write transaction: ```swift let token = dog.observe(keyPaths: [\Dog.age]) { change in guard case let .change(dog, _) = change else { return } dog.realm!.writeAsync { dog.isPuppy = dog.age < 2 } } ``` However, because the write is async the realm may have changed between the notification and when the write happens. In this case, the change information passed to the notification may no longer be applicable. ### Updating a UITableView Based on Notifications If you only update a `UITableView` via notifications, in the time between a write transaction and the next notification arriving, the TableView's state is out of sync with the data. The TableView could have a pending update scheduled, which can appear to cause delayed or inconsistent updates. You can address these behaviors in a few ways. The following examples use this very basic `UITableViewController`. ```swift class TableViewController: UITableViewController { let realm = try! Realm() let results = try! Realm().objects(DemoObject.self).sorted(byKeyPath: "date") var notificationToken: NotificationToken! override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") notificationToken = results.observe { (changes: RealmCollectionChange) in switch changes { case .initial: self.tableView.reloadData() case .update(_, let deletions, let insertions, let modifications): // Always apply updates in the following order: deletions, insertions, then modifications. // Handling insertions before deletions may result in unexpected behavior. self.tableView.beginUpdates() self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.endUpdates() case .error(let err): fatalError("\(err)") } } } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return results.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let object = results[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = object.title return cell } func delete(at index: Int) throws { try realm.write { realm.delete(results[index]) } } } ``` #### Update the UITableView Directly Without a Notification Updating the `UITableView` directly without waiting for a notification provides the most responsive UI. This code updates the TableView immediately instead of requiring hops between threads, which add a small amount of lag to each update. The downside is that it requires frequent manual updates to the view. ```swift func delete(at index: Int) throws { try realm.write(withoutNotifying: [notificationToken]) { realm.delete(results[index]) } tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic) } ``` #### Force a Refresh After a Write Forcing a `refresh()` after a write provides the notifications from the write immediately rather than on a future run of the run loop. There's no window for the TableView to read out-of-sync values. The downside is that this means things we recommend doing in the background, such as writing, rerunning the query and re-sorting the results, happen on the main thread. When these operations are computationally expensive, this can cause delays on the main thread. ```swift func delete(at index: Int) throws { try realm.write { realm.delete(results[index]) } realm.refresh() } ``` #### Perform the Write on a Background Thread Performing a write on a background thread blocks the main thread for the least amount of time. However, the code to perform a write on the background requires more familiarity with Realm's threading model and Swift DispatchQueue usage. Since the write doesn't happen on the main thread, the main thread never sees the write before the notifications arrive. ```swift func delete(at index: Int) throws { func delete(at index: Int) throws { @ThreadSafe var object = results[index] DispatchQueue.global().async { guard let object = object else { return } let realm = object.realm! try! realm.write { if !object.isInvalidated { realm.delete(object) } } } } } ``` ## Change Notification Limits Changes in nested documents deeper than four levels down do not trigger change notifications. If you have a data structure where you need to listen for changes five levels down or deeper, workarounds include: - Refactor the schema to reduce nesting. - Add something like "push-to-refresh" to enable users to manually refresh data. In the Swift SDK, you can also use key path filtering to work around this limitation. This feature is not available in the other SDKs. ================================================ FILE: docs/guides/crud/read.md ================================================ # CRUD - Read - Swift SDK ## Read from a Realm A read from a realm generally consists of the following steps: - Get all objects of a certain type from the realm. - Optionally, filter the results. - Optionally, sort the results. - Alternately, get all objects of a certain type, divided into sections. As with regular results, you can filter and sort sectioned results. Query, filter, and sort operations return either a results or SectionedResults collection. These collections are live, meaning they always contain the latest results of the associated query. ### Read Characteristics When you design your app's data access patterns around the following three key characteristics of reads in Realm, you can be confident you are reading data as efficiently as possible. #### Results Are Not Copies Results to a query are not copies of your data: modifying the results of a query will modify the data on disk directly. This memory mapping also means that results are **live**: that is, they always reflect the current state on disk. See also: Collections are Live. #### Results Are Lazy Realm only runs a query when you actually request the results of that query. This lazy evaluation enables you to write elegant, highly performant code for handling large data sets and complex queries. You can chain several filter and sort operations without requiring extra work to process the intermediate state. #### References Are Retained One benefit of Realm's object model is that Realm automatically retains all of an object's relationships as direct references, so you can traverse your graph of relationships directly through the results of a query. A **direct reference**, or pointer, allows you to access a related object's properties directly through the reference. Other databases typically copy objects from database storage into application memory when you need to work with them directly. Because application objects contain direct references, you are left with a choice: copy the object referred to by each direct reference out of the database in case it's needed, or just copy the foreign key for each object and query for the object with that key if it's accessed. If you choose to copy referenced objects into application memory, you can use up a lot of resources for objects that are never accessed, but if you choose to only copy the foreign key, referenced object lookups can cause your application to slow down. Realm bypasses all of this using zero-copy live objects. Realm object accessors point directly into database storage using memory mapping, so there is no distinction between the objects in Realm and the results of your query in application memory. Because of this, you can traverse direct references across an entire realm from any query result. ### Limiting Query Results As a result of lazy evaluation, you do not need any special mechanism to limit query results with Realm. For example, if your query matches thousands of objects, but you only want to load the first ten, simply access only the first ten elements of the results collection. ### Pagination Thanks to lazy evaluation, the common task of pagination becomes quite simple. For example, suppose you have a results collection associated with a query that matches thousands of objects in your realm. You display one hundred objects per page. To advance to any page, simply access the elements of the results collection starting at the index that corresponds to the target page. ## Read Realm Objects ### About The Examples On This Page The examples on this page use the following models: #### Objective-C ```objectivec // DogToy.h @interface DogToy : RLMObject @property NSString *name; @end // Dog.h @interface Dog : RLMObject @property NSString *name; @property int age; @property NSString *color; // To-one relationship @property DogToy *favoriteToy; @end // Enable Dog for use in RLMArray RLM_COLLECTION_TYPE(Dog) // Person.h // A person has a primary key ID, a collection of dogs, and can be a member of multiple clubs. @interface Person : RLMObject @property int _id; @property NSString *name; // To-many relationship - a person can have many dogs @property RLMArray *dogs; // Inverse relationship - a person can be a member of many clubs @property (readonly) RLMLinkingObjects *clubs; @end RLM_COLLECTION_TYPE(Person) // DogClub.h @interface DogClub : RLMObject @property NSString *name; @property RLMArray *members; @end // Dog.m @implementation Dog @end // DogToy.m @implementation DogToy @end // Person.m @implementation Person // Define the primary key for the class + (NSString *)primaryKey { return @"_id"; } // Define the inverse relationship to dog clubs + (NSDictionary *)linkingObjectsProperties { return @{ @"clubs": [RLMPropertyDescriptor descriptorWithClass:DogClub.class propertyName:@"members"], }; } @end // DogClub.m @implementation DogClub @end ``` #### Swift ```swift class DogToy: Object { @Persisted var id: ObjectId @Persisted var name = "" } class Dog: Object { @Persisted var name = "" @Persisted var age = 0 @Persisted var color = "" @Persisted var currentCity = "" @Persisted var citiesVisited: MutableSet @Persisted var companion: AnyRealmValue // To-one relationship @Persisted var favoriteToy: DogToy? // Map of city name -> favorite park in that city @Persisted var favoriteParksByCity: Map // Computed variable that is not persisted, but only // used to section query results. var firstLetter: String { return name.first.map(String.init(_:)) ?? "" } } class Person: Object { @Persisted(primaryKey: true) var id = 0 @Persisted var name = "" // To-many relationship - a person can have many dogs @Persisted var dogs: List // Inverse relationship - a person can be a member of many clubs @Persisted(originProperty: "members") var clubs: LinkingObjects // Embed a single object. // Embedded object properties must be marked optional. @Persisted var address: Address? convenience init(name: String, address: Address) { self.init() self.name = name self.address = address } } class DogClub: Object { @Persisted var name = "" @Persisted var members: List // DogClub has an array of regional office addresses. // These are embedded objects. @Persisted var regionalOfficeAddresses: List
convenience init(name: String, addresses: [Address]) { self.init() self.name = name self.regionalOfficeAddresses.append(objectsIn: addresses) } } class Address: EmbeddedObject { @Persisted var street: String? @Persisted var city: String? @Persisted var country: String? @Persisted var postalCode: String? } ``` ### Find a Specific Object by Primary Key #### Objective-C If you know the primary key for a given object, you can look it up directly with `+[RLMObject objectForPrimaryKey:]`. ```objectivec // Get a specific person from the default realm Person *specificPerson = [Person objectForPrimaryKey:@12345]; ``` #### Swift If you know the primary key for a given object, you can look it up directly with `Realm.object(ofType:forPrimaryKey:)`. ```swift let realm = try! Realm() let specificPerson = realm.object(ofType: Person.self, forPrimaryKey: 12345) ``` ### Query All Objects of a Given Type #### Objective-C To query for objects of a given type in a realm, pass the realm instance to `+[YourRealmObjectClass allObjectsInRealm:]`. Replace `YourRealmObjectClass` with your Realm object class name. This returns an `RLMResults` object representing all objects of the given type in the realm. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *dogs = [Dog allObjectsInRealm:realm]; RLMResults *people = [Person allObjectsInRealm:realm]; ``` #### Swift To query for objects of a given type in a realm, pass the metatype instance `YourClassName.self` to `Realm.objects(_)`. This returns a `Results` object representing all objects of the given type in the realm. ```swift let realm = try! Realm() // Access all dogs in the realm let dogs = realm.objects(Dog.self) ``` ### Filter Queries Based on Object Properties A filter selects a subset of results based on the value(s) of one or more object properties. Realm provides a full-featured query engine that you can use to define filters. #### Swift > Version added: 10.19.0 To use the Realm Swift Query API, call `.where` with a closure that contains a query expression as an argument. ```swift let realm = try! Realm() // Access all dogs in the realm let dogs = realm.objects(Dog.self) // Query by age let puppies = dogs.where { $0.age < 2 } // Query by person let dogsWithoutFavoriteToy = dogs.where { $0.favoriteToy == nil } // Query by person's name let dogsWhoLikeTennisBalls = dogs.where { $0.favoriteToy.name == "Tennis ball" } ``` #### Swift Nspredicate To filter, call `Results.filter(_:)` with a query predicate. ```swift let realm = try! Realm() // Access all dogs in the realm let dogs = realm.objects(Dog.self) // Filter by age let puppies = dogs.filter("age < 2") // Filter by person let dogsWithoutFavoriteToy = dogs.filter("favoriteToy == nil") // Filter by person's name let dogsWhoLikeTennisBalls = dogs.filter("favoriteToy.name == 'Tennis ball'") ``` #### Objective-C To filter, call `-[RLMResults objectsWhere:]` with a query predicate. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Access all dogs in the realm RLMResults *dogs = [Dog allObjectsInRealm:realm]; // Filter by age RLMResults *puppies = [dogs objectsWhere:@"age < 2"]; // Filter by favorite toy RLMResults *dogsWithoutFavoriteToy = [dogs objectsWhere:@"favoriteToy == nil"]; // Filter by favorite toy's name RLMResults *dogsWhoLikeTennisBalls = [dogs objectsWhere:@"favoriteToy.name == %@", @"Tennis ball"]; ``` > Tip: > To filter a query based on a property of an embedded object or a related object, use dot-notation as if it were in a regular, nested object. > ### Filter on Object ID Properties The types in your predicate must match the types of the properties. Avoid comparing `ObjectId` properties to strings, as Realm does not automatically convert strings to ObjectIds. #### Swift > Version added: 10.19.0 The Realm Swift Query API's built-in type safety simplifies writing a query with an ObjectId: ```swift let realm = try! Realm() let dogToys = realm.objects(DogToy.self) // Get specific user by ObjectId id let specificToy = dogToys.where { $0.id == ObjectId("11223344556677889900aabb") } ``` #### Swift Nspredicate The following example shows the correct and incorrect way to write a query with an ObjectId given the following Realm object: ```swift let realm = try! Realm() let dogToys = realm.objects(DogToy.self) // Get specific toy by ObjectId id let specificToy = dogToys.filter("id = %@", ObjectId("11223344556677889900aabb")).first // WRONG: Realm will not convert the string to an object id // users.filter("id = '11223344556677889900aabb'") // not ok // users.filter("id = %@", "11223344556677889900aabb") // not ok ``` ### Query a Relationship You can query through a relationship the same way you would access a member of a regular Swift or Objective-C object. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Establish a relationship Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; dog.age = 10; Person *person = [[Person alloc] init]; person._id = 12345; [person.dogs addObject:dog]; [realm transactionWithBlock:^() { [realm addObject:person]; }]; // Later, query the specific person Person *specificPerson = [Person objectForPrimaryKey:@12345]; // Access directly through a relationship NSLog(@"# dogs: %lu", [specificPerson.dogs count]); NSLog(@"First dog's name: %@", specificPerson.dogs[0].name); ``` #### Swift ```swift let realm = try! Realm() // Establish a relationship let dog = Dog() dog.name = "Rex" dog.age = 10 let person = Person() person.id = 12345 person.dogs.append(dog) try! realm.write { realm.add(person) } // Later, query the specific person let specificPerson = realm.object(ofType: Person.self, forPrimaryKey: 12345) // Access directly through a relationship let specificPersonDogs = specificPerson!.dogs let firstDog = specificPersonDogs[0] print("# dogs: \(specificPersonDogs.count)") print("First dog's name: \(firstDog.name)") ``` ### Query an Inverse Relationship You can query through an inverse relationship the same way you would access a member of a regular Swift or Objective-C object. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Establish a relationship Person *person = [[Person alloc] init]; person._id = 12345; DogClub *club = [[DogClub alloc] init]; club.name = @"Pooch Pals"; [club.members addObject:person]; [realm transactionWithBlock:^() { [realm addObject:club]; }]; // Later, query the specific person Person *specificPerson = [Person objectForPrimaryKey:@12345]; // Access directly through an inverse relationship NSLog(@"# memberships: %lu", [specificPerson.clubs count]); NSLog(@"First club's name: %@", [specificPerson.clubs[0] name]); ``` #### Swift ```swift let realm = try! Realm() // Establish an inverse relationship let person = Person() person.id = 12345 let club = DogClub() club.name = "Pooch Pals" club.members.append(person) try! realm.write { realm.add(club) } // Later, query the specific person let specificPerson = realm.object(ofType: Person.self, forPrimaryKey: 12345) // Access directly through an inverse relationship let clubs = specificPerson!.clubs let firstClub = clubs[0] print("# memberships: \(clubs.count)") print("First club's name: \(firstClub.name)") ``` ### Query a Collection on Embedded Object Properties Use dot notation to filter or sort a collection of objects based on an embedded object property value: > Note: > It is not possible to query embedded objects directly. Instead, access embedded objects through a query for the parent object type. > #### Swift > Version added: 10.19.0 ```swift // Open the default realm let realm = try! Realm() // Get all contacts in Los Angeles, sorted by street address let losAngelesPeople = realm.objects(Person.self) .where { $0.address.city == "Los Angeles" } .sorted(byKeyPath: "address.street") print("Los Angeles Person: \(losAngelesPeople)") ``` #### Swift Nspredicate ```swift // Open the default realm let realm = try! Realm() // Get all people in Los Angeles, sorted by street address let losAngelesPeople = realm.objects(Person.self) .filter("address.city = %@", "Los Angeles") .sorted(byKeyPath: "address.street") print("Los Angeles Person: \(losAngelesPeople)") ``` #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *losAngelesContacts = [Contact objectsInRealm:realm where:@"address.city = %@", @"Los Angeles"]; losAngelesContacts = [losAngelesContacts sortedResultsUsingKeyPath:@"address.street" ascending:YES]; NSLog(@"Los Angeles Contacts: %@", losAngelesContacts); ``` ### Query a Map Property You can iterate and check the values of a realm `map` as you would a standard [Dictionary](https://developer.apple.com/documentation/swift/dictionary): ```swift let realm = try! Realm() let dogs = realm.objects(Dog.self) // Find dogs who have favorite parks let dogsWithFavoriteParks = dogs.where { $0.favoriteParksByCity.count >= 1 } for dog in dogsWithFavoriteParks { // Check if an entry exists if dog.favoriteParksByCity.keys.contains("Chicago") { print("\(dog.name) has a favorite park in Chicago") } // Iterate over entries for element in dog.favoriteParksByCity { print("\(dog.name)'s favorite park in \(element.key) is \(element.value)") } } ``` ### Query a MutableSet Property You can query a `MutableSet` to check if it contains an element. If you are working with multiple sets, you can check for the intersection of two sets, or check whether one set is a subset of the other set. ```swift let realm = try! Realm() // Find dogs who have visited New York let newYorkDogs = realm.objects(Dog.self).where { $0.citiesVisited.contains("New York") } // Get some information about the cities they have visited for dog in newYorkDogs { print("Cities \(dog.name) has visited: \(dog.citiesVisited)") } // Check whether two dogs have visited some of the same cities. // Use "intersects" to find out whether the values of the two sets share common elements. let isInBothCitiesVisited = (dog.citiesVisited.intersects(dog2.citiesVisited)) print("The two dogs have visited some of the same cities: \(isInBothCitiesVisited)") // Prints "The two dogs have visited some of the same cities: true" // Or you can check whether a set is a subset of another set. In this example, // the first dog has visited "New York" and "Toronto", while dog2 has visited both of // those but also "Toronto" and "Boston". let isSubset = (dog.citiesVisited.isSubset(of: dog2.citiesVisited)) print("\(dog.name)'s set of cities visited is a subset of \(dog2.name)'s: \(isSubset)") // Prints "Maui's set of cities visited is a subset of Lita's: true" ``` ### Read and Query AnyRealmValue Property When you read an AnyRealmValue property, check the value's type before doing anything with it. The Realm Swift SDK provides an `AnyRealmValue enum` that iterates through all of the types the AnyRealmValue can store. ```swift let realm = try! Realm() let dogs = realm.objects(Dog.self) for dog in dogs { // Verify the type of the ``AnyRealmProperty`` when attempting to get it. This // returns an object whose property contains the matched type. // If you only care about one type, check for that type. if case let .string(companion) = dog.companion { print("\(dog.name)'s companion is: \(companion)") // Prints "Wolfie's companion is: Fluffy the Cat" } // Or if you want to do something with multiple types of data // that could be in the value, switch on the type. switch dog.companion { case .string: print("\(dog.name)'s companion is: \(dog.companion)") // Prints "Wolfie's companion is: string("Fluffy the Cat") case .object: print("\(dog.name)'s companion is: \(dog.companion)") // Prints "Fido's companion is: object(Dog { name = Spot })" case .none: print("\(dog.name) has no companion") // Prints "Rex has no companion" and "Spot has no companion" default: print("\(dog.name)'s companion is another type.") } } ``` You can compare these mixed value types: - Numeric: int, bool, float, double, decimal - Byte-based: string, binary - Time-based: timestamp, objectId When using the `AnyRealmValue` mixed data type, keep these things in mind: - `equals` queries match on value and type - `not equals` queries match objects with either different values or different types - realm converts comparable numeric properties where possible. For example, in a mixed type field, 1 matches all of 1.0, 1, and true. - String properties do not match numeric queries. For example, in a mixed type field, 1 does not match "1". "1" does not match 1, 1.0, or true. ### Query Geospatial Data > Version added: 10.47.0 The Swift SDK provides several shapes to simplify querying geospatial data. You can use the `GeoCircle`, `GeoBox`, and `GeoPolygon` shapes to set the boundaries for your geospatial data queries. The SDK provides two specialized non-persistable data types to define shapes: - `GeoPoint`: A struct that represents the coordinates of a point formed by a pair of doubles consisting of these values: Latitude: ranges between -90 and 90 degrees, inclusive.Longitude: ranges between -180 and 180 degrees, inclusive. - `RLMDistance`: A helper struct to represent and convert a distance. #### Define Geospatial Shapes #### Geocircle A `GeoCircle` is a circular shape whose bounds originate from a central `GeoPoint`, and has a size corresponding to a radius measured in radians. You can use the SDK's convenience `RLMDistance` data type to easily work with radii in different units. `RLMDistance` enables you to specify the radius distance for your geo shapes in one of four units: - `.degrees` - `.kilometers` - `.miles` - `.radians` You can optionally use the supplied convenience methods to convert a measurement to a different distance units. ```swift // You can create a GeoCircle radius measured in radians. // This radian distance corresponds with 0.25 degrees. let smallCircle = GeoCircle(center: (47.3, -121.9), radiusInRadians: 0.004363323) // You can also create a GeoCircle radius measured with a Distance. // You can specify a Distance in .degrees, .kilometers, .miles, or .radians. let largeCircle = GeoCircle(center: GeoPoint(latitude: 47.8, longitude: -122.6)!, radius: Distance.kilometers(44.4)!) ``` ![Two GeoCircles](../../images/geocircles.png) #### Geobox A `GeoBox` is a rectangular shape whose bounds are determined by coordinates for a bottom-left and a top-right corner. ```swift let largeBox = GeoBox(bottomLeft: (47.3, -122.7), topRight: (48.1, -122.1)) let smallBoxBottomLeft = GeoPoint(latitude: 47.5, longitude: -122.4)! let smallBoxTopRight = GeoPoint(latitude: 47.9, longitude: -121.8) let smallBox = GeoBox(bottomLeft: smallBoxBottomLeft, topRight: smallBoxTopRight!) ``` ![2 GeoBoxes](../../images/geoboxes.png) #### Geopolygon A `GeoPolygon` is a polygon shape whose bounds consist of an outer ring, and 0 or more inner holes to exclude from the geospatial query. A polygon's outer ring must contain at least three segments. The last and the first `GeoPoint` must be the same, which indicates a closed polygon. This means that it takes at least four `GeoPoint` values to construct a polygon. Inner holes in a `GeoPolygon` must be entirely contained within the outer ring. Holes have the following restrictions: - Holes may not cross. The boundary of a hole may not intersect both the interior and the exterior of any other hole. - Holes may not share edges. If a hole contains an edge AB, then no other hole may contain it. - Holes may share vertices. However, no vertex may appear twice in a single hole. - No hole may be empty. - Only one nesting is allowed. ```swift // Create a basic polygon let basicPolygon = GeoPolygon(outerRing: [ (48.0, -122.8), (48.2, -121.8), (47.6, -121.6), (47.0, -122.0), (47.2, -122.6), (48.0, -122.8) ]) // Create a polygon with one hole let outerRing: [GeoPoint] = [ GeoPoint(latitude: 48.0, longitude: -122.8)!, GeoPoint(latitude: 48.2, longitude: -121.8)!, GeoPoint(latitude: 47.6, longitude: -121.6)!, GeoPoint(latitude: 47.0, longitude: -122.0)!, GeoPoint(latitude: 47.2, longitude: -122.6)!, GeoPoint(latitude: 48.0, longitude: -122.8)! ] let hole: [GeoPoint] = [ GeoPoint(latitude: 47.8, longitude: -122.6)!, GeoPoint(latitude: 47.7, longitude: -122.2)!, GeoPoint(latitude: 47.4, longitude: -122.6)!, GeoPoint(latitude: 47.6, longitude: -122.5)!, GeoPoint(latitude: 47.8, longitude: -122.6)! ] let polygonWithOneHole = GeoPolygon(outerRing: outerRing, holes: [hole]) // Add a second hole to the polygon let hole2: [GeoPoint] = [ GeoPoint(latitude: 47.55, longitude: -122.05)!, GeoPoint(latitude: 47.55, longitude: -121.9)!, GeoPoint(latitude: 47.3, longitude: -122.1)!, GeoPoint(latitude: 47.55, longitude: -122.05)! ] let polygonWithTwoHoles = GeoPolygon(outerRing: outerRing, holes: [hole, hole2]) ``` ![3 GeoPolygons](../../images/geopolygons.png) #### Query with Geospatial Shapes You can then use these shapes in a geospatial query. You can query geospatial data in three ways: - Using the `.geoWithin()` operator with the type-safe Realm Swift Query API - Using a `.filter()` with RQL - Using a `.filter()` with an NSPredicate query The examples below show the results of queries using these two `Company` objects: ```swift let company1 = Geospatial_Company() company1.location = CustomGeoPoint(47.68, -122.35) let company2 = Geospatial_Company(CustomGeoPoint(47.9, -121.85)) ``` ![2 GeoPoints](../../images/geopoints.png) #### Geocircle ```swift let companiesInSmallCircle = realm.objects(Geospatial_Company.self).where { $0.location.geoWithin(smallCircle!) } print("Number of companies in small circle: \(companiesInSmallCircle.count)") let companiesInLargeCircle = realm.objects(Geospatial_Company.self) .filter("location IN %@", largeCircle) print("Number of companies in large circle: \(companiesInLargeCircle.count)") ``` ![Querying a GeoCircle example.](../../images/geocircles-query.png) #### Geobox ```swift let companiesInSmallBox = realm.objects(Geospatial_Company.self).where { $0.location.geoWithin(smallBox) } print("Number of companies in small box: \(companiesInSmallBox.count)") let filterArguments = NSMutableArray() filterArguments.add(largeBox) let companiesInLargeBox = realm.objects(Geospatial_Company.self) .filter(NSPredicate(format: "location IN %@", argumentArray: filterArguments as? [Any])) print("Number of companies in large box: \(companiesInLargeBox.count)") ``` ![Querying a GeoBox example.](../../images/geoboxes-query.png) #### Geopolygon ```swift let companiesInBasicPolygon = realm.objects(Geospatial_Company.self).where { $0.location.geoWithin(basicPolygon!) } print("Number of companies in basic polygon: \(companiesInBasicPolygon.count)") let companiesInPolygonWithTwoHoles = realm.objects(Geospatial_Company.self).where { $0.location.geoWithin(polygonWithTwoHoles!) } print("Number of companies in polygon with two holes: \(companiesInPolygonWithTwoHoles.count)") ``` ![Querying a GeoPolygon example.](../../images/geopolygons-query.png) ### Query a Custom Persistable Property When you use type projection to map unsupported types to supported types, accessing those properties is often based on the persisted type. #### Queries on Realm Objects When working with projected types, queries operate on the persisted type. However, you can use the mapped types interchangeably with the persisted types in arguments in most cases. The exception is queries on embedded objects. > Tip: > Projected types support sorting and aggregates where the persisted type supports them. > ```swift let akcClub = realm.objects(Club.self).where { $0.name == "American Kennel Club" }.first! // You can use type-safe expressions to check for equality XCTAssert(akcClub.url == URL(string: "https://akc.org")!) let clubs = realm.objects(Club.self) // You can use the persisted property type in NSPredicate query expressions let akcByUrl = clubs.filter("url == 'https://akc.org'").first! XCTAssert(akcByUrl.name == "American Kennel Club") ``` #### Queries on Embedded Objects You can query embedded types on the supported property types within the object using memberwise equality. Object link properties support equality comparisons, but do not support memberwise comparisons. You can query embedded objects for memberwise equality on all primitive types. You cannot perform memberwise comparison on objects and collections. #### Dynamic APIs Because the schema has no concept of custom type mappings, reading data via any of the dynamic APIs gives the underlying persisted type. Realm does support writing mapped types via a dynamic API, and converts the projected type to the persisted type. The most common use of the dynamic APIs is migration. You can write projected types during migration, and Realm converts the projected type to the persisted type. However, reading data during a migration gives the underlying persisted type. ## Read an Object Asynchronously When you use an actor-isolated realm, you can use Swift concurrency features to asynchronously query objects. ```swift let actor = try await RealmActor() // Read objects in functions isolated to the actor and pass primitive values to the caller func getObjectId(in actor: isolated RealmActor, forTodoNamed name: String) async -> ObjectId { let todo = actor.realm.objects(Todo.self).where { $0.name == name }.first! return todo._id } let objectId = await getObjectId(in: actor, forTodoNamed: "Keep it safe") ``` If you need to manually advance the state of an observed realm on the main thread or an actor-isolated realm, call `await realm.asyncRefresh()`. This updates the realm and outstanding objects managed by the Realm to point to the most recent data and deliver any applicable notifications. For more information about working with realm using Swift concurrency features, refer to Use Realm with Actors - Swift SDK. ## Sort Query Results A sort operation allows you to configure the order in which Realm Database returns queried objects. You can sort based on one or more properties of the objects in the results collection. Realm only guarantees a consistent order of results if you explicitly sort them. #### Objective-C To sort, call [-[RLMResults sortedResultsUsingKeyPath:ascending:]](https://www.mongodb.com/docs/realm-sdks/objc/latest/Classes/RLMResults.html#/c:objc(cs)RLMResults(im)sortedResultsUsingKeyPath:ascending:) with the desired key path to sort by. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *dogs = [Dog allObjectsInRealm:realm]; // Sort dogs by name RLMResults *dogsSorted = [dogs sortedResultsUsingKeyPath:@"name" ascending:NO]; // You can also sort on the members of linked objects. In this example, // we sort the dogs by their favorite toys' names. RLMResults *dogsSortedByFavoriteToyName = [dogs sortedResultsUsingKeyPath:@"favoriteToy.name" ascending:YES]; ``` #### Swift > Version added: 10.11.0 You can sort using the type-safe keyPath by calling `Results.sorted(by: )` with the keyPath name and optional sort order: ```swift let realm = try! Realm() // Access all dogs in the realm let dogs = realm.objects(Dog.self) // Sort by type-safe keyPath let dogsSorted = dogs.sorted(by: \.name) ``` To sort using the older API, call `Results.sorted(byKeyPath:ascending:)` with the desired key path to sort by. ```swift let realm = try! Realm() // Access all dogs in the realm let dogs = realm.objects(Dog.self) let dogsSorted = dogs.sorted(byKeyPath: "name", ascending: false) // You can also sort on the members of linked objects. In this example, // we sort the dogs by their favorite toys' names. let dogsSortedByFavoriteToyName = dogs.sorted(byKeyPath: "favoriteToy.name") ``` > Tip: > To sort a query based on a property of an embedded object or a related object, use dot-notation as if it were in a regular, nested object. > > Note: > String sorting and case-insensitive queries are only supported for character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', and 'Latin Extended B' (UTF-8 range 0-591). > ## Section Query Results You can split results into individual sections. Each section corresponds to a key generated from a property on the object it represents. For example, you might add a computed variable to your object to get the first letter of the `name` property: ```swift // Computed variable that is not persisted, but only // used to section query results. var firstLetter: String { return name.first.map(String.init(_:)) ?? "" } ``` Then, you can create a `SectionedResults` type-safe collection for that object, and use it to retrieve objects sectioned by that computed variable: ```swift var dogsByFirstLetter: SectionedResults dogsByFirstLetter = realm.objects(Dog.self).sectioned(by: \.firstLetter, ascending: true) ``` You can get a count of the sections, get a list of keys, or access an individual `ResultSection` by index: ```swift let realm = try! Realm() var dogsByFirstLetter: SectionedResults dogsByFirstLetter = realm.objects(Dog.self).sectioned(by: \.firstLetter, ascending: true) // You can get a count of the sections in the SectionedResults let sectionCount = dogsByFirstLetter.count // Get an array containing all section keys for objects that match the query. let sectionKeys = dogsByFirstLetter.allKeys // This example realm contains 4 dogs, "Rex", "Wolfie", "Fido", "Spot". // Prints ["F", "R", "S", "W"] print(sectionKeys) // Get a specific key by index position let sectionKey = dogsByFirstLetter[0].key // Prints "Key for index 0: F" print("Key for index 0: \(sectionKey)") // You can access Results Sections by the index of the key you want in SectionedResults. // "F" is the key at index position 0. When we access this Results Section, we get dogs whose name begins with "F". let dogsByF = dogsByFirstLetter[0] // Prints "Fido" print(dogsByF.first?.name) ``` You can also section using a callback. This enables you to section a collection of primitives, or have more control over how the section key is generated. ```swift let realm = try! Realm() let results = realm.objects(Dog.self) let sectionedResults = results.sectioned(by: { String($0.name.first!) }, sortDescriptors: [SortDescriptor.init(keyPath: "name", ascending: true)]) let sectionKeys = sectionedResults.allKeys ``` You can observe `SectionedResults` and `ResultsSection` instances, and both conform to `ThreadConfined`. ## Aggregate Data You can use Realm's aggregation operators for sophisticated queries against list properties. #### Swift > Version added: 10.19.0 ```swift let realm = try! Realm() let people = realm.objects(Person.self) // People whose dogs' average age is 5 people.where { $0.dogs.age.avg == 5 } // People with older dogs people.where { $0.dogs.age.min > 5 } // People with younger dogs people.where { $0.dogs.age.max < 2 } // People with many dogs people.where { $0.dogs.count > 2 } // People whose dogs' ages combined > 10 years people.where { $0.dogs.age.sum > 10 } ``` #### Swift Nspredicate ```swift let realm = try! Realm() let people = realm.objects(Person.self) // People whose dogs' average age is 5 people.filter("dogs.@avg.age == 5") // People with older dogs people.filter("dogs.@min.age > 5") // People with younger dogs people.filter("dogs.@max.age < 2") // People with many dogs people.filter("dogs.@count > 2") // People whose dogs' ages combined > 10 years people.filter("dogs.@sum.age > 10") ``` #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; RLMResults *people = [Person allObjectsInRealm:realm]; // People whose dogs' average age is 5 [people objectsWhere:@"dogs.@avg.age == 5"]; // People with older dogs [people objectsWhere:@"dogs.@min.age > 5"]; // People with younger dogs [people objectsWhere:@"dogs.@max.age < 2"]; // People with many dogs [people objectsWhere:@"dogs.@count > 2"]; // People whose dogs' ages combined > 10 years [people objectsWhere:@"dogs.@sum.age > 10"]; ``` ## Chain Queries Because results are lazily evaluated, you can chain several queries together. Unlike traditional databases, this does not require a separate trip to the database for each successive query. > Example: > To get a result set for tan dogs, and tan dogs whose names start with 'B', chain two queries like this: > > #### Swift > > > Version added: 10.19.0 > > ```swift > let realm = try! Realm() > let tanDogs = realm.objects(Dog.self).where { > $0.color == "tan" > } > let tanDogsWithBNames = tanDogs.where { > $0.name.starts(with: "B") > } > > ``` > > > #### Swift Nspredicate > > ```swift > let realm = try! Realm() > let tanDogs = realm.objects(Dog.self).filter("color = 'tan'") > let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'") > > ``` > > > #### Objective-C > > ```objectivec > RLMRealm *realm = [RLMRealm defaultRealm]; > RLMResults *tanDogs = [Dog objectsInRealm:realm where:@"color = 'tan'"]; > RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"]; > > ``` > > ## Query Class Projections To query for class projections in a realm, pass the metatype instance `YourProjectionName.self` to `Realm.objects(_)`. This returns a `Results` object representing all of the class projection objects in the realm. ```swift // Retrieve all class projections of the given type `PersonProjection` let people = realm.objects(PersonProjection.self) // Use projection data in your view print(people.first?.firstName) print(people.first?.homeCity) print(people.first?.firstFriendsName) ``` > Tip: > Don't do derived queries on top of class projection results. Instead, run a query against the Realm object directly and then project the result. If you try to do a derived query on top of class projection results, querying a field with the same name and type as the original object works, but querying a field with a name or type that isn't in the original object fails. > ================================================ FILE: docs/guides/crud/threading.md ================================================ # Threading - Swift SDK To make your iOS and tvOS apps fast and responsive, you must balance the computing time needed to lay out the visuals and handle user interactions with the time needed to process your data and run your business logic. Typically, app developers spread this work across multiple threads: the main or UI thread for all of the user interface-related work, and one or more background threads to compute heavier workloads before sending it to the UI thread for presentation. By offloading heavy work to background threads, the UI thread can remain highly responsive regardless of the size of the workload. But it can be notoriously difficult to write thread-safe, performant, and maintainable multithreaded code that avoids issues like deadlocking and race conditions. Realm aims to simplify this for you. > Seealso: > As of 10.26.0, Realm provides async write methods to perform background writes. See: Perform a Background Write. With async write, you don't need to pass a thread-safe reference or frozen objects across threads. > This page describes how to manually manage realm files and objects across threads. Realm also supports using a [Swift actor](https://developer.apple.com/documentation/swift/actor) to manage realm access using Swift concurrency features. For an overview of Realm's actor support, refer to Use Realm with Actors - Swift SDK. ## Three Rules to Follow Before exploring Realm's tools for multithreaded apps, you need to understand and follow these three rules: Realm's Multiversion Concurrency Control (MVCC) architecture eliminates the need to lock for read operations. The values you read will never be corrupted or in a partially-modified state. You can freely read from the same Realm file on any thread without the need for locks or mutexes. Unnecessarily locking would be a performance bottleneck since each thread might need to wait its turn before reading. You can write to a Realm file from any thread, but there can be only one writer at a time. Consequently, synchronous write transactions block each other. A synchronous write on the UI thread may result in your app appearing unresponsive while it waits for a write on a background thread to complete. Live objects, collections, and realm instances are **thread-confined**: that is, they are only valid on the thread on which they were created. Practically speaking, this means you cannot pass live instances to other threads. However, Realm offers several mechanisms for sharing objects across threads. ## Perform a Background Write > Version added: 10.26.0 You can add, modify, or delete objects in the background using `writeAsync`. With `writeAsync`, you don't need to pass a thread-safe reference or frozen objects across threads. Instead, call `realm.writeAsync`. You can provide a completion block for the method to execute on the source thread after the write completes or fails. Things to consider when performing background writes: - Async writes block closing or invalidating the realm - You can explicitly commit or cancel transactions ```swift let realm = try! Realm() // Query for a specific person object on the main thread let people = realm.objects(Person.self) let thisPerson = people.where { $0.name == "Dachary" }.first // Perform an async write to add dogs to that person's dog list. // No need to pass a thread-safe reference or frozen object. realm.writeAsync { thisPerson?.dogs.append(objectsIn: [ Dog(value: ["name": "Ben", "age": 13]), Dog(value: ["name": "Lita", "age": 9]), Dog(value: ["name": "Maui", "age": 1]) ]) } onComplete: { _ in // Confirm the three dogs were successfully added to the person's dogs list XCTAssertEqual(thisPerson!.dogs.count, 3) // Query for one of the dogs we added and see that it is present let dogs = realm.objects(Dog.self) let benDogs = dogs.where { $0.name == "Ben" } XCTAssertEqual(benDogs.count, 1) } ``` ### Wait for Async Writes to Complete The SDK provides a `Bool` to signal whether the realm is currently performing an async write. The `isPerformingAsynchronousWriteOperations` variable becomes `true` after a call to one of: - `writeAsync` - `beginAsyncWrite` - `commitAsyncWrite` It remains true until all scheduled async write operations have completed. While this is true, this blocks closing or `invalidating` the realm. ### Commit or Cancel an Async Write To complete an async write, you or the SDK must call either: - `commitAsyncWrite` - `cancelAsyncWrite` When you use the `writeAsync` method, the SDK handles committing or canceling the transaction. This provides the convenience of the async write without the need to manually keep state tied to the scope of the object. However, while in the `writeAsync` block, you *can* explicitly call `commitAsyncWrite` or `cancelAsyncWrite`. If you return without calling one of these methods, `writeAsync` either: - Commits the write after executing the instructions in the write block - Returns an error In either case, this completes the `writeAsync` operation. For more control over when to commit or cancel the async write transaction, use the `beginAsyncWrite` method. When you use this method, you must explicitly commit the transactions. Returning without committing an async write cancels the transaction. `beginAsyncWrite` returns an ID that you can pass to `cancelAsyncWrite`. `commitAsyncWrite` asynchronously commits a write transaction. This is the step that persists the data to the realm. `commitAsyncWrite` can take an `onComplete` block. . This block executes on the source thread once the commit completes or fails with an error. Calling `commitAsyncWrite` immediately returns. This allows the caller to proceed while the SDK performs the I/O on a background thread. This method returns an ID that you can pass to `cancelAsyncWrite`. This cancels the pending invocation of the completion block. It does not cancel the commit itself. You can group sequential calls to `commitAsyncWrite`. Batching these calls improves write performance; particularly when the batched transactions are small. To permit grouping transactions, set the `isGroupingAllowed` parameter to `true`. You can call `cancelAsyncWrite` on either `beginAsyncWrite` or `commitAsyncWrite`. When you call it on `beginAsyncWrite`, this cancels the entire write transaction. When you call it on `commitAsyncWrite`, this cancels only an `onComplete` block you may have passed to `commitAsyncWrite`. It does not cancel the commit itself. You need the ID of the `beginAsyncWrite` or the `commitAsyncWrite` you want to cancel. ## Communication Across Threads To access the same Realm file from different threads, you must instantiate a realm instance on every thread that needs access. As long as you specify the same configuration, all realm instances will map to the same file on disk. One of the key rules when working with Realm in a multithreaded environment is that objects are thread-confined: **you cannot access the instances of a realm, collection, or object that originated on other threads.** Realm's Multiversion Concurrency Control (MVCC) architecture means that there could be many active versions of an object at any time. Thread-confinement ensures that all instances in that thread are of the same internal version. When you need to communicate across threads, you have several options depending on your use case: - To modify an object on two threads, query for the object on both threads. - To react to changes made on any thread, use Realm's notifications. - To see changes that happened on another thread in the current thread's realm instance, refresh your realm instance. - To send a fast, read-only view of the object to other threads, "freeze" the object. - To keep and share many read-only views of the object in your app, copy the object from the realm. - To share an instance of a realm or specific object with another thread or across actor boundaries, share a thread-safe reference to the realm instance or object. For more information, refer to Pass a ThreadSafeReference. ### Create a Serial Queue to use Realm on a Background Thread When using Realm on a background thread, create a serial queue. Realm does not support using realms in concurrent queues, such as the `global()` queue. ```swift // Initialize a serial queue, and // perform realm operations on it let serialQueue = DispatchQueue(label: "serial-queue") serialQueue.async { let realm = try! Realm(configuration: .defaultConfiguration, queue: serialQueue) // Do something with Realm on the non-main thread } ``` ### Pass Instances Across Threads Instances of `Realm`, `Results`, `List`, and managed `Objects` are *thread-confined*. That means you may only use them on the thread where you created them. However, Realm provides a mechanism called **thread-safe references** that allows you to copy an instance created on one thread to another thread. #### Sendable Conformance > Version added: 10.20.0 > @ThreadSafe wrapper and ThreadSafeReference conform to `Sendable` > If you are using Swift 5.6 or higher, both the `@ThreadSafe property wrapper` and `ThreadSafeReference` conform to [Sendable](https://developer.apple.com/documentation/swift/sendable). #### Use the @ThreadSafe Wrapper > Version added: 10.17.0 You can pass thread-confined instances to another thread as follows: 1. Use the `@ThreadSafe` property wrapper to declare a variable that references the original object. By definition, `@ThreadSafe`-wrapped variables are always optional. 2. Pass the `@ThreadSafe`-wrapped variable to the other thread. 3. Use the `@ThreadSafe`-wrapped variable as you would any optional. If the referenced object is removed from the realm, the referencing variable becomes nil. ```swift let realm = try! Realm() let person = Person(name: "Jane") try! realm.write { realm.add(person) } // Create thread-safe reference to person @ThreadSafe var personRef = person // @ThreadSafe vars are always optional. If the referenced object is deleted, // the @ThreadSafe var will be nullified. print("Person's name: \(personRef?.name ?? "unknown")") // Pass the reference to a background thread DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { let realm = try! Realm() try! realm.write { // Resolve within the transaction to ensure you get the // latest changes from other threads. If the person // object was deleted, personRef will be nil. guard let person = personRef else { return // person was deleted } person.name = "Jane Doe" } } ``` Another way to work with an object on another thread is to query for it again on that thread. But if the object does not have a primary key, it is not trivial to query for it. You can use the `@ThreadSafe` wrapper on any object, regardless of whether it has a primary key. > Example: > The following example shows how to use `@ThreadSafe` on a function parameter. This is useful for functions that may run asynchronously or on another thread. > > > Tip: > > If your app accesses Realm in an `async/await` context, mark the code with `@MainActor` to avoid threading-related crashes. > > > > ```swift > func someLongCallToGetNewName() async -> String { > return "Janet" > } > > @MainActor > func loadNameInBackground(@ThreadSafe person: Person?) async { > let newName = await someLongCallToGetNewName() > let realm = try! await Realm() > try! realm.write { > person?.name = newName > } > } > > @MainActor > func createAndUpdatePerson() async { > let realm = try! await Realm() > > let person = Person(name: "Jane") > try! realm.write { > realm.add(person) > } > await loadNameInBackground(person: person) > } > > await createAndUpdatePerson() > > ``` > #### Use ThreadSafeReference (Legacy Swift / Objective-C) Before Realm Swift SDK version 10.17.0 or in Objective-C, you can pass thread-confined instances to another thread as follows: 1. Initialize a `ThreadSafeReference` with the thread-confined object. 2. Pass the reference to the other thread or queue. 3. Resolve the reference on the other thread's realm by calling `Realm.resolve(_:)`. Use the returned object as normal. > Important: > You must resolve a `ThreadSafeReference` exactly once. Otherwise, the source realm remains pinned until the reference gets deallocated. For this reason, `ThreadSafeReference` should be short-lived. > ```swift let person = Person(name: "Jane") let realm = try! Realm() try! realm.write { realm.add(person) } // Create thread-safe reference to person let personRef = ThreadSafeReference(to: person) // Pass the reference to a background thread DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { let realm = try! Realm() try! realm.write { // Resolve within the transaction to ensure you get the latest changes from other threads guard let person = realm.resolve(personRef) else { return // person was deleted } person.name = "Jane Doe" } } ``` Another way to work with an object on another thread is to query for it again on that thread. But if the object does not have a primary key, it is not trivial to query for it. You can use `ThreadSafeReference` on any object, regardless of whether it has a primary key. You can also use it with lists and results. The downside is that `ThreadSafeReference` requires some boilerplate. You must remember to wrap everything in a `DispatchQueue` with a properly-scoped `autoreleaseFrequency` so the objects do not linger on the background thread. So, it can be helpful to make a convenience extension to handle the boilerplate as follows: ```swift extension Realm { func writeAsync(_ passedObject: T, errorHandler: @escaping ((_ error: Swift.Error) -> Void) = { _ in return }, block: @escaping ((Realm, T?) -> Void)) { let objectReference = ThreadSafeReference(to: passedObject) let configuration = self.configuration DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { do { let realm = try Realm(configuration: configuration) try realm.write { // Resolve within the transaction to ensure you get the latest changes from other threads let object = realm.resolve(objectReference) block(realm, object) } } catch { errorHandler(error) } } } } ``` This extension adds a `writeAsync()` method to the Realm class. This method passes an instance to a background thread for you. > Example: > Suppose you made an email app and want to delete all read emails in the background. You can now do it with two lines of code. Note that the closure runs on the background thread and receives its own version of both the realm and passed object: > > ```swift > let realm = try! Realm() > let readEmails = realm.objects(Email.self).where { > $0.read == true > } > realm.writeAsync(readEmails) { (realm, readEmails) in > guard let readEmails = readEmails else { > // Already deleted > return > } > realm.delete(readEmails) > } > > ``` > ### Use the Same Realm Across Threads You cannot share realm instances across threads. To use the same Realm file across threads, open a different realm instance on each thread. As long as you use the same `configuration`, all Realm instances will map to the same file on disk. ### Refreshing Realms When you open a realm, it reflects the most recent successful write commit and remains on that version until it is **refreshed**. This means that the realm will not see changes that happened on another thread until the next refresh. A realm on the UI thread -- more precisely, on any event loop thread -- automatically refreshes itself at the beginning of that thread's loop. However, you must manually refresh realm instances that do not exist on loop threads or that have auto-refresh disabled. #### Objective-C ```objective-c if (![realm autorefresh]) { [realm refresh] } ``` #### Swift ```swift if (!realm.autorefresh) { // Manually refresh realm.refresh() } ``` ### Frozen Objects Live, thread-confined objects work fine in most cases. However, some apps -- those based on reactive, event stream-based architectures, for example -- need to send immutable copies around to many threads for processing before ultimately ending up on the UI thread. Making a deep copy every time would be expensive, and Realm does not allow live instances to be shared across threads. In this case, you can **freeze** and **thaw** objects, collections, and realms. Freezing creates an immutable view of a specific object, collection, or realm. The frozen object, collection, or realm still exists on disk, and does not need to be deeply copied when passed around to other threads. You can freely share the frozen object across threads without concern for thread issues. When you freeze a realm, its child objects also become frozen. > Tip: > Realm does not currently support using `thaw()` with Swift Actors. To work with Realm data across actor boundaries, use `ThreadSafeReference` instead of frozen objects. For more information, refer to Pass a ThreadSafeReference. > Frozen objects are not live and do not automatically update. They are effectively snapshots of the object state at the time of freezing. Thawing an object returns a live version of the frozen object. #### Objective-C ```objectivec // Get an immutable copy of the realm that can be passed across threads RLMRealm *frozenRealm = [realm freeze]; RLMResults *dogs = [Dog allObjectsInRealm:realm]; // You can freeze collections RLMResults *frozenDogs = [dogs freeze]; // You can still read from frozen realms RLMResults *frozenDogs2 = [Dog allObjectsInRealm:frozenRealm]; Dog *dog = [dogs firstObject]; // You can freeze objects Dog *frozenDog = [dog freeze]; // To modify frozen objects, you can thaw them // You can thaw collections RLMResults *thawedDogs = [dogs thaw]; // You can thaw objects Dog *thawedDog = [dog thaw]; // You can thaw frozen realms RLMRealm *thawedRealm = [realm thaw]; ``` #### Swift ```swift let realm = try! Realm() // Get an immutable copy of the realm that can be passed across threads let frozenRealm = realm.freeze() assert(frozenRealm.isFrozen) let people = realm.objects(Person.self) // You can freeze collections let frozenPeople = people.freeze() assert(frozenPeople.isFrozen) // You can still read from frozen realms let frozenPeople2 = frozenRealm.objects(Person.self) assert(frozenPeople2.isFrozen) let person = people.first! assert(!person.realm!.isFrozen) // You can freeze objects let frozenPerson = person.freeze() assert(frozenPerson.isFrozen) // Frozen objects have a reference to a frozen realm assert(frozenPerson.realm!.isFrozen) ``` When working with frozen objects, an attempt to do any of the following throws an exception: - Opening a write transaction on a frozen realm. - Modifying a frozen object. - Adding a change listener to a frozen realm, collection, or object. You can use `isFrozen` to check if the object is frozen. This is always thread-safe. #### Objective-C ```objective-c if ([realm isFrozen]) { // ... } ``` #### Swift ```swift if (realm.isFrozen) { // ... } ``` Frozen objects remain valid as long as the live realm that spawned them stays open. Therefore, avoid closing the live realm until all threads are done with the frozen objects. You can close a frozen realm before the live realm is closed. > Important: > Caching too many frozen objects can have a negative impact on the realm file size. "Too many" depends on your specific target device and the size of your Realm objects. If you need to cache a large number of versions, consider copying what you need out of the realm instead. > #### Modify a Frozen Object To modify a frozen object, you must thaw the object. Alternately, you can query for it on an unfrozen realm, then modify it. Calling `thaw` on a live object, collection, or realm returns itself. Thawing an object or collection also thaws the realm it references. ```swift // Read from a frozen realm let frozenPeople = frozenRealm.objects(Person.self) // The collection that we pull from the frozen realm is also frozen assert(frozenPeople.isFrozen) // Get an individual person from the collection let frozenPerson = frozenPeople.first! // To modify the person, you must first thaw it // You can also thaw collections and realms let thawedPerson = frozenPerson.thaw() // Check to make sure this person is valid. An object is // invalidated when it is deleted from its managing realm, // or when its managing realm has invalidate() called on it. assert(thawedPerson?.isInvalidated == false) // Thawing the person also thaws the frozen realm it references assert(thawedPerson!.realm!.isFrozen == false) // Let's make the code easier to follow by naming the thawed realm let thawedRealm = thawedPerson!.realm! // Now, you can modify the todo try! thawedRealm.write { thawedPerson!.name = "John Michael Kane" } ``` #### Append to a Frozen Collection When you append to a frozen collection, you must thaw both the collection and the object that you want to append. In this example, we query for two objects in a frozen Realm: - A Person object that has a List property of Dog objects - A Dog object We must thaw both objects before we can append the Dog to the Dog List collection on the Person. If we thaw only the Person object but not the Dog, Realm throws an error. The same rule applies when passing frozen objects across threads. A common case might be calling a function on a background thread to do some work instead of blocking the UI. ```swift // Get a copy of frozen objects. // Here, we're getting them from a frozen realm, // but you might also be passing them across threads. let frozenTimmy = frozenRealm.objects(Person.self).where { $0.name == "Timmy" }.first! let frozenLassie = frozenRealm.objects(Dog.self).where { $0.name == "Lassie" }.first! // Confirm the objects are frozen. assert(frozenTimmy.isFrozen == true) assert(frozenLassie.isFrozen == true) // Thaw the frozen objects. You must thaw both the object // you want to append and the collection you want to append it to. let thawedTimmy = frozenTimmy.thaw() let thawedLassie = frozenLassie.thaw() let realm = try! Realm() try! realm.write { thawedTimmy?.dogs.append(thawedLassie!) } XCTAssertEqual(thawedTimmy?.dogs.first?.name, "Lassie") ``` ## Realm's Threading Model in Depth Realm provides safe, fast, lock-free, and concurrent access across threads with its [Multiversion Concurrency Control (MVCC)](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) architecture. ### Compared and Contrasted with Git If you are familiar with a distributed version control system like [Git](https://git-scm.com/), you may already have an intuitive understanding of MVCC. Two fundamental elements of Git are: - Commits, which are atomic writes. - Branches, which are different versions of the commit history. Similarly, Realm has atomically-committed writes in the form of transactions. Realm also has many different versions of the history at any given time, like branches. Unlike Git, which actively supports distribution and divergence through forking, a realm only has one true latest version at any given time and always writes to the head of that latest version. Realm cannot write to a previous version. This means your data converges on one latest version of the truth. ### Internal Structure A realm is implemented using a [B+ tree](https://en.wikipedia.org/wiki/B%2B_tree) data structure. The top-level node represents a version of the realm; child nodes are objects in that version of the realm. The realm has a pointer to its latest version, much like how Git has a pointer to its HEAD commit. Realm uses a copy-on-write technique to ensure [isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)) and [durability](https://en.wikipedia.org/wiki/Durability_(database_systems)). When you make changes, Realm copies the relevant part of the tree for writing. Realm then commits the changes in two phases: - Realm writes changes to disk and verifies success. - Realm then sets its latest version pointer to point to the newly-written version. This two-step commit process guarantees that even if the write failed partway, the original version is not corrupted in any way because the changes were made to a copy of the relevant part of the tree. Likewise, the realm's root pointer will point to the original version until the new version is guaranteed to be valid. > Example: > The following diagram illustrates the commit process: > > ![Realm copies the relevant part of the tree for writes, then replaces the latest version by updating a pointer.](../../images/mvcc-diagram.png) > > 1. The realm is structured as a tree. The realm has a pointer to its latest version, V1. > 2. When writing, Realm creates a new version V2 based on V1. Realm makes copies of objects for modification (A 1, C 1), while links to unmodified objects continue to point to the original versions (B, D). > 3. After validating the commit, Realm updates the pointer to the new latest version, V2. Realm then discards old nodes no longer connected to the tree. > Realm uses zero-copy techniques like memory mapping to handle data. When you read a value from the realm, you are virtually looking at the value on the actual disk, not a copy of it. This is the basis for live objects. This is also why a realm head pointer can be set to point to the new version after the write to disk has been validated. ## Summary - Realm enables simple and safe multithreaded code when you follow these rules: don't lock to readavoid writes on the UI thread if you write on background threads, and don't pass live objects to other threads. - There is a proper way to share objects across threads for each use case. - In order to see changes made on other threads in your realm instance, you must manually **refresh** realm instances that do not exist on "loop" threads or that have auto-refresh disabled. - For apps based on reactive, event-stream-based architectures, you can **freeze** objects, collections, and realms in order to pass shallow copies around efficiently to different threads for processing. - Realm's multiversion concurrency control (MVCC) architecture is similar to Git's. Unlike Git, Realm has only one true latest version for each realm. - Realm commits in two stages to guarantee isolation and durability. ## Sendable, Non-Sendable and Thread-Confined Types The Realm Swift SDK public API contains types that fall into three broad categories: - Sendable - Not Sendable and not thread confined - Thread-confined You can share types that are not Sendable and not thread confined between threads, but you must synchronize them. Thread-confined types, unless frozen, are confined to an isolation context. You cannot pass them between these contexts even with synchronization. ================================================ FILE: docs/guides/crud/update.md ================================================ # CRUD - Update - Swift SDK ## Update Realm Objects Updates to Realm Objects must occur within write transactions. For more information about write transactions, see: Transactions. ### About The Examples On This Page The examples on this page use the following models: #### Objective-C ```objectivec // DogToy.h @interface DogToy : RLMObject @property NSString *name; @end // Dog.h @interface Dog : RLMObject @property NSString *name; @property int age; @property NSString *color; // To-one relationship @property DogToy *favoriteToy; @end // Enable Dog for use in RLMArray RLM_COLLECTION_TYPE(Dog) // Person.h // A person has a primary key ID, a collection of dogs, and can be a member of multiple clubs. @interface Person : RLMObject @property int _id; @property NSString *name; // To-many relationship - a person can have many dogs @property RLMArray *dogs; // Inverse relationship - a person can be a member of many clubs @property (readonly) RLMLinkingObjects *clubs; @end RLM_COLLECTION_TYPE(Person) // DogClub.h @interface DogClub : RLMObject @property NSString *name; @property RLMArray *members; @end // Dog.m @implementation Dog @end // DogToy.m @implementation DogToy @end // Person.m @implementation Person // Define the primary key for the class + (NSString *)primaryKey { return @"_id"; } // Define the inverse relationship to dog clubs + (NSDictionary *)linkingObjectsProperties { return @{ @"clubs": [RLMPropertyDescriptor descriptorWithClass:DogClub.class propertyName:@"members"], }; } @end // DogClub.m @implementation DogClub @end ``` #### Swift ```swift class Dog: Object { @Persisted var name = "" @Persisted var age = 0 @Persisted var color = "" @Persisted var currentCity = "" @Persisted var citiesVisited: MutableSet @Persisted var companion: AnyRealmValue // Map of city name -> favorite park in that city @Persisted var favoriteParksByCity: Map } class Person: Object { @Persisted(primaryKey: true) var id = 0 @Persisted var name = "" // To-many relationship - a person can have many dogs @Persisted var dogs: List // Embed a single object. // Embedded object properties must be marked optional. @Persisted var address: Address? } class Address: EmbeddedObject { @Persisted var street: String? @Persisted var city: String? @Persisted var country: String? @Persisted var postalCode: String? } ``` ### Update an Object You can modify properties of a Realm object inside of a write transaction in the same way that you would update any other Swift or Objective-C object. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; // Open a thread-safe transaction. [realm transactionWithBlock:^{ // Get a dog to update. Dog *dog = [[Dog allObjectsInRealm: realm] firstObject]; // Update some properties on the instance. // These changes are saved to the realm. dog.name = @"Wolfie"; dog.age += 1; }]; ``` #### Swift ```swift let realm = try! Realm() // Get a dog to update let dog = realm.objects(Dog.self).first! // Open a thread-safe transaction try! realm.write { // Update some properties on the instance. // These changes are saved to the realm dog.name = "Wolfie" dog.age += 1 } ``` > Tip: > To update a property of an embedded object or a related object, modify the property with dot-notation or bracket-notation as if it were in a regular, nested object. > ### Update Properties with Key-value Coding `Object`, `Result`, and `List` all conform to [key-value coding](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/). This can be useful when you need to determine which property to update at runtime. Applying KVC to a collection is a great way to update objects in bulk. Avoid the overhead of iterating over a collection while creating accessors for every item. ```swift let realm = try! Realm() let allDogs = realm.objects(Dog.self) try! realm.write { allDogs.first?.setValue("Sparky", forKey: "name") // Move the dogs to Toronto for vacation allDogs.setValue("Toronto", forKey: "currentCity") } ``` You can also add values for embedded objects or relationships this way. In this example, we add a collection to an object's list property: #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^() { // Create a person to take care of some dogs. Person *ali = [[Person alloc] initWithValue:@{@"_id": @1, @"name": @"Ali"}]; [realm addObject:ali]; // Find dogs younger than 2. RLMResults *puppies = [Dog objectsInRealm:realm where:@"age < 2"]; // Batch update: give all puppies to Ali. [ali setValue:puppies forKey:@"dogs"]; }]; ``` #### Swift ```swift let realm = try! Realm() try! realm.write { // Create a person to take care of some dogs. let person = Person(value: ["id": 1, "name": "Ali"]) realm.add(person) let dog = Dog(value: ["name": "Rex", "age": 1]) realm.add(dog) // Find dogs younger than 2. let puppies = realm.objects(Dog.self).filter("age < 2") // Give all puppies to Ali. person.setValue(puppies, forKey: "dogs") } ``` ### Upsert an Object An **upsert** either inserts or updates an object depending on whether the object already exists. Upserts require the data model to have a primary key. #### Objective-C To upsert an object, call `-[RLMRealm addOrUpdateObject:]`. ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ Person *jones = [[Person alloc] initWithValue:@{@"_id": @1234, @"name": @"Jones"}]; // Add a new person to the realm. Since nobody with ID 1234 // has been added yet, this adds the instance to the realm. [realm addOrUpdateObject:jones]; Person *bowie = [[Person alloc] initWithValue:@{@"_id": @1234, @"name": @"Bowie"}]; // Judging by the ID, it's the same person, just with a different name. // This overwrites the original entry (i.e. Jones -> Bowie). [realm addOrUpdateObject:bowie]; }]; ``` #### Swift To upsert an object, call `Realm.add(_:update:)` with the second parameter, update policy, set to `.modified`. ```swift let realm = try! Realm() try! realm.write { let person1 = Person(value: ["id": 1234, "name": "Jones"]) // Add a new person to the realm. Since nobody with ID 1234 // has been added yet, this adds the instance to the realm. realm.add(person1, update: .modified) let person2 = Person(value: ["id": 1234, "name": "Bowie"]) // Judging by the ID, it's the same person, just with a // different name. When `update` is: // - .modified: update the fields that have changed. // - .all: replace all of the fields regardless of // whether they've changed. // - .error: throw an exception if a key with the same // primary key already exists. realm.add(person2, update: .modified) } ``` You can also partially update an object by passing the primary key and a subset of the values to update: #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ // Only update the provided values. // Note that the "name" property will remain the same // for the person with primary key "_id" 123. [Person createOrUpdateModifiedInRealm:realm withValue:@{@"_id": @123, @"dogs": @[@[@"Buster", @5]]}]; }]; ``` #### Swift ```swift let realm = try! Realm() try! realm.write { // Use .modified to only update the provided values. // Note that the "name" property will remain the same // for the person with primary key "id" 123. realm.create(Person.self, value: ["id": 123, "dogs": [["Buster", 5]]], update: .modified) } ``` ### Update a Map/Dictionary You can update a realm `map` as you would a standard [Dictionary](https://developer.apple.com/documentation/swift/dictionary): ```swift let realm = try! Realm() // Find the dog we want to update let wolfie = realm.objects(Dog.self).where { $0.name == "Wolfie" }.first! print("Wolfie's favorite park in New York is: \(wolfie.favoriteParksByCity["New York"])") XCTAssertTrue(wolfie.favoriteParksByCity["New York"] == "Domino Park") // Update values for keys, or add values if the keys do not currently exist try! realm.write { wolfie.favoriteParksByCity["New York"] = "Washington Square Park" wolfie.favoriteParksByCity.updateValue("A Street Park", forKey: "Boston") wolfie.favoriteParksByCity.setValue("Little Long Pond", forKey: "Seal Harbor") } XCTAssertTrue(wolfie.favoriteParksByCity["New York"] == "Washington Square Park") ``` ### Update a MutableSet Property You can `insert` elements into a `MutableSet` during write transactions to add them to the property. If you are working with multiple sets, you can also insert or remove set elements contained in one set from the other set. Alternately, you can mutate a set to contain only the common elements from both. ```swift let realm = try! Realm() // Record a dog's name, current city, and store it to the cities visited. let dog = Dog() dog.name = "Maui" dog.currentCity = "New York" try! realm.write { realm.add(dog) dog.citiesVisited.insert(dog.currentCity) } // Update the dog's current city, and add it to the set of cities visited. try! realm.write { dog.currentCity = "Toronto" dog.citiesVisited.insert(dog.currentCity) } XCTAssertEqual(dog.citiesVisited.count, 2) // If you're operating with two sets, you can insert the elements from one set into another set. // The dog2 set contains one element that isn't present in the dog set. try! realm.write { dog.citiesVisited.formUnion(dog2.citiesVisited) } XCTAssertEqual(dog.citiesVisited.count, 3) // Or you can remove elements that are present in the second set. This removes the one element // that we added above from the dog2 set. try! realm.write { dog.citiesVisited.subtract(dog2.citiesVisited) } XCTAssertEqual(dog.citiesVisited.count, 2) // If the sets contain common elements, you can mutate the set to only contain those common elements. // In this case, the two sets contain no common elements, so this set should now contain 0 items. try! realm.write { dog.citiesVisited.formIntersection(dog2.citiesVisited) } XCTAssertEqual(dog.citiesVisited.count, 0) ``` ### Update an AnyRealmValue Property You can update an AnyRealmValue property through assignment, but you must specify the type of the value when you assign it. The Realm Swift SDK provides an `AnyRealmValue enum` that iterates through all of the types the AnyRealmValue can store. ```swift let realm = try! Realm() // Get a dog to update let rex = realm.objects(Dog.self).where { $0.name == "Rex" }.first! try! realm.write { // As with creating an object with an AnyRealmValue, you must specify the // type of the value when you update the property. rex.companion = .object(Dog(value: ["name": "Regina"])) } ``` ### Update an Embedded Object Property To update a property in an embedded object, modify the property in a write transaction. If the embedded object is null, updating an embedded object property has no effect. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock: ^{ Contact *contact = [Contact objectInRealm:realm forPrimaryKey:[[RLMObjectId alloc] initWithString:@"5f481c21f634a1f4eeaa7268" error:nil]]; contact.address.street = @"Hollywood Upstairs Medical College"; contact.address.city = @"Los Angeles"; contact.address.postalCode = @"90210"; NSLog(@"Updated contact: %@", contact); }]; ``` #### Swift ```swift // Open the default realm let realm = try! Realm() let idOfPersonToUpdate = 123 // Find the person to update by ID guard let person = realm.object(ofType: Person.self, forPrimaryKey: idOfPersonToUpdate) else { print("Person \(idOfPersonToUpdate) not found") return } try! realm.write { // Update the embedded object directly through the person // If the embedded object is null, updating these properties has no effect person.address?.street = "789 Any Street" person.address?.city = "Anytown" person.address?.postalCode = "12345" print("Updated person: \(person)") } ``` ### Overwrite an Embedded Object To overwrite an embedded object, reassign the embedded object property of a party to a new instance in a write transaction. #### Objective-C ```objectivec RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock: ^{ Contact *contact = [Contact objectInRealm:realm forPrimaryKey:[[RLMObjectId alloc] initWithString:@"5f481c21f634a1f4eeaa7268" error:nil]]; Address *newAddress = [[Address alloc] init]; newAddress.street = @"Hollywood Upstairs Medical College"; newAddress.city = @"Los Angeles"; newAddress.country = @"USA"; newAddress.postalCode = @"90210"; contact.address = newAddress; NSLog(@"Updated contact: %@", contact); }]; ``` #### Swift ```swift // Open the default realm let realm = try! Realm() let idOfPersonToUpdate = 123 // Find the person to update by ID guard let person = realm.object(ofType: Person.self, forPrimaryKey: idOfPersonToUpdate) else { print("Person \(idOfPersonToUpdate) not found") return } try! realm.write { let newAddress = Address() newAddress.street = "789 Any Street" newAddress.city = "Anytown" newAddress.country = "USA" newAddress.postalCode = "12345" // Overwrite the embedded object person.address = newAddress print("Updated person: \(person)") } ``` ## Update an Object Asynchronously You can use Swift concurrency features to asynchronously update objects using an actor-isolated realm. This function from the example `RealmActor` defined on the Use Realm with Actors page shows how you might update an object in an actor-isolated realm: ```swift func updateTodo(_id: ObjectId, name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": _id, "name": name, "owner": owner, "status": status ], update: .modified) } } ``` And you might perform this update using Swift's async syntax: ```swift let actor = try await RealmActor() // Read objects in functions isolated to the actor and pass primitive values to the caller func getObjectId(in actor: isolated RealmActor, forTodoNamed name: String) async -> ObjectId { let todo = actor.realm.objects(Todo.self).where { $0.name == name }.first! return todo._id } let objectId = await getObjectId(in: actor, forTodoNamed: "Keep it safe") try await actor.updateTodo(_id: objectId, name: "Keep it safe", owner: "Frodo", status: "Completed") ``` This operation does not block or perform I/O on the calling thread. For more information about writing to realm using Swift concurrency features, refer to Use Realm with Actors - Swift SDK. ## Update Properties through Class Projections ### Change Class Projection Properties You can make changes to a class projection's properties in a write transaction. ```swift // Retrieve all class projections of the given type `PersonProjection` // and filter for the first class projection where the `firstName` property // value is "Jason" let person = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })! // Update class projection property in a write transaction try! realm.write { person.firstName = "David" } ``` ================================================ FILE: docs/guides/logging.md ================================================ # Logging - Swift SDK You can set or change your app's log level when developing or debugging your application. You might want to change the log level to log different amounts of data depending on your development needs. ## Set or Change the Realm Log Level You can set the level of detail reported by the Realm Swift SDK. Set the log level for the default logger with `Logger.shared.level`: ```swift Logger.shared.level = .trace ``` The `RLMLogLevel` enum represents the different levels of logging you can configure. You can change the log level to increase or decrease verbosity at different points in your code. ```swift // Set a log level that isn't too verbose Logger.shared.level = .warn // Later, when trying to debug something, change the log level for more verbosity Logger.shared.level = .trace ``` ## Turn Off Logging The default log threshold level for the Realm Swift SDK is `.info`. This displays some information in the console. You can disable logging entirely by setting the log level to `.off`: ```swift Logger.shared.level = .off ``` ## Customize the Logging Function Initialize an instance of a `Logger` and define the function to use for logging. ```swift // Create an instance of `Logger` and define the log function to invoke. let logger = Logger(level: .detail) { level, message in // You may pass log information to a logging service, or // you could simply print the logs for debugging. Define // the log function that makes sense for your application. print("REALM DEBUG: \(Date.now) \(level) \(message) \n") } ``` > Tip: > To diagnose and troubleshoot errors while developing your application, set the log level to `debug` or `trace`. For production deployments, decrease the log level for improved performance. > You can set a logger as the default logger for your app with `Logger.shared`. After you set the default logger, you can change the log level during the app lifecycle as needed. ```swift let logger = Logger(level: .info) { level, message in // You may pass log information to a logging service, or // you could simply print the logs for debugging. Define // the log function that makes sense for your application. print("REALM DEBUG: \(Date.now) \(level) \(message) \n") } // Set a logger as the default Logger.shared = logger // After setting a default logger, you can change // the log level at any point during the app lifecycle Logger.shared.level = .debug ``` ================================================ FILE: docs/guides/model-data/change-an-object-model.md ================================================ # Change an Object Model - Swift SDK ## Overview When you update your object schema, you must increment the schema version and perform a migration. > Seealso: > This page provides general Swift and Objective-C migration examples. If you are using Realm with SwiftUI, see the SwiftUI-specific migration examples. > If your schema update adds optional properties or removes properties, Realm can perform the migration automatically. You only need to increment the `schemaVersion`. For more complex schema updates, you must also manually specify the migration logic in a `migrationBlock`. This might include changes such as: - Adding required properties that must be populated with default values - Combining fields - Renaming a field - Changing a field's type - Converting from an object to an embedded object > Tip: > When developing or debugging your application, you may prefer to delete the realm instead of migrating it. Use the `deleteRealmIfMigrationNeeded` flag to delete the database automatically when a schema mismatch would require a migration. > > Never release an app to production with this flag set to `true`. > ## Schema Version A **schema version** identifies the state of a Realm Schema at some point in time. Realm tracks the schema version of each realm and uses it to map the objects in each realm to the correct schema. Schema versions are integers that you may include in the realm configuration when you open a realm. If a client application does not specify a version number when it opens a realm then the realm defaults to version `0`. > Important: > Migrations must update a realm to a higher schema version. Realm will throw an error if a client application opens a realm with a schema version that is lower than the realm's current version or if the specified schema version is the same as the realm's current version but includes different object schemas. > ### Migrations Local migrations have access to the existing Realm Schema, version, and objects and define logic that incrementally updates the realm to its new schema version. To perform a local migration you must specify a new schema version that is higher than the current version and provide a migration function when you open the out-of-date realm. In iOS, you can update underlying data to reflect schema changes using manual migrations. During such a manual migration, you can define new and deleted properties when they are added or removed from your schema. ## Automatically Update Schema ### Add a Property Realm can automatically migrate added properties, but you must specify an updated schema version when you make these changes. > Note: > Realm does not automatically set values for new required properties. You must use a migration block to set default values for new required properties. For new optional properties, existing records can have null values. This means you don't need a migration block when adding optional properties. > > Example: > A realm using schema version `1` has a `Person` object type that has first name, last name, and age properties: > > #### Objective-C > > ```objectivec > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > @interface Person : RLMObject > @property NSString *firstName; > @property NSString *lastName; > @property int age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"firstName", @"lastName", @"age"]; > } > @end > > ``` > > > #### Swift > > ```swift > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > class Person: Object { > @Persisted var firstName = "" > @Persisted var lastName = "" > @Persisted var age = 0 > } > > ``` > > > The developer decides that the `Person` class needs an `email` field and updates the schema. > > #### Objective-C > > ```objectivec > // In a new version, you add a property > // on the Person model. > @interface Person : RLMObject > @property NSString *firstName; > @property NSString *lastName; > // Add a new "email" property. > @property NSString *email; > // New properties can be migrated > // automatically, but must update the schema version. > @property int age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"firstName", @"lastName", @"email", @"age"]; > } > @end > > ``` > > > #### Swift > > ```swift > // In a new version, you add a property > // on the Person model. > class Person: Object { > @Persisted var firstName = "" > @Persisted var lastName = "" > // Add a new "email" property. > @Persisted var email: String? > // New properties can be migrated > // automatically, but must update the schema version. > @Persisted var age = 0 > > } > > ``` > > > Realm automatically migrates the realm to conform to the updated `Person` schema. But the developer must set the realm's schema version to `2`. > > #### Objective-C > > ```objectivec > // When you open the realm, specify that the schema > // is now using a newer version. > RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; > // Set the new schema version > config.schemaVersion = 2; > // Use this configuration when opening realms > [RLMRealmConfiguration setDefaultConfiguration:config]; > RLMRealm *realm = [RLMRealm defaultRealm]; > > ``` > > > #### Swift > > ```swift > // When you open the realm, specify that the schema > // is now using a newer version. > let config = Realm.Configuration( > schemaVersion: 2) > // Use this configuration when opening realms > Realm.Configuration.defaultConfiguration = config > let realm = try! Realm() > > ``` > > ### Delete a Property To delete a property from a schema, remove the property from the object's class and set a `schemaVersion` of the realm's configuration object. Deleting a property will not impact existing objects. > Example: > A realm using schema version `1` has a `Person` object type that has first name, last name, and age properties: > > #### Objective-C > > ```objectivec > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > @interface Person : RLMObject > @property NSString *firstName; > @property NSString *lastName; > @property int age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"firstName", @"lastName", @"age"]; > } > @end > > ``` > > > #### Swift > > ```swift > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > class Person: Object { > @Persisted var firstName = "" > @Persisted var lastName = "" > @Persisted var age = 0 > } > > ``` > > > The developer decides that the `Person` does not need the `age` field and updates the schema. > > #### Objective-C > > ```objectivec > // In a new version, you remove a property > // on the Person model. > @interface Person : RLMObject > @property NSString *firstName; > @property NSString *lastName; > // Remove the "age" property. > // @property int age; > // Removed properties can be migrated > // automatically, but must update the schema version. > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"firstName", @"lastName"]; > } > @end > > ``` > > > #### Swift > > ```swift > // In a new version, you remove a property > // on the Person model. > class Person: Object { > @Persisted var firstName = "" > @Persisted var lastName = "" > // Remove the "age" property. > // @Persisted var age = 0 > // Removed properties can be migrated > // automatically, but must update the schema version. > > } > > ``` > > > Realm automatically migrates the realm to conform to the updated `Person` schema. But the developer must set the realm's schema version to `2`. > > #### Objective-C > > ```objectivec > // When you open the realm, specify that the schema > // is now using a newer version. > RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; > // Set the new schema version > config.schemaVersion = 2; > // Use this configuration when opening realms > [RLMRealmConfiguration setDefaultConfiguration:config]; > RLMRealm *realm = [RLMRealm defaultRealm]; > > ``` > > > #### Swift > > ```swift > // When you open the realm, specify that the schema > // is now using a newer version. > let config = Realm.Configuration( > schemaVersion: 2) > // Use this configuration when opening realms > Realm.Configuration.defaultConfiguration = config > let realm = try! Realm() > > ``` > > > Tip: > SwiftUI developers may see an error that a migration is required when they add or delete properties. This is related to the lifecycle in SwiftUI. The Views are laid out, and then the `.environment` modifier sets the config. > > To resolve a migration error in these circumstances, pass `Realm.Configuration(schemaVersion: )` into the `ObservedResults` constructor. > ## Manually Migrate Schema For more complex schema updates, Realm requires a manual migration for old instances of a given object to the new schema. ### Rename a Property To rename a property during a migration, use the `Migration.renameProperty(onType:from:to:)` method. Realm applies any new nullability or indexing settings during the rename operation. > Example: > Rename `age` to `yearsSinceBirth` within a `migrationBlock`. > > #### Objective-C > > ```objectivec > RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; > config.schemaVersion = 2; > config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { > if (oldSchemaVersion < 2) { > // Rename the "age" property to "yearsSinceBirth". > // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`. > [migration renamePropertyForClass:[Person className] oldName:@"age" newName:@"yearsSinceBirth"]; > } > }; > > ``` > > > #### Swift > > ```swift > let config = Realm.Configuration( > schemaVersion: 2, > migrationBlock: { migration, oldSchemaVersion in > if oldSchemaVersion < 2 { > // Rename the "age" property to "yearsSinceBirth". > // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`. > migration.renameProperty(onType: Person.className(), from: "age", to: "yearsSinceBirth") > } > }) > > ``` > > ### Modify Properties > Tip: > You can use the `deleteRealmIfMigrationNeeded` method to delete the realm if it would require a migration. This can be useful during development when you need to iterate quickly and don't want to perform the migration. > To define custom migration logic, set the `migrationBlock` property of the `Configuration` when opening a realm. The migration block receives a `Migration object` that you can use to perform the migration. You can use the Migration object's `enumerateObjects(ofType:_:)` method to iterate over and update all instances of a given Realm type in the realm. > Example: > A realm using schema version `1` has a `Person` object type that has separate fields for first and last names: > > #### Objective-C > > ```objectivec > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > @interface Person : RLMObject > @property NSString *firstName; > @property NSString *lastName; > @property int age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"firstName", @"lastName", @"age"]; > } > @end > > ``` > > > #### Swift > > ```swift > // In the first version of the app, the Person model > // has separate fields for first and last names, > // and an age property. > class Person: Object { > @Persisted var firstName = "" > @Persisted var lastName = "" > @Persisted var age = 0 > } > > ``` > > > The developer decides that the `Person` class should use a combined `fullName` field instead of the separate `firstName` and `lastName` fields and updates the schema. > > To migrate the realm to conform to the updated `Person` schema, the developer sets the realm's schema version to `2` and defines a migration function to set the value of `fullName` based on the existing `firstName` and `lastName` properties. > > #### Objective-C > > ```objectivec > // In version 2, the Person model has one > // combined field for the full name and age as a Int. > // A manual migration will be required to convert from > // version 1 to this version. > @interface Person : RLMObject > @property NSString *fullName; > @property int age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"fullName", @"age"]; > } > @end > > ``` > > ```objectivec > RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; > // Set the new schema version > config.schemaVersion = 2; > config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { > if (oldSchemaVersion < 2) { > // Iterate over every 'Person' object stored in the Realm file to > // apply the migration > [migration enumerateObjects:[Person className] > block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { > // Combine name fields into a single field > newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", > oldObject[@"firstName"], > oldObject[@"lastName"]]; > }]; > } > }; > > // Tell Realm to use this new configuration object for the default Realm > [RLMRealmConfiguration setDefaultConfiguration:config]; > > // Now that we've told Realm how to handle the schema change, opening the realm > // will automatically perform the migration > RLMRealm *realm = [RLMRealm defaultRealm]; > > ``` > > > #### Swift > > ```swift > // In version 2, the Person model has one > // combined field for the full name and age as a Int. > // A manual migration will be required to convert from > // version 1 to this version. > class Person: Object { > @Persisted var fullName = "" > @Persisted var age = 0 > } > > ``` > > ```swift > // In application(_:didFinishLaunchingWithOptions:) > let config = Realm.Configuration( > schemaVersion: 2, // Set the new schema version. > migrationBlock: { migration, oldSchemaVersion in > if oldSchemaVersion < 2 { > // The enumerateObjects(ofType:_:) method iterates over > // every Person object stored in the Realm file to apply the migration > migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in > // combine name fields into a single field > let firstName = oldObject!["firstName"] as? String > let lastName = oldObject!["lastName"] as? String > newObject!["fullName"] = "\(firstName!) \(lastName!)" > } > } > } > ) > > // Tell Realm to use this new configuration object for the default Realm > Realm.Configuration.defaultConfiguration = config > > // Now that we've told Realm how to handle the schema change, opening the file > // will automatically perform the migration > let realm = try! Realm() > > ``` > > > Later, the developer decides that the `age` field should be of type `String` rather than `Int` and updates the schema. > > To migrate the realm to conform to the updated `Person` schema, the developer sets the realm's schema version to `3` and adds a conditional to the migration function so that the function defines how to migrate from any previous version to the new one. > > #### Objective-C > > ```objectivec > // In version 3, the Person model has one > // combined field for the full name and age as a String. > // A manual migration will be required to convert from > // version 2 to this version. > @interface Person : RLMObject > @property NSString *fullName; > @property NSString *age; > @end > > @implementation Person > + (NSArray *)requiredProperties { > return @[@"fullName", @"age"]; > } > @end > > ``` > > ```objectivec > RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; > // Set the new schema version > config.schemaVersion = 3; > config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { > if (oldSchemaVersion < 2) { > // Previous Migration. > [migration enumerateObjects:[Person className] > block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { > newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", > oldObject[@"firstName"], > oldObject[@"lastName"]]; > }]; > } > if (oldSchemaVersion < 3) { > // New Migration > [migration enumerateObjects:[Person className] > block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { > // Make age a String instead of an Int > newObject[@"age"] = [oldObject[@"age"] stringValue]; > }]; > } > }; > > // Tell Realm to use this new configuration object for the default Realm > [RLMRealmConfiguration setDefaultConfiguration:config]; > > // Now that we've told Realm how to handle the schema change, opening the realm > // will automatically perform the migration > RLMRealm *realm = [RLMRealm defaultRealm]; > > ``` > > > #### Swift > > ```swift > // In version 3, the Person model has one > // combined field for the full name and age as a String. > // A manual migration will be required to convert from > // version 2 to this version. > class Person: Object { > @Persisted var fullName = "" > @Persisted var age = "0" > } > > ``` > > ```swift > // In application(_:didFinishLaunchingWithOptions:) > let config = Realm.Configuration( > schemaVersion: 3, // Set the new schema version. > migrationBlock: { migration, oldSchemaVersion in > if oldSchemaVersion < 2 { > // Previous Migration. > migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in > let firstName = oldObject!["firstName"] as? String > let lastName = oldObject!["lastName"] as? String > newObject!["fullName"] = "\(firstName!) \(lastName!)" > } > } > if oldSchemaVersion < 3 { > // New Migration. > migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in > // Make age a String instead of an Int > newObject!["age"] = "\(oldObject!["age"] ?? 0)" > } > } > } > ) > > // Tell Realm to use this new configuration object for the default Realm > Realm.Configuration.defaultConfiguration = config > > // Now that we've told Realm how to handle the schema change, opening the file > // will automatically perform the migration > let realm = try! Realm() > > ``` > > > Tip: > Avoid nesting or otherwise skipping `if (oldSchemaVersion < X)` statements in migration blocks. This ensures that all updates can be applied in the correct order, no matter which schema version a client starts from. The goal is to define migration logic which can transform data from any outdated schema version to match the current schema. > ### Convert from Object to EmbeddedObject Embedded objects cannot exist independently of a parent object. When changing an Object to an EmbeddedObject, the migration block must ensure that every embedded object has exactly one backlink to a parent object. Having no backlinks or multiple backlinks raises the following exceptions: ``` At least one object does not have a backlink (data would get lost). ``` ``` At least one object does have multiple backlinks. ``` > Seealso: > Define an Embedded Object Property > ## Additional Migration Examples Please check out the additional migration examples on the [realm-swift repo](https://github.com/realm/realm-swift/tree/master/examples/ios/swift/Migration). ================================================ FILE: docs/guides/model-data/model-data.md ================================================ # Model Data - Swift SDK ## Object Types & Schemas Realm applications model data as objects composed of field-value pairs that each contain one or more supported data types. Realm objects are regular Swift or Objective-C classes, but they also bring a few additional features like live queries. The Swift SDK memory maps Realm objects directly to native Swift or Objective-C objects, which means there's no need to use a special data access library, such as an [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping). Instead, you can work with Realm objects as you would any other class instance. Every Realm object conforms to a specific **object type**, which is essentially a class that defines the properties and relationships for objects of that type. Realm guarantees that all objects in a realm conform to the schema for their object type and validates objects whenever they're created, modified, or deleted. > Example: > The following schema defines a `Dog` object type with a string name, optional string breed, date of birth, and primary key ID. > > #### Objective-C > > ```objectivec > // A dog has an _id primary key, a string name, an optional > // string breed, and a date of birth. > @interface Dog : RLMObject > @property RLMObjectId *_id; > @property NSString *name; > @property NSString *breed; > @property NSDate *dateOfBirth; > @end > > @implementation Dog > + (NSString *)primaryKey { > return @"_id"; > } > > + (NSArray *)requiredProperties { > return @[ > @"_id", @"name", @"dateOfBirth" > ]; > } > @end > > ``` > > > #### Swift > > ```swift > // A dog has an _id primary key, a string name, an optional > // string breed, and a date of birth. > class Dog: Object { > @Persisted(primaryKey: true) var _id: ObjectId > @Persisted var name = "" > @Persisted var breed: String? > @Persisted var dateOfBirth = Date() > } > > ``` > > ### Realm Schema A **realm schema** is a list of valid object schemas that a realm may contain. Every Realm object must conform to an object type that's included in its realm's schema. By default, the Swift SDK automatically adds all classes in your project that derive from [RLMObject](https://www.mongodb.com/docs/realm-sdks/objc/latest/Classes/RLMObject.html) or [RLMEmbeddedObject](https://www.mongodb.com/docs/realm-sdks/objc/latest/Classes/RLMEmbeddedObject.html) to the realm schema. > Tip: > To control which classes Realm adds to a realm schema, see Provide a Subset of Classes to a Realm. > If a realm already contains data when you open it, Realm validates each object to ensure that an object schema was provided for its type and that it meets all of the constraints specified in the schema. > Tip: > For code examples that show how to configure and open a realm in the Swift SDK, see Configure & Open a Realm - Swift SDK. > ### Model Inheritance You can subclass Realm models to share behavior between classes, but there are limitations. In particular, Realm does not allow you to: - Cast between polymorphic classes: subclass to subclass, subclass to parent, parent to subclass - Query on multiple classes simultaneously: for example, "get all instances of parent class and subclass" - Multi-class containers: `List` and `Results` with a mixture of parent and subclass > Tip: > Check out the [code samples](https://github.com/realm/realm-swift/issues/1109#issuecomment-143834756) for working around these limitations. > > Version added: 10.10.0While you can't mix and property declarations within a class, you can mix the notation styles across base and subclasses. For example, a base class could have a property, and a subclass could have an property, with both persisted. However, the property would be ignored if the property were within the same base or subclass. ### Swift Structs Realm does not support Swift structs as models for a variety of reasons. Realm's design focuses on "live" objects. This concept is not compatible with value type structs. By design, Realm provides features that are incompatible with these semantics, such as: - Live data - Reactive APIs - Low memory footprint of data - Good operation performance - Lazy and cheap access to partial data - Lack of data serialization/deserialization - Keeping potentially complex object graphs synchronized That said, it is sometimes useful to detach objects from their backing realm. This typically isn't an ideal design decision. Instead, developers use this as a workaround for temporary limitations in our library. You can use key-value coding to initialize an unmanaged object as a copy of a managed object. Then, you can work with that unmanaged object like any other [NSObject](https://developer.apple.com/documentation/objectivec/nsobject). ```swift let standaloneModelObject = MyModel(value: persistedModelObject) ``` ## Properties Your Realm object model is a collection of properties. On the most basic level, when you create your model, your declarations give Realm information about each property: - The data type and whether the property is optional or required - Whether Realm should store or ignore the property - Whether the property is a primary key or should be indexed Properties are also the mechanism for establishing relationships between Realm object types. The Realm Swift SDK uses reflection to determine the properties in your models at runtime. Your project must not set `SWIFT_REFLECTION_METADATA_LEVEL = none`, or Realm cannot discover children of types, such as properties and enum cases. Reflection is enabled by default if your project does not specifically set a level for this setting. ## View Models with Realm > Version added: 10.21.0 You can work with a subset of your Realm object's properties by creating a class projection. A class projection is a class that passes through or transforms some or all of your Realm object's properties. Class projection enables you to build view models that use an abstraction of your object model. This simplifies using and testing Realm objects in your application. With class projection, you can use a subset of your object's properties directly in the UI or transform them. When you use a class projection for this, you get all the benefits of Realm's live objects: - The class-projected object live updates - You can observe it for changes - You can apply changes directly to the properties in write transactions > Seealso: > Define a Class Projection > ## Relationships Realm doesn't use bridge tables or explicit joins to define relationships as you would in a relational database. Realm handles relationships through embedded objects or reference properties to other Realm objects. You read from and write to these properties directly. This makes querying relationships as performant as querying against any other property. Realm supports **to-one**, **to-many**, and **inverse** relationships. ### To-One Relationship A **to-one** relationship means that an object relates to one other object. You define a to-one relationship for an object type in its object schema. Specify a property where the type is the related Realm object type. For example, a dog might have a to-one relationship with a favorite toy. > Tip: > To learn how to define a to-one relationship, see Define a To-One Relationship Property. > ### To-Many Relationship A **to-many** relationship means that an object relates to more than one other object. In Realm, a to-many relationship is a list of references to other objects. For example, a person might have many dogs. A `List` represents the to-many relationship between two Realm types. Lists are mutable: within a write transaction, you can add and remove elements to and from a list. Lists are not associated with a query and are usually declared as a property of an object model. > Tip: > To learn how to define a to-many relationship, see Define a To-Many Relationship Property. > ### Inverse Relationship Relationship definitions in Realm are unidirectional. An **inverse relationship** links an object back to an object that refers to it. You must explicitly define a property in the object's model as an inverse relationship. Inverse relationships can link back to objects in a to-one or to-many relationship. A `LinkingObjects` collection represents the inverse relationship between two Realm types. You cannot directly add or remove items from a LinkingObjects collection. Inverse relationships automatically update themselves with corresponding backlinks. You can find the same set of Realm objects with a manual query, but the inverse relationship field reduces boilerplate query code and capacity for error. For example, consider a task tracker with the to-many relationship "User has many Tasks". This does not automatically create the inverse relationship "Task belongs to User". To create the inverse relationship, add a User property on the Task that points back to the task's owner. When you specify the inverse relationship from task to user, you can query on that. If you don't specify the inverse relationship, you must run a separate query to look up the user to whom the task is assigned. > Important: > You cannot manually set the value of an inverse relationship property. Instead, Realm updates implicit relationships when you add or remove an object in the relationship. > Relationships can be many-to-one or many-to-many. So following inverse relationships can result in zero, one, or many objects. > Tip: > To learn how to define an inverse relationship, see Define an Inverse Relationship Property. > ================================================ FILE: docs/guides/model-data/object-models.md ================================================ # Define a Realm Object Model - Swift SDK ## Define a New Object Type #### Objective-C You can define a Realm object by deriving from the `RLMObject` or `RLMEmbeddedObject` class. The name of the class becomes the table name in the realm, and properties of the class persist in the database. This makes it as easy to work with persisted objects as it is to work with regular Objective-C objects. ```objectivec // A dog has an _id primary key, a string name, an optional // string breed, and a date of birth. @interface Dog : RLMObject @property RLMObjectId *_id; @property NSString *name; @property NSString *breed; @property NSDate *dateOfBirth; @end @implementation Dog + (NSString *)primaryKey { return @"_id"; } + (NSArray *)requiredProperties { return @[ @"_id", @"name", @"dateOfBirth" ]; } @end ``` #### Swift You can define a Realm object by deriving from the `Object` or `EmbeddedObject` class. The name of the class becomes the table name in the realm, and properties of the class persist in the database. This makes it as easy to work with persisted objects as it is to work with regular Swift objects. ```swift // A dog has an _id primary key, a string name, an optional // string breed, and a date of birth. class Dog: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var name = "" @Persisted var breed: String? @Persisted var dateOfBirth = Date() } ``` > Note: > Class names are limited to a maximum of 57 UTF-8 characters. > ## Declare Properties When you declare the property attributes of a class, you can specify whether or not those properties should be managed by the realm. **Managed properties** are stored or updated in the database. **Ignored properties** are not stored to the database. You can mix managed and ignored properties within a class. The syntax to mark properties as managed or ignored varies depending on which version of the SDK you use. ### Persisted Property Attributes > Version added: 10.10.0The declaration style replaces the , , and declaration notations from older versions of the SDK. For an older version of the SDK, see: . Declare model properties that you want to store to the database as `@Persisted`. This enables them to access the underlying database data. When you declare any properties as `@Persisted` within a class, the other properties within that class are automatically ignored. If you mix `@Persisted` and `@objc dynamic` property declarations within a class definition, any property attributes marked as `@objc dynamic` will be ignored. > Seealso: > Our Supported Property Types page contains a property declaration cheatsheet. > ### Objective-C Dynamic Property Attributes > Version changed: 10.10.0This property declaration information is for versions of the SDK before 10.10.0. Declare dynamic Realm model properties in the Objective-C runtime. This enables them to access the underlying database data. You can either: - Use `@objc dynamic var` to declare individual properties - Use `@objcMembers` to declare a class. Then, declare individual properties with `dynamic var`. Use `let` to declare `LinkingObjects`, `List`, `RealmOptional` and `RealmProperty`. The Objective-C runtime cannot represent these generic properties. > Version changed: 10.8.0 > `RealmProperty` replaces `RealmOptional` > > Seealso: > Our Supported Property Types page contains a property declaration cheatsheet. > > Tip: > For reference on which types Realm supports for use as properties, see Supported Property Types. > #### Swift When declaring non-generic properties, use the `@Persisted` annotation. The `@Persisted` attribute turns Realm model properties into accessors for the underlying database data. #### Objective-C Declare properties on your object type as you would on a normal Objective-C interface. In order to use your interface in a Realm array, pass your interface name to the `RLM_COLLECTION_TYPE()` macro. You can put this at the bottom of your interface's header file. The `RLM_COLLECTION_TYPE()` macro creates a protocol that allows you to tag `RLMArray` with your type: ```objectivec // Task.h @interface Task : RLMObject @property NSString *description; @end // Define an RLMArray type RLM_COLLECTION_TYPE(Task) // User.h // #include "Task.h" @interface User : RLMObject @property NSString *name; // Use RLMArray to have a list of tasks // Note the required double tag () @property RLMArray *tasks; @end ``` #### Swift Pre 10.10.0 When declaring non-generic properties, use the `@objc dynamic var` annotation. The `@objc dynamic var` attribute turns Realm model properties into accessors for the underlying database data. If the class is declared as `@objcMembers` (Swift 4 or later), you can declare properties as `dynamic var` without `@objc`. To declare properties of generic types `LinkingObjects`, `List`, and `RealmProperty`, use `let`. Generic properties cannot be represented in the Objective‑C runtime, which Realm uses for dynamic dispatch of dynamic properties. > Note: > Property names are limited to a maximum of 63 UTF-8 characters. > ### Specify an Optional/Required Property #### Swift You can declare properties as optional or required (non-optional) using standard Swift syntax. ```swift class Person: Object { // Required string property @Persisted var name = "" // Optional string property @Persisted var address: String? // Required numeric property @Persisted var ageYears = 0 // Optional numeric property @Persisted var heightCm: Float? } ``` #### Objective-C To declare a given property as required, implement the `requiredProperties` method and return an array of required property names. ```objectivec @interface Person : RLMObject // Required property - included in `requiredProperties` // return value array @property NSString *name; // Optional string property - not included in `requiredProperties` @property NSString *address; // Required numeric property @property int ageYears; // Optional numeric properties use NSNumber tagged // with RLMInt, RLMFloat, etc. @property NSNumber *heightCm; @end @implementation Person // Specify required pointer-type properties here. // Implicitly required properties (such as properties // of primitive types) do not need to be named here. + (NSArray *)requiredProperties { return @[@"name"]; } @end ``` #### Swift Pre 10.10.0 > Version changed: 10.8.0 > `RealmProperty` replaces `RealmOptional` > You can declare `String`, `Date`, `Data`, and `ObjectId` properties as optional or required (non-optional) using standard Swift syntax. Declare optional numeric types using the `RealmProperty` type. ```swift class Person: Object { // Required string property @objc dynamic var name = "" // Optional string property @objc dynamic var address: String? // Required numeric property @objc dynamic var ageYears = 0 // Optional numeric property let heightCm = RealmProperty() } ``` RealmProperty supports `Int`, `Float`, `Double`, `Bool`, and all of the sized versions of `Int` (`Int8`, `Int16`, `Int32`, `Int64`). ### Specify a Primary Key You can designate a property as the **primary key** of your class. Primary keys allow you to efficiently find, update, and upsert objects. Primary keys are subject to the following limitations: - You can define only one primary key per object model. - Primary key values must be unique across all instances of an object in a realm. Realm throws an error if you try to insert a duplicate primary key value. - Primary key values are immutable. To change the primary key value of an object, you must delete the original object and insert a new object with a different primary key value. - Embedded objects cannot define a primary key. #### Swift Declare the property with `primaryKey: true` on the `@Persisted` notation to set the model's primary key. ```swift class Project: Object { @Persisted(primaryKey: true) var id = 0 @Persisted var name = "" } ``` #### Objective-C Override `+[RLMObject primaryKey]` to set the model's primary key. ```objectivec @interface Project : RLMObject @property NSInteger id; // Intended primary key @property NSString *name; @end @implementation Project // Return the name of the primary key property + (NSString *)primaryKey { return @"id"; } @end ``` #### Swift Pre 10.10.0 Override `Object.primaryKey()` to set the model's primary key. ```swift class Project: Object { @objc dynamic var id = 0 @objc dynamic var name = "" // Return the name of the primary key property override static func primaryKey() -> String? { return "id" } } ``` ### Index a Property You can create an index on a given property of your model. Indexes speed up queries using equality and IN operators. They make insert and update operation speed slightly slower. Indexes use memory and take up more space in the realm file. Each index entry is a minimum of 12 bytes. It's best to only add indexes when optimizing the read performance for specific situations. Realm supports indexing for string, integer, boolean, `Date`, `UUID`, `ObjectId`, and `AnyRealmValue` properties. > Version added: 10.8.0 > `UUID` and `AnyRealmValue` types > #### Swift To index a property, declare the property with `indexed:true` on the `@Persisted` notation. ```swift class Book: Object { @Persisted var priceCents = 0 @Persisted(indexed: true) var title = "" } ``` #### Objective-C To index a property, override `RLMObject indexedProperties` and return a list of indexed property names. ```objectivec @interface Book : RLMObject @property int priceCents; @property NSString *title; @end @implementation Book // Return a list of indexed property names + (NSArray *)indexedProperties { return @[@"title"]; } @end ``` #### Swift Pre 10.10.0 To index a property, override `Object.indexedProperties()` and return a list of indexed property names. ```swift class Book: Object { @objc dynamic var priceCents = 0 @objc dynamic var title = "" // Return a list of indexed property names override static func indexedProperties() -> [String] { return ["title"] } } ``` ### Ignore a Property Ignored properties behave exactly like normal properties. They can't be used in queries and won't trigger Realm notifications. You can still observe them using [KVO](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html). > Tip: > Realm automatically ignores read-only properties. > #### Swift > Deprecated: If you don't want to save a field in your model to its realm, leave the `@Persisted` notation off the property attribute. Additionally, if you mix `@Persisted` and `@objc dynamic` property declarations within a class, the `@objc dynamic` properties will be ignored. ```swift class Person: Object { // If some properties are marked as @Persisted, // any properties that do not have the @Persisted // annotation are automatically ignored. var tmpId = 0 // The @Persisted properties are managed @Persisted var firstName = "" @Persisted var lastName = "" // Read-only properties are automatically ignored var name: String { return "\(firstName) \(lastName)" } // If you mix the pre-10.10 property declaration // syntax `@objc dynamic` with the 10.10+ @Persisted // annotation within a class, `@objc dynamic` // properties are ignored. @objc dynamic var email = "" } ``` #### Objective-C If you don't want to save a field in your model to its realm, override `+[RLMObject ignoredProperties]` and return a list of ignored property names. ```objectivec @interface Person : RLMObject @property NSInteger tmpId; @property (readonly) NSString *name; // read-only properties are automatically ignored @property NSString *firstName; @property NSString *lastName; @end @implementation Person + (NSArray *)ignoredProperties { return @[@"tmpId"]; } - (NSString *)name { return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; } @end ``` #### Swift Pre 10.10.0 If you don't want to save a field in your model to its realm, override `Object.ignoredProperties()` and return a list of ignored property names. ```swift class Person: Object { @objc dynamic var tmpId = 0 @objc dynamic var firstName = "" @objc dynamic var lastName = "" // Read-only properties are automatically ignored var name: String { return "\(firstName) \(lastName)" } // Return a list of ignored property names override static func ignoredProperties() -> [String] { return ["tmpId"] } } ``` ### Declare Enum Properties #### Swift > Version changed: 10.10.0 > Protocol is now `PersistableEnum` rather than `RealmEnum`. > You can use enums with `@Persisted` by marking them as complying with the `PersistableEnum` protocol. A `PersistableEnum` can be any `RawRepresentable` enum whose raw type is a type that Realm supports. ```swift // Define the enum enum TaskStatusEnum: String, PersistableEnum { case notStarted case inProgress case complete } // To use the enum: class Task: Object { @Persisted var name: String = "" @Persisted var owner: String? // Required enum property @Persisted var status = TaskStatusEnum.notStarted // Optional enum property @Persisted var optionalTaskStatusEnumProperty: TaskStatusEnum? } ``` #### Swift Pre 10.10.0 Realm supports only `Int`-backed `@objc` enums. ```swift // Define the enum @objc enum TaskStatusEnum: Int, RealmEnum { case notStarted = 1 case inProgress = 2 case complete = 3 } // To use the enum: class Task: Object { @objc dynamic var name: String = "" @objc dynamic var owner: String? // Required enum property @objc dynamic var status = TaskStatusEnum.notStarted // Optional enum property let optionalTaskStatusEnumProperty = RealmProperty() } ``` > Seealso: > `RealmEnum` > ### Remap a Property Name > Version added: 10.33.0 You can map the public name of a property in your object model to a different private name to store in the realm. Declare the name you want to use in your project as the `@Persisted` property on the object model. Then, pass a dictionary containing the public and private values for the property names via the `propertiesMapping()` function. In this example, `firstName` is the public property name we use in the code throughout the project to perform CRUD operations. Using the `propertiesMapping()` function, we map that to store values using the private property name `first_name` in the realm. ```swift class Person: Object { @Persisted var firstName = "" @Persisted var lastName = "" override class public func propertiesMapping() -> [String: String] { ["firstName": "first_name", "lastName": "last_name"] } } ``` ## Define a Class Projection ### About These Examples The examples in this section use a simple data set. The two Realm object types are `Person` and an embedded object `Address`. A `Person` has a first and last name, an optional `Address`, and a list of friends consisting of other `Person` objects. An `Address` has a city and country. See the schema for these two classes, `Person` and `Address`, below: ```swift class Person: Object { @Persisted var firstName = "" @Persisted var lastName = "" @Persisted var address: Address? @Persisted var friends = List() } class Address: EmbeddedObject { @Persisted var city: String = "" @Persisted var country = "" } ``` ### How to Define a Class Projection > Version added: 10.21.0 Define a class projection by creating a class of type `Projection`. Specify the `Object` or `EmbeddedObject` base whose properties you want to use in the class projection. Use the `@Projected` property wrapper to declare a property that you want to project from a `@Persisted` property on the base object. > Note: > When you use a List or a MutableSet in a class projection, the type in the class projection should be `ProjectedCollection`. > ```swift class PersonProjection: Projection { @Projected(\Person.firstName) var firstName // Passthrough from original object @Projected(\Person.address?.city) var homeCity // Rename and access embedded object property through keypath @Projected(\Person.friends.projectTo.firstName) var firstFriendsName: ProjectedCollection // Collection mapping } ``` When you define a class projection, you can transform the original `@Persisted` property in several ways: - Passthrough: the property is the same name and type as the original object - Rename: the property has the same type as the original object, but a different name - Keypath resolution: use keypath resolution to access properties of the original object, including embedded object properties - Collection mapping: Project lists or mutable sets of `Object` s or `EmbeddedObject` s as a collection of primitive values - Exclusion: when you use a class projection, the underlying object's properties that are not `@Projected` through the class projection are excluded. This enables you to watch for changes to a class projection and not see changes for properties that are not part of the class projection. ## Define Unstructured Data > Version added: 10.51.0 Starting in SDK version 10.51.0, you can store collections of mixed data within a `AnyRealmValue` property. You can use this feature to model complex data structures, such as JSON, without having to define a strict data model. **Unstructured data** is data that doesn't easily conform to an expected schema, making it difficult or impractical to model to individual data classes. For example, your app might have highly variable data or dynamic data whose structure is unknown at runtime. Storing collections in a mixed property offers flexibility without sacrificing functionality. And you can work with them the same way you would a non-mixed collection: - You can nest mixed collections up to 100 levels. - You can query on and react to changes on mixed collections. - You can find and update individual mixed collection elements. However, storing data in mixed collections is less performant than using a structured schema or serializing JSON blobs into a single string property. To model unstructured data in your app, define the appropriate properties in your schema as AnyRealmValue types. You can then set these `AnyRealmValue` properties as a list or a dictionary collection of `AnyRealmValue` elements. Note that `AnyRealmValue` *cannot* represent a `MutableSet` or an embedded object. > Tip: > - Use a map of mixed data types when the type is unknown but each value will have a unique identifier. > - Use a list of mixed data types when the type is unknown but the order of objects is meaningful. > ================================================ FILE: docs/guides/model-data/relationships.md ================================================ # Model Relationships - Swift SDK ## Declare Relationship Properties ### Define a To-One Relationship Property A **to-one** relationship maps one property to a single instance of another object type. For example, you can model a person having at most one companion dog as a to-one relationship. Setting a relationship field to null removes the connection between objects. Realm does not delete the referenced object, though, unless it is an embedded object. > Important: > When you declare a to-one relationship in your object model, it must be an optional property. If you try to make a to-one relationship required, Realm throws an exception at runtime. > #### Objective-C ```objectivec // Dog.h @interface Dog : RLMObject @property NSString *name; // No backlink to person -- one-directional relationship @end // Define an RLMArray type RLM_COLLECTION_TYPE(Dog) // Person.h @interface Person : RLMObject @property NSString *name; // A person can have one dog @property Dog *dog; @end // Dog.m @implementation Dog @end // Person.m @implementation Person @end ``` #### Swift ```swift class Person: Object { @Persisted var name: String = "" @Persisted var birthdate: Date = Date(timeIntervalSince1970: 1) // A person can have one dog @Persisted var dog: Dog? } class Dog: Object { @Persisted var name: String = "" @Persisted var age: Int = 0 @Persisted var breed: String? // No backlink to person -- one-directional relationship } ``` > Seealso: > For more information about to-one relationships, see: To-One Relationship. > ### Define a To-Many Relationship Property A **to-many** relationship maps one property to zero or more instances of another object type. For example, you can model a person having any number of companion dogs as a to-many relationship. #### Objective-C Use `RLMArray` tagged with your target type to define your to-many relationship property. > Tip: > Remember to use the `RLM_COLLECTION_TYPE()` macro with your type to declare the RLMArray protocol for your type. > ```objectivec // Dog.h @interface Dog : RLMObject @property NSString *name; // No backlink to person -- one-directional relationship @end // Define an RLMArray type RLM_COLLECTION_TYPE(Dog) // Person.h @interface Person : RLMObject @property NSString *name; // A person can have many dogs @property RLMArray *dogs; @end // Dog.m @implementation Dog @end // Person.m @implementation Person @end ``` #### Swift Use `List` tagged with your target type to define your to-many relationship property. ```swift class Person: Object { @Persisted var name: String = "" @Persisted var birthdate: Date = Date(timeIntervalSince1970: 1) // A person can have many dogs @Persisted var dogs: List } class Dog: Object { @Persisted var name: String = "" @Persisted var age: Int = 0 @Persisted var breed: String? // No backlink to person -- one-directional relationship } ``` > Seealso: > For more information about to-many relationships, see: To-Many Relationship. > ### Define an Inverse Relationship Property An **inverse relationship** property is an automatic backlink relationship. Realm automatically updates implicit relationships whenever an object is added or removed in a corresponding to-many list or to-one relationship property. You cannot manually set the value of an inverse relationship property. #### Swift To define an inverse relationship, use `LinkingObjects` in your object model. The `LinkingObjects` definition specifies the object type and property name of the relationship that it inverts. ```swift class User: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var _partition: String = "" @Persisted var name: String = "" // A user can have many tasks. @Persisted var tasks: List } class Task: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var _partition: String = "" @Persisted var text: String = "" // Backlink to the user. This is automatically updated whenever // this task is added to or removed from a user's task list. @Persisted(originProperty: "tasks") var assignee: LinkingObjects } ``` #### Objective-C To define an inverse relationship, use `RLMLinkingObjects` in your object model. Override `+[RLMObject linkingObjectProperties]` method in your class to specify the object type and property name of the relationship that it inverts. ```objectivec // Task.h @interface Task : RLMObject @property NSString *description; @property (readonly) RLMLinkingObjects *assignees; @end // Define an RLMArray type RLM_COLLECTION_TYPE(Task) // User.h @interface User : RLMObject @property NSString *name; @property RLMArray *tasks; @end // Task.m @implementation Task + (NSDictionary *)linkingObjectsProperties { return @{ @"assignees": [RLMPropertyDescriptor descriptorWithClass:User.class propertyName:@"tasks"], }; } @end // User.m @implementation User @end ``` #### Swift Pre 10.10.0 To define an inverse relationship, use `LinkingObjects` in your object model. The `LinkingObjects` definition specifies the object type and property name of the relationship that it inverts. ```swift class User: Object { @objc dynamic var _id: ObjectId = ObjectId.generate() @objc dynamic var _partition: String = "" @objc dynamic var name: String = "" // A user can have many tasks. let tasks = List() override static func primaryKey() -> String? { return "_id" } } class Task: Object { @objc dynamic var _id: ObjectId = ObjectId.generate() @objc dynamic var _partition: String = "" @objc dynamic var text: String = "" // Backlink to the user. This is automatically updated whenever // this task is added to or removed from a user's task list. let assignee = LinkingObjects(fromType: User.self, property: "tasks") override static func primaryKey() -> String? { return "_id" } } ``` > Seealso: > For more information about inverse relationships, see: Inverse Relationship. > ### Define an Embedded Object Property An **embedded object** exists as nested data inside of a single, specific parent object. It inherits the lifecycle of its parent object and cannot exist as an independent Realm object. Realm automatically deletes embedded objects if their parent object is deleted or when overwritten by a new embedded object instance. > Note: > When you delete a Realm object, any embedded objects referenced by that object are deleted with it. If you want the referenced objects to persist after the deletion of the parent object, your type should not be an embedded object at all. Use a regular Realm object with a to-one relationship instead. > #### Objective-C You can define an embedded object by deriving from the `RLMEmbeddedObject` class. You can use your embedded object in another model as you would any other type. ```objectivec // Define an embedded object @interface Address : RLMEmbeddedObject @property NSString *street; @property NSString *city; @property NSString *country; @property NSString *postalCode; @end // Enable Address for use in RLMArray RLM_COLLECTION_TYPE(Address) @implementation Address @end // Define an object with one embedded object @interface Contact : RLMObject @property NSString *name; // Embed a single object. @property Address *address; @end @implementation Contact @end // Define an object with an array of embedded objects @interface Business : RLMObject @property NSString *name; // Embed an array of objects @property RLMArray
*addresses; @end ``` #### Swift You can define an embedded object by deriving from the `EmbeddedObject` class. You can use your embedded object in another model as you would any other type. ```swift class Person: Object { @Persisted(primaryKey: true) var id = 0 @Persisted var name = "" // To-many relationship - a person can have many dogs @Persisted var dogs: List // Inverse relationship - a person can be a member of many clubs @Persisted(originProperty: "members") var clubs: LinkingObjects // Embed a single object. // Embedded object properties must be marked optional. @Persisted var address: Address? convenience init(name: String, address: Address) { self.init() self.name = name self.address = address } } class DogClub: Object { @Persisted var name = "" @Persisted var members: List // DogClub has an array of regional office addresses. // These are embedded objects. @Persisted var regionalOfficeAddresses: List
convenience init(name: String, addresses: [Address]) { self.init() self.name = name self.regionalOfficeAddresses.append(objectsIn: addresses) } } class Address: EmbeddedObject { @Persisted var street: String? @Persisted var city: String? @Persisted var country: String? @Persisted var postalCode: String? } ``` ================================================ FILE: docs/guides/model-data/supported-types.md ================================================ # Supported Types - Swift SDK ## Collection Types Realm has several types to represent groups of objects, which we call **collections**. A collection is an object that contains zero or more instances of one Realm type. Realm collections are **homogenous**: all objects in a collection are of the same type. You can filter and sort any collection using Realm's query engine. Collections are live, so they always reflect the current state of the realm instance on the current thread. You can also listen for changes in the collection by subscribing to collection notifications. All collection types conform to the `RealmCollection` protocol. This protocol inherits from `CollectionType`, so you can use a Realm collection as you would any other standard library collections. Using the RealmCollection protocol, you can write generic code that can operate on any Realm collection: ```swift func operateOn(collection: C) { // Collection could be either Results or List print("operating on collection containing \(collection.count) objects") } ``` ### Results and Sectioned Results The Swift SDK `Results` collection is a class representing objects retrieved from queries. A `[Results` collection represents the lazily-evaluated results of a query operation. Results are immutable: you cannot add or remove elements to or from the results collection. Results have an associated query that determines their contents. The Swift SDK also provides `SectionedResults`, a type-safe collection which holds `ResultsSection` as its elements. Each `ResultSection` is a results collection that contains only objects that belong to a given section key. For example, an app that includes a contact list might use SectionedResults to display a list of contacts divided into sections, where each section contains all the contacts whose first name starts with the given letter. The `ResultsSection` whose key is "L" would contain "Larry", "Liam", and "Lisa". > Seealso: > Reads > ### Collections as Properties The Swift SDK also offers several collection types you can use as properties in your data model: 1. `List`, a class representing to-many relationships in models. 2. `LinkingObjects`, a class representing inverse relationships in models. 3. MutableSet, a class representing a to-many relationship. 4. Map, a class representing an associative array of key-value pairs with unique keys. 5. `AnyRealmCollection`, a [type-erased](https://en.wikipedia.org/wiki/Type_erasure) class that can forward calls to a concrete Realm collection like Results, List or LinkingObjects. ### Collections are Live Like live objects, Realm collections are usually **live**: - Live results collections always reflect the current results of the associated query. - Live lists always reflect the current state of the relationship on the realm instance. There are two cases when a collection is **not** live: - The collection is unmanaged. For example, a List property of a Realm object that has not been added to a realm yet or that has been copied from a realm is not live. - The collection is frozen. Combined with collection notifications, live collections enable clean, reactive code. For example, suppose your view displays the results of a query. You can keep a reference to the results collection in your view class, then read the results collection as needed without having to refresh it or validate that it is up-to-date. > Important: > Since results update themselves automatically, do not store the positional index of an object in the collection or the count of objects in a collection. The stored index or count value could be outdated by the time you use it. > ## Supported Property Types You can use the following types to define your object model properties. ### Property Cheat Sheet #### Swift > Version changed: 10.10.0 > `@Persisted` property declaration syntax > |Type|Required|Optional| | --- | --- | --- | |Bool|`@Persisted var boolName: Bool`|`@Persisted var optBoolName: Bool?`| |Int, Int8, Int16, Int32, Int64|`@Persisted var intName: Int`|`@Persisted var optIntName: Int?`| |Float|`@Persisted var floatName: Float`|`@Persisted var optFloatName: Float?`| |Double|`@Persisted var doubleName: Double`|`@Persisted var optDoubleName: Double?`| |String|`@Persisted var stringName: String`|`@Persisted var optStringName: String?`| |Data|`@Persisted var dataName: Data`|`@Persisted var optDataName: Data?`| |Date|`@Persisted var dateName: Date`|`@Persisted var optDateName: Date?`| |Decimal128|`@Persisted var decimalName: Decimal128`|`@Persisted var optDecimalName: Decimal128?`| |`UUID`|`@Persisted var uuidName: UUID`|`@Persisted var optUuidName: UUID?`| |`ObjectId`|`@Persisted var objectIdName: ObjectId`|`@Persisted var optObjectIdName: ObjectId?`| |`List`|`@Persisted var listName: List`|N/A| |MutableSet|`@Persisted var mutableSetName: MutableSet`|N/A| |Map|`@Persisted var mapName: Map`|N/A| |AnyRealmValue|`@Persisted var anyRealmValueName: AnyRealmValue`|N/A| |User-defined `Object`|N/A|`@Persisted var optObjectPropertyName: MyCustomObjectType?`| |User-defined `EmbeddedObject`|N/A|`@Persisted var optEmbeddedObjectPropertyName: MyEmbeddedObjectType?`| |User-defined `Enums`|`@Persisted var enumName: MyPersistableEnum`|`@Persisted var optEnumName: MyPersistableEnum?`| `CGFloat` properties are discouraged, as the type is not platform independent. To use Key-Value Coding with a user-defined object in the `@Persisted` syntax, add the `@objc` attribute: `@Persisted @objc var myObject: MyClass?` ##### Setting Default Values With the `@Persisted` property declaration syntax, you may see a performance impact when setting default values for: - `List` - `MutableSet` - `Dictionary` - `Decimal128` - `UUID` - `ObjectId` `@Persisted var listProperty: List` and `@Persisted var listProperty = List()` are both valid, and are functionally equivalent. However, the second declaration will result in poorer performance. This is because the List is created when the parent object is created, rather than lazily as needed. For most types, this is a difference so small you can't measure it. For the types listed here, you may see a performance impact when using the second declaration style. #### Objective-C |Type|Required|Optional| | --- | --- | --- | |Boolean|`@property BOOL boolName;`|`@property NSNumber *optBoolName;`| |Integer|`@property int intName;`|`@property NSNumber *optIntName;`| |Float|`@property float floatName;`|`@property NSNumber *optFloatName;`| |Double|`@property double doubleName;`|`@property NSNumber *optDoubleName;`| |String|`@property NSString *stringName;`|`@property NSString *optStringName;`| |Data|`@property NSData *dataName;`|`@property NSData *optDataName;`| |Date|`@property NSDate *dateName;`|`@property NSDate *optDateName;`| |Decimal128|`@property RLMDecimal128 *decimalName;`|`@property RLMDecimal128 *optDecimalName;`| |NSUUID|`@property NSUUID *uuidName;`|`@property NSUUID *optUuidName;`| |`RLMObjectId`|`@property RLMObjectId *objectIdName;`|`@property RLMObjectId *optObjectIdName;`| |`RLMArray`|`@property RLMArray *arrayName;`|N/A| |`RLMSet`|`@property RLMSet *setName;`|N/A| |`RLMDictionary`|`@property RLMDictionary *dictionaryName;`|N/A| |User-defined `RLMObject`|N/A|`@property MyObject *optObjectPropertyName;`| |User-defined `RLMEmbeddedObject`|N/A|`@property MyEmbeddedObject *optEmbeddedObjectPropertyName;`| Additionally: - Integral types `int`, `NSInteger`, `long`, `long long` `CGFloat` properties are discouraged, as the type is not platform independent. #### Swift Pre 10.10.0 > Version changed: 10.8.0 > `RealmProperty` replaces `RealmOptional` > |Type|Required|Optional| | --- | --- | --- | |Bool|`@objc dynamic var value = false`|`let value = RealmProperty()`| |Int, Int8, Int16, Int32, Int64|`@objc dynamic var value = 0`|`let value = RealmProperty()`| |Float|`@objc dynamic var value: Float = 0.0`|`let value = RealmProperty()`| |Double|`@objc dynamic var value: Double = 0.0`|`let value = RealmProperty()`| |String|`@objc dynamic var value = ""`|`@objc dynamic var value: String? = nil`| |Data|`@objc dynamic var value = Data()`|`@objc dynamic var value: Data? = nil`| |Date|`@objc dynamic var value = Date()`|`@objc dynamic var value: Date? = nil`| |Decimal128|`@objc dynamic var decimal: Decimal128 = 0`|`@objc dynamic var decimal: Decimal128?`| |`UUID`|`@objc dynamic var uuid = UUID()`|`@objc dynamic var uuidOpt: UUID?`| |`ObjectId`|`@objc dynamic var objectId = ObjectId.generate()`|`@objc dynamic var objectId: ObjectId?`| |`List`|`let value = List()`|| |MutableSet|`let value = MutableSet()`|| |Map|`let value = Map()`|| |AnyRealmValue|`let value = RealmProperty()`|N/A| |User-defined `Object`|N/A|`@objc dynamic var value: MyClass?`| Additionally: - `EmbeddedObject`-derived types - `Enum` You can use `RealmProperty ` to represent integers, doubles, and other types as optional. `CGFloat` properties are discouraged, as the type is not platform independent. ### Unique Identifiers > Version added: 10.8.0 > `UUID` type > `ObjectId` is a 12-byte unique value. `UUID` is a 16-byte globally-unique value. You can index both types, and use either as a primary key. > Note: > When declaring default values for `@Persisted` UUID or ObjectId property attributes, both of these syntax types are valid: > > - `@Persisted var value: UUID` > - `@Persisted var value = UUID()` > > However, the second will result in poorer performance. This is because the latter creates a new identifier that is never used any time an object is read from the realm, while the former only creates them when needed. > > `@Persisted var id: ObjectId` has equivalent behavior to `@objc dynamic var _id = ObjectId.generate()`. They both make random ObjectIds. > > `@Persisted var _id = ObjectId()` has equivalent behavior to `@objc dynamic var _id = ObjectId()`. They both make zero-initialized ObjectIds. > ### Size Limitations Data and string properties cannot hold more than 16MB. To store larger amounts of data, either: - Break the data into 16MB chunks, or - Store data directly on the file system and store paths to the files in the realm. Realm throws a runtime exception if your app attempts to store more than 16MB in a single property. To avoid size limitations and a performance impact, it is best not to store large blobs, such as image and video files, directly in a realm. Instead, save the file to a file store and keep only the location of the file and any relevant metadata in the realm. ### AnyRealmCollection To store a collection as a property or variable without needing to know the concrete collection type, Swift's type system requires a type-erased wrapper like `AnyRealmCollection`: ```swift class ViewController { // let collection: RealmCollection // ^ // error: protocol 'RealmCollection' can only be used // as a generic constraint because it has Self or // associated type requirements // // init(collection: C) where C.ElementType == MyModel { // self.collection = collection // } let collection: AnyRealmCollection init(collection: C) where C.ElementType == MyModel { self.collection = AnyRealmCollection(collection) } } ``` ### Mutable Set > Version added: 10.8.0 A `MutableSet` collection represents a to-many relationship containing distinct values. A `MutableSet` supports the following types (and their optional versions): - Bool - Data - Date - Decimal128 - Double - Float - Int - Int8 - Int16 - Int32 - Int64 - Object - ObjectId - String - UUID Like Swift's [Set](https://developer.apple.com/documentation/swift/set), `MutableSet` is a generic type that is parameterized on the type it stores. Unlike [native Swift collections](https://developer.apple.com/documentation/swift/swift_standard_library/collections), Realm mutable sets are reference types, as opposed to value types (structs). You can only call the `MutableSets` mutation methods during a write transaction. As a result, `MutableSets` are immutable if you open the managing realm as a read-only realm. You can filter and sort a `MutableSet` with the same predicates as Results. Like other Realm collections, you can register a change listener on a `MutableSet`. For example, a `Dog` class model might contain a `MutableSet` for `citiesVisited`: ```swift class Dog: Object { @Persisted var name = "" @Persisted var currentCity = "" @Persisted var citiesVisited: MutableSet } ``` > Note: > When declaring default values for `@Persisted` MutableSet property attributes, both of these syntax types is valid: > > - `@Persisted var value: MutableSet` > - `@Persisted var value = MutableSet()` > > However, the second will result in significantly worse performance. This is because the MutableSet is created when the parent object is created, rather than lazily as needed. > ### Map/Dictionary > Version added: 10.8.0 The `Map` is an associative array that contains key-value pairs with unique keys. Like Swift's [Dictionary](https://developer.apple.com/documentation/swift/dictionary), `Map` is a generic type that is parameterized on its key and value types. Unlike [native Swift collections](https://developer.apple.com/documentation/swift/swift_standard_library/collections), Realm Maps are reference types (classes), as opposed to value types (structs). You can declare a Map as a property of an object: ```swift class Dog: Object { @Persisted var name = "" @Persisted var currentCity = "" // Map of city name -> favorite park in that city @Persisted var favoriteParksByCity: Map } ``` Realm disallows the use of `.` or `$` characters in map keys. You can use percent encoding and decoding to store a map key that contains one of these disallowed characters. ``` // Percent encode . or $ characters to use them in map keys let mapKey = "New York.Brooklyn" let encodedMapKey = "New York%2EBrooklyn" ``` > Note: > When declaring default values for `@Persisted` Map property attributes, both of these syntax types is valid: > > - `@Persisted var value: Map` > - `@Persisted var value = Map()` > > However, the second will result in significantly worse performance. This is because the Map is created when the parent object is created, rather than lazily as needed. > ### AnyRealmValue > Version changed: 10.51.0 > `AnyRealmValue` properties can hold lists or maps of mixed data. > > Version added: 10.8.0 `AnyRealmValue` is a Realm property type that can hold different data types. Supported `AnyRealmValue` data types include: - Int - Float - Double - Decimal128 - ObjectID - UUID - Bool - Date - Data - String - List - Map - Object `AnyRealmValue` *cannot* hold a `MutableSet` or embedded object. This mixed data type is indexable, but you can't use it as a primary key. Because `null` is a permitted value, you can't declare an `AnyRealmValue` as optional. ```swift class Dog: Object { @Persisted var name = "" @Persisted var currentCity = "" @Persisted var companion: AnyRealmValue } ``` #### Collections as Mixed In version 10.51.0 and later, a `AnyRealmValue` data type can contain collections (a list or map, but *not* a set) of `AnyRealmValue` elements. You can use mixed collections to model unstructured or variable data. For more information, refer to Define Unstructured Data. - You can nest mixed collections up to 100 levels. - You can query mixed collection properties and register a listener for changes, as you would a normal collection. - You can find and update individual mixed collection elements - You *cannot* store sets or embedded objects in mixed collections. To use mixed collections in your app, define the `AnyRealmValue` type property in your data model. Then, you can create the list or map collections like any other mixed data value. ### Geospatial Data > Version added: 10.47.0 Geospatial data, or "geodata", specifies points and geometric objects on the Earth's surface. If you want to persist geospatial data, it must conform to the [GeoJSON spec](https://datatracker.ietf.org/doc/html/rfc7946). To persist geospatial data with the Swift SDK, create a GeoJSON-compatible embedded class that you can use in your data model. Your custom embedded object must contain the two fields required by the GeoJSON spec: - A field of type `String` property that maps to a `type` property with the value of `"Point"`: `@Persisted var type: String = "Point"` - A field of type `List` that maps to a `coordinates` property containing a latitude/longitude pair: `@Persisted private var coordinates: List` ```swift class CustomGeoPoint: EmbeddedObject { @Persisted private var type: String = "Point" @Persisted private var coordinates: List public var latitude: Double { return coordinates[1] } public var longitude: Double { return coordinates[0] } convenience init(_ latitude: Double, _ longitude: Double) { self.init() // Longitude comes first in the coordinates array of a GeoJson document coordinates.append(objectsIn: [longitude, latitude]) } } ``` ## Map Unsupported Types to Supported Types > Version added: 10.20.0 You can use Type Projection to persist unsupported types as supported types in Realm. This enables you to work with Swift types that Realm does not support, but store them as types that Realm does support. You could store a URL as a `String`, for example, but read it from Realm and use it in your application as though it were a URL. ### Declare Type Projections To use type projection with Realm: 1. Use one of Realm's custom type protocols to map an unsupported data type to a type that Realm supports 2. Use the projected types as @Persisted properties in the Realm object model #### Conform to the Type Projection Protocol You can map an unsupported data type to a type that Realm supports using one of the Realm type projection protocols. The Swift SDK provides two type projection protocols: - CustomPersistable - FailableCustomPersistable Use `CustomPersistable` when there is no chance the conversion can fail. Use `FailableCustomPersistable` when it is possible for the conversion to fail. ```swift // Extend a type as a CustomPersistable if if is impossible for // conversion between the mapped type and the persisted type to fail. extension CLLocationCoordinate2D: CustomPersistable { // Define the storage object that is persisted to the database. // The `PersistedType` must be a type that Realm supports. // In this example, the PersistedType is an embedded object. public typealias PersistedType = Location // Construct an instance of the mapped type from the persisted type. // When reading from the database, this converts the persisted type to the mapped type. public init(persistedValue: PersistedType) { self.init(latitude: persistedValue.latitude, longitude: persistedValue.longitude) } // Construct an instance of the persisted type from the mapped type. // When writing to the database, this converts the mapped type to a persistable type. public var persistableValue: PersistedType { Location(value: [self.latitude, self.longitude]) } } // Extend a type as a FailableCustomPersistable if it is possible for // conversion between the mapped type and the persisted type to fail. // This returns nil on read if the underlying column contains nil or // something that can't be converted to the specified type. extension URL: FailableCustomPersistable { // Define the storage object that is persisted to the database. // The `PersistedType` must be a type that Realm supports. public typealias PersistedType = String // Construct an instance of the mapped type from the persisted type. // When reading from the database, this converts the persisted type to the mapped type. // This must be a failable initializer when the conversion may fail. public init?(persistedValue: String) { self.init(string: persistedValue) } // Construct an instance of the persisted type from the mapped type. // When writing to the database, this converts the mapped type to a persistable type. public var persistableValue: String { self.absoluteString } } ``` > Seealso: > These are protocols modeled after Swift's built-in [RawRepresentable](https://developer.apple.com/documentation/swift/rawrepresentable). > ##### Supported PersistedTypes The `PersistedType` can use any of the primitive types that the Swift SDK supports. It can also be an Embedded Object. `PersistedType` cannot be an optional or a collection. However you can use the mapped type as an optional or collection property in your object model. ```swift extension URL: FailableCustomPersistable { // The `PersistedType` cannot be an optional, so this is not a valid // conformance to the FailableCustomPersistable protocol. public typealias PersistedType = String? ... } class Club: Object { @Persisted var id: ObjectId @Persisted var name: String // Although the `PersistedType` cannot be optional, you can use the // custom-mapped type as an optional in your object model. @Persisted var url: URL? } ``` #### Use Type Projection in the Model A type that conforms to one of the type projection protocols can be used with the `@Persisted` property declaration syntax introduced in Swift SDK version 10.10.0. It does not work with the `@objc dynamic` syntax. You can use projected types for: - Top-level types - Optional versions of the type - The types for a collection When using a `FailableCustomPersistable` as a property, define it as an optional property. When it is optional, the `FailableCustomPersistable` protocol maps invalid values to `nil`. When it is a required property, it is force-unwrapped. If you have a value that can't be converted to the projected type, reading that property throws an unwrapped fail exception. ```swift class Club: Object { @Persisted var id: ObjectId @Persisted var name: String // Since we declared the URL as a FailableCustomPersistable, // it must be optional. @Persisted var url: URL? // Here, the `location` property maps to an embedded object. // We can declare the property as required. // If the underlying field contains nil, this becomes // a default-constructed instance of CLLocationCoordinate // with field values of `0`. @Persisted var location: CLLocationCoordinate2D } public class Location: EmbeddedObject { @Persisted var latitude: Double @Persisted var longitude: Double } ``` When your model contains projected types, you can create the object with values using the persisted type, or by assigning to the field properties of an initialized object using the projected types. ```swift // Initialize objects and assign values let club = Club(value: ["name": "American Kennel Club", "url": "https://akc.org"]) let club2 = Club() club2.name = "Continental Kennel Club" // When assigning the value to a type-projected property, type safety // checks for the mapped type - not the persisted type. club2.url = URL(string: "https://ckcusa.com/")! club2.location = CLLocationCoordinate2D(latitude: 40.7509, longitude: 73.9777) ``` ##### Type Projection in the Schema When you declare your type as conforming to a type projection protocol, you specify the type that should be persisted in realm. For example, if you map a custom type `URL` to a persisted type of `String`, a `URL` property appears as a `String` in the schema, and dynamic access to the property acts on strings. The schema does not directly represent mapped types. Changing a property from its persisted type to its mapped type, or vice versa, does not require a migration. ================================================ FILE: docs/guides/quick-start.md ================================================ # Quick Start - Swift SDK This Quick Start demonstrates how to use Realm with the Realm Swift SDK. Before you begin, ensure you have installed the Swift SDK. > Seealso: > If your app uses SwiftUI, check out the [SwiftUI Quick Start](swiftui-tutorial.md). > ## Import Realm Near the top of any Swift file that uses Realm, add the following import statement: ```swift import RealmSwift ``` ## Define Your Object Model For a local realm, you can define your object model directly in code. ```swift class Todo: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var name: String = "" @Persisted var status: String = "" @Persisted var ownerId: String convenience init(name: String, ownerId: String) { self.init() self.name = name self.ownerId = ownerId } } ``` ## Open a Realm In a local realm, the simplest option to open a realm is to use the default realm with no configuration parameter: ```swift // Open the default realm let realm = try! Realm() ``` You can also specify a `Realm.Configuration` parameter to open a realm at a specific file URL, in-memory, or with a subset of classes. For more information, see: Configure and Open a Realm. ## Create, Read, Update, and Delete Objects Once you have opened a realm, you can modify it and its objects in a write transaction block. To create a new Todo object, instantiate the Todo class and add it to the realm in a write block: ```swift let todo = Todo(name: "Do laundry", ownerId: user.id) try! realm.write { realm.add(todo) } ``` You can retrieve a live collection of all todos in the realm: ```swift // Get all todos in the realm let todos = realm.objects(Todo.self) ``` You can also filter that collection using where: ```swift let todosInProgress = todos.where { $0.status == "InProgress" } print("A list of all todos in progress: \(todosInProgress)") ``` To modify a todo, update its properties in a write transaction block: ```swift // All modifications to a realm must happen in a write block. let todoToUpdate = todos[0] try! realm.write { todoToUpdate.status = "InProgress" } ``` Finally, you can delete a todo: ```swift // All modifications to a realm must happen in a write block. let todoToDelete = todos[0] try! realm.write { // Delete the Todo. realm.delete(todoToDelete) } ``` ## Watch for Changes You can watch a realm, collection, or object for changes with the `observe` method. ```swift // Retain notificationToken as long as you want to observe let notificationToken = todos.observe { (changes) in switch changes { case .initial: break // Results are now populated and can be accessed without blocking the UI case .update(_, let deletions, let insertions, let modifications): // Query results have changed. print("Deleted indices: ", deletions) print("Inserted indices: ", insertions) print("Modified modifications: ", modifications) case .error(let error): // An error occurred while opening the Realm file on the background worker thread fatalError("\(error)") } } ``` Be sure to retain the notification token returned by `observe` as long as you want to continue observing. When you are done observing, invalidate the token to free the resources: ```swift // Invalidate notification tokens when done observing notificationToken.invalidate() ``` ================================================ FILE: docs/guides/realm-files/bundle-a-realm.md ================================================ # Bundle a Realm File - Swift SDK Realm supports **bundling** realm files. When you bundle a realm file, you include a database and all of its data in your application download. This allows users to start applications for the first time with a set of initial data. ## Overview To create and bundle a realm file with your application: 1. Create a realm file that contains the data you'd like to bundle. 2. Bundle the realm file in your production application. 3. In your production application, open the realm from the bundled asset file. ## Create a Realm File for Bundling 1. Build a temporary realm app that shares the data model of your application. 2. Open a realm and add the data you wish to bundle. 3. Use the `writeCopy(configuration:)` method to copy the realm to a new file: > Tip: > If your app accesses Realm in an `async/await` context, mark the code with `@MainActor` to avoid threading-related crashes. > `writeCopy(configuration: )` automatically compacts your realm to the smallest possible size before copying. ## Bundle a Realm File in Your Production Application Now that you have a copy of the realm that contains the initial data, bundle it with your production application. At a broad level, this entails: 1. Create a new project with the exact same data models as your production app. Open a realm and add the data you wish to bundle. Since realm files are cross-platform, you can do this in a macOS app. 2. Drag the compacted copy of your realm file to your production app's Xcode Project Navigator. 3. Go to your app target's Build Phases tab in Xcode. Add the realm file to the Copy Bundle Resources build phase. 4. At this point, your app can access the bundled realm file. Find its path with [Bundle.main.path(forResource:ofType)](https://developer.apple.com/documentation/foundation/bundle/1410989-path). You can open the realm at the bundle path directly if the `readOnly` property is set to `true` on the `Realm.Configuration`. If you want to modify the bundled realm, first copy the bundled file to your app's Documents folder with setting `seedFilePath` with the URL of the bundled Realm on your Configuration. > Tip: > See the [migration sample app](https://github.com/realm/realm-swift/tree/master/examples/ios/swift/Migration) for a complete working app that uses a bundled local realm. > ## Open a Realm from a Bundled Realm File Now that you have a copy of the realm included with your production application, you need to add code to use it. Use the `seedFilePath` method when configuring your realm to open the realm from the bundled file: > Tip: > If your app accesses Realm in an `async/await` context, mark the code with `@MainActor` to avoid threading-related crashes. > ```swift try await openBundledSyncedRealm() // Opening a realm and accessing it must be done from the same thread. // Marking this function as `@MainActor` avoids threading-related issues. @MainActor func openBundledRealm() async throws { // Find the path of the seed.realm file in your project let realmURL = Bundle.main.url(forResource: "seed", withExtension: ".realm") print("The bundled realm URL is: \(realmURL)") // When you use the `seedFilePath` parameter, this copies the // realm at the specified path for use with the user's config newUserConfig.seedFilePath = realmURL // Open the realm, downloading any changes before opening it. // This starts with the existing data in the bundled realm, but checks // for any updates to the data before opening it in your application. let realm = try await Realm(configuration: newUserConfig, downloadBeforeOpen: .always) print("Successfully opened the bundled realm") // Read and write to the bundled realm as normal let todos = realm.objects(Todo.self) // There should be one todo whose owner is Daenerys because that's // what was in the bundled realm. var daenerysTodos = todos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysTodos.count, 1) print("The bundled realm has \(daenerysTodos.count) todos whose owner is Daenerys") // Write as usual to the realm, and see the object count increment let todo = Todo(value: ["name": "Banish Ser Jorah", "owner": "Daenerys", "status": "In Progress"]) try realm.write { realm.add(todo) } print("Successfully added a todo to the realm") daenerysTodos = todos.where { $0.owner == "Daenerys" } XCTAssertEqual(daenerysTodos.count, 2) } ``` ================================================ FILE: docs/guides/realm-files/compacting.md ================================================ # Reduce Realm File Size - Swift SDK ## Overview The size of a realm file is always larger than the total size of the objects stored within it. This architecture enables some of realm's great performance, concurrency, and safety benefits. Realm writes new data within unused space tracked inside a file. In some situations, unused space may comprise a significant portion of a realm file. Realm's default behavior is to automatically compact a realm file to prevent it from growing too large. You can use manual compaction strategies when automatic compaction is not sufficient for your use case or you're using a version of the SDK that doesn't have automatic compaction. ## Realm File Size Generally, a realm file takes less space on disk than a comparable SQLite database. These factors can affect file size: - Pinning transactions - Threading - Dispatch Queues When you consider reducing the file size through compacting, there are a couple of things to keep in mind: - Compacting can be a resource-intensive operation - Compacting can block the UI thread Because of these factors, you probably don't want to compact a realm every time you open it, but instead want to consider when to compact a realm. This varies based on your application's platform and usage patterns. When deciding when to compact, consider iOS file size limitations. ### Avoid Pinning Transactions Realm ties read transaction lifetimes to the memory lifetime of realm instances. Avoid "pinning" old Realm transactions. Use auto-refreshing realms, and wrap the use of Realm APIs from background threads in explicit autorelease pools. ### Threading Realm updates the version of your data that it accesses at the start of a run loop iteration. While this gives you a consistent view of your data, it has file size implications. Imagine this scenario: - **Thread A**: Read some data from a realm, and then block the thread on a long-running operation. - **Thread B**: Write data on another thread. - **Thread A**: The version on the read thread isn't updated. Realm has to hold intermediate versions of the data, growing in file size with every write. To avoid this issue, call `invalidate()` on the realm. This tells the realm that you no longer need the objects you've read so far. This frees realm from tracking intermediate versions of those objects. The next time you access it, realm will have the latest version of the objects. You can also use these two methods to compact your Realm: - Set `shouldCompactOnLaunch` in the configuration - Use `writeCopy(toFile:encryptionKey:)` > Seealso: > Advanced Guides: Threading > ### Dispatch Queues When accessing Realm using [Grand Central Dispatch](https://developer.apple.com/documentation/dispatch), you may see similar file growth. A dispatch queue's autorelease pool may not drain immediately upon executing your code. Realm cannot reuse intermediate versions of the data until the dispatch pool deallocates the realm object. Use an explicit autorelease pool when accessing realm from a dispatch queue. ## Automatic Compaction > Version added: 10.35.0 The SDK automatically compacts Realm files in the background by continuously reallocating data within the file and removing unused file space. Automatic compaction is sufficient for minimizing the Realm file size for most applications. Automatic compaction begins when the size of unused space in the file is more than twice the size of user data in the file. Automatic compaction only takes place when the file is not being accessed. ## Manual Compaction Manual compaction can be used for applications that require stricter management of file size or that use an older version of the SDK that does not support automatic compaction. Realm manual compaction works by: 1. Reading the entire contents of the realm file 2. Writing the contents to a new file at a different location 3. Replacing the original file If the file contains a lot of data, this can be an expensive operation. Use `shouldCompactOnLaunch()` (Swift) or `shouldCompactOnLaunch` (Objective-C) on a realm's configuration object to compact a realm. Specify conditions to execute this method, such as: - The size of the file on disk - How much free space the file contains For more information about the conditions to execute in the method, see: Tips for Using Manual Compaction. > Important: > Compacting cannot occur while a realm is being accessed, regardless of any configuration settings. > #### Objective-C ```objectivec RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes) { // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' NSUInteger oneHundredMB = 100 * 1024 * 1024; return (totalBytes > oneHundredMB) && ((double)usedBytes / totalBytes) < 0.5; }; NSError *error = nil; // Realm is compacted on the first open if the configuration block conditions were met. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (error) { // handle error compacting or opening Realm } ``` #### Swift ```swift let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' let oneHundredMB = 100 * 1024 * 1024 return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5 }) do { // Realm is compacted on the first open if the configuration block conditions were met. let realm = try Realm(configuration: config) } catch { // handle error compacting or opening Realm } ``` ### Compact a Realm Asynchronously When you use the Swift async/await syntax to open a realm asynchronously, you can compact a realm in the background. ```swift func testAsyncCompact() async { let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' let oneHundredMB = 100 * 1024 * 1024 return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5 }) do { // Realm is compacted asynchronously on the first open if the // configuration block conditions were met. let realm = try await Realm(configuration: config) } catch { // handle error compacting or opening Realm } } ``` Starting with Realm Swift SDK Versions 10.15.0 and 10.16.0, many of the Realm APIs support the Swift async/await syntax. Projects must meet these requirements: |Swift SDK Version|Swift Version Requirement|Supported OS| | --- | --- | --- | |10.25.0|Swift 5.6|iOS 13.x| |10.15.0 or 10.16.0|Swift 5.5|iOS 15.x| If your app accesses Realm in an `async/await` context, mark the code with `@MainActor` to avoid threading-related crashes. ### Make a Compacted Copy You can save a compacted (and optionally encrypted) copy of a realm to another file location with the `Realm.writeCopy(toFile:encryptionKey:)` method. The destination file cannot already exist. > Important: > Avoid calling this method within a write transaction. If called within a write transaction, this method copies the absolute latest data. This includes any **uncommitted** changes you made in the transaction before this method call. > ### Tips for Using Manual Compaction Compacting a realm can be an expensive operation that can block the UI thread. Your application should not compact every time you open a realm. Instead, try to optimize compacting so your application does it just often enough to prevent the file size from growing too large. If your application runs in a resource-constrained environment, you may want to compact when you reach a certain file size or when the file size negatively impacts performance. These recommendations can help you optimize manual compaction for your application: - Set the max file size to a multiple of your average realm state size. If your average realm state size is 10MB, you might set the max file size to 20MB or 40MB, depending on expected usage and device constraints. - As a starting point, compact realms when more than 50% of the realm file size is no longer in use. Divide the currently used bytes by the total file size to determine the percentage of space that is currently used. Then, check for that to be less than 50%. This means that greater than 50% of your realm file size is unused space, and it is a good time to compact. After experimentation, you may find a different percentage works best for your application. These calculations might look like this in your `shouldCompactOnLaunch` callback: ```swift // Set a maxFileSize equal to 20MB in bytes let maxFileSize = 20 * 1024 * 1024 // Check for the realm file size to be greater than the max file size, // and the amount of bytes currently used to be less than 50% of the // total realm file size return (realmFileSizeInBytes > maxFileSize) && (Double(usedBytes) / Double(realmFileSizeInBytes)) < 0.5 ``` Experiment with conditions to find the right balance of how often to compact realm files in your application. #### Consider iOS File Size Limitations A large realm file can impact the performance and reliability of your app. Any single realm file cannot be larger than the amount of memory your application would be allowed to map in iOS. This limit depends on the device and on how fragmented the memory space is at that point in time. If you need to store more data, map it over multiple realm files. ## Summary - Realm's architecture enables threading-related benefits, but can result in file size growth. - Automatic compaction manages file size growth when the file is not being accessed. - Manual compaction strategies like `shouldCompactOnLaunch()` can be used when automatic compaction does not meet application needs. - Compacting cannot occur if another process is accessing the realm. - You can compact a realm in the background when you use async/await syntax. ================================================ FILE: docs/guides/realm-files/configure-and-open-a-realm.md ================================================ # Configure & Open a Realm - Swift SDK When you open a realm, you can pass a `Realm.Configuration` that specifies additional details about how to configure the realm file. This includes things like: - Pass a fileURL or in-memory identifier to customize how the realm is stored on device - Specify the realm use only a subset of your app's classes - Whether and when to compact a realm to reduce its file size - Pass an encryption key to encrypt a realm - Provide a schema version or migration block when making schema changes ## Open a Realm You can open a local realm with several different configuration options: - No configuration - i.e. default configuration - Specify a file URL for the realm - Open the realm only in memory, without saving a file to the file system ### Open a Default Realm or Realm at a File URL #### Objective-C You can open the default realm with [+[RLMRealm defaultRealm]]. You can also pass a `RLMRealmConfiguration` object to `+[RLMRealm realmWithConfiguration:error:]` to open a realm at a specific file URL or in memory. You can set the default realm configuration by passing a RLMRealmConfiguration instance to `+[RLMRealmConfiguration setDefaultConfiguration:]`. ```objectivec // Open the default realm RLMRealm *defaultRealm = [RLMRealm defaultRealm]; // Open the realm with a specific file URL, for example a username NSString *username = @"GordonCole"; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = [[[configuration.fileURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:username] URLByAppendingPathExtension:@"realm"]; NSError *error = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; ``` #### Swift You can open a realm with the `Realm()` initializer. If you omit the `Realm.Configuration` parameter, you will open the default realm. You can set the default realm configuration by assigning a new Realm.Configuration instance to the `Realm.Configuration.defaultConfiguration` class property. ```swift // Open the default realm let defaultRealm = try! Realm() // Open the realm with a specific file URL, for example a username let username = "GordonCole" var config = Realm.Configuration.defaultConfiguration config.fileURL!.deleteLastPathComponent() config.fileURL!.appendPathComponent(username) config.fileURL!.appendPathExtension("realm") let realm = try! Realm(configuration: config) ``` ### Open an In-Memory Realm You can open a realm entirely in memory, which will not create a `.realm` file or its associated auxiliary files. Instead the SDK stores objects in memory while the realm is open and discards them immediately when all instances are closed. #### Objective-C Set the `inMemoryIdentifier` property of the realm configuration. ```objectivec // Open the realm with a specific in-memory identifier. NSString *identifier = @"MyRealm"; RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init]; configuration.inMemoryIdentifier = identifier; // Open the realm RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; ``` #### Swift Set the `inMemoryIdentifier` property of the realm configuration. ```swift // Open the realm with a specific in-memory identifier. let identifier = "MyRealm" let config = Realm.Configuration( inMemoryIdentifier: identifier) // Open the realm let realm = try! Realm(configuration: config) ``` > Important: > When all *in-memory* realm instances with a particular identifier go out of scope, Realm deletes **all data** in that realm. To avoid this, hold onto a strong reference to any in-memory realms during your app's lifetime. > ### Open a Realm with Swift Concurrency Features You can use Swift's async/await syntax to open a MainActor-isolated realm, or specify an actor when opening a realm asynchronously: ```swift @MainActor func mainThreadFunction() async throws { // These are identical: the async init produces a // MainActor-isolated Realm if no actor is supplied let realm1 = try await Realm() let realm2 = try await Realm(actor: MainActor.shared) try await useTheRealm(realm: realm1) } ``` Or you can define a custom realm actor to manage all of your realm operations: ```swift actor RealmActor { // An implicitly-unwrapped optional is used here to let us pass `self` to // `Realm(actor:)` within `init` var realm: Realm! init() async throws { realm = try await Realm(actor: self) } var count: Int { realm.objects(Todo.self).count } func createTodo(name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": name, "owner": owner, "status": status ]) } } func getTodoOwner(forTodoNamed name: String) -> String { let todo = realm.objects(Todo.self).where { $0.name == name }.first! return todo.owner } struct TodoStruct { var id: ObjectId var name, owner, status: String } func getTodoAsStruct(forTodoNamed name: String) -> TodoStruct { let todo = realm.objects(Todo.self).where { $0.name == name }.first! return TodoStruct(id: todo._id, name: todo.name, owner: todo.owner, status: todo.status) } func updateTodo(_id: ObjectId, name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": _id, "name": name, "owner": owner, "status": status ], update: .modified) } } func deleteTodo(id: ObjectId) async throws { try await realm.asyncWrite { let todoToDelete = realm.object(ofType: Todo.self, forPrimaryKey: id) realm.delete(todoToDelete!) } } func close() { realm = nil } } ``` An actor-isolated realm may be used with either local or global actors. ```swift // A simple example of a custom global actor @globalActor actor BackgroundActor: GlobalActor { static var shared = BackgroundActor() } @BackgroundActor func backgroundThreadFunction() async throws { // Explicitly specifying the actor is required for anything that is not MainActor let realm = try await Realm(actor: BackgroundActor.shared) try await realm.asyncWrite { _ = realm.create(Todo.self, value: [ "name": "Pledge fealty and service to Gondor", "owner": "Pippin", "status": "In Progress" ]) } // Thread-confined Realms would sometimes throw an exception here, as we // may end up on a different thread after an `await` let todoCount = realm.objects(Todo.self).count print("The number of Realm objects is: \(todoCount)") } @MainActor func mainThreadFunction() async throws { try await backgroundThreadFunction() } ``` For more information about working with actor-isolated realms, refer to Use Realm with Actors - Swift SDK. ## Close a Realm There is no need to manually close a realm in Swift or Objective-C. When a realm goes out of scope and is removed from memory due to [ARC](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html), the realm is closed. ## Handle Errors When Accessing a Realm #### Objective-C To handle errors when accessing a realm, provide an `NSError` pointer to the `error` parameter: ```objectivec NSError *error = nil; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (!realm) { // Handle error return; } // Use realm ``` #### Swift To handle errors when accessing a realm, use Swift's built-in error handling mechanism: ```swift do { let realm = try Realm() // Use realm } catch let error as NSError { // Handle error } ``` ## Provide a Subset of Classes to a Realm > Tip: > Some applications, such as watchOS apps and iOS app extensions, have tight constraints on their memory footprints. To optimize your data model for low-memory environments, open the realm with a subset of classes. > #### Objective-C By default, the Swift SDK automatically adds all `RLMObject`- and `RLMEmbeddedObject`-derived classes in your executable to the realm schema. You can control which objects get added by setting the `objectClasses` property of the `RLMRealmConfiguration` object. ```objectivec RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // Given a RLMObject subclass called `Task` // Limit the realm to only the Task object. All other // Object- and EmbeddedObject-derived classes are not added. config.objectClasses = @[[Task class]]; NSError *error = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (error != nil) { // Something went wrong } else { // Use realm } ``` #### Swift By default, the Swift SDK automatically adds all `Object`- and `EmbeddedObject`-derived classes in your executable to the realm schema. You can control which objects get added by setting the `objectTypes` property of the `Realm.Configuration` object. ```swift var config = Realm.Configuration.defaultConfiguration // Given: `class Dog: Object` // Limit the realm to only the Dog object. All other // Object- and EmbeddedObject-derived classes are not added. config.objectTypes = [Dog.self] let realm = try! Realm(configuration: config) ``` ## Initialize Properties Using Realm APIs You might define properties whose values are initialized using Realm APIs. For example: ```swift class SomeSwiftType { let persons = try! Realm().objects(Person.self) // ... } ``` If this initialization code runs before you set up your Realm configurations, you might get unexpected behavior. For example, if you set a migration block for the default realm configuration in `applicationDidFinishLaunching()`, but you create an instance of `SomeSwiftType` before `applicationDidFinishLaunching()`, you might be accessing your realm before it has been correctly configured. To avoid such issues, consider doing one of the following: - Defer instantiation of any type that eagerly initializes properties using Realm APIs until after your app has completed setting up its realm configurations. - Define your properties using Swift's `lazy` keyword. This allows you to safely instantiate such types at any time during your application's lifecycle, as long as you do not attempt to access your `lazy` properties until after your app has set up its realm configurations. - Only initialize your properties using Realm APIs that explicitly take in user-defined configurations. You can be sure that the configuration values you are using have been set up properly before they are used to open realms. ## Use Realm When the Device Is Locked By default, iOS 8 and above encrypts app files using `NSFileProtection` whenever the device is locked. If your app attempts to access a realm while the device is locked, you might see the following error: ```text open() failed: Operation not permitted ``` To handle this, downgrade the file protection of the folder containing the Realm files. > Tip: > If you reduce iOS file encryption, consider using Realm's built-in encryption to secure your data instead. > This example shows how to apply a less strict protection level to the parent directory of the default realm. ```swift let realm = try! Realm() // Get the realm file's parent directory let folderPath = realm.configuration.fileURL!.deletingLastPathComponent().path // Disable file protection for this directory after the user has unlocked the device once try! FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: folderPath) ``` Realm may create and delete auxiliary files at any time. Instead of downgrading file protection on the files, apply it to the parent folder. This way, the file protection applies to all relevant files regardless of creation time. ================================================ FILE: docs/guides/realm-files/delete-a-realm.md ================================================ # Delete a Realm File - Swift SDK In some cases, you may want to completely delete a realm file from disk. Realm avoids copying data into memory except when absolutely required. As a result, all objects managed by a realm have references to the file on disk. Before you can safely delete the file, you must ensure the deallocation of these objects: - All objects read from or added to the realm - All List and Results objects - All ThreadSafeReference objects - The realm itself > Warning: > If you delete a realm file or any of its auxiliary files while one or more instances of the realm are open, you might corrupt the realm or disrupt sync. > ## Delete a Realm File to Avoid Migration If you iterate rapidly as you develop your app, you may want to delete a realm file instead of migrating it when you make schema changes. The Realm configuration provides a `deleteRealmIfMigrationNeeded` parameter to help with this case. When you set this property to `true`, the SDK deletes the realm file when a migration would be required. Then, you can create objects that match the new schema instead of writing migration blocks for development or test data. ```swift do { // Delete the realm if a migration would be required, instead of migrating it. // While it's useful during development, do not leave this set to `true` in a production app! let configuration = Realm.Configuration(deleteRealmIfMigrationNeeded: true) let realm = try Realm(configuration: configuration) } catch { print("Error opening realm: \(error.localizedDescription)") } ``` ## Delete a Realm File In practice, there are two safe times to delete the realm file: 1. On application startup before ever opening the realm. 2. After only having opened the realm within an explicit `autorelease` pool, which ensures deallocation of all of objects within it. ================================================ FILE: docs/guides/realm-files/encrypt-a-realm.md ================================================ # Encrypt a Realm - Swift SDK ## Overview You can encrypt the realm file on disk with AES-256 + SHA-2 by supplying a 64-byte encryption key when opening a realm. Realm transparently encrypts and decrypts data with standard [AES-256 encryption](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) using the first 256 bits of the given 512-bit encryption key. Realm uses the other 256 bits of the 512-bit encryption key to validate integrity using a [hash-based message authentication code (HMAC)](https://en.wikipedia.org/wiki/HMAC). > Warning: > Do not use cryptographically-weak hashes for realm encryption keys. For optimal security, we recommend generating random rather than derived encryption keys. > > Note: > You must encrypt a realm the first time you open it. If you try to open an existing unencrypted realm using a configuration that contains an encryption key, Realm throws an error. > ## Considerations The following are key impacts to consider when encrypting a realm. ### Storing & Reusing Keys You **must** pass the same encryption key every time you open the encrypted realm. If you don't provide a key or specify the wrong key for an encrypted realm, the Realm SDK throws an error. Apps should store the encryption key in the Keychain so that other apps cannot read the key. ### Performance Impact Reads and writes on encrypted realms can be up to 10% slower than unencrypted realms. ### Accessing an Encrypted Realm from Multiple Processes > Version changed: 10.38.0 Starting with Realm Swift SDK version 10.38.0, Realm supports opening the same encrypted realm in multiple processes. If your app uses Realm Swift SDK version 10.37.2 or earlier, attempting to open an encrypted realm from multiple processes throws this error: `Encrypted interprocess sharing is currently unsupported.` Apps using earlier SDK versions have two options to work with realms in multiple processes: - Use an unencrypted realm. - Store data that you want to encrypt as `NSData` properties on realm objects. Then, you can encrypt and decrypt individual fields. One possible tool to encrypt and decrypt fields is [Apple's CryptoKit framework](https://developer.apple.com/documentation/cryptokit). You can use [Swift Crypto](https://github.com/apple/swift-crypto) to simplify app development with CryptoKit. ## Example The following code demonstrates how to generate an encryption key and open an encrypted realm: #### Objective-C ```objectivec // Generate a random encryption key NSMutableData *key = [NSMutableData dataWithLength:64]; (void)SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes); // Open the encrypted Realm file RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = key; NSError *error = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (!realm) { // If the encryption key is wrong, `error` will say that it's an invalid database NSLog(@"Error opening realm: %@", error); } else { // Use the realm as normal... } ``` #### Swift ```swift // Generate a random encryption key var key = Data(count: 64) _ = key.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!) } // Configure for an encrypted realm var config = Realm.Configuration(encryptionKey: key) do { // Open the encrypted realm let realm = try Realm(configuration: config) // ... use the realm as normal ... } catch let error as NSError { // If the encryption key is wrong, `error` will say that it's an invalid database fatalError("Error opening realm: \(error.localizedDescription)") } ``` The following Swift example demonstrates how to store and retrieve a generated key from the Keychain: ```swift // Retrieve the existing encryption key for the app if it exists or create a new one func getKey() -> Data { // Identifier for our keychain entry - should be unique for your application let keychainIdentifier = "io.Realm.EncryptionExampleKey" let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)! // First check in the keychain for an existing key var query: [NSString: AnyObject] = [ kSecClass: kSecClassKey, kSecAttrApplicationTag: keychainIdentifierData as AnyObject, kSecAttrKeySizeInBits: 512 as AnyObject, kSecReturnData: true as AnyObject ] // To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item // See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328 var dataTypeRef: AnyObject? var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } if status == errSecSuccess { // swiftlint:disable:next force_cast return dataTypeRef as! Data } // No pre-existing key from this application, so generate a new one // Generate a random encryption key var key = Data(count: 64) key.withUnsafeMutableBytes({ (pointer: UnsafeMutableRawBufferPointer) in let result = SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!) assert(result == 0, "Failed to get random bytes") }) // Store the key in the keychain query = [ kSecClass: kSecClassKey, kSecAttrApplicationTag: keychainIdentifierData as AnyObject, kSecAttrKeySizeInBits: 512 as AnyObject, kSecValueData: key as AnyObject ] status = SecItemAdd(query as CFDictionary, nil) assert(status == errSecSuccess, "Failed to insert the new key in the keychain") return key } // ... // Use the getKey() function to get the stored encryption key or create a new one var config = Realm.Configuration(encryptionKey: getKey()) do { // Open the realm with the configuration let realm = try Realm(configuration: config) // Use the realm as normal } catch let error as NSError { // If the encryption key is wrong, `error` will say that it's an invalid database fatalError("Error opening realm: \(error)") } ``` ================================================ FILE: docs/guides/realm-files/realm-files.md ================================================ # Work with Realm Files - Swift SDK A **realm** is the core data structure used to organize data in Realm. A realm is a collection of the objects that you use in your application, called Realm objects, as well as additional metadata that describe the objects. To learn how to define a Realm object, see Define an Object Model. ## Realm Files Realm stores a binary encoded version of every object and type in a realm in a single `.realm` file. The file is located at a specific path that you can define when you open the realm. You can open, view, and edit the contents of these files with Realm Studio. ### In-Memory Realms You can also open a realm entirely in memory, which does not create a `.realm` file or its associated auxiliary files. Instead the SDK stores objects in memory while the realm is open and discards them immediately when all instances are closed. > See: > To open an in-memory realm, refer to Open an In-Memory Realm. > ### Default Realm Calling `Realm()` or `RLMRealm` opens the default realm. This method returns a realm object that maps to a file named `default.realm`. You can find this file: - iOS: in the Documents folder of your app - macOS: in the Application Support folder of your app > See: > To open a default realm, refer to Open a Default Realm or Realm at a File URL. > ### Auxiliary Realm Files Realm creates additional files for each realm: - **realm files**, suffixed with "realm", e.g. default.realm: contain object data. - **lock files**, suffixed with "lock", e.g. default.realm.lock: keep track of which versions of data in a realm are actively in use. This prevents realm from reclaiming storage space that is still used by a client application. - **note files**, suffixed with "note", e.g. default.realm.note: enable inter-thread and inter-process notifications. - **management files**, suffixed with "management", e.g. default.realm.management: internal state management. Deleting these files has important implications. For more information about deleting `.realm` or auxiliary files, see: Delete a Realm ## Find a Realm File Path The realm file is located at a specific path that you can optionally define when you open the realm. ```swift // Get on-disk location of the default Realm let realm = try! Realm() print("Realm is located at:", realm.configuration.fileURL!) ``` > See: > To open a realm at a specific path, refer to Open a Default Realm or Realm at a File URL. > ================================================ FILE: docs/guides/realm-files/tvos.md ================================================ # Build for tvOS ## Overview This page details considerations when using Realm on tvOS. > Seealso: > Install the SDK for iOS, macOS, tvOS, and watchOS > ## Avoid Storing Important User Data Avoid storing important user data in a realm on tvOS. Instead, it's best to treat Realm as a rebuildable cache. > Note: > The reason for this has to do with where Realm writes its Realm files. On other Apple platforms, Realm writes its Realm files to the "Documents" directory. Because tvOS restricts writes to that directory, the default Realm file location on tvOS is instead `NSCachesDirectory`. tvOS can purge files in that directory at any time, so reliable long-term persistence is not possible. > You can also use Realm as an initial data source by bundling prebuilt Realm files in your app. Note that the [App Store guidelines](https://developer.apple.com/tvos/submit/) limit your app size to 4GB. > Tip: > Browse our [tvOS examples](https://github.com/realm/realm-swift/tree/master/examples/tvos) for sample tvOS apps that demonstrate how to use Realm as an offline cache. > ## Share Realm Files with TV Services Extensions To share a Realm file between a tvOS app and a TV services extension such as [Top Shelf](https://developer.apple.com/design/human-interface-guidelines/tvos/overview/top-shelf/), use the `Library/Caches/` directory in the shared container for the application group: ```swift let fileUrl = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: "group.com.mongodb.realm.examples.extension")! .appendingPathComponent("Library/Caches/default.realm") ``` ================================================ FILE: docs/guides/swift-concurrency.md ================================================ # Swift Concurrency - Swift SDK Swift's concurrency system provides built-in support for writing asynchronous and parallel code in a structured way. For a detailed overview of the Swift concurrency system, refer to the [Swift Programming Language Concurrency topic](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html). While the considerations on this page broadly apply to using realm with Swift concurrency features, Realm Swift SDK version 10.39.0 adds support for using Realm with Swift Actors. You can use Realm isolated to a single actor or use Realm across actors. Realm's actor support simplifies using Realm in a MainActor and background actor context, and supersedes much of the advice on this page regarding concurrency considerations. For more information, refer to Use Realm with Actors - Swift SDK. ## Realm Concurrency Caveats As you implement concurrency features in your app, consider this caveat about Realm's threading model and Swift concurrency threading behaviors. ### Suspending Execution with Await Anywhere you use the Swift keyword `await` marks a possible suspension point in the execution of your code. With Swift 5.7, once your code suspends, subsequent code might not execute on the same thread. This means that anywhere you use `await` in your code, the subsequent code could be executed on a different thread than the code that precedes or follows it. This is inherently incompatible with Realm's live object paradigm. Live objects, collections, and realm instances are **thread-confined**: that is, they are only valid on the thread on which they were created. Practically speaking, this means you cannot pass live instances to other threads. However, Realm offers several mechanisms for sharing objects across threads. These mechanisms typically require your code to do some explicit handling to safely pass data across threads. You can use some of these mechanisms, such as frozen objects or the ThreadSafeReference, to safely use Realm objects and instances across threads with the `await` keyword. You can also avoid threading-related issues by marking any asynchronous Realm code with `@MainActor` to ensure your apps always execute this code on the main thread. As a general rule, keep in mind that using Realm in an `await` context *without* incorporating threading protection may yield inconsistent behavior. Sometimes, the code may succeed. In other cases, it may throw an error related to writing on an incorrect thread. ### Perform Background Writes A commonly-requested use case for asynchronous code is to perform write operations in the background without blocking the main thread. Realm has two APIs that allow for performing asynchronous writes: - The [writeAsync()](https://www.mongodb.com/docs/realm-sdks/swift/latest/Structs/Realm.html#/s:10RealmSwift0A0V10writeAsync_10onCompletes6UInt32Vyyc_ys5Error_pSgcSgtF) API allows for performing async writes using Swift completion handlers. - The [asyncWrite()](https://www.mongodb.com/docs/realm-sdks/swift/latest/Structs/Realm.html#/s:10RealmSwift0A0V10asyncWriteyxxyKXEYaKlF) API allows for performing async writes using Swift async/await syntax. Both of these APIs allow you to add, update, or delete objects in the background without using frozen objects or passing a thread-safe reference. With the `writeAsync()` API, waiting to obtain the write lock and committing a transaction occur in the background. The write block itself runs on the calling thread. This provides thread-safety without requiring you to manually handle frozen objects or passing references across threads. However, while the write block itself is executed, this does block new transactions on the calling thread. This means that a large write using the `writeAsync()` API could block small, quick writes while it executes. The `asyncWrite()` API suspends the calling task while waiting for its turn to write rather than blocking the thread. In addition, the actual I/O to write data to disk is done by a background worker thread. For small writes, using this function on the main thread may block the main thread for less time than manually dispatching the write to a background thread. For more information, including code examples, refer to: Perform a Background Write. ## Tasks and TaskGroups Swift concurrency provides APIs to manage [Tasks](https://developer.apple.com/documentation/swift/task) and [TaskGroups](https://developer.apple.com/documentation/swift/taskgroup). The [Swift concurrency documentation](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) defines a task as a unit of work that can be run asynchronously as part of your program. Task allows you to specifically define a unit of asynchronous work. TaskGroup lets you define a collection of Tasks to execute as a unit under the parent TaskGroup. Tasks and TaskGroups provide the ability to yield the thread to other important work or to cancel a long-running task that could be blocking other operations. To get these benefits, you might be tempted to use Tasks and TaskGroups to manage realm writes in the background. However, the thread-confined constraints described in Suspending Execution with Await above apply in the Task context. If your Task contains `await` points, subsequent code might run or resume on a different thread and violate Realm's thread confinement. You must annotate functions that you run in a Task context with `@MainActor` to ensure code that accesses Realm only runs on the main thread. This negates some of the benefits of using Tasks, and may mean this is not a good design choice for apps that use Realm unless you are using Tasks solely for networking activities like managing users. ## Actor Isolation > Seealso: > The information in this section is applicable to Realm SDK versions earlier than 10.39.0. Starting in Realm Swift SDK version 10.39.0 and newer, the SDK supports using Realm with Swift Actors and related async functionality. > > For more information, refer to Use Realm with Actors - Swift SDK. > Actor isolation provides the perception of confining Realm access to a dedicated actor, and therefore seems like a safe way to manage Realm access in an asynchronous context. However, using Realm in a non-`@MainActor` async function is currently not supported. In Swift 5.6, this would often work by coincidence. Execution after an `await` would continue on whatever thread the awaited thing ran on. Using `await Realm()` in an async function would result in the code following that running on the main thread until your next call to an actor-isolated function. Swift 5.7 instead hops threads whenever changing actor isolation contexts. An unisolated async function always runs on a background thread instead. If you have code which uses `await Realm()` and works in 5.6, marking the function as `@MainActor` will make it work with Swift 5.7. It will function how it did - unintentionally - in 5.6. ## Errors Related to Concurrency Code Most often, the error you see related to accessing Realm through concurrency code is `Realm accessed from incorrect thread.` This is due to the thread-isolation issues described on this page. To avoid threading-related issues in code that uses Swift concurrency features: - Upgrade to a version of the Realm Swift SDK that supports actor-isolated realms, and use this as an alternative to manually managing threading. For more information, refer to Use Realm with Actors - Swift SDK. - Do not change execution contexts when accessing a realm. If you open a realm on the main thread to provide data for your UI, annotate subsequent functions where you access the realm asynchronously with `@MainActor` to ensure it always runs on the main thread. Remember that `await` marks a suspension point that could change to a different thread. - Apps that do not use actor-isolated realms can use the `writeAsync` API to perform a background write. This manages realm access in a thread-safe way without requiring you to write specialized code to do it yourself. This is a special API that outsources aspects of the write process - where it is safe to do so - to run in an async context. Unless you are writing to an actor-isolated realm, you do not use this method with Swift's `async/await` syntax. Use this method synchronously in your code. Alternately, you can use the `asyncWrite` API with Swift's `async/await` syntax when awaiting writes to asynchronous realms. - If you want to explicitly write concurrency code that is not actor-isolated where accessing a realm is done in a thread-safe way, you can explicitly pass instances across threads where applicable to avoid threading-related crashes. This does require a good understanding of Realm's threading model, as well as being mindful of Swift concurrency threading behaviors. ## Sendable, Non-Sendable and Thread-Confined Types The Realm Swift SDK public API contains types that fall into three broad categories: - Sendable - Not Sendable and not thread confined - Thread-confined You can share types that are not Sendable and not thread confined between threads, but you must synchronize them. Thread-confined types, unless frozen, are confined to an isolation context. You cannot pass them between these contexts even with synchronization. ================================================ FILE: docs/guides/swiftui/configure-and-open-realm.md ================================================ # Configure and Open a Realm - SwiftUI The Swift SDK provides property wrappers to open a realm in a SwiftUI-friendly way. You can: - Implicitly open a realm with a `defaultConfiguration` or specify a different configuration. ## Open a Realm with a Configuration When you use `@ObservedRealmObject` or `@ObservedResults`, these property wrappers implicitly open a realm and retrieve the specified objects or results. ```swift // Implicitly use the default realm's objects(Dog.self) @ObservedResults(Dog.self) var dogs ``` > Note: > The `@ObservedResults` property wrapper is intended for use in a SwiftUI View. If you want to observe results in a view model, register a change listener. > When you do not specify a configuration, these property wrappers use the `defaultConfiguration`. You can set the defaultConfiguration globally, and property wrappers across the app can use that configuration when they implicitly open a realm. You can provide alternative configurations that the property wrappers use to implicitly open the realm. To do this, create explicit configurations. Then, use environment injection to pass the respective configurations to the views that need them. Passing a configuration to a view where property wrappers open a realm uses the passed configuration instead of the `defaultConfiguration`. ================================================ FILE: docs/guides/swiftui/filter-data.md ================================================ # Filter Data - SwiftUI ## Observe in SwiftUI Views The `@ObservedResults` property wrapper used in the examples on this page is intended for use in a SwiftUI View. If you want to observe results in a view model instead, register a change listener. ## Search a Realm Collection > Version added: 10.19.0 The Realm Swift SDK allows you to extend [.searchable](https://developer.apple.com/documentation/swiftui/view/searchable(text:placement:prompt:)-18a8f). When you use `ObservedResults` to query a realm, you can specify collection and keypath in the result set to mark it as searchable. The collection is the bound collection represented by your ObservedResults query. In this example, it is the `dogs` variable that represents the collection of all Dog objects in the realm. The keypath is the object property that you want to search. In this example, we search the dogs collection by dog name. The Realm Swift `.searchable` implementation only supports keypaths with `String` types. ```swift struct SearchableDogsView: View { @ObservedResults(Dog.self) var dogs @State private var searchFilter = "" var body: some View { NavigationView { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) } } .searchable(text: $searchFilter, collection: $dogs, keyPath: \.name) { ForEach(dogs) { dogsFiltered in Text(dogsFiltered.name).searchCompletion(dogsFiltered.name) } } } } } ``` ## Filter or Query a Realm with ObservedResults The `@ObservedResults` property wrapper opens a realm and returns all objects of the specified type. However, you can filter or query `@ObservedResults` to use only a subset of the objects in your view. > Seealso: > For more information about the query syntax and types of queries that Realm supports, see: Read and Filter Data. > ### Filter with an NSPredicate To filter `@ObservedResults` using the NSPredicate Query API, pass an [NSPredicate](https://developer.apple.com/documentation/foundation/nspredicate) as an argument to `filter`: ```swift struct FilterDogsViewNSPredicate: View { @ObservedResults(Dog.self, filter: NSPredicate(format: "weight > 40")) var dogs var body: some View { NavigationView { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) } } } } } ``` ### Query with the Realm Type-Safe Query API > Version added: 10.24.0 > Use *where* to perform type-safe queries on ObservedResults. > To use `@ObservedResults` with the Realm Type-Safe Query API, pass a query in a closure as an argument to `where`: ```swift struct FilterDogsViewTypeSafeQuery: View { @ObservedResults(Dog.self, where: ( { $0.weight > 40 } )) var dogs var body: some View { NavigationView { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) } } } } } ``` ## Section Filtered Results > Version added: 10.29.0 The `@ObservedSectionedResults` property wrapper opens a realm and returns all objects of the specified type, divided into sections by the specified key path. Similar to `@ObservedResults` above, you can filter or query `@ObservedSectionedResults` to use only a subset of the objects in your view: ```swift @ObservedSectionedResults(Dog.self, sectionKeyPath: \.firstLetter, where: ( { $0.weight > 40 } )) var dogs ``` ================================================ FILE: docs/guides/swiftui/model-data/change-an-object-model.md ================================================ # Change an Object Model - SwiftUI ## Overview When you update your object schema, you must increment the schema version and perform a migration. You might update your object schema between major version releases of your app. For information on how to actually perform the migration, see: Change an Object Model. This page focuses on how to use migrated data in SwiftUI Views. ## Use Migrated Data with SwiftUI To perform a migration: - Update your schema and write a migration block, if required - Specify a `Realm.Configuration` that uses this migration logic and/or updated schema version when you initialize your realm. From here, you have a few options to pass the configuration object. You can: - Set the configuration as the default configuration. If you do not explicitly pass the configuration via environment injection or as a parameter, property wrappers use the default configuration. - Use environment injection to provide this configuration to the first view in your hierarchy that uses Realm - Explicitly provide the configuration to a Realm property wrapper that takes a configuration object, such as `@ObservedResults` or `@AsyncOpen`. > Example: > For example, you might want to add a property to an existing object. We could add a `favoriteTreat` property to the `Dog` object in DoggoDB: > > ```swift > @Persisted var favoriteTreat = "" > ``` > > After you add your new property to the schema, you must increment the schema version. Your `Realm.Configuration` might look like this: > > ```swift > let config = Realm.Configuration(schemaVersion: 2) > > ``` > > Declare this configuration somewhere that is accessible to the first view in the hierarchy that needs it. Declaring this above your `@main` app entrypoint makes it available everywhere, but you could also put it in the file where you first open a realm. > ### Set a Default Configuration You can set a default configuration in a SwiftUI app the same as any other Realm Swift app. Set the default realm configuration by assigning a new Realm.Configuration instance to the `Realm.Configuration.defaultConfiguration` class property. ```swift // Open the default realm let defaultRealm = try! Realm() // Open the realm with a specific file URL, for example a username let username = "GordonCole" var config = Realm.Configuration.defaultConfiguration config.fileURL!.deleteLastPathComponent() config.fileURL!.appendPathComponent(username) config.fileURL!.appendPathExtension("realm") let realm = try! Realm(configuration: config) ``` ### Pass the Configuration Object as an Environment Object Once you have declared the configuration, you can inject it as an environment object to the first view in your hierarchy that opens a realm. If you are using the `@ObservedResults` or `@ObservedRealmObject` property wrappers, these views implicitly open a realm, so they also need access to this configuration. ```swift .environment(\.realmConfiguration, config) ``` You can pass the realm configuration environment object directly to the `LocalOnlyContentView`: ```swift .environment(\.realmConfiguration, config) ``` Which opens a realm implicitly with: ```swift struct LocalOnlyContentView: View { // Implicitly use the default realm's objects(Dog.self) @ObservedResults(Dog.self) var dogs var body: some View { if dogs.first != nil { // If dogs exist, go to the DogsView DogsView() } else { // If there is no Dog object, add one here. AddDogView() } } } ``` ### Explicitly Pass the Updated Configuration to a Realm SwiftUI Property Wrapper You can explicitly pass the configuration object to a Realm SwiftUI property wrapper that takes a configuration object, such as `@ObservedResults` or `@AutoOpen`. In this case, you might pass it directly to `@ObservedResults` in our `DogsView`. ```swift // Use a `config` that you've passed in from above. @ObservedResults(Dog.self, configuration: config) var dogs ``` ================================================ FILE: docs/guides/swiftui/model-data/define-a-realm-object-model.md ================================================ # Realm Object Models - SwiftUI ## Concepts: Object Models and Relationships Modeling data for SwiftUI builds on the same object model and relationship concepts in the Swift SDK. If you are unfamiliar with Realm Swift SDK data modeling concepts, see: Define a Realm Object Model - Swift SDK. ## Binding the Object Model to the UI The Model-View-ViewModel (MVVM) design pattern advocates creating a view model that abstracts the model from the View code. While you can certainly do that with Realm, the Swift SDK provides tools that make it easy to work directly with your data in SwiftUI Views. These tools include things like: - Property wrappers that create bindings to underlying observable objects - A class to project and transform underlying model objects for use in specific views ## Transforming Data for SwiftUI Views The Realm Swift SDK provides a special type of object, called a `Projection`, to transform and work with subsets of your data. Consider a projection similar to a view model. It lets you pass through or transform the original object's properties in different ways: - Passthrough: The projection's property has the same name and type as the original object. - Rename: The projection's property has the same type as the original object, but a different name. - Keypath resolution: Use this to access specific properties of the projected Object. - Collection mapping: You can map some collection types to a collection of primitive values. - Exclusion: All properties of the original Realm object not defined in the projection model. Any changes to those properties do not trigger a change notification when observing the projection. When you use a Projection, you get all the benefits of Realm's live objects: - The class-projected object live updates - You can observe it for changes - You can apply changes directly to the properties in write transactions ## Define a New Object You can define a Realm object by deriving from the `Object` or `EmbeddedObject` class. The name of the class becomes the table name in the realm, and properties of the class persist in the database. This makes it as easy to work with persisted objects as it is to work with regular Swift objects. The Realm SwiftUI documentation uses a model for a fictional app, DoggoDB. This app is a company directory of employees who have dogs. It lets people share a few details about their dogs with other employees. The data model includes a Person object, with a to-many relationship to that person's Dog objects. It also uses a special Realm Swift SDK data type, `PersistableEnum`, to store information about the person's business unit. ```swift class Person: Object, ObjectKeyIdentifiable { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var firstName = "" @Persisted var lastName = "" @Persisted var personId = "" @Persisted var company = "my business" @Persisted var businessUnit = BusinessUnitEnum.engineering @Persisted var profileImageUrl: URL? @Persisted var dogs: List } enum BusinessUnitEnum: String, PersistableEnum, CaseIterable { case customerEngineering = "Customer Engineering" case educationCommunityAndDocs = "Education, Community and Docs" case engineering = "Engineering" case financeAndOperations = "Finance and Operations" case humanResourcesAndRescruiting = "Human Resources and Recruiting" case management = "Management" case marketing = "Marketing" case product = "Product" case sales = "Sales" } class Dog: Object, ObjectKeyIdentifiable { @Persisted(primaryKey: true) var _id: UUID @Persisted var name = "" @Persisted var breed = "" @Persisted var weight = 0 @Persisted var favoriteToy = "" @Persisted var profileImageUrl: URL? @Persisted var dateLastUpdated = Date() @Persisted(originProperty: "dogs") var person: LinkingObjects var firstLetter: String { guard let char = name.first else { return "" } return String(char) } } ``` > Seealso: > For complete details about defining a Realm object model, see: > > - Object Models > - Relationships > - Supported Data Types > ## Define a Projection Our fictional DoggoDB app has a user Profile view. This view displays some details about the person, but we don't need all of the properties of the `Person` model. We can create a `Projection` with only the details we want. We can also modify the `lastName` property to use just the first initial of the last name. ```swift class Profile: Projection { @Projected(\Person.firstName) var firstName // Passthrough from original object @Projected(\Person.lastName.localizedCapitalized.first) var lastNameInitial // Access and transform the original property @Projected(\Person.personId) var personId @Projected(\Person.businessUnit) var businessUnit @Projected(\Person.profileImageUrl) var profileImageUrl @Projected(\Person.dogs) var dogs } ``` We can use this projection in the Profile view instead of the original `Person` object. Class projection works with SwiftUI property wrappers: - `ObservedRealmObject` - `ObservedResults` > Seealso: > For a complete example of using a class projection in a SwiftUI application, see [the Projections example app](https://github.com/realm/realm-cocoa/tree/master/examples#projections). > ================================================ FILE: docs/guides/swiftui/pass-realm-data-between-views.md ================================================ # Pass Realm Data Between SwiftUI Views The Realm Swift SDK provides several ways to pass realm data between views: - Pass Realm objects to a view - Use environment injection to: Inject a partition value into a viewInject an opened realm into a viewInject a realm configuration into a view ## Pass Realm Objects to a View When you use the `@ObservedRealmObject` or `@ObservedResults` property wrapper, you implicitly open a realm and retrieve the specified objects or results. You can then pass those objects to a view further down the hierarchy. ```swift struct DogsView: View { @ObservedResults(Dog.self) var dogs /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the dogs in the realm. // The ``@ObservedResults`` above implicitly opens a realm and retrieves // all the Dog objects. We can then pass those objects to views further down the // hierarchy. List { ForEach(dogs) { dog in DogRow(dog: dog) }.onDelete(perform: $dogs.remove) }.listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) }.padding() } } } ``` ## Pass Environment Values [Environment](https://developer.apple.com/documentation/swiftui/environment) injection is a useful tool in SwiftUI development with Realm. Realm property wrappers provide different ways for you to work with environment values when developing your SwiftUI application. ### Inject an Opened Realm You can inject a realm that you opened in another SwiftUI view into a view as an environment value. The property wrapper uses this passed-in realm to populate the view: ```swift ListView() .environment(\.realm, realm) ``` ### Inject a Realm Configuration You can use a realm other than the default realm by passing a different configuration in an environment object. ```swift LocalOnlyContentView() .environment(\.realmConfiguration, Realm.Configuration( /* ... */ )) ``` ================================================ FILE: docs/guides/swiftui/react-to-changes.md ================================================ # React to Changes - SwiftUI ## Observe an Object The Swift SDK provides the `@ObservedRealmObject` property wrapper that invalidates a view when an observed object changes. You can use this property wrapper to create a view that automatically updates itself when the observed object changes. ```swift struct DogDetailView: View { @ObservedRealmObject var dog: Dog var body: some View { VStack { Text(dog.name) .font(.title2) Text("\(dog.name) is a \(dog.breed)") AsyncImage(url: dog.profileImageUrl) { image in image.resizable() } placeholder: { ProgressView() } .aspectRatio(contentMode: .fit) .frame(width: 150, height: 150) Text("Favorite toy: \(dog.favoriteToy)") } } } ``` ## Observe Query Results The Swift SDK provides the `@ObservedResults` property wrapper that lets you observe a collection of query results. You can perform a quick write to an ObservedResults collection, and the view automatically updates itself when the observed query changes. For example, you can remove a dog from an observed list of dogs using `onDelete`. > Note: > The `@ObservedResults` property wrapper is intended for use in a SwiftUI View. If you want to observe results in a view model, register a change listener. > ```swift struct DogsView: View { @ObservedResults(Dog.self) var dogs /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the dogs in the realm. // The ``@ObservedResults`` above implicitly opens a realm and retrieves // all the Dog objects. We can then pass those objects to views further down the // hierarchy. List { ForEach(dogs) { dog in DogRow(dog: dog) }.onDelete(perform: $dogs.remove) }.listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) }.padding() } } } ``` > Seealso: > For more information about the query syntax and types of queries that Realm supports, see: Read and Filter Data. > ### Sort Observed Results The `@ObservedResults` property wrapper can take a `SortDescriptor` parameter to sort the query results. ```swift struct SortedDogsView: View { @ObservedResults(Dog.self, sortDescriptor: SortDescriptor(keyPath: "name", ascending: true)) var dogs var body: some View { NavigationView { // The list shows the dogs in the realm, sorted by name List(dogs) { dog in DogRow(dog: dog) } } } } ``` > Tip: > You cannot use a computed property as a `SortDescriptor` for `@ObservedResults`. > ### Observe Sectioned Results > Version added: 10.29.0 You can observe a results set that is divided into sections by a key generated from a property on the object. We've added a computed variable to the model that we don't persist; we just use this to section the results set. ```swift var firstLetter: String { guard let char = name.first else { return "" } return String(char) } ``` Then, we can use the `@ObservedSectionedResults` property wrapper to observe the results set divided into sections based on the computed variable key. ```swift @ObservedSectionedResults(Dog.self, sectionKeyPath: \.firstLetter) var dogs ``` You might use these observed sectioned results to populate a List view divided by sections: ```swift struct SectionedDogsView: View { @ObservedSectionedResults(Dog.self, sectionKeyPath: \.firstLetter) var dogs /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the dogs in the realm, split into sections according to the keypath. List { ForEach(dogs) { section in Section(header: Text(section.key)) { ForEach(section) { dog in DogRow(dog: dog) } } } } .listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) }.padding() } } } ``` ================================================ FILE: docs/guides/swiftui/swiftui-previews.md ================================================ # Use Realm with SwiftUI Previews ## Overview SwiftUI Previews are a useful tool during development. You can work with Realm data in SwiftUI Previews in a few ways: - Initialize individual objects to use in detail views - Conditionally use an array of objects in place of `@ObservedResults` - Create a realm that contains data for the previews SwiftUI Preview debugging can be opaque, so we also have a few tips to debug issue with persisting Realms within SwiftUI Previews. ### Initialize an Object for a Detail View In the simplest case, you can use SwiftUI Previews with one or more objects that use Realm properties you can set directly at initialization. You might want to do this when previewing a Detail view. Consider DoggoDB's `DogDetailView`: ```swift struct DogDetailView: View { @ObservedRealmObject var dog: Dog var body: some View { VStack { Text(dog.name) .font(.title2) Text("\(dog.name) is a \(dog.breed)") AsyncImage(url: dog.profileImageUrl) { image in image.resizable() } placeholder: { ProgressView() } .aspectRatio(contentMode: .fit) .frame(width: 150, height: 150) Text("Favorite toy: \(dog.favoriteToy)") } } } ``` Create an extension for your model object. Where you put this extension depends on convention in your codebase. You may put it directly in the model file, have a dedicated directory for sample data, or use some other convention in your codebase. In this extension, initialize one or more Realm objects with `static let`: ```swift extension Dog { static let dog1 = Dog(value: ["name": "Lita", "breed": "Lab mix", "weight": 27, "favoriteToy": "Squeaky duck", "profileImageUrl": "https://www.corporaterunaways.com/images/2021/04/lita-768x768.jpeg"]) static let dog2 = Dog(value: ["name": "Maui", "breed": "English Springer Spaniel", "weight": 42, "favoriteToy": "Wubba", "profileImageUrl": "https://www.corporaterunaways.com/images/2021/04/maui_with_leaf-768x576.jpeg"]) static let dog3 = Dog(value: ["name": "Ben", "breed": "Border Collie mix", "weight": 48, "favoriteToy": "Frisbee", "profileImageUrl": "https://www.corporaterunaways.com/images/2012/03/ben-630x420.jpg"]) } ``` In this example, we initialize objects with a value. You can only initialize objects with a value when your model contains properties that you can directly initialize. If your model object contains properties that are only mutable within a write transaction, such as a List property, you must instead create a realm to use with your SwiftUI Previews. After you have initialized an object as an extension of your model class, you can use it in your SwiftUI Preview. You can pass the object directly to the View in the Preview: ```swift struct DogDetailView_Previews: PreviewProvider { static var previews: some View { NavigationView { DogDetailView(dog: Dog.dog1) } } } ``` ### Conditionally Use ObservedResults in a List View When you use `@ObservedResults` in a List view, this implicitly opens a realm and queries it. For this to work in a Preview, you need a realm populated with data. As an alternative, you can conditionally use a static array in Previews and only use the `@ObservedResults` variable when running the app. You could do this in multiple ways, but for the sake of making our code easier to read and understand, we'll create an `EnvironmentValue` that can detect whether the app is running in a Preview: ```swift import Foundation import SwiftUI public extension EnvironmentValues { var isPreview: Bool { #if DEBUG return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" #else return false #endif } } ``` Then, we can use this as an environment value in our view, and conditionally change which variable we use based on whether or not we are in a Preview. This example builds on the Dog extension we defined above. We'll create an `dogArray` as a `static let` in our Dog extension, and include the item objects we already created: ```swift static let dogArray = [dog1, dog2, dog3] ``` Then, when we iterate through our List, use the static `dogArray` if running in a Preview, or use the `@ObservedResults` query if not in a Preview. ```swift struct DogsView: View { @Environment(\.isPreview) var isPreview @ObservedResults(Dog.self) var dogs var previewDogs = Dog.dogArray var body: some View { NavigationView { VStack { List { if isPreview { ForEach(previewDogs) { dog in DogRow(dog: dog) } } else { ForEach(dogs) { dog in DogRow(dog: dog) }.onDelete(perform: $dogs.remove) } } ... More View code ``` This has the benefit of being lightweight and not persisting any data, but the downside of making the View code more verbose. If you prefer cleaner View code, you can create a realm with data that you use in the Previews. ### Create a Realm with Data for Previews In some cases, your only option to see realm data in a SwiftUI Preview is to create a realm that contains the data. You might do this when populating a property that can only be populated during a write transaction, rather than initialized directly with a value, such as a List or MutableSet. You might also want to do this if your view relies on more complex object hierarchies being passed in from other views. However, using a realm directly does inject state into your SwiftUI Previews, which can come with drawbacks. Whether you're using Realm or Core Data, stateful SwiftUI Previews can cause issues like: - Seeing unexpected or duplicated data due to re-running the realm file creation steps repeatedly - Needing to perform a migration within the SwiftUI Preview when you make model changes - Potential issues related to changing state within views - Unexplained crashes or performance issues related to issues that are not surfaced in a visible way in SwiftUI Previews You can avoid or fix some of these issues with these tips: - Use an in-memory realm, when possible (demonstrated in the example above) - Manually delete all preview data from the command line to reset state - Check out diagnostic logs to try to troubleshoot SwiftUI Preview issues You can create a static variable for your realm in your model extension. This is where you do the work to populate your realm. In our case, we create a `Person` and append some `Dog` objects to the `dogs` List property. This example builds on the example above where we initialized a few Dog objects in an Dog extension. We'll create a `Person` extension, and create a single `Person` object in that extension. Then, we'll create a `previewRealm` by adding the `Person` we just created, and appending the example `Dog` objects from the `Dog` extension. To avoid adding these objects more than once, we add a check to see if the Person already exists by querying for Person objects and checking that the count is 1. If the realm contains a Person, we can use it in our SwiftUI Preview. If not, we add the data. ```swift static var previewRealm: Realm { var realm: Realm let identifier = "previewRealm" let config = Realm.Configuration(inMemoryIdentifier: identifier) do { realm = try Realm(configuration: config) // Check to see whether the in-memory realm already contains a Person. // If it does, we'll just return the existing realm. // If it doesn't, we'll add a Person append the Dogs. let realmObjects = realm.objects(Person.self) if realmObjects.count == 1 { return realm } else { try realm.write { realm.add(person) person.dogs.append(objectsIn: [Dog.dog1, Dog.dog2, Dog.dog3]) } return realm } } catch let error { fatalError("Can't bootstrap item data: \(error.localizedDescription)") } } ``` To use it in the SwiftUI Preview, our ProfileView code expects a Profile. This is a projection of the Person object. In our Preview, we can get the realm, query it for the Profile, and pass it to the view: ```swift struct ProfileView_Previews: PreviewProvider { static var previews: some View { let realm = Person.previewRealm let profile = realm.objects(Profile.self) ProfileView(profile: profile.first!) } } ``` If you don't have a View that is expecting a realm object to be passed in, but instead uses `@ObservedResults` to query a realm or otherwise work with an existing realm, you can inject the realm into the view as an environment value: ```swift struct SomeListView_Previews: PreviewProvider { static var previews: some View { SomeListView() .environment(\.realm, Person.previewRealm) } } ``` #### Use an In-Memory Realm When possible, use an in-memory realm to get around some of the state-related issues that can come from using a database within a SwiftUI Preview. Use the `inMemoryIdentifier` configuration property when you initialize the realm. ```swift static var previewRealm: Realm { var realm: Realm let identifier = "previewRealm" let config = Realm.Configuration(inMemoryIdentifier: identifier) do { realm = try Realm(configuration: config) // ... Add data to realm ``` > Note: > Do not use the the `deleteRealmIfMigrationNeeded` configuration property when you initialize a realm for SwiftUI Previews. Due to the way Apple has implemented SwiftUI Previews, using this property to bypass migration issues causes SwiftUI Previews to crash. > #### Delete SwiftUI Previews If you run into other SwiftUI Preview issues related to state, such as a failure to load a realm in a Preview due to migration being required, there are a few things you can do to remove cached Preview data. The Apple-recommended fix is to close Xcode and use the command line to delete all your existing SwiftUI Preview data. 1. Close Xcode. 2. From your command line, run: `xcrun simctl --set previews delete all` It's possible that data may persist after running this command. This is likely due to Xcode retaining a reference due to something in the Preview and being unable to delete it. You can also try these steps to resolve issues: - Build for a different simulator - Restart the computer and re-run `xcrun simctl --set previews delete all` - Delete stored Preview data directly. This data is stored in `~/Library/Developer/Xcode/UserData/Previews`. #### Get Detailed Information about SwiftUI Preview Crashes If you have an unexplained SwiftUI Preview crash when using realm, first try running the application on the simulator. The error messaging and logs available for the simulator make it easier to find and diagnose issues. If you can debug the issue in the simulator, this is the easiest route. If you cannot replicate a SwiftUI Preview crash in the simulator, you can view crash logs for the SwiftUI Preview app. These logs are available in `~/Library/Logs/DiagnosticReports/`. These logs sometimes appear after a delay, so wait a few minutes after a crash if you don't see the relevant log immediately. ================================================ FILE: docs/guides/swiftui/swiftui-tutorial.md ================================================ # Realm with SwiftUI QuickStart ## Prerequisites - Have Xcode 12.4 or later (minimum Swift version 5.3.1). - Create a new Xcode project using the SwiftUI "App" template with a minimum iOS target of 15.0. - Install the Swift SDK. This SwiftUI app requires a minimum SDK version of 10.19.0. ## Overview > Seealso: > This page provides a small working app to get you up and running with Realm and SwiftUI quickly. If you'd like to see additional examples, including more explanation about Realm's SwiftUI features, see: SwiftUI. > This page contains all of the code for a working Realm and SwiftUI app. The app starts on the `ItemsView`, where you can edit a list of items: - Press the `Add` button on the bottom right of the screen to add randomly-generated items. - Press the `Edit` button on the top right to modify the list order, which the app persists in the realm. - You can also swipe to delete items. When you have items in the list, you can press one of the items to navigate to the `ItemDetailsView`. This is where you can modify the item name or mark it as a favorite: - Press the text field in the center of the screen and type a new name. When you press Return, the item name should update across the app. - You can also toggle its favorite status by pressing the heart toggle in the top right. ### Get Started We assume you have created an Xcode project with the SwiftUI "App" template. Open the main Swift file and delete all of the code inside, including any `@main` `App` classes that Xcode generated for you. At the top of the file, import the Realm and SwiftUI frameworks: ```swift import RealmSwift import SwiftUI ``` > Tip: > Just want to dive right in with the complete code? Jump to Complete Code below. > ### Define Models A common Realm data modeling use case is to have "things" and "containers of things". This app defines two related Realm object models: item and itemGroup. An item has two user-facing properties: - A randomly generated-name, which the user can edit. - An `isFavorite` boolean property, which shows whether the user "favorited" the item. An itemGroup contains items. You can extend the itemGroup to have a name and an association with a specific user, but that's out of scope of this guide. Paste the following code into your main Swift file to define the models: ```swift /// Random adjectives for more interesting demo item names let randomAdjectives = [ "fluffy", "classy", "bumpy", "bizarre", "wiggly", "quick", "sudden", "acoustic", "smiling", "dispensable", "foreign", "shaky", "purple", "keen", "aberrant", "disastrous", "vague", "squealing", "ad hoc", "sweet" ] /// Random noun for more interesting demo item names let randomNouns = [ "floor", "monitor", "hair tie", "puddle", "hair brush", "bread", "cinder block", "glass", "ring", "twister", "coasters", "fridge", "toe ring", "bracelet", "cabinet", "nail file", "plate", "lace", "cork", "mouse pad" ] /// An individual item. Part of an `ItemGroup`. final class Item: Object, ObjectKeyIdentifiable { /// The unique ID of the Item. `primaryKey: true` declares the /// _id member as the primary key to the realm. @Persisted(primaryKey: true) var _id: ObjectId /// The name of the Item, By default, a random name is generated. @Persisted var name = "\(randomAdjectives.randomElement()!) \(randomNouns.randomElement()!)" /// A flag indicating whether the user "favorited" the item. @Persisted var isFavorite = false /// Users can enter a description, which is an empty string by default @Persisted var itemDescription = "" /// The backlink to the `ItemGroup` this item is a part of. @Persisted(originProperty: "items") var group: LinkingObjects } /// Represents a collection of items. final class ItemGroup: Object, ObjectKeyIdentifiable { /// The unique ID of the ItemGroup. `primaryKey: true` declares the /// _id member as the primary key to the realm. @Persisted(primaryKey: true) var _id: ObjectId /// The collection of Items in this group. @Persisted var items = RealmSwift.List() } ``` ### Views and Observed Objects The entrypoint of the app is the `ContentView` class that derives from `SwiftUI.App`. For now, this always displays the `LocalOnlyContentView`. ```swift @main struct ContentView: SwiftUI.App { var body: some Scene { WindowGroup { LocalOnlyContentView() } } } ``` > Tip: > You can use a realm other than the default realm by passing an environment object from higher in the View hierarchy: > > ```swift > LocalOnlyContentView() > .environment(\.realmConfiguration, Realm.Configuration( /* ... */ )) > ``` > The LocalOnlyContentView has an `@ObservedResults` itemGroups. This implicitly uses the default realm to load all itemGroups when the view appears. This app only expects there to ever be one itemGroup. If there is an itemGroup in the realm, the LocalOnlyContentView renders an `ItemsView` for that itemGroup. If there is no itemGroup already in the realm, then the LocalOnlyContentView displays a ProgressView while it adds one. Because the view observes the itemGroups thanks to the `@ObservedResults` property wrapper, the view immediately refreshes upon adding that first itemGroup and displays the ItemsView. ```swift /// The main content view struct LocalOnlyContentView: View { @State var searchFilter: String = "" // Implicitly use the default realm's objects(ItemGroup.self) @ObservedResults(ItemGroup.self) var itemGroups var body: some View { if let itemGroup = itemGroups.first { // Pass the ItemGroup objects to a view further // down the hierarchy ItemsView(itemGroup: itemGroup) } else { // For this small app, we only want one itemGroup in the realm. // You can expand this app to support multiple itemGroups. // For now, if there is no itemGroup, add one here. ProgressView().onAppear { $itemGroups.append(ItemGroup()) } } } } ``` > Tip: > Starting in SDK version 10.12.0, you can use an optional key path parameter with `@ObservedResults` to filter change notifications to only those occurring on the provided key path or key paths. For example: > > ``` > @ObservedResults(MyObject.self, keyPaths: ["myList.property"]) > ``` > The ItemsView receives the itemGroup from the parent view and stores it in an `@ObservedRealmObject` property. This allows the ItemsView to "know" when the object has changed regardless of where that change happened. The ItemsView iterates over the itemGroup's items and passes each item to an `ItemRow` for rendering as a list. To define what happens when a user deletes or moves a row, we pass the `remove` and `move` methods of the Realm `List` as the handlers of the respective remove and move events of the SwiftUI List. Thanks to the `@ObservedRealmObject` property wrapper, we can use these methods without explicitly opening a write transaction. The property wrapper automatically opens a write transaction as needed. ```swift /// The screen containing a list of items in an ItemGroup. Implements functionality for adding, rearranging, /// and deleting items in the ItemGroup. struct ItemsView: View { @ObservedRealmObject var itemGroup: ItemGroup /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the items in the realm. List { ForEach(itemGroup.items) { item in ItemRow(item: item) }.onDelete(perform: $itemGroup.items.remove) .onMove(perform: $itemGroup.items.move) } .listStyle(GroupedListStyle()) .navigationBarTitle("Items", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) // Action bar at bottom contains Add button. HStack { Spacer() Button(action: { // The bound collection automatically // handles write transactions, so we can // append directly to it. $itemGroup.items.append(Item()) }) { Image(systemName: "plus") } }.padding() } } } } ``` Finally, the `ItemRow` and `ItemDetailsView` classes use the `@ObservedRealmObject` property wrapper with the item passed in from above. These classes demonstrate a few more examples of how to use the property wrapper to display and update properties. ```swift /// Represents an Item in a list. struct ItemRow: View { @ObservedRealmObject var item: Item var body: some View { // You can click an item in the list to navigate to an edit details screen. NavigationLink(destination: ItemDetailsView(item: item)) { Text(item.name) if item.isFavorite { // If the user "favorited" the item, display a heart icon Image(systemName: "heart.fill") } } } } /// Represents a screen where you can edit the item's name. struct ItemDetailsView: View { @ObservedRealmObject var item: Item var body: some View { VStack(alignment: .leading) { Text("Enter a new name:") // Accept a new name TextField("New name", text: $item.name) .navigationBarTitle(item.name) .navigationBarItems(trailing: Toggle(isOn: $item.isFavorite) { Image(systemName: item.isFavorite ? "heart.fill" : "heart") }) }.padding() } } ``` > Tip: > `@ObservedRealmObject` is a frozen object. If you want to modify the properties of an `@ObservedRealmObject` directly in a write transaction, you must `.thaw()` it first. > At this point, you have everything you need to work with Realm and SwiftUI. Test it out and see if everything is working as expected. ## Complete Code If you would like to copy and paste or examine the complete code, see below. ```swift import RealmSwift import SwiftUI // MARK: Models /// Random adjectives for more interesting demo item names let randomAdjectives = [ "fluffy", "classy", "bumpy", "bizarre", "wiggly", "quick", "sudden", "acoustic", "smiling", "dispensable", "foreign", "shaky", "purple", "keen", "aberrant", "disastrous", "vague", "squealing", "ad hoc", "sweet" ] /// Random noun for more interesting demo item names let randomNouns = [ "floor", "monitor", "hair tie", "puddle", "hair brush", "bread", "cinder block", "glass", "ring", "twister", "coasters", "fridge", "toe ring", "bracelet", "cabinet", "nail file", "plate", "lace", "cork", "mouse pad" ] /// An individual item. Part of an `ItemGroup`. final class Item: Object, ObjectKeyIdentifiable { /// The unique ID of the Item. `primaryKey: true` declares the /// _id member as the primary key to the realm. @Persisted(primaryKey: true) var _id: ObjectId /// The name of the Item, By default, a random name is generated. @Persisted var name = "\(randomAdjectives.randomElement()!) \(randomNouns.randomElement()!)" /// A flag indicating whether the user "favorited" the item. @Persisted var isFavorite = false /// Users can enter a description, which is an empty string by default @Persisted var itemDescription = "" /// The backlink to the `ItemGroup` this item is a part of. @Persisted(originProperty: "items") var group: LinkingObjects } /// Represents a collection of items. final class ItemGroup: Object, ObjectKeyIdentifiable { /// The unique ID of the ItemGroup. `primaryKey: true` declares the /// _id member as the primary key to the realm. @Persisted(primaryKey: true) var _id: ObjectId /// The collection of Items in this group. @Persisted var items = RealmSwift.List() } extension Item { static let item1 = Item(value: ["name": "fluffy coasters", "isFavorite": false, "ownerId": "previewRealm"]) static let item2 = Item(value: ["name": "sudden cinder block", "isFavorite": true, "ownerId": "previewRealm"]) static let item3 = Item(value: ["name": "classy mouse pad", "isFavorite": false, "ownerId": "previewRealm"]) } extension ItemGroup { static let itemGroup = ItemGroup(value: ["ownerId": "previewRealm"]) static var previewRealm: Realm { var realm: Realm let identifier = "previewRealm" let config = Realm.Configuration(inMemoryIdentifier: identifier) do { realm = try Realm(configuration: config) // Check to see whether the in-memory realm already contains an ItemGroup. // If it does, we'll just return the existing realm. // If it doesn't, we'll add an ItemGroup and append the Items. let realmObjects = realm.objects(ItemGroup.self) if realmObjects.count == 1 { return realm } else { try realm.write { realm.add(itemGroup) itemGroup.items.append(objectsIn: [Item.item1, Item.item2, Item.item3]) } return realm } } catch let error { fatalError("Can't bootstrap item data: \(error.localizedDescription)") } } } // MARK: Views // MARK: Main Views /// The main screen that determines whether to present the SyncContentView or the LocalOnlyContentView. /// For now, it always displays the LocalOnlyContentView. @main struct ContentView: SwiftUI.App { var body: some Scene { WindowGroup { LocalOnlyContentView() } } } /// The main content view struct LocalOnlyContentView: View { @State var searchFilter: String = "" // Implicitly use the default realm's objects(ItemGroup.self) @ObservedResults(ItemGroup.self) var itemGroups var body: some View { if let itemGroup = itemGroups.first { // Pass the ItemGroup objects to a view further // down the hierarchy ItemsView(itemGroup: itemGroup) } else { // For this small app, we only want one itemGroup in the realm. // You can expand this app to support multiple itemGroups. // For now, if there is no itemGroup, add one here. ProgressView().onAppear { $itemGroups.append(ItemGroup()) } } } } // MARK: Item Views /// The screen containing a list of items in an ItemGroup. Implements functionality for adding, rearranging, /// and deleting items in the ItemGroup. struct ItemsView: View { @ObservedRealmObject var itemGroup: ItemGroup /// The button to be displayed on the top left. var leadingBarButton: AnyView? var body: some View { NavigationView { VStack { // The list shows the items in the realm. List { ForEach(itemGroup.items) { item in ItemRow(item: item) }.onDelete(perform: $itemGroup.items.remove) .onMove(perform: $itemGroup.items.move) } .listStyle(GroupedListStyle()) .navigationBarTitle("Items", displayMode: .large) .navigationBarBackButtonHidden(true) .navigationBarItems( leading: self.leadingBarButton, // Edit button on the right to enable rearranging items trailing: EditButton()) // Action bar at bottom contains Add button. HStack { Spacer() Button(action: { // The bound collection automatically // handles write transactions, so we can // append directly to it. $itemGroup.items.append(Item()) }) { Image(systemName: "plus") } }.padding() } } } } struct ItemsView_Previews: PreviewProvider { static var previews: some View { let realm = ItemGroup.previewRealm let itemGroup = realm.objects(ItemGroup.self) ItemsView(itemGroup: itemGroup.first!) } } /// Represents an Item in a list. struct ItemRow: View { @ObservedRealmObject var item: Item var body: some View { // You can click an item in the list to navigate to an edit details screen. NavigationLink(destination: ItemDetailsView(item: item)) { Text(item.name) if item.isFavorite { // If the user "favorited" the item, display a heart icon Image(systemName: "heart.fill") } } } } /// Represents a screen where you can edit the item's name. struct ItemDetailsView: View { @ObservedRealmObject var item: Item var body: some View { VStack(alignment: .leading) { Text("Enter a new name:") // Accept a new name TextField("New name", text: $item.name) .navigationBarTitle(item.name) .navigationBarItems(trailing: Toggle(isOn: $item.isFavorite) { Image(systemName: item.isFavorite ? "heart.fill" : "heart") }) }.padding() } } struct ItemDetailsView_Previews: PreviewProvider { static var previews: some View { NavigationView { ItemDetailsView(item: Item.item2) } } } ``` ================================================ FILE: docs/guides/swiftui/write.md ================================================ # Write Data - SwiftUI ## Perform a Quick Write In addition to performing writes inside a transaction block, the Realm Swift SDK offers a convenience feature to enable quick writes without explicitly performing a write transaction. When you use the `@ObservedRealmObject` or `@ObservedResults` property wrappers, you can implicitly open a write transaction. Use the `$` operator to create a two-way binding to the state object. Then, when you make changes to the bound object or collection, you initiate an implicit write. The Realm SwiftUI property wrappers work with frozen data to provide thread safety. When you use `$` to create a two-way binding, the Realm Swift SDK manages thawing the frozen objects so you can write to them. ### Update an Object's Properties In this example, we create a two-way binding with one of the state object's properties. `$dog.favoriteToy` creates a binding to the model Dog object's `favoriteToy` property When the app user updates that field in this example, Realm opens an implicit write transaction and saves the new value to the database. ```swift struct EditDogDetails: View { @ObservedRealmObject var dog: Dog var body: some View { VStack { Text(dog.name) .font(.title2) TextField("Favorite toy", text: $dog.favoriteToy) } } } ``` ### Add or Remove Objects in an ObservedResults Collection While a regular Realm Results collection is immutable, `ObservedResults` is a mutable collection that allows you to perform writes using a two-way binding. When you update the bound collection, Realm opens an implicit write transaction and saves the changes to the collection. In this example, we remove an element from the results set using `$dogs.remove` in the `onDelete`. Using the `$dogs` here creates a two-way binding to a `BoundCollection` that lets us mutate the `@ObservedResults` `dogs` collection. We add an item to the results using `$dogs.append` in the `addDogButton`. These actions write directly to the `@ObservedResults` collection. ```swift struct DogsListView: View { @ObservedResults(Dog.self) var dogs var body: some View { NavigationView { VStack { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) // Because `$dogs` here accesses an ObservedResults // collection, we can remove the specific dog from the collection. // Regular Realm Results are immutable, but you can write directly // to an `@ObservedResults` collection. }.onDelete(perform: $dogs.remove) }.listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) // Action bar at bottom contains Add button. HStack { Spacer() Button(action: { // The bound collection automatically // handles write transactions, so we can // append directly to it. This example assumes // we have some values to populate the Dog object. $dogs.append(Dog(value: ["name":"Bandido"])) }) { Image(systemName: "plus") } .accessibilityIdentifier("addDogButton") }.padding() } } } } ``` > Note: > The `@ObservedResults` property wrapper is intended for use in a SwiftUI View. If you want to observe results in a view model, register a change listener. > ### Append an Object to a List When you have a two-way binding with an `@ObservedRealmObject` that has a list property, you can add new objects to the list. In this example, the `Person` object has a list property that forms a to-many relationship with one or more dogs. ```swift class Person: Object, ObjectKeyIdentifiable { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var firstName = "" @Persisted var lastName = "" ... @Persisted var dogs: List } ``` When the user presses the `Save` button, this: - Creates a `Dog` object with the details that the user has entered - Appends the `Dog` object to the `Person` object's `dogs` list ```swift struct AddDogToPersonView: View { @ObservedRealmObject var person: Person @Binding var isInAddDogView: Bool @State var name = "" @State var breed = "" @State var weight = 0 @State var favoriteToy = "" @State var profileImageUrl: URL? var body: some View { Form { TextField("Dog's name", text: $name) TextField("Dog's breed", text: $breed) TextField("Dog's weight", value: $weight, format: .number) TextField("Dog's favorite toy", text: $favoriteToy) TextField("Image link", value: $profileImageUrl, format: .url) .keyboardType(.URL) .textInputAutocapitalization(.never) .disableAutocorrection(true) Section { Button(action: { let dog = createDog(name: name, breed: breed, weight: weight, favoriteToy: favoriteToy, profileImageUrl: profileImageUrl) $person.dogs.append(dog) isInAddDogView.toggle() }) { Text("Save") } Button(action: { isInAddDogView.toggle() }) { Text("Cancel") } } } } } ``` ### Use Create to Copy an Object Into the Realm There may be times when you create a new object, and set one of its properties to an object that already exists in the realm. Then, when you go to add the new object to the realm, you see an error similar to: ```shell Object is already managed by another Realm. Use create instead to copy it into this Realm. ``` When this occurs, you can use the `.create` method to initialize the object, and use `modified: .update` to set its property to the existing object. > Example: > Consider a version of the DoggoDB `Dog` model where the `favoriteToy` property isn't just a `String`, but is an optional `DogToy` object: > > ```swift > class Dog: Object, ObjectKeyIdentifiable { > @Persisted(primaryKey: true) var _id: UUID > @Persisted var name = "" > ... > @Persisted var favoriteToy: DogToy? > ... > } > ``` > > When your app goes to create a new `Dog` object, perhaps it checks to see if the `DogToy` already exists in the realm, and then set the `favoriteToy` property to the existing dog toy. > > When you go to append the new `Dog` to the `Person` object, you may see an error similar to: > > ```shell > Object is already managed by another Realm. Use create instead to copy it into this Realm. > ``` > > The `Dog` object remains unmanaged until you append it to the `Person` object's `dogs` property. When the Realm Swift SDK checks the `Dog` object to find the realm that is currently managing it, it finds nothing. > > When you use the `$` notation to perform a quick write that appends the `Dog` object to the `Person` object, this write uses the realm it has access to in the view. This is a realm instance implicitly opened by the `@ObservedRealmObject` or `@ObservedResults` property wrapper. The existing `DogToy` object, however, may be managed by a different realm instance. > > To solve this error, use the `.create` method when you initialize the `Dog` object, and use `modified: .update` to set its `favoriteToy` value to the existing object: > > ```swift > // When working with an `@ObservedRealmObject` `Person`, this is a frozen object. > // Thaw the object and get its realm to perform the write to append the new dog. > let thawedPersonRealm = frozenPerson.thaw()!.realm! > try! thawedPersonRealm.write { > // Use the .create method with `update: .modified` to copy the > // existing object into the realm > let dog = thawedPersonRealm.create(Dog.self, value: > ["name": "Maui", > "favoriteToy": wubba], > update: .modified) > person.dogs.append(dog) > } > > ``` > ## Perform an Explicit Write In some cases, you may want or need to explicitly perform a write transaction instead of using the implicit `$` to perform a quick write. You may want to do this when: - You need to look up additional objects to perform a write - You need to perform a write to objects you don't have access to in the view If you pass an object you are observing with `@ObservedRealmObject` or `@ObservedResults` into a function where you perform an explicit write transaction that modifies the object, you must thaw it first. ```swift let thawedCompany = company.thaw()! ``` You can access the realm that is managing the object or objects by calling `.realm` on the object or collection: ```swift let realm = company.realm!.thaw() ``` Because the SwiftUI property wrappers use frozen objects, you must thaw the realm before you can write to it. > Example: > Consider a version of the DoggoDB app where a `Company` object has a list of `Employee` objects. Each `Employee` has a list of `Dog` objects. But for business reasons, you also wanted to have a list of `Dog` objects available directly on the `Company` object, without being associated with an `Employee`. The model might look something like: > > ```swift > class Company: Object, ObjectKeyIdentifiable { > @Persisted(primaryKey: true) var _id: ObjectId > @Persisted var companyName = "" > @Persisted var employees: List > @Persisted var dogs: List > } > > ``` > > Consider a view where you have access to the `Company` object, but want to perform an explicit write to add an existing dog to an existing employee. Your function might look something like: > > ```swift > // The `frozenCompany` here represents an `@ObservedRealmObject var company: Company` > performAnExplicitWrite(company: frozenCompany, employeeName: "Dachary", dogName: "Maui") > > func performAnExplicitWrite(company: Company, employeeName: String, dogName: String) { > // Get the realm that is managing the `Company` object you passed in. > // Thaw the realm so you can write to it. > let realm = company.realm!.thaw() > // Thawing the `Company` object that you passed in also thaws the objects in its List properties. > // This lets you append the `Dog` to the `Employee` without individually thawing both of them. > let thawedCompany = company.thaw()! > let thisEmployee = thawedCompany.employees.where { $0.name == employeeName }.first! > let thisDog = thawedCompany.dogs.where { $0.name == dogName }.first! > try! realm.write { > thisEmployee.dogs.append(thisDog) > } > } > > ``` > ================================================ FILE: docs/guides/swiftui.md ================================================ # SwiftUI - Swift SDK ## Overview The Realm Swift SDK offers features that integrate into SwiftUI. This documentation provides an overview of those features. - [Quick Start](swiftui-tutorial.md) - [Model Data](model-data/define-a-realm-object-model.md) - [Configure and Open a Realm](configure-and-open-realm.md) - [React to Changes](react-to-changes.md) - [Pass Realm Data Between Views](pass-realm-data-between-views.md) - [Write Data](write.md) - [Filter Data](filter-data.md) - [Use Realm with SwiftUI Previews](swiftui-previews.md) ## Requirements - Xcode project using the SwiftUI "App" template. To use all of the Realm Swift SDK's SwiftUI features, the minimum iOS target is 15.0. Some features are compatible with older versions of iOS. - Install the Swift SDK. Use the most recent version of the Realm Swift SDK to get all of the features and enhancements for SwiftUI. ================================================ FILE: docs/guides/test-and-debug.md ================================================ # Test and Debug - Swift SDK ## Testing ### Test Using a Default Realm The easiest way to use and test Realm-backed applications is to use the default realm. To avoid overriding application data or leaking state between tests, set the default realm to a new file for each test. ```swift // A base class which each of your Realm-using tests should inherit from rather // than directly from XCTestCase class TestCaseBase: XCTestCase { override func setUp() { super.setUp() // Use an in-memory Realm identified by the name of the current test. // This ensures that each test can't accidentally access or modify the data // from other tests or the application itself, and because they're in-memory, // there's nothing that needs to be cleaned up. Realm.Configuration.defaultConfiguration.inMemoryIdentifier = self.name } } ``` ### Injecting Realm Instances Another way to test Realm-related code is to have all the methods you'd like to test accept a realm instance as an argument. This enables you to pass in different realms when running the app and when testing it. For example, suppose your app has a method to `GET` a user profile from a JSON API. You want to test that the local profile is properly created. ### Simplify Testing with Class Projections > Version added: 10.21.0 If you want to work with a subset of an object's properties for testing, you can create a class projection. A class projection is a model abstraction where you can pass through, rename, or exclude realm object properties. While this feature simplifies view model implementation, it also simplifies testing with Realm. > Example: > This example uses the object models and the class projection from the Define and Use Class Projections page. > > In this example, we create a realm object using the full object model. Then, we view retrieve the object as a class projection, working with only a subset of its properties. > > With this class projection, we don't need to access or account for properties that we don't need to test. > > ```swift > func testWithProjection() { > let realm = try! Realm() > // Create a Realm object, populate it with values > let jasonBourne = Person(value: ["firstName": "Jason", > "lastName": "Bourne", > "address": [ > "city": "Zurich", > "country": "Switzerland"]]) > try! realm.write { > realm.add(jasonBourne) > } > > // Retrieve all class projections of the given type `PersonProjection` > // and filter for the first class projection where the `firstName` property > // value is "Jason" > let person = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })! > // Verify that we have the correct PersonProjection > XCTAssert(person.firstName == "Jason") > // See that `homeCity` exists as a projection property > // Although it is not on the object model > XCTAssert(person.homeCity == "Zurich") > > // Change a value on the class projection > try! realm.write { > person.firstName = "David" > } > > // Verify that the projected property's value has changed > XCTAssert(person.firstName == "David") > } > > ``` > ### Test Targets Don't link the Realm framework directly to your test target. This can cause your tests to fail with an exception message "Object type 'YourObject' is not managed by the Realm." Unlinking Realm from your test target should resolve this issue. Compile your model class files in your application or framework targets; don't add them to your unit test targets. Otherwise, those classes are duplicated when testing, which can lead to difficult-to-debug issues. Expose all the code that you need for testing to your unit test targets. Use the `public` access modifier or [@testable](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/04-writing_tests.html). Since you're using Realm as a dynamic framework, you'll need to make sure your unit test target can find Realm. Add the parent path to `RealmSwift.framework` to your unit test's "Framework Search Paths". ## Debugging ### Debug Using Realm Studio Realm Studio enables you to open and edit local realms. It supports Mac, Windows and Linux. ### LLDB Debugging apps using Realm's Swift API must be done through the LLDB console. Although the LLDB script allows inspecting the contents of your realm variables in Xcode's UI, this doesn't yet work for Swift. Those variables will show incorrect data. Instead, use LLDB's `po` command to inspect the contents of data stored in a realm. ## Troubleshooting ### Resolve Build Issues Some developers experience build issues after installing the Realm Swift SDK via CocoaPods or Carthage. Common causes of these issues include: - Installation issues: Initial install failedUsing an unsupported version of the dependency manager - Build tool issues: Build tools have stale cachesUpdating build tool versions - Making changes to your project setup, such as: Adding a new targetSharing dependencies across targets A fix that often clears these issues is to delete derived data and clean the Xcode build folder. #### Cocoapods ##### Reset the Cocoapods Integration State Run these commands in the terminal, in the root of your project: ```bash pod cache clean Realm pod cache clean RealmSwift pod deintegrate || rm -rf Pods pod install --repo-update --verbose # Assumes the default DerivedData location: rm -rf ~/Library/Developer/Xcode/DerivedData ``` ##### Clean the Xcode Build Folder With your project open in Xcode, go to the Product drop-down menu, and select Clean Build Folder. #### Carthage ##### Reset Carthage-managed Dependency State Run these commands in the terminal, in the root of your project: ```bash rm -rf Carthage # Assumes default DerivedData location: rm -rf ~/Library/Developer/Xcode/DerivedData carthage update ``` ##### Clean the Xcode Build Folder With your project open in Xcode, go to the Product drop-down menu, and select Clean Build Folder. ### Issues Opening Realm Before Loading the UI You may open a realm and immediately see crashes with error messages related to properties being optional or required. Issues with your object model can cause these types of crashes. These errors occur after you open a realm, but before you get to the UI. Realm has a "schema discovery" phase when a realm opens on the device. At this time, Realm examines the schema for any objects that it manages. You can specify that a given realm should manage only a subset of objects in your application. If you see errors related to properties during schema discovery, these are likely due to schema issues and not issues with data from a specific object. For example, you may see schema discovery errors if you define a to-one relationship as required instead of optional. To debug these crashes, check the schema you've defined. You can tell these are schema discovery issues because they occur before the UI loads. This means that no UI element is attempting to incorrectly use a property, and there aren't any objects in memory that could have bad data. If you get errors related to properties *after* the UI loads, this is probably not due to invalid schema. Instead, those errors are likely a result of incorrect, wrongly-typed or missing data. ### No Properties are Defined for Model The Realm Swift SDK uses the Swift language reflection feature to determine the properties in your model at runtime. If you get a crash similar to the following, confirm that your project has not disabled reflection metadata: ```shell Terminating app due to uncaught exception 'RLMException', reason: 'No properties are defined for 'ObjectName'. ``` If you set `SWIFT_REFLECTION_METADATA_LEVEL = none`, Realm cannot discover children of types, such as properties and enums. Reflection is enabled by default if your project does not specifically set a level for this setting. ### Bad Alloc/Not Enough Memory Available In iOS or iPad devices with little available memory, or where you have a memory-intensive application that uses multiple realms or many notifications, you may encounter the following error: ```console libc++abi: terminating due to an uncaught exception of type std::bad_alloc: std::bad_alloc ``` This error typically indicates that a resource cannot be allocated because not enough memory is available. If you are building for iOS 15+ or iPad 15+, you can add the [Extended Virtual Addressing Entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_extended-virtual-addressing) to resolve this issue. Add these keys to your Property List, and set the values to `true`: ```xml com.apple.developer.kernel.extended-virtual-addressing com.apple.developer.kernel.increased-memory-limit ``` ### Swift Package Target Cannot be Built Dynamically > Version changed: 10.49.3 Swift SDK v10.49.3 changed the details for installing the package with Swift Package Manager (SPM). When you update from an older version of the package to v10.49.3 or newer, you may get a build error similar to: ```console Swift package target `Realm` is linked as a static library by `TargetName` and `Realm`, but cannot be built dynamically because there is a package product with the same name. ``` To resolve this error, unlink either the `Realm` or the `RealmSwift` package from your build target. You can do this in Xcode by following these steps: 1. In your project Targets, select your build target. 2. Go to the Build Phases tab. 3. Expand the Link Binary With Libraries element. 4. Select either `Realm` or `RealmSwift`, and click the Remove items (-) button to remove the unneeded binary. If you use Swift or Swift and Objective-C APIs, keep `RealmSwift`. If you use only Objective-C APIs, keep `Realm`. Now your target should build without this error. ================================================ FILE: docs/guides/use-realm-with-actors.md ================================================ # Use Realm with Actors - Swift SDK Starting with Realm Swift SDK version 10.39.0, Realm supports built-in functionality for using Realm with Swift Actors. Realm's actor support provides an alternative to managing threads or dispatch queues to perform asynchronous work. You can use Realm with actors in a few different ways: - Work with realm *only* on a specific actor with an actor-isolated realm - Use Realm across actors based on the needs of your application You might want to use an actor-isolated realm if you want to restrict all realm access to a single actor. This negates the need to pass data across the actor boundary, and can simplify data race debugging. You might want to use realms across actors in cases where you want to perform different types of work on different actors. For example, you might want to read objects on the MainActor but use a background actor for large writes. For general information about Swift actors, refer to [Apple's Actor documentation](https://developer.apple.com/documentation/swift/actor). ## Prerequisites To use Realm in a Swift actor, your project must: - Use Realm Swift SDK version 10.39.0 or later - Use Swift 5.8/Xcode 14.3 In addition, we strongly recommend enabling these settings in your project: - `SWIFT_STRICT_CONCURRENCY=complete`: enables strict concurrency checking - `OTHER_SWIFT_FLAGS=-Xfrontend-enable-actor-data-race-checks`: enables runtime actor data-race detection ## About the Examples on This Page The examples on this page use the following model: ```swift class Todo: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var name: String @Persisted var owner: String @Persisted var status: String } ``` ## Open an Actor-Isolated Realm You can use the Swift async/await syntax to await opening a realm. Initializing a realm with `try await Realm()` opens a MainActor-isolated realm. Alternately, you can explicitly specify an actor when opening a realm with the `await` syntax. ```swift @MainActor func mainThreadFunction() async throws { // These are identical: the async init produces a // MainActor-isolated Realm if no actor is supplied let realm1 = try await Realm() let realm2 = try await Realm(actor: MainActor.shared) try await useTheRealm(realm: realm1) } ``` You can specify a default configuration or customize your configuration when opening an actor-isolated realm: ```swift @MainActor func mainThreadFunction() async throws { let username = "Galadriel" // Customize the default realm config var config = Realm.Configuration.defaultConfiguration config.fileURL!.deleteLastPathComponent() config.fileURL!.appendPathComponent(username) config.fileURL!.appendPathExtension("realm") // Open an actor-isolated realm with a specific configuration let realm = try await Realm(configuration: config, actor: MainActor.shared) try await useTheRealm(realm: realm) } ``` For more general information about configuring a realm, refer to Configure & Open a Realm. ## Define a Custom Realm Actor You can define a specific actor to manage Realm in asynchronous contexts. You can use this actor to manage realm access and perform write operations. ```swift actor RealmActor { // An implicitly-unwrapped optional is used here to let us pass `self` to // `Realm(actor:)` within `init` var realm: Realm! init() async throws { realm = try await Realm(actor: self) } var count: Int { realm.objects(Todo.self).count } func createTodo(name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": name, "owner": owner, "status": status ]) } } func getTodoOwner(forTodoNamed name: String) -> String { let todo = realm.objects(Todo.self).where { $0.name == name }.first! return todo.owner } struct TodoStruct { var id: ObjectId var name, owner, status: String } func getTodoAsStruct(forTodoNamed name: String) -> TodoStruct { let todo = realm.objects(Todo.self).where { $0.name == name }.first! return TodoStruct(id: todo._id, name: todo.name, owner: todo.owner, status: todo.status) } func updateTodo(_id: ObjectId, name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": _id, "name": name, "owner": owner, "status": status ], update: .modified) } } func deleteTodo(id: ObjectId) async throws { try await realm.asyncWrite { let todoToDelete = realm.object(ofType: Todo.self, forPrimaryKey: id) realm.delete(todoToDelete!) } } func close() { realm = nil } } ``` An actor-isolated realm may be used with either local or global actors. ```swift // A simple example of a custom global actor @globalActor actor BackgroundActor: GlobalActor { static var shared = BackgroundActor() } @BackgroundActor func backgroundThreadFunction() async throws { // Explicitly specifying the actor is required for anything that is not MainActor let realm = try await Realm(actor: BackgroundActor.shared) try await realm.asyncWrite { _ = realm.create(Todo.self, value: [ "name": "Pledge fealty and service to Gondor", "owner": "Pippin", "status": "In Progress" ]) } // Thread-confined Realms would sometimes throw an exception here, as we // may end up on a different thread after an `await` let todoCount = realm.objects(Todo.self).count print("The number of Realm objects is: \(todoCount)") } @MainActor func mainThreadFunction() async throws { try await backgroundThreadFunction() } ``` ### Use a Realm Actor Synchronously in an Isolated Function When a function is confined to a specific actor, you can use the actor-isolated realm synchronously. ```swift func createObject(in actor: isolated RealmActor) async throws { // Because this function is isolated to this actor, you can use // realm synchronously in this context without async/await keywords try actor.realm.write { actor.realm.create(Todo.self, value: [ "name": "Keep it secret", "owner": "Frodo", "status": "In Progress" ]) } let taskCount = actor.count print("The actor currently has \(taskCount) tasks") } let actor = try await RealmActor() try await createObject(in: actor) ``` ### Use a Realm Actor in Async Functions When a function isn't confined to a specific actor, you can use your Realm actor with Swift's async/await syntax. ```swift func createObject() async throws { // Because this function is not isolated to this actor, // you must await operations completed on the actor try await actor.createTodo(name: "Take the ring to Mount Doom", owner: "Frodo", status: "In Progress") let taskCount = await actor.count print("The actor currently has \(taskCount) tasks") } let actor = try await RealmActor() try await createObject() ``` ## Write to an Actor-Isolated Realm Actor-isolated realms can use Swift async/await syntax for asynchronous writes. Using `try await realm.asyncWrite { ... }` suspends the current task, acquires the write lock without blocking the current thread, and then invokes the block. Realm writes the data to disk on a background thread and resumes the task when that completes. This function from the example `RealmActor` defined above shows how you might write to an actor-isolated realm: ```swift func createTodo(name: String, owner: String, status: String) async throws { try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": name, "owner": owner, "status": status ]) } } ``` And you might perform this write using Swift's async syntax: ```swift func createObject() async throws { // Because this function is not isolated to this actor, // you must await operations completed on the actor try await actor.createTodo(name: "Take the ring to Mount Doom", owner: "Frodo", status: "In Progress") let taskCount = await actor.count print("The actor currently has \(taskCount) tasks") } let actor = try await RealmActor() try await createObject() ``` This does not block the calling thread while waiting to write. It does not perform I/O on the calling thread. For small writes, this is safe to use from `@MainActor` functions without blocking the UI. Writes that negatively impact your app's performance due to complexity and/or platform resource constraints may still benefit from being done on a background thread. Asynchronous writes are only supported for actor-isolated Realms or in `@MainActor` functions. ## Pass Realm Data Across the Actor Boundary Realm objects are not [Sendable](https://developer.apple.com/documentation/swift/sendable), and cannot cross the actor boundary directly. To pass Realm data across the actor boundary, you have two options: - Pass a `ThreadSafeReference` to or from the actor - Pass other types that *are* Sendable, such as passing values directly or by creating structs to pass across actor boundaries ### Pass a ThreadSafeReference You can create a `ThreadSafeReference` on an actor where you have access to the object. In this case, we create a `ThreadSafeReference` on the `MainActor`. Then, pass the `ThreadSafeReference` to the destination actor. ```swift // We can pass a thread-safe reference to an object to update it on a different actor. let todo = todoCollection.where { $0.name == "Arrive safely in Bree" }.first! let threadSafeReferenceToTodo = ThreadSafeReference(to: todo) try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo) ``` On the destination actor, you must `resolve()` the reference within a write transaction before you can use it. This retrieves a version of the object local to that actor. ```swift actor BackgroundActor { public func deleteTodo(tsrToTodo tsr: ThreadSafeReference) throws { let realm = try! Realm() try realm.write { // Resolve the thread safe reference on the Actor where you want to use it. // Then, do something with the object. let todoOnActor = realm.resolve(tsr) realm.delete(todoOnActor!) } } } ``` > Important: > You must resolve a `ThreadSafeReference` exactly once. Otherwise, the source realm remains pinned until the reference gets deallocated. For this reason, `ThreadSafeReference` should be short-lived. > > If you may need to share the same realm object across actors more than once, you may prefer to share the primary key and query for it on the actor where you want to use it. Refer to the "Pass a Primary Key and Query for the Object on Another Actor" section on this page for an example. > ### Pass a Sendable Type While Realm objects are not Sendable, you can work around this by passing Sendable types across actor boundaries. You can use a few strategies to pass Sendable types and work with data across actor boundaries: - Pass Sendable Realm types or primitive values instead of complete Realm objects - Pass an object's primary key and query for the object on another actor - Create a Sendable representation of your Realm object, such as a struct #### Pass Sendable Realm Types and Primitive Values If you only need a piece of information from the Realm object, such as a `String` or `Int`, you can pass the value directly across actors instead of passing the Realm object. For a full list of which Realm types are Sendable, refer to Sendable, Non-Sendable and Thread-Confined Types. ```swift @MainActor func mainThreadFunction() async throws { // Create an object in an actor-isolated realm. // Pass primitive data to the actor instead of // creating the object here and passing the object. let actor = try await RealmActor() try await actor.createTodo(name: "Prepare fireworks for birthday party", owner: "Gandalf", status: "In Progress") // Later, get information off the actor-confined realm let todoOwner = await actor.getTodoOwner(forTodoNamed: "Prepare fireworks for birthday party") } ``` #### Pass a Primary Key and Query for the Object on Another Actor If you want to use a Realm object on another actor, you can share the primary key and query for it on the actor where you want to use it. ```swift // Execute code on a specific actor - in this case, the @MainActor @MainActor func mainThreadFunction() async throws { // Create an object off the main actor func createObject(in actor: isolated BackgroundActor) async throws -> ObjectId { let realm = try await Realm(actor: actor) let newTodo = try await realm.asyncWrite { return realm.create(Todo.self, value: [ "name": "Pledge fealty and service to Gondor", "owner": "Pippin", "status": "In Progress" ]) } // Share the todo's primary key so we can easily query for it on another actor return newTodo._id } // Initialize an actor where you want to perform background work let actor = BackgroundActor() let newTodoId = try await createObject(in: actor) let realm = try await Realm() let todoOnMainActor = realm.object(ofType: Todo.self, forPrimaryKey: newTodoId) } ``` #### Create a Sendable Representation of Your Object If you need to work with more than a simple value, but don't want the overhead of passing around `ThreadSafeReferences` or querying objects on different actors, you can create a struct or other Sendable representation of your data to pass across the actor boundary. For example, your actor might have a function that creates a struct representation of the Realm object. ```swift struct TodoStruct { var id: ObjectId var name, owner, status: String } func getTodoAsStruct(forTodoNamed name: String) -> TodoStruct { let todo = realm.objects(Todo.self).where { $0.name == name }.first! return TodoStruct(id: todo._id, name: todo.name, owner: todo.owner, status: todo.status) } ``` Then, you can call a function to get the data as a struct on another actor. ```swift @MainActor func mainThreadFunction() async throws { // Create an object in an actor-isolated realm. let actor = try await RealmActor() try await actor.createTodo(name: "Leave the ring on the mantle", owner: "Bilbo", status: "In Progress") // Get information as a struct or other Sendable type. let todoAsStruct = await actor.getTodoAsStruct(forTodoNamed: "Leave the ring on the mantle") } ``` ## Observe Notifications on a Different Actor You can observe notifications on an actor-isolated realm using Swift's async/await syntax. Calling `await object.observe(on: Actor)` or `await collection.observe(on: Actor)` registers a block to be called each time the object or collection changes. The SDK asynchronously calls the block on the given actor's executor. For write transactions performed on different threads or in different processes, the SDK calls the block when the realm is (auto)refreshed to a version including the changes. For local writes, the SDK calls the block at some point in the future after the write transaction is committed. Like other Realm notifications, you can only observe objects or collections managed by a realm. You must retain the returned token for as long as you want to watch for updates. If you need to manually advance the state of an observed realm on the main thread or on another actor, call `await realm.asyncRefresh()`. This updates the realm and outstanding objects managed by the Realm to point to the most recent data and deliver any applicable notifications. ### Observation Limitations You *cannot* call the `.observe()` method: - During a write transaction - When the containing realm is read-only - On an actor-confined realm from outside the actor ### Register a Collection Change Listener The SDK calls a collection notification block after each write transaction which: - Deletes an object from the collection. - Inserts an object into the collection. - Modifies any of the managed properties of an object in the collection. This includes self-assignments that set a property to its existing value. > Important: > In collection notification handlers, always apply changes in the following order: deletions, insertions, then modifications. Handling insertions before deletions may result in unexpected behavior. > These notifications provide information about the actor on which the change occurred. Like non-actor-isolated collection notifications, they also provide a `change` parameter that reports which objects are deleted, added, or modified during the write transaction. This `RealmCollectionChange` resolves to an array of index paths that you can pass to a `UITableView`'s batch update methods. ```swift // Create a simple actor actor BackgroundActor { public func deleteTodo(tsrToTodo tsr: ThreadSafeReference) throws { let realm = try! Realm() try realm.write { // Resolve the thread safe reference on the Actor where you want to use it. // Then, do something with the object. let todoOnActor = realm.resolve(tsr) realm.delete(todoOnActor!) } } } // Execute some code on a different actor - in this case, the MainActor @MainActor func mainThreadFunction() async throws { let backgroundActor = BackgroundActor() let realm = try! await Realm() // Create a todo item so there is something to observe try await realm.asyncWrite { realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": "Arrive safely in Bree", "owner": "Merry", "status": "In Progress" ]) } // Get the collection of todos on the current actor let todoCollection = realm.objects(Todo.self) // Register a notification token, providing the actor where you want to observe changes. // This is only required if you want to observe on a different actor. let token = await todoCollection.observe(on: backgroundActor, { actor, changes in print("A change occurred on actor: \(actor)") switch changes { case .initial: print("The initial value of the changed object was: \(changes)") case .update(_, let deletions, let insertions, let modifications): if !deletions.isEmpty { print("An object was deleted: \(changes)") } else if !insertions.isEmpty { print("An object was inserted: \(changes)") } else if !modifications.isEmpty { print("An object was modified: \(changes)") } case .error(let error): print("An error occurred: \(error.localizedDescription)") } }) // Update an object to trigger the notification. // This example triggers a notification that the object is deleted. // We can pass a thread-safe reference to an object to update it on a different actor. let todo = todoCollection.where { $0.name == "Arrive safely in Bree" }.first! let threadSafeReferenceToTodo = ThreadSafeReference(to: todo) try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo) // Invalidate the token when done observing token.invalidate() } ``` ### Register an Object Change Listener The SDK calls an object notification block after each write transaction which: - Deletes the object. - Modifies any of the managed properties of the object. This includes self-assignments that set a property to its existing value. The block is passed a copy of the object isolated to the requested actor, along with information about what changed. This object can be safely used on that actor. By default, only direct changes to the object's properties produce notifications. Changes to linked objects do not produce notifications. If a non-nil, non-empty keypath array is passed in, only changes to the properties identified by those keypaths produce change notifications. The keypaths may traverse link properties to receive information about changes to linked objects. ```swift // Execute some code on a specific actor - in this case, the MainActor @MainActor func mainThreadFunction() async throws { // Initialize an instance of another actor // where you want to do background work let backgroundActor = BackgroundActor() // Create a todo item so there is something to observe let realm = try! await Realm() let scourTheShire = try await realm.asyncWrite { return realm.create(Todo.self, value: [ "_id": ObjectId.generate(), "name": "Scour the Shire", "owner": "Merry", "status": "In Progress" ]) } // Register a notification token, providing the actor let token = await scourTheShire.observe(on: backgroundActor, { actor, change in print("A change occurred on actor: \(actor)") switch change { case .change(let object, let properties): for property in properties { print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'") } case .error(let error): print("An error occurred: \(error)") case .deleted: print("The object was deleted.") } }) // Update the object to trigger the notification. // This triggers a notification that the object's `status` property has been changed. try await realm.asyncWrite { scourTheShire.status = "Complete" } // Invalidate the token when done observing token.invalidate() } ``` ================================================ FILE: docs/guides/xcode-playgrounds.md ================================================ # Use Realm in Xcode Playgrounds ## Prerequisites You can only use Swift packages within Xcode projects that have at least one scheme and target. To use Realm in Xcode Playgrounds, you must first have an Xcode project where you have Installed the Swift SDK. ## Create a Playground Within a project, go to File > New > Playground. Select the type of Playground you want. For this example, we've used a Blank iOS Playground. Name and save the playground in the root of your project. Be sure to add it to the project. You should see your new Playground in your Project navigator. ## Import Realm Add the following import statement to use Realm in the playground: ```swift import RealmSwift ``` ## Experiment with Realm Experiment with Realm. For this example, we'll: - Define a new Realm object type - Create a new object of that type and write it to realm - Query objects of the type, and filter them ```swift class Drink: Object { @Persisted var name = "" @Persisted var rating = 0 @Persisted var source = "" @Persisted var drinkType = "" } let drink = Drink(value: ["name": "Los Cabellos", "rating": 10, "source": "AeroPress", "drinkType": "Coffee"]) let realm = try! Realm(configuration: config) try! realm.write { realm.add(drink) } let drinks = realm.objects(Drink.self) let coffeeDrinks = drinks.where { $0.drinkType == "Coffee" } print(coffeeDrinks.first?.name) ``` ## Managing the Realm File in Your Playground When you work with a default realm in a Playground, you might run into a situation where you need to delete the realm. For example, if you are experimenting with an object type and add properties to the object, you may get an error that you must migrate the realm. You can specify `Realm.configuration` details to open the file at a specific path, and delete the realm if it exists at the path. ```swift var config = Realm.Configuration() config.fileURL!.deleteLastPathComponent() config.fileURL!.appendPathComponent("playgroundRealm") config.fileURL!.appendPathExtension("realm") if Realm.fileExists(for: config) { try Realm.deleteFiles(for: config) print("Successfully deleted existing realm at path: \(config.fileURL!)") } else { print("No file currently exists at path") } ``` Alternately, you can open the realm in-memory only, or use the `deleteRealmIfMigrationNeeded` method to automatically delete a realm when migration is needed. ================================================ FILE: docs/install.md ================================================ # Install the SDK for iOS, macOS, tvOS, and watchOS ## Overview Realm SDK for Swift enables you to build iOS, macOS, tvOS, and watchOS applications using either the Swift or Objective-C programming languages. This page details how to install the SDK in your project and get started. ## Prerequisites Before getting started, ensure your development environment meets the following prerequisites: - Your project uses an Xcode version and minimum OS version listed in the OS Support section of this page. - Reflection is enabled in your project. The Swift SDK uses reflection to determine your model's properties. Your project must not set `SWIFT_REFLECTION_METADATA_LEVEL = none`, or the SDK cannot see properties in your model. Reflection is enabled by default if your project does not specifically set a level for this setting. ## Installation You can use `SwiftPM`, `CocoaPods`, or `Carthage` to add the Swift SDK to your project. > Tip: > The SDK uses Realm Core database for device data persistence. When you install the Swift SDK, the package names reflect Realm naming. > #### Swiftpm ##### Add Package Dependency In Xcode, select `File` > `Add Packages...`. ##### Specify the Repository Copy and paste the following into the search/input box. ```sh https://github.com/realm/realm-swift.git ``` ##### Specify Options In the options for the `realm-swift` package, we recommend setting the `Dependency Rule` to `Up to Next Major Version`, and enter the [current Realm Swift SDK version](https://github.com/realm/realm-swift/releases) . Then, click `Add Package`. ##### Select the Package Products > Version changed: 10.49.3 > Instead of adding both, only add one package. > Select either `RealmSwift` or `Realm`, then click `Add Package`. - If you use Swift or Swift and Objective-C APIs, add `RealmSwift`. - If you use *only* Objective-C APIs, add `Realm`. ##### (Optional) Build RealmSwift as a Dynamic Framework To use the Privacy Manifest supplied by the SDK, build `RealmSwift` as a dynamic framework. If you build `RealmSwift` as a static framework, you must supply your own Privacy Manifest. To build `RealmSwift` as a dynamic framework: 1. In your project Targets, select your build target. 2. Go to the General tab. 3. Expand the Frameworks and Libraries element. 4. For the `RealmSwift` framework, change the Embed option from "Do Not Embed" to "Embed & Sign." Now, Xcode builds `RealmSwift` dynamically, and can provide the SDK-supplied Privacy Manifest. #### Cocoapods If you are installing with [CocoaPods](https://guides.cocoapods.org/using/getting-started.html), you need CocoaPods 1.10.1 or later. ##### Update the CocoaPods repositories On the command line, run `pod repo update` to ensure CocoaPods can access the latest available Realm versions. ##### Initialize CocoaPods for Your Project If you do not already have a Podfile for your project, run `pod init` in the root directory of your project to create a Podfile for your project. A Podfile allows you to specify project dependencies to CocoaPods. ##### Add the SDK as a Dependency in Your Podfile #### Objective-C Add the line `pod 'Realm', '~>10'` to your main and test targets. Add the line `use_frameworks!` as well if it is not already there. When done, your Podfile should look something like this: ```text # Uncomment the next line to define a global platform for your project # platform :ios, '11.0' target 'MyDeviceSDKProject' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for MyDeviceSDKProject pod 'Realm', '~>10' target 'MyRealmProjectTests' do inherit! :search_paths # Pods for testing pod 'Realm', '~>10' end end ``` #### Swift Add the line `use_frameworks!` if it is not already there. Add the line `pod 'RealmSwift', '~>10'` to your main and test targets. When done, your Podfile should look something like this: ```text platform :ios, '12.0' target 'MyDeviceSDKProject' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for MyDeviceSDKProject pod 'RealmSwift', '~>10' end ``` ##### Install the Dependencies From the command line, run `pod install` to fetch the dependencies. ###### Use the CocoaPods-Generated File ##### Use the CocoaPods-Generated .xcworkspace File CocoaPods generates an `.xcworkspace` file for you. This file has all of the dependencies configured. From now on, open this file -- not the `.xcodeproj` file -- to work on your project. #### Carthage If you are installing with [Carthage](https://github.com/Carthage/Carthage#installing-carthage), you need Carthage 0.33 or later. ##### Add the SDK as a Dependency in Your Cartfile Add the SDK as a dependency by appending the line `github "realm/realm-swift"` to your Cartfile. You can create a Cartfile or append to an existing one by running the following command in your project directory: ```bash echo 'github "realm/realm-swift"' >> Cartfile ``` ##### Install the Dependencies From the command line, run `carthage update --use-xcframeworks` to fetch the dependencies. ##### Add the Frameworks to Your Project Carthage places the built dependencies in the `Carthage/Build` directory. Open your project's `xcodeproj` file in Xcode. Go to the Project Navigator panel and click your application name to open the project settings editor. Select the General tab. In Finder, open the `Carthage/Build/` directory. Drag the `RealmSwift.xcframework` and `Realm.xcframework` files found in that directory to the Frameworks, Libraries, and Embedded Content section of your project's General settings. #### Dynamic Framework ##### Download and Extract the Framework Download the [latest release of the Swift SDK](https://github.com/realm/realm-swift/releases) and extract the zip. ##### Copy Framework(s) Into Your Project Drag `Realm.xcframework` and `RealmSwift.xcframework` (if using) to the File Navigator of your Xcode project. Select the Copy items if needed checkbox and press Finish. > Tip: > If using the Objective-C API within a Swift project, we recommend you include both Realm Swift and Realm Objective-C in your project. Within your Swift files, you can access the Swift API and all required wrappers. Using the RealmSwift API in mixed Swift/Objective-C projects is possible because the vast majority of RealmSwift types are directly aliased from their Objective-C counterparts. > ## Import the SDK > Tip: > The SDK uses Realm Core database for device data persistence. When you import the Swift SDK, the package names reflect Realm naming. > Add the following line at the top of your source files to use the SDK: #### Objective-C ```objectivec #include ``` #### Swift ```swift import RealmSwift ``` ## App Download File Size The SDK should only add around 5 to 8 MB to your app's download size. The releases we distribute are significantly larger because they include support for the iOS, watchOS and tvOS simulators, some debug symbols, and bitcode, all of which are stripped by the App Store automatically when apps are downloaded. ## Troubleshooting If you have build issues after using one of these methods to install the SDK, see our troubleshooting guidelines for information about resolving those issues. ## OS Support > Important: > There are special considerations when using the SDK with tvOS. See Build for tvOS for more information. > ### Xcode 15 > Version changed: 10.50.0 > Minimum required Xcode version is 15.1 > |Supported OS|Realm| | --- | --- | |iOS 12.0+|X| |macOS 10.14+|X| |tvOS 12.0+|X| |watchOS 4.0+|X| |visionOS 1.0+|X| ### Xcode 14 > Version changed: 10.50.0 > Removed support for Xcode 14. > Swift SDK version 10.50.0 drops support for Xcode 14. For v10.49.3 and earlier, these Xcode 14 requirements apply: - [Xcode](https://developer.apple.com/xcode/) version 14.1 or higher. - When using Xcode 14, a target of iOS 11.0 or higher, macOS 10.13 or higher, tvOS 11.0 or higher, or watchOS 4.0 or higher. ## Swift Concurrency Support The Swift SDK supports Swift's concurrency-related language features. For best practices on using the Swift SDK's concurrency features, refer to the documentation below. ### Async/Await Support Starting with Realm Swift SDK Versions 10.15.0 and 10.16.0, many of the Realm APIs support the Swift async/await syntax. Projects must meet these requirements: |Swift SDK Version|Swift Version Requirement|Supported OS| | --- | --- | --- | |10.25.0|Swift 5.6|iOS 13.x| |10.15.0 or 10.16.0|Swift 5.5|iOS 15.x| If your app accesses Realm in an `async/await` context, mark the code with `@MainActor` to avoid threading-related crashes. For more information about async/await support in the Swift SDK, refer to Swift Concurrency: Async/Await APIs. ### Actor Support The Swift SDK supports actor-isolated realm instances. For more information, refer to Use Realm with Actors - Swift SDK. ## Apple Privacy Manifest > Version changed: 10.49.3 > Build RealmSwift as a dynamic framework to include the Privacy Manifest. > Apple requires apps that use `RealmSwift` to provide a privacy manifest containing details about the SDK's data collection and use practices. The bundled manifest file must be included when submitting new apps or app updates to the App Store. For more details about Apple's requirements, refer to [Upcoming third-party SDK requirements](https://developer.apple.com/support/third-party-SDK-requirements/) on the Apple Developer website. Starting in Swift SDK version 10.46.0, the SDK ships with privacy manifests for `Realm` and `RealmSwift`. Each package contains its own privacy manifest with Apple's required API disclosures and the reasons for using those APIs. You can view the privacy manifests in each package, or in the `realm-swift` GitHub repository: - `Realm`: [https://github.com/realm/realm-swift/blob/master/Realm/PrivacyInfo.xcprivacy](https://github.com/realm/realm-swift/blob/master/Realm/PrivacyInfo.xcprivacy) - `RealmSwift`: [https://github.com/realm/realm-swift/blob/master/RealmSwift/PrivacyInfo.xcprivacy](https://github.com/realm/realm-swift/blob/master/RealmSwift/PrivacyInfo.xcprivacy) To include these manifests in a build target that uses `RealmSwift`, you must build `RealmSwift` as a dynamic framework. For details, refer to the Swift Package Manager Installation instructions step **(Optional) Build RealmSwift as a Dynamic Framework**. The Swift SDK does not include analytics code in builds for the App Store. You may need to add additional disclosures to your app's privacy manifest detailing your data collection and use practices when using these APIs. For more information, refer to Apple's [Privacy manifest files documentation](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files). ================================================ FILE: examples/README.md ================================================ # Realm Examples Included in this folder are sample iOS/OSX apps using Realm. ## iOS (Objective-C) The following examples are located in the `ios/objc/RealmExamples.xcodeproj` project: ### Simple This app covers several introductory concepts about Realm. Without any UI distractions, just a little console output. ### TableView This app demonstrates how Realm can be the data source for UITableViews. You can add rows by tapping the add button and remove rows by swiping right-to-left. The application also demonstrates how to import data in a background thread. ### GroupedTableView A sample app to demonstrate how to use Realm to populate a table view with sections. ### Migration This example showcases Realm's migration features. ### REST Using data from FourSquare, this example demonstrates how to populate a Realm with external json data. ### Encryption This simple app shows how to use an encrypted realm. ### Backlink This simple app demonstrates how to define models with inverse relationships using `-linkingObjectsOfClass:forProperty:`. #### Installation Instructions 1. [Download the macOS version](https://realm.io/docs/realm-mobile-platform/get-started/) of the Realm Mobile Platform. 2. Run a local instance of the Realm Mobile Platform. 3. Open the Realm Object Server Dashboard in your browser by visiting 'http://localhost:9080'. 4. Create a user account with the email 'demo@realm.io' and the password 'password'. 5. Build the Draw app and deploy it to iOS devices on the same network as your Mac. ## iOS (Swift) In the `ios/swift/RealmExamples.xcodeproj` project, you will find the following examples: ### GettingStarted.playground This is a Swift Playground that goes over a few Realm basics. ### Simple This app covers several introductory concepts about Realm. Without any UI distractions, just a little console output. ### TableView This app demonstrates how Realm can be the data source for UITableViews. You can add rows by tapping the add button and remove rows by swiping right-to-left. The application also demonstrates how to import data in a background thread. ### GroupedTableView A sample app to demonstrate how to use Realm to populate a table view with sections. ### Migration This example showcases Realm's migration features. ### Encryption This simple app shows how to use an encrypted realm. ### Backlink This simple app demonstrates how to define models with inverse relationships using `linkingObjectsOfClass(_:forProperty:)`. ### Projections This app demonstrates how to define Projection on Realm Object and how to use it in SwiftUI application. ### AppClip / AppClipParent These two targets demonstrate how to use Realm to persist data between an App Clip and its parent. #### Example Usage For the purpose of this example, the app clip invocation and parent application download is simulated by running each target. For more information on complete App Clip flow see: [Responding to invocations](https://developer.apple.com/documentation/app_clips/responding_to_invocations) and [Launch Experience](https://developer.apple.com/documentation/app_clips/testing_your_app_clip_s_launch_experience). ![alt text](https://github.com/realm/realm-swift/blob/em/appclip_ex/examples/ios/swift/AppClip/appclip_ex.gif?raw=true) **Note:** When testing App Group Entitlements on MacOS (including the iOS simulator), `containerURL(forSecurityApplicationGroupIdentifier:)` will always return the shared directory URL, even when the group identifier is invalid. Be sure to test on physical devices with non-simulated iOS for expected security behavior. See [Return Value](https://developer.apple.com/documentation/foundation/filemanager/1412643-containerurl). ## OSX (Objective-C) In the `osx/objc/RealmExamples.xcodeproj` project, you will find the following examples: ### JSONImport This is a small OS X command-line program which demonstrates how to import data from JSON into a Realm. Open the project in Xcode, and press "Run" to build and run the program. It will write output to the console. ## Installation Examples The `installation/` directory contains example Xcode projects demonstrating how to install Realm Objective-C and Realm Swift from all available methods defined in . ## tvOS (Objective-C) ### DownloadCache A tvOS app that demonstrates how to use Realm to store data and display data from a REST API. ### PreloadedData A tvOS app that demonstrates how to use a Realm file included in your app bundle. ## tvOS (Swift) ### DownloadCache A tvOS app that demonstrates how to use Realm to store data and display data from a REST API. ### PreloadedData A tvOS app that demonstrates how to use a Realm file included in your app bundle. ================================================ FILE: examples/installation/.gitignore ================================================ Podfile.lock Pods/ realm-objc-latest realm-swift-latest CocoaPodsExample.xcworkspace CocoaPodsDynamicExample.xcworkspace Cartfile.resolved SwiftPackageManager.xcodeproj SwiftPackageManagerDynamic.xcodeproj ================================================ FILE: examples/installation/Carthage.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 3F938BA62A4E1886002356FE /* RealmSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */; }; 3F938BA72A4E1886002356FE /* RealmSwift.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F938BA82A4E1886002356FE /* Realm.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA52A4E1886002356FE /* Realm.xcframework */; }; 3F938BA92A4E1886002356FE /* Realm.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA52A4E1886002356FE /* Realm.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */; }; 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */; }; 3FEC91832A4D41250044BFF5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 3F938BAA2A4E1886002356FE /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F938BA72A4E1886002356FE /* RealmSwift.xcframework in Embed Frameworks */, 3F938BA92A4E1886002356FE /* Realm.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RealmSwift.xcframework; path = Carthage/Build/RealmSwift.xcframework; sourceTree = ""; }; 3F938BA52A4E1886002356FE /* Realm.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Realm.xcframework; path = Carthage/Build/Realm.xcframework; sourceTree = ""; }; 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExampleApp.swift; sourceTree = ""; }; 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftExample.entitlements; sourceTree = ""; }; 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 3FEC91872A4D41250044BFF5 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3FEC91802A4D41250044BFF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F938BA62A4E1886002356FE /* RealmSwift.xcframework in Frameworks */, 3F938BA82A4E1886002356FE /* Realm.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3F938BA32A4E1886002356FE /* Frameworks */ = { isa = PBXGroup; children = ( 3F938BA52A4E1886002356FE /* Realm.xcframework */, 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */, ); name = Frameworks; sourceTree = ""; }; 3FEC915D2A4D3B140044BFF5 = { isa = PBXGroup; children = ( 3FEC91682A4D3B140044BFF5 /* Source */, 3FEC91672A4D3B140044BFF5 /* Products */, 3F938BA32A4E1886002356FE /* Frameworks */, ); sourceTree = ""; }; 3FEC91672A4D3B140044BFF5 /* Products */ = { isa = PBXGroup; children = ( 3FEC91872A4D41250044BFF5 /* App.app */, ); name = Products; sourceTree = ""; }; 3FEC91682A4D3B140044BFF5 /* Source */ = { isa = PBXGroup; children = ( 3FEC91702A4D3B150044BFF5 /* Preview Content */, 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */, 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */, 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */, ); path = Source; sourceTree = ""; }; 3FEC91702A4D3B150044BFF5 /* Preview Content */ = { isa = PBXGroup; children = ( 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3FEC917D2A4D41250044BFF5 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( 3FEC917E2A4D41250044BFF5 /* Sources */, 3FEC91802A4D41250044BFF5 /* Frameworks */, 3FEC91812A4D41250044BFF5 /* Resources */, 3F938BAA2A4E1886002356FE /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = App; productName = SwiftPM; productReference = 3FEC91872A4D41250044BFF5 /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FEC915E2A4D3B140044BFF5 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "Carthage" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3FEC915D2A4D3B140044BFF5; productRefGroup = 3FEC91672A4D3B140044BFF5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FEC917D2A4D41250044BFF5 /* App */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3FEC91812A4D41250044BFF5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */, 3FEC91832A4D41250044BFF5 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3FEC917E2A4D41250044BFF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 3FEC91732A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 3FEC91742A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 3FEC91852A4D41250044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; }; name = Debug; }; 3FEC91862A4D41250044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,5,6,7"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "Carthage" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91732A4D3B150044BFF5 /* Debug */, 3FEC91742A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91852A4D41250044BFF5 /* Debug */, 3FEC91862A4D41250044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3FEC915E2A4D3B140044BFF5 /* Project object */; } ================================================ FILE: examples/installation/CocoaPods.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */; }; 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */; }; 3FEC91832A4D41250044BFF5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */; }; E314772E6D33E037E705FDBB /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25EBEF352D634D7820A0E2BF /* Pods_App.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 012DB6ABD37FF9723F260762 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; 25EBEF352D634D7820A0E2BF /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 30696D5AF3299BB30E01E6B6 /* Pods-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS.debug.xcconfig"; path = "Target Support Files/Pods-macOS/Pods-macOS.debug.xcconfig"; sourceTree = ""; }; 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExampleApp.swift; sourceTree = ""; }; 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftExample.entitlements; sourceTree = ""; }; 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 3FEC91872A4D41250044BFF5 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45A2DE050EFA4D7BE8D8D682 /* Pods-watchOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-watchOS.release.xcconfig"; path = "Target Support Files/Pods-watchOS/Pods-watchOS.release.xcconfig"; sourceTree = ""; }; 47A9606E545D1C71F51747F9 /* libPods-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 551C4089A220B7CAC99DE535 /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; 611B5C3940ABD898BBC1347B /* Pods-watchOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-watchOS.debug.xcconfig"; path = "Target Support Files/Pods-watchOS/Pods-watchOS.debug.xcconfig"; sourceTree = ""; }; 6AF03745C896A33FEB088C17 /* Pods-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOS.release.xcconfig"; path = "Target Support Files/Pods-tvOS/Pods-tvOS.release.xcconfig"; sourceTree = ""; }; 83F10638D6B5E68A2535A6B6 /* Pods-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS.debug.xcconfig"; path = "Target Support Files/Pods-iOS/Pods-iOS.debug.xcconfig"; sourceTree = ""; }; 84848F925DE3E6A4D8A1D337 /* Pods-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS.release.xcconfig"; path = "Target Support Files/Pods-iOS/Pods-iOS.release.xcconfig"; sourceTree = ""; }; A4D891FC83CF283E80C98471 /* libPods-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B34B12B11F827A5F60187F42 /* libPods-watchOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-watchOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; BF77A6DAF403942F6695ADFE /* Pods-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-tvOS/Pods-tvOS.debug.xcconfig"; sourceTree = ""; }; F8A8C4CE362469BBBBACF8B7 /* Pods-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS.release.xcconfig"; path = "Target Support Files/Pods-macOS/Pods-macOS.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3FEC91802A4D41250044BFF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E314772E6D33E037E705FDBB /* Pods_App.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3FEC915D2A4D3B140044BFF5 = { isa = PBXGroup; children = ( 3FEC91682A4D3B140044BFF5 /* Source */, 3FEC91672A4D3B140044BFF5 /* Products */, 926A319CC8B885CD63E21B9E /* Pods */, 9BFD37A5E01011406A863C9A /* Frameworks */, ); sourceTree = ""; }; 3FEC91672A4D3B140044BFF5 /* Products */ = { isa = PBXGroup; children = ( 3FEC91872A4D41250044BFF5 /* App.app */, ); name = Products; sourceTree = ""; }; 3FEC91682A4D3B140044BFF5 /* Source */ = { isa = PBXGroup; children = ( 3FEC91702A4D3B150044BFF5 /* Preview Content */, 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */, 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */, 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */, ); path = Source; sourceTree = ""; }; 3FEC91702A4D3B150044BFF5 /* Preview Content */ = { isa = PBXGroup; children = ( 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 926A319CC8B885CD63E21B9E /* Pods */ = { isa = PBXGroup; children = ( 83F10638D6B5E68A2535A6B6 /* Pods-iOS.debug.xcconfig */, 84848F925DE3E6A4D8A1D337 /* Pods-iOS.release.xcconfig */, 30696D5AF3299BB30E01E6B6 /* Pods-macOS.debug.xcconfig */, F8A8C4CE362469BBBBACF8B7 /* Pods-macOS.release.xcconfig */, BF77A6DAF403942F6695ADFE /* Pods-tvOS.debug.xcconfig */, 6AF03745C896A33FEB088C17 /* Pods-tvOS.release.xcconfig */, 611B5C3940ABD898BBC1347B /* Pods-watchOS.debug.xcconfig */, 45A2DE050EFA4D7BE8D8D682 /* Pods-watchOS.release.xcconfig */, 551C4089A220B7CAC99DE535 /* Pods-App.debug.xcconfig */, 012DB6ABD37FF9723F260762 /* Pods-App.release.xcconfig */, ); path = Pods; sourceTree = ""; }; 9BFD37A5E01011406A863C9A /* Frameworks */ = { isa = PBXGroup; children = ( 47A9606E545D1C71F51747F9 /* libPods-macOS.a */, A4D891FC83CF283E80C98471 /* libPods-tvOS.a */, B34B12B11F827A5F60187F42 /* libPods-watchOS.a */, 25EBEF352D634D7820A0E2BF /* Pods_App.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3FEC917D2A4D41250044BFF5 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( B56F1236CC1F04E652F44D86 /* [CP] Check Pods Manifest.lock */, 3FEC917E2A4D41250044BFF5 /* Sources */, 3FEC91802A4D41250044BFF5 /* Frameworks */, 3FEC91812A4D41250044BFF5 /* Resources */, 3653D19BAAE97F1AB839AD70 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = App; productName = SwiftPM; productReference = 3FEC91872A4D41250044BFF5 /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FEC915E2A4D3B140044BFF5 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "CocoaPods" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3FEC915D2A4D3B140044BFF5; productRefGroup = 3FEC91672A4D3B140044BFF5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FEC917D2A4D41250044BFF5 /* App */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3FEC91812A4D41250044BFF5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */, 3FEC91832A4D41250044BFF5 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3653D19BAAE97F1AB839AD70 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; B56F1236CC1F04E652F44D86 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3FEC917E2A4D41250044BFF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 3FEC91732A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 3FEC91742A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 3FEC91852A4D41250044BFF5 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 551C4089A220B7CAC99DE535 /* Pods-App.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; }; name = Debug; }; 3FEC91862A4D41250044BFF5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 012DB6ABD37FF9723F260762 /* Pods-App.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,5,6,7"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "CocoaPods" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91732A4D3B150044BFF5 /* Debug */, 3FEC91742A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91852A4D41250044BFF5 /* Debug */, 3FEC91862A4D41250044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3FEC915E2A4D3B140044BFF5 /* Project object */; } ================================================ FILE: examples/installation/CocoaPods.xcodeproj/xcshareddata/xcschemes/App.xcscheme ================================================ ================================================ FILE: examples/installation/CocoaPods.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/installation/CocoaPods.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: examples/installation/Podfile ================================================ project 'CocoaPods.xcodeproj' use_modular_headers! os = (ENV['REALM_PLATFORM'] || :ios).to_sym if os == :catalyst os = :ios end version = case os when :osx then 10.15 when :ios, :tvos then 12.0 when :watchos then 5.0 end target 'App' do platform os, version use_frameworks! unless ENV['REALM_BUILD_STATIC'] if ENV['REALM_TEST_RELEASE'] pod 'RealmSwift', ENV['REALM_TEST_RELEASE'] elsif ENV['REALM_TEST_BRANCH'] pod 'Realm', git: 'https://github.com/realm/realm-swift', branch: ENV['REALM_TEST_BRANCH'] pod 'RealmSwift', git: 'https://github.com/realm/realm-swift', branch: ENV['REALM_TEST_BRANCH'] else pod 'RealmSwift' end pod 'SubRealm', path: 'SubRealm' end ================================================ FILE: examples/installation/Source/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/installation/Source/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/installation/Source/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/installation/Source/ObjCImport.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 @interface ObjcModel : RLMObject @property (nonatomic) int value; @end @implementation ObjcModel @end ================================================ FILE: examples/installation/Source/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/installation/Source/SwiftExample.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.user-selected.read-only ================================================ FILE: examples/installation/Source/SwiftExampleApp.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 SwiftUI import RealmSwift #if COCOAPODS import SubRealm #endif class MyModel: Object { @Persisted var value: Int } @main struct SwiftExampleApp: SwiftUI.App { let realm = try! Realm() #if COCOAPODS let subrealm = try! SubRealm.findTestModel() #endif var body: some Scene { WindowGroup { Text("Hello, world: \(realm.objects(MyModel.self).count)!") } } } ================================================ FILE: examples/installation/Static/StaticExample/Base.lproj/LaunchScreen.xib ================================================ ================================================ FILE: examples/installation/Static/StaticExample/Base.lproj/Main.storyboard ================================================ ================================================ FILE: examples/installation/Static/StaticExample/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/installation/Static/StaticExample/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/installation/Static/StaticExample/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import @interface MyModel : RLMObject @property (nonatomic) int value; @end @implementation MyModel @end @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RLMRealm *realm = [RLMRealm defaultRealm]; return YES; } @end @interface ViewController : UIViewController @end @implementation ViewController @end int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/installation/Static/StaticExample.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 3FCABC1F28BE9B9D008C966A /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FCABC1E28BE9B9C008C966A /* libcompression.tbd */; }; 3FCABC2128BE9BC5008C966A /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FCABC2028BE9BB3008C966A /* libc++.tbd */; }; E88ABBB51AFA9DE300FA1E1D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E88ABBB41AFA9DE300FA1E1D /* main.m */; }; E88ABBBE1AFA9DE300FA1E1D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E88ABBBC1AFA9DE300FA1E1D /* Main.storyboard */; }; E88ABBC01AFA9DE300FA1E1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E88ABBBF1AFA9DE300FA1E1D /* Images.xcassets */; }; E88ABBC31AFA9DE300FA1E1D /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = E88ABBC11AFA9DE300FA1E1D /* LaunchScreen.xib */; }; E88ABBDB1AFAA5D600FA1E1D /* Realm.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E88ABBDA1AFAA5D600FA1E1D /* Realm.xcframework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 3FCABC1E28BE9B9C008C966A /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = usr/lib/libcompression.tbd; sourceTree = SDKROOT; }; 3FCABC2028BE9BB3008C966A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; E88ABBAF1AFA9DE300FA1E1D /* StaticExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StaticExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; E88ABBB31AFA9DE300FA1E1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E88ABBB41AFA9DE300FA1E1D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E88ABBBD1AFA9DE300FA1E1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; E88ABBBF1AFA9DE300FA1E1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; E88ABBC21AFA9DE300FA1E1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; E88ABBDA1AFAA5D600FA1E1D /* Realm.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Realm.xcframework; path = "../../../build/Static/Realm.xcframework"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ E88ABBAC1AFA9DE300FA1E1D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FCABC2128BE9BC5008C966A /* libc++.tbd in Frameworks */, 3FCABC1F28BE9B9D008C966A /* libcompression.tbd in Frameworks */, E88ABBDB1AFAA5D600FA1E1D /* Realm.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3FCABC1D28BE9B9C008C966A /* Frameworks */ = { isa = PBXGroup; children = ( 3FCABC2028BE9BB3008C966A /* libc++.tbd */, 3FCABC1E28BE9B9C008C966A /* libcompression.tbd */, ); name = Frameworks; sourceTree = ""; }; E88ABBA61AFA9DE300FA1E1D = { isa = PBXGroup; children = ( 3FCABC1D28BE9B9C008C966A /* Frameworks */, E88ABBB01AFA9DE300FA1E1D /* Products */, E88ABBB11AFA9DE300FA1E1D /* StaticExample */, E88ABBDA1AFAA5D600FA1E1D /* Realm.xcframework */, ); sourceTree = ""; }; E88ABBB01AFA9DE300FA1E1D /* Products */ = { isa = PBXGroup; children = ( E88ABBAF1AFA9DE300FA1E1D /* StaticExample.app */, ); name = Products; sourceTree = ""; }; E88ABBB11AFA9DE300FA1E1D /* StaticExample */ = { isa = PBXGroup; children = ( E88ABBB41AFA9DE300FA1E1D /* main.m */, E88ABBB31AFA9DE300FA1E1D /* Info.plist */, E88ABBBF1AFA9DE300FA1E1D /* Images.xcassets */, E88ABBC11AFA9DE300FA1E1D /* LaunchScreen.xib */, E88ABBBC1AFA9DE300FA1E1D /* Main.storyboard */, ); path = StaticExample; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ E88ABBAE1AFA9DE300FA1E1D /* StaticExample */ = { isa = PBXNativeTarget; buildConfigurationList = E88ABBD21AFA9DE400FA1E1D /* Build configuration list for PBXNativeTarget "StaticExample" */; buildPhases = ( E88ABBAB1AFA9DE300FA1E1D /* Sources */, E88ABBAC1AFA9DE300FA1E1D /* Frameworks */, E88ABBAD1AFA9DE300FA1E1D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = StaticExample; productName = StaticExample; productReference = E88ABBAF1AFA9DE300FA1E1D /* StaticExample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E88ABBA71AFA9DE300FA1E1D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { E88ABBAE1AFA9DE300FA1E1D = { CreatedOnToolsVersion = 6.3.1; }; }; }; buildConfigurationList = E88ABBAA1AFA9DE300FA1E1D /* Build configuration list for PBXProject "StaticExample" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, ); mainGroup = E88ABBA61AFA9DE300FA1E1D; productRefGroup = E88ABBB01AFA9DE300FA1E1D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( E88ABBAE1AFA9DE300FA1E1D /* StaticExample */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ E88ABBAD1AFA9DE300FA1E1D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E88ABBC01AFA9DE300FA1E1D /* Images.xcassets in Resources */, E88ABBC31AFA9DE300FA1E1D /* LaunchScreen.xib in Resources */, E88ABBBE1AFA9DE300FA1E1D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ E88ABBAB1AFA9DE300FA1E1D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E88ABBB51AFA9DE300FA1E1D /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ E88ABBBC1AFA9DE300FA1E1D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( E88ABBBD1AFA9DE300FA1E1D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; E88ABBC11AFA9DE300FA1E1D /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( E88ABBC21AFA9DE300FA1E1D /* Base */, ); name = LaunchScreen.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ E88ABBD01AFA9DE400FA1E1D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; E88ABBD11AFA9DE400FA1E1D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; E88ABBD31AFA9DE400FA1E1D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "../../../realm-objc-latest/ios/static", ); INFOPLIST_FILE = StaticExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = "-lz"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E88ABBD41AFA9DE400FA1E1D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "../../../realm-objc-latest/ios/static", ); INFOPLIST_FILE = StaticExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = "-lz"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ E88ABBAA1AFA9DE300FA1E1D /* Build configuration list for PBXProject "StaticExample" */ = { isa = XCConfigurationList; buildConfigurations = ( E88ABBD01AFA9DE400FA1E1D /* Debug */, E88ABBD11AFA9DE400FA1E1D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E88ABBD21AFA9DE400FA1E1D /* Build configuration list for PBXNativeTarget "StaticExample" */ = { isa = XCConfigurationList; buildConfigurations = ( E88ABBD31AFA9DE400FA1E1D /* Debug */, E88ABBD41AFA9DE400FA1E1D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = E88ABBA71AFA9DE300FA1E1D /* Project object */; } ================================================ FILE: examples/installation/Static/StaticExample.xcodeproj/xcshareddata/xcschemes/StaticExample.xcscheme ================================================ ================================================ FILE: examples/installation/SubRealm/SubRealm.podspec ================================================ Pod::Spec.new do |s| s.name = "SubRealm" s.version = "1.0.0" s.summary = "Test Realm as a transitive dependency" s.homepage = "https://realm.io" s.author = { 'Realm' => 'realm-help@mongodb.com' } s.license = { type: 'Apache 2.0', file: '../../../LICENSE' } s.source = { git: 'https://github.com/realm/realm-swift.git', tag: "v#{s.version}" } s.swift_version = '5' s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.15' s.watchos.deployment_target = '5.0' s.tvos.deployment_target = '12.0' s.visionos.deployment_target = '1.0' s.source_files = "*.swift" s.dependency 'RealmSwift' end ================================================ FILE: examples/installation/SubRealm/SubRealm.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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 RealmSwift public class TestObject: Object { @Persisted public var name: String } public class SubRealm { public static func storeTestModel() throws { let realm = try Realm() let model = TestObject() model.name = "Test" try realm.write { realm.add(model) } } public static func findTestModel() throws -> TestObject? { let realm = try Realm() return realm.objects(TestObject.self).first } } ================================================ FILE: examples/installation/SwiftPackageManager.notxcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 3FEC916A2A4D3B140044BFF5 /* SwiftExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */; }; 3FEC916E2A4D3B150044BFF5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */; }; 3FEC91722A4D3B150044BFF5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */; }; 3FEC917C2A4D3DB90044BFF5 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 3FEC91662A4D3B140044BFF5 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExampleApp.swift; sourceTree = ""; }; 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftExample.entitlements; sourceTree = ""; }; 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3FEC91632A4D3B140044BFF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FEC917C2A4D3DB90044BFF5 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3FEC915D2A4D3B140044BFF5 = { isa = PBXGroup; children = ( 3FEC91682A4D3B140044BFF5 /* Source */, 3FEC91672A4D3B140044BFF5 /* Products */, ); sourceTree = ""; }; 3FEC91672A4D3B140044BFF5 /* Products */ = { isa = PBXGroup; children = ( 3FEC91662A4D3B140044BFF5 /* App.app */, ); name = Products; sourceTree = ""; }; 3FEC91682A4D3B140044BFF5 /* Source */ = { isa = PBXGroup; children = ( 3FEC91702A4D3B150044BFF5 /* Preview Content */, 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */, 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */, 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */, ); path = Source; sourceTree = ""; }; 3FEC91702A4D3B150044BFF5 /* Preview Content */ = { isa = PBXGroup; children = ( 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3FEC91652A4D3B140044BFF5 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 3FEC91752A4D3B150044BFF5 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( 3FEC91622A4D3B140044BFF5 /* Sources */, 3FEC91632A4D3B140044BFF5 /* Frameworks */, 3FEC91642A4D3B140044BFF5 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = App; packageProductDependencies = ( 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */, ); productName = SwiftPM; productReference = 3FEC91662A4D3B140044BFF5 /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FEC915E2A4D3B140044BFF5 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { 3FEC91652A4D3B140044BFF5 = { CreatedOnToolsVersion = 15.0; }; }; }; buildConfigurationList = 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "SwiftPackageManager" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3FEC915D2A4D3B140044BFF5; packageReferences = ( 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */, ); productRefGroup = 3FEC91672A4D3B140044BFF5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FEC91652A4D3B140044BFF5 /* App */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3FEC91642A4D3B140044BFF5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC916E2A4D3B150044BFF5 /* Assets.xcassets in Resources */, 3FEC91722A4D3B150044BFF5 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3FEC91622A4D3B140044BFF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC916A2A4D3B140044BFF5 /* SwiftExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 3FEC91732A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 3FEC91742A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 3FEC91762A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Debug; }; 3FEC91772A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "SwiftPackageManager" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91732A4D3B150044BFF5 /* Debug */, 3FEC91742A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FEC91752A4D3B150044BFF5 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91762A4D3B150044BFF5 /* Debug */, 3FEC91772A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { branch = master; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3FEC915E2A4D3B140044BFF5 /* Project object */; } ================================================ FILE: examples/installation/SwiftPackageManagerDynamic.notxcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 3FEC916A2A4D3B140044BFF5 /* SwiftExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */; }; 3FEC916E2A4D3B150044BFF5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */; }; 3FEC91722A4D3B150044BFF5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */; }; 3FEC917C2A4D3DB90044BFF5 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */; }; 3FF673252A684E4400500A25 /* Framework1.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF6731F2A684E4400500A25 /* Framework1.framework */; }; 3FF673262A684E4400500A25 /* Framework1.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF6731F2A684E4400500A25 /* Framework1.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3FF673362A684E4E00500A25 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF673302A684E4E00500A25 /* Framework2.framework */; }; 3FF673372A684E4E00500A25 /* Framework2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF673302A684E4E00500A25 /* Framework2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3FF6733F2A684E7200500A25 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3FF6733E2A684E7200500A25 /* RealmSwift */; }; 3FF673432A684E7700500A25 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3FF673422A684E7700500A25 /* RealmSwift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 3FF673232A684E4400500A25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FEC915E2A4D3B140044BFF5 /* Project object */; proxyType = 1; remoteGlobalIDString = 3FF6731E2A684E4400500A25; remoteInfo = Framework1; }; 3FF673342A684E4E00500A25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FEC915E2A4D3B140044BFF5 /* Project object */; proxyType = 1; remoteGlobalIDString = 3FF6732F2A684E4E00500A25; remoteInfo = Framework2; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 3FF673272A684E4400500A25 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3FF673262A684E4400500A25 /* Framework1.framework in Embed Frameworks */, 3FF673372A684E4E00500A25 /* Framework2.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 3FEC91662A4D3B140044BFF5 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExampleApp.swift; sourceTree = ""; }; 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftExample.entitlements; sourceTree = ""; }; 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 3FF6731F2A684E4400500A25 /* Framework1.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework1.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3FF673302A684E4E00500A25 /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3FEC91632A4D3B140044BFF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FF673252A684E4400500A25 /* Framework1.framework in Frameworks */, 3FF673362A684E4E00500A25 /* Framework2.framework in Frameworks */, 3FEC917C2A4D3DB90044BFF5 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6731C2A684E4400500A25 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FF6733F2A684E7200500A25 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6732D2A684E4E00500A25 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3FF673432A684E7700500A25 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3FEC915D2A4D3B140044BFF5 = { isa = PBXGroup; children = ( 3FEC91682A4D3B140044BFF5 /* Source */, 3FEC91672A4D3B140044BFF5 /* Products */, 3FF6733B2A684E7200500A25 /* Frameworks */, ); sourceTree = ""; }; 3FEC91672A4D3B140044BFF5 /* Products */ = { isa = PBXGroup; children = ( 3FEC91662A4D3B140044BFF5 /* App.app */, 3FF6731F2A684E4400500A25 /* Framework1.framework */, 3FF673302A684E4E00500A25 /* Framework2.framework */, ); name = Products; sourceTree = ""; }; 3FEC91682A4D3B140044BFF5 /* Source */ = { isa = PBXGroup; children = ( 3FEC91702A4D3B150044BFF5 /* Preview Content */, 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */, 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */, 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */, ); path = Source; sourceTree = ""; }; 3FEC91702A4D3B150044BFF5 /* Preview Content */ = { isa = PBXGroup; children = ( 3FEC91712A4D3B150044BFF5 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 3FF6733B2A684E7200500A25 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 3FF6731A2A684E4400500A25 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6732B2A684E4E00500A25 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 3FEC91652A4D3B140044BFF5 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 3FEC91752A4D3B150044BFF5 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( 3FEC91622A4D3B140044BFF5 /* Sources */, 3FEC91632A4D3B140044BFF5 /* Frameworks */, 3FEC91642A4D3B140044BFF5 /* Resources */, 3FF673272A684E4400500A25 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 3FF673242A684E4400500A25 /* PBXTargetDependency */, 3FF673352A684E4E00500A25 /* PBXTargetDependency */, ); name = App; packageProductDependencies = ( 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */, ); productName = SwiftPM; productReference = 3FEC91662A4D3B140044BFF5 /* App.app */; productType = "com.apple.product-type.application"; }; 3FF6731E2A684E4400500A25 /* Framework1 */ = { isa = PBXNativeTarget; buildConfigurationList = 3FF6732A2A684E4400500A25 /* Build configuration list for PBXNativeTarget "Framework1" */; buildPhases = ( 3FF6731A2A684E4400500A25 /* Headers */, 3FF6731B2A684E4400500A25 /* Sources */, 3FF6731C2A684E4400500A25 /* Frameworks */, 3FF6731D2A684E4400500A25 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Framework1; packageProductDependencies = ( 3FF6733E2A684E7200500A25 /* RealmSwift */, ); productName = Framework1; productReference = 3FF6731F2A684E4400500A25 /* Framework1.framework */; productType = "com.apple.product-type.framework"; }; 3FF6732F2A684E4E00500A25 /* Framework2 */ = { isa = PBXNativeTarget; buildConfigurationList = 3FF673382A684E4E00500A25 /* Build configuration list for PBXNativeTarget "Framework2" */; buildPhases = ( 3FF6732B2A684E4E00500A25 /* Headers */, 3FF6732C2A684E4E00500A25 /* Sources */, 3FF6732D2A684E4E00500A25 /* Frameworks */, 3FF6732E2A684E4E00500A25 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Framework2; packageProductDependencies = ( 3FF673422A684E7700500A25 /* RealmSwift */, ); productName = Framework2; productReference = 3FF673302A684E4E00500A25 /* Framework2.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FEC915E2A4D3B140044BFF5 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { 3FEC91652A4D3B140044BFF5 = { CreatedOnToolsVersion = 15.0; }; 3FF6731E2A684E4400500A25 = { CreatedOnToolsVersion = 15.0; }; 3FF6732F2A684E4E00500A25 = { CreatedOnToolsVersion = 15.0; }; }; }; buildConfigurationList = 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "SwiftPackageManagerDynamic" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3FEC915D2A4D3B140044BFF5; packageReferences = ( 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */, ); productRefGroup = 3FEC91672A4D3B140044BFF5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FEC91652A4D3B140044BFF5 /* App */, 3FF6731E2A684E4400500A25 /* Framework1 */, 3FF6732F2A684E4E00500A25 /* Framework2 */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3FEC91642A4D3B140044BFF5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC916E2A4D3B150044BFF5 /* Assets.xcassets in Resources */, 3FEC91722A4D3B150044BFF5 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6731D2A684E4400500A25 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6732E2A684E4E00500A25 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3FEC91622A4D3B140044BFF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC916A2A4D3B140044BFF5 /* SwiftExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6731B2A684E4400500A25 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 3FF6732C2A684E4E00500A25 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 3FF673242A684E4400500A25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3FF6731E2A684E4400500A25 /* Framework1 */; targetProxy = 3FF673232A684E4400500A25 /* PBXContainerItemProxy */; }; 3FF673352A684E4E00500A25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3FF6732F2A684E4E00500A25 /* Framework2 */; targetProxy = 3FF673342A684E4E00500A25 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 3FEC91732A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 3FEC91742A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 3FEC91762A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Debug; }; 3FEC91772A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Release; }; 3FF673282A684E4400500A25 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", ); "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( "@executable_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.Framework1; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = auto; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "$(AVAILABLE_PLATFORMS)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 3FF673292A684E4400500A25 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", ); "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( "@executable_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.Framework1; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = auto; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "$(AVAILABLE_PLATFORMS)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 3FF673392A684E4E00500A25 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.Framework2; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "$(AVAILABLE_PLATFORMS)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 3FF6733A2A684E4E00500A25 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.Framework2; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "$(AVAILABLE_PLATFORMS)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "SwiftPackageManagerDynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91732A4D3B150044BFF5 /* Debug */, 3FEC91742A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FEC91752A4D3B150044BFF5 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91762A4D3B150044BFF5 /* Debug */, 3FEC91772A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FF6732A2A684E4400500A25 /* Build configuration list for PBXNativeTarget "Framework1" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FF673282A684E4400500A25 /* Debug */, 3FF673292A684E4400500A25 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FF673382A684E4E00500A25 /* Build configuration list for PBXNativeTarget "Framework2" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FF673392A684E4E00500A25 /* Debug */, 3FF6733A2A684E4E00500A25 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { branch = master; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 3FEC917B2A4D3DB90044BFF5 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; 3FF6733E2A684E7200500A25 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; 3FF673422A684E7700500A25 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = 3FEC91782A4D3DB90044BFF5 /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3FEC915E2A4D3B140044BFF5 /* Project object */; } ================================================ FILE: examples/installation/XCFramework.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 3F938BA62A4E1886002356FE /* RealmSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */; }; 3F938BA72A4E1886002356FE /* RealmSwift.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F938BA82A4E1886002356FE /* Realm.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA52A4E1886002356FE /* Realm.xcframework */; }; 3F938BA92A4E1886002356FE /* Realm.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3F938BA52A4E1886002356FE /* Realm.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3FD2CB222A4F34F500DF7B4F /* ObjCImport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FD2CB212A4F34F500DF7B4F /* ObjCImport.m */; }; 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */; }; 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 3F938BAA2A4E1886002356FE /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F938BA72A4E1886002356FE /* RealmSwift.xcframework in Embed Frameworks */, 3F938BA92A4E1886002356FE /* Realm.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RealmSwift.xcframework; path = ../../build/RealmSwift.xcframework; sourceTree = ""; }; 3F938BA52A4E1886002356FE /* Realm.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Realm.xcframework; path = ../../build/Realm.xcframework; sourceTree = ""; }; 3FD2CB212A4F34F500DF7B4F /* ObjCImport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjCImport.m; sourceTree = ""; }; 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExampleApp.swift; sourceTree = ""; }; 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftExample.entitlements; sourceTree = ""; }; 3FEC91872A4D41250044BFF5 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3FEC91802A4D41250044BFF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F938BA62A4E1886002356FE /* RealmSwift.xcframework in Frameworks */, 3F938BA82A4E1886002356FE /* Realm.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3F938BA32A4E1886002356FE /* Frameworks */ = { isa = PBXGroup; children = ( 3F938BA52A4E1886002356FE /* Realm.xcframework */, 3F938BA42A4E1886002356FE /* RealmSwift.xcframework */, ); name = Frameworks; sourceTree = ""; }; 3FEC915D2A4D3B140044BFF5 = { isa = PBXGroup; children = ( 3FEC91682A4D3B140044BFF5 /* Source */, 3FEC91672A4D3B140044BFF5 /* Products */, 3F938BA32A4E1886002356FE /* Frameworks */, ); sourceTree = ""; }; 3FEC91672A4D3B140044BFF5 /* Products */ = { isa = PBXGroup; children = ( 3FEC91872A4D41250044BFF5 /* App.app */, ); name = Products; sourceTree = ""; }; 3FEC91682A4D3B140044BFF5 /* Source */ = { isa = PBXGroup; children = ( 3FEC916D2A4D3B150044BFF5 /* Assets.xcassets */, 3FEC916F2A4D3B150044BFF5 /* SwiftExample.entitlements */, 3FEC91692A4D3B140044BFF5 /* SwiftExampleApp.swift */, 3FD2CB212A4F34F500DF7B4F /* ObjCImport.m */, ); path = Source; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3FEC917D2A4D41250044BFF5 /* App */ = { isa = PBXNativeTarget; buildConfigurationList = 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( 3FEC917E2A4D41250044BFF5 /* Sources */, 3FEC91802A4D41250044BFF5 /* Frameworks */, 3FEC91812A4D41250044BFF5 /* Resources */, 3F938BAA2A4E1886002356FE /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = App; productName = SwiftPM; productReference = 3FEC91872A4D41250044BFF5 /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3FEC915E2A4D3B140044BFF5 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { 3FEC917D2A4D41250044BFF5 = { LastSwiftMigration = 1500; }; }; }; buildConfigurationList = 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "XCFramework" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3FEC915D2A4D3B140044BFF5; productRefGroup = 3FEC91672A4D3B140044BFF5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 3FEC917D2A4D41250044BFF5 /* App */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3FEC91812A4D41250044BFF5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FEC91822A4D41250044BFF5 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3FEC917E2A4D41250044BFF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FD2CB222A4F34F500DF7B4F /* ObjCImport.m in Sources */, 3FEC917F2A4D41250044BFF5 /* SwiftExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 3FEC91732A4D3B150044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 3FEC91742A4D3B150044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 3FEC91852A4D41250044BFF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,6,7"; }; name = Debug; }; 3FEC91862A4D41250044BFF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Source/SwiftExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Source/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.4; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.Realm.App; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4,5,6,7"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3FEC91612A4D3B140044BFF5 /* Build configuration list for PBXProject "XCFramework" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91732A4D3B150044BFF5 /* Debug */, 3FEC91742A4D3B150044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3FEC91842A4D41250044BFF5 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 3FEC91852A4D41250044BFF5 /* Debug */, 3FEC91862A4D41250044BFF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3FEC915E2A4D3B140044BFF5 /* Project object */; } ================================================ FILE: examples/installation/build.rb ================================================ #!/usr/bin/env ruby require 'fileutils' def usage() puts <<~END Usage: ruby #{__FILE__} test-all Usage: ruby #{__FILE__} platform method [linkage] platform: ios osx tvos visionos watchos method: cocoapods carthage spm xcframework linkage: static dynamic (default) environment variables: REALM_XCODE_VERSION: Xcode version to use REALM_TEST_RELEASE: Version number to test, or "latest" to test the latest release REALM_TEST_BRANCH: Name of a branch to test END exit 1 end usage unless ARGV.length >= 1 def read_setting(name) `sh -c 'source ../../scripts/swift-version.sh; set_xcode_version; echo "$#{name}"'`.chomp() end ENV['DEVELOPER_DIR'] = read_setting 'DEVELOPER_DIR' ENV['REALM_XCODE_VERSION'] ||= read_setting 'REALM_XCODE_VERSION' if ENV['REALM_TEST_RELEASE'] == 'latest' ENV['REALM_TEST_RELEASE'] = `curl --silent https://static.realm.io/update/cocoa` end TEST_RELEASE = ENV['REALM_TEST_RELEASE'] TEST_BRANCH = ENV['REALM_TEST_BRANCH'] XCODE_VERSION = ENV['REALM_XCODE_VERSION'] REALM_CORE_VERSION = ENV['REALM_CORE_VERSION'] DEPENDENCIES = File.open("../../dependencies.list").map { |line| line.chomp.split("=") }.to_h def replace_in_file(filepath, *args) contents = File.read(filepath) File.open(filepath, "w") do |file| args.each_slice(2) { |pattern, replacement| contents = contents.gsub pattern, replacement } file.puts contents end end def sh(*args) system(*args) or exit(1) end # Copy a xcframework to the location which the installation example looks. This # shells out to `cp` because Ruby currently doesn't have native bindings for # clonefile-based copying. def copy_xcframework(path, framework, dir = '') FileUtils.mkdir_p "../../build/#{dir}" FileUtils.rm_rf "../../build/#{dir}/#{framework}.xcframework" source = "#{path}/#{framework}.xcframework" if not Dir.exist? source raise "Missing XCFramework to test at '#{source}'" end puts "Copying xcframework from #{source} into ../../build/#{dir}" sh 'cp', '-cR', source, "../../build/#{dir}" end def download_release(version) # Download and extract the zip if the extracted directory doesn't already # exist. For master-push workflow testing, we already downloaded a local copy of the zip that # just needs to be extracted. unless Dir.exist? "realm-swift-#{version}" unless File.exist? "realm-swift-#{version}.zip" sh 'curl', '-OL', "https://github.com/realm/realm-swift/releases/download/v#{version}/realm-swift-#{version}.zip" end sh 'unzip', "realm-swift-#{version}.zip" FileUtils.rm "realm-swift-#{version}.zip" end unless Dir.exist?("realm-swift-#{version}/#{XCODE_VERSION}") raise "No build for Xcode version #{XCODE_VERSION} found in #{version} release package" end copy_xcframework "realm-swift-#{version}", 'Realm' copy_xcframework "realm-swift-#{version}/static", 'Realm', 'Static' copy_xcframework "realm-swift-#{version}/#{XCODE_VERSION}", 'RealmSwift' end def download_realm(platform, method, static) case method when 'cocoapods' # The podfile takes care of reading the env variables and importing the # correct thing ENV['REALM_PLATFORM'] = platform sh 'pod', 'install' when 'carthage' version = if TEST_RELEASE " == #{TEST_RELEASE}" elsif TEST_BRANCH " \"#{TEST_BRANCH}\"" else '' end File.write 'Cartfile', 'github "realm/realm-swift"' + version platformName = case platform when 'ios' then 'iOS' when 'osx' then 'Mac' when 'tvos' then 'tvOS' when 'watchos' then 'watchOS' else raise "Unsupported platform for Carthage: #{platform}" end sh 'carthage', 'update', '--use-xcframeworks', '--platform', platformName when 'spm' project = static ? 'SwiftPackageManager' : 'SwiftPackageManagerDynamic' # We have to hide the spm example from carthage because otherwise # it'll fetch the example's package dependencies as part of deciding # what to build from this repo. unless File.symlink? "#{project}.xcodeproj/project.pbxproj" FileUtils.mkdir_p "#{project}.xcodeproj" File.symlink "../#{project}.notxcodeproj/project.pbxproj", "#{project}.xcodeproj/project.pbxproj" end # Update the XcodeProj to reference the requested branch or version if TEST_RELEASE replace_in_file "#{project}.xcodeproj/project.pbxproj", /(branch|version) = .*;/, "version = #{TEST_RELEASE};", /kind = .*;/, "kind = exactVersion;" elsif TEST_BRANCH replace_in_file "#{project}.xcodeproj/project.pbxproj", /(branch|version) = .*;/, "branch = #{TEST_BRANCH};", /kind = .*;/, "kind = branch;" end sh 'xcodebuild', '-project', "#{project}.xcodeproj", '-resolvePackageDependencies', '-IDEPackageOnlyUseVersionsFromResolvedFile=NO', '-IDEDisableAutomaticPackageResolution=NO' when 'xcframework' # If we're testing a branch then we should already have a built zip # supplied by Github actions, but we need to know what version tag it has. If # we're testing a release, we'll download the zip. version = TEST_BRANCH ? DEPENDENCIES['VERSION'] : TEST_RELEASE if version download_release version else if static copy_xcframework "../../build/Static/#{platform}", 'Realm', 'Static' else copy_xcframework "../../build/Release/#{platform}", 'Realm' copy_xcframework "../../build/Release/#{platform}", 'RealmSwift' end end else usage end end def build_app(platform, method, static) archive_path = "#{Dir.pwd}/out.xcarchive" FileUtils.rm_rf archive_path build_args = ['clean', 'archive', '-archivePath', archive_path] case platform when 'ios' build_args += ['-sdk', 'iphoneos', '-destination', 'generic/platform=iphoneos'] when 'tvos' build_args += ['-sdk', 'appletvos', '-destination', 'generic/platform=appletvos'] when 'watchos' build_args += ['-sdk', 'watchos', '-destination', 'generic/platform=watchos'] when 'osx' build_args += ['-sdk', 'macosx', '-destination', 'generic/platform=macOS'] when 'catalyst' build_args += ['-destination', 'generic/platform=macOS,variant=Mac Catalyst'] end build_args += ['CODE_SIGN_IDENTITY=', 'CODE_SIGNING_REQUIRED=NO', 'AD_HOC_CODE_SIGNING_ALLOWED=YES'] case method when 'cocoapods' sh 'xcodebuild', '-workspace', 'CocoaPods.xcworkspace', '-scheme', 'App', *build_args when 'carthage' sh 'xcodebuild', '-project', 'Carthage.xcodeproj', '-scheme', 'App', *build_args when 'spm' sh 'xcodebuild', '-project', static ? 'SwiftPackageManager.xcodeproj' : 'SwiftPackageManagerDynamic.xcodeproj', '-scheme', 'App', *build_args when 'xcframework' if static sh 'xcodebuild', '-project', 'Static/StaticExample.xcodeproj', '-scheme', 'StaticExample', *build_args else sh 'xcodebuild', '-project', 'XCFramework.xcodeproj', '-scheme', 'App', *build_args end end end def validate_build(static) has_frameworks = Dir["out.xcarchive/Products/Applications/**/Frameworks/*.framework"].length != 0 if has_frameworks and static raise 'Static build configuration has embedded frameworks' elsif not has_frameworks and not static raise 'Dyanmic build configuration is missing embedded frameworks' end end def test(platform, method, linkage = 'dynamic') static = linkage == 'static' if static ENV['REALM_BUILD_STATIC'] = '1' else ENV.delete 'REALM_BUILD_STATIC' end puts "Testing #{method} for #{platform} and #{linkage}" download_realm(platform, method, static) build_app(platform, method, static) validate_build(static) end if ARGV[0] == 'test-all' platforms = ['ios', 'osx', 'tvos', 'watchos', 'catalyst'] if /15\..*/ =~ XCODE_VERSION platforms += ['visionos'] end for platform in platforms for method in ['cocoapods', 'carthage', 'spm', 'xcframework'] next if platform == 'catalyst' && method == 'carthage' next if platform == 'visionos' && method != 'spm' && method != 'xcframework' test platform, method, 'dynamic' end test platform, 'cocoapods', 'static' unless platform == 'visionos' test platform, 'spm', 'static' end test 'ios', 'xcframework', 'static' else test(*ARGV) end ================================================ FILE: examples/ios/objc/.gitignore ================================================ Pods/ ================================================ FILE: examples/ios/objc/Backlink/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/Backlink/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" #import // Define your models @interface Dog : RLMObject @property NSString *name; @property NSInteger age; @property (readonly) RLMLinkingObjects *owners; @end RLM_COLLECTION_TYPE(Dog) @interface Person : RLMObject @property NSString *name; @property RLMArray *dogs; @end @implementation Person @end @implementation Dog + (NSDictionary *)linkingObjectsProperties { // Define "owners" as the inverse relationship to Person.dogs return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"] }; } @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UIViewController alloc] init]; [self.window makeKeyAndVisible]; [[NSFileManager defaultManager] removeItemAtURL:[RLMRealmConfiguration defaultConfiguration].fileURL error:nil]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [Person createInRealm:realm withValue:@[@"John", @[@[@"Fido", @1]]]]; [Person createInRealm:realm withValue:@[@"Mary", @[@[@"Rex", @2]]]]; }]; // Log all dogs and their owners using the "owners" inverse relationship RLMResults *allDogs = [Dog allObjects]; for (Dog *dog in allDogs) { NSArray *ownerNames = [dog.owners valueForKeyPath:@"name"]; NSLog(@"%@ has %lu owners (%@)", dog.name, (unsigned long)ownerNames.count, ownerNames); } return YES; } @end ================================================ FILE: examples/ios/objc/Backlink/Backlink-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/Backlink/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/Common/LaunchScreen.xib ================================================ ================================================ FILE: examples/ios/objc/Encryption/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/Encryption/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" #import "LabelViewController.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[LabelViewController alloc] init]; [self.window makeKeyAndVisible]; return YES; } @end ================================================ FILE: examples/ios/objc/Encryption/Encryption-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/Encryption/LabelViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface LabelViewController : UIViewController @end ================================================ FILE: examples/ios/objc/Encryption/LabelViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "LabelViewController.h" #import #import // Model definition @interface StringObject : RLMObject @property NSString *stringProp; @end @implementation StringObject // Nothing needed @end @interface LabelViewController () @property (nonatomic, strong) UITextView *textView; @end @implementation LabelViewController // Create a view to display output in - (void)loadView { CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame]; UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame]; contentView.backgroundColor = [UIColor whiteColor]; self.view = contentView; self.textView = [[UITextView alloc] initWithFrame:applicationFrame]; [self.view addSubview:self.textView]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Use an autorelease pool to close the Realm at the end of the block, so // that we can try to reopen it with different keys @autoreleasepool { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.encryptionKey = [self getKey]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; // Add an object [realm beginWriteTransaction]; StringObject *obj = [[StringObject alloc] init]; obj.stringProp = @"abcd"; [realm addObject:obj]; [realm commitWriteTransaction]; } // Opening with wrong key fails since it decrypts to the wrong thing @autoreleasepool { uint8_t buffer[64]; int status = SecRandomCopyBytes(kSecRandomDefault, 64, buffer); NSAssert(status == 0, @"Failed to generate random bytes for key"); (void)status; NSError *error; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.encryptionKey = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)]; [RLMRealm realmWithConfiguration:configuration error:&error]; [self log:@"Open with wrong key: %@", error]; } // Opening wihout supplying a key at all fails @autoreleasepool { NSError *error; [RLMRealm realmWithConfiguration:[RLMRealmConfiguration defaultConfiguration] error:&error]; [self log:@"Open with no key: %@", error]; } // Reopening with the correct key works and can read the data @autoreleasepool { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.encryptionKey = [self getKey]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [self log:@"Saved object: %@", [[[StringObject allObjectsInRealm:realm] firstObject] stringProp]]; } } // Log a message to the screen since we can't just use NSLog() with no debugger attached - (void)log:(NSString *)format, ... { va_list args; va_start(args, format); NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); self.textView.text = [[self.textView.text stringByAppendingString:str] stringByAppendingString:@"\n\n"]; } - (NSData *)getKey { // Identifier for our keychain entry - should be unique for your application static const uint8_t kKeychainIdentifier[] = "io.Realm.EncryptionExampleKey"; NSData *tag = [[NSData alloc] initWithBytesNoCopy:(void *)kKeychainIdentifier length:sizeof(kKeychainIdentifier) freeWhenDone:NO]; // First check in the keychain for an existing key NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrApplicationTag: tag, (__bridge id)kSecAttrKeySizeInBits: @512, (__bridge id)kSecReturnData: @YES}; CFTypeRef dataRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef); if (status == errSecSuccess) { return (__bridge NSData *)dataRef; } // No pre-existing key from this application, so generate a new one uint8_t buffer[64]; status = SecRandomCopyBytes(kSecRandomDefault, 64, buffer); NSAssert(status == 0, @"Failed to generate random bytes for key"); NSData *keyData = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)]; // Store the key in the keychain query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrApplicationTag: tag, (__bridge id)kSecAttrKeySizeInBits: @512, (__bridge id)kSecValueData: keyData}; status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); NSAssert(status == errSecSuccess, @"Failed to insert new key in the keychain"); return keyData; } @end ================================================ FILE: examples/ios/objc/Encryption/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/Extension/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/Extension/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "AppDelegate.h" #import "Tick.h" @interface TickViewController : UIViewController @property (nonatomic, strong) UIButton *button; @property (nonatomic, strong) Tick *tick; @property (nonatomic, strong) RLMNotificationToken *notificationToken; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.io.realm.examples.extension"] URLByAppendingPathComponent:@"extension.realm"]; [RLMRealmConfiguration setDefaultConfiguration:configuration]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[TickViewController alloc] init]; [self.window makeKeyAndVisible]; return YES; } @end @implementation TickViewController - (void)viewDidLoad { [super viewDidLoad]; self.tick = [Tick allObjects].firstObject; if (!self.tick) { [[RLMRealm defaultRealm] transactionWithBlock:^{ self.tick = [Tick createInDefaultRealmWithValue:@[@"", @0]]; }]; } self.notificationToken = [self.tick.realm addNotificationBlock:^(NSString *notification, RLMRealm *realm) { // Occasionally, respond immediately to the notification by triggering a new notification. if (self.tick.count % 13 == 0) { [self tock]; } [self updateLabel]; }]; self.button = [UIButton buttonWithType:UIButtonTypeSystem]; self.button.frame = self.view.bounds; [self.button addTarget:self action:@selector(tock) forControlEvents:UIControlEventTouchUpInside]; self.button.titleLabel.textAlignment = NSTextAlignmentCenter; [self.button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.view addSubview:self.button]; self.view.backgroundColor = [UIColor purpleColor]; [self updateLabel]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.button.frame = self.view.bounds; [self updateLabel]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self tock]; [self updateLabel]; } - (void)updateLabel { [self.button setTitle:@(self.tick.count).stringValue forState:UIControlStateNormal]; } - (void)tock { [[RLMRealm defaultRealm] transactionWithBlock:^{ self.tick.count++; }]; } @end ================================================ FILE: examples/ios/objc/Extension/Extension.entitlements ================================================ com.apple.security.application-groups group.io.realm.examples.extension ================================================ FILE: examples/ios/objc/Extension/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/Extension/Tick.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 @interface Tick : RLMObject @property (nonatomic, strong) NSString *tickID; @property (nonatomic, assign) NSInteger count; @end ================================================ FILE: examples/ios/objc/Extension/Tick.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "Tick.h" @implementation Tick @end ================================================ FILE: examples/ios/objc/Extension/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/GroupedTableView/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/GroupedTableView/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" #import "TableViewController.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: [[TableViewController alloc] initWithStyle:UITableViewStylePlain]]; [self.window makeKeyAndVisible]; return YES; } @end ================================================ FILE: examples/ios/objc/GroupedTableView/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/objc/GroupedTableView/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/objc/GroupedTableView/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/GroupedTableView/TableViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface TableViewController : UITableViewController @end ================================================ FILE: examples/ios/objc/GroupedTableView/TableViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "TableViewController.h" #import // Realm model object @interface DemoObject : RLMObject @property NSString *phoneNumber; @property NSDate *date; @property NSString *contactName; @end @implementation DemoObject // None needed @end static NSString * const kCellID = @"cell"; static NSString * const kTableName = @"table"; @interface TableViewController () @property (nonatomic, strong) RLMSectionedResults *sectionedResults; @property (nonatomic, strong) RLMNotificationToken *notification; @end @implementation TableViewController #pragma mark - View Lifecycle - (void)viewDidLoad { [super viewDidLoad]; // Section Titles [self setupUI]; self.sectionedResults = [[DemoObject allObjects] sectionedResultsSortedUsingKeyPath:@"contactName" ascending:YES keyBlock:^(DemoObject *object) { return [object.contactName substringToIndex:1]; }]; // Set realm notification block __weak typeof(self) weakSelf = self; self.notification = [self.sectionedResults addNotificationBlock:^(RLMSectionedResults *col, RLMSectionedResultsChange *changes) { if (changes) { [weakSelf.tableView performBatchUpdates:^{ [weakSelf.tableView deleteRowsAtIndexPaths:changes.deletions withRowAnimation:UITableViewRowAnimationAutomatic]; [weakSelf.tableView insertRowsAtIndexPaths:changes.insertions withRowAnimation:UITableViewRowAnimationAutomatic]; [weakSelf.tableView reloadRowsAtIndexPaths:changes.modifications withRowAnimation:UITableViewRowAnimationAutomatic]; [weakSelf.tableView insertSections:changes.sectionsToInsert withRowAnimation:UITableViewRowAnimationAutomatic]; [weakSelf.tableView deleteSections:changes.sectionsToRemove withRowAnimation:UITableViewRowAnimationAutomatic]; } completion:^(BOOL finished) { // Noop }]; } }]; [self.tableView reloadData]; } #pragma mark - UI - (void)setupUI { self.title = @"GroupedTableView"; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"BG Add" style:UIBarButtonItemStylePlain target:self action:@selector(backgroundAdd)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)]; } #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.sectionedResults.count; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return self.sectionedResults[section].key; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { return self.sectionedResults.allKeys; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.sectionedResults[section].count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kCellID]; } DemoObject *object = self.sectionedResults[indexPath.section][indexPath.row]; cell.textLabel.text = [NSString stringWithFormat:@"%@: %@", object.contactName, object.phoneNumber]; cell.detailTextLabel.text = object.date.description; return cell; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; DemoObject *object = self.sectionedResults[indexPath.section][indexPath.row]; [realm deleteObject:object]; [realm commitWriteTransaction]; } } #pragma mark - Actions - (void)backgroundAdd { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Import many items in a background thread dispatch_async(queue, ^{ // Get new realm and table since we are in a new thread @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; for (NSInteger index = 0; index < 5; index++) { // Add row via dictionary. Order is ignored. [DemoObject createInRealm:realm withValue:@{@"phoneNumber": [self randomContactInfo], @"date": [NSDate date], @"contactName": [self randomContactName]}]; } [realm commitWriteTransaction]; } }); } - (void)add { [[RLMRealm defaultRealm] transactionWithBlock:^{ [DemoObject createInDefaultRealmWithValue:@[[self randomContactInfo], [NSDate date], [self randomContactName]]]; }]; } #pragma - Helpers - (NSInteger)randomNumberBetween:(NSInteger)min maxNumber:(NSInteger)max { return min + arc4random_uniform((uint32_t)(max - min + 1)); } - (NSString *)randomContactInfo { NSInteger rand1 = [self randomNumberBetween:0 maxNumber:9]; NSInteger rand2 = [self randomNumberBetween:0 maxNumber:9]; NSInteger rand3 = [self randomNumberBetween:0 maxNumber:9]; return [NSString stringWithFormat:@"555-55%ld-%ld%ld55", (long)rand1, rand2, rand3]; } - (NSString *)randomContactName { NSArray *names = @[@"John", @"Mary", @"Fred", @"Sarah", @"Sally", @"James"]; return [names objectAtIndex:arc4random()%[names count]]; } @end ================================================ FILE: examples/ios/objc/GroupedTableView/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/Migration/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/Migration/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 "AppDelegate.h" #import #import "Example_v0.h" #import "Example_v1.h" #import "Example_v2.h" #import "Example_v3.h" #import "Example_v4.h" #import "Example_v5.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UIViewController alloc] init]; [self.window makeKeyAndVisible]; #if CREATE_EXAMPLES [self addExampleDataToRealm:exampleData]; #else [self performMigration]; #endif return YES; } - (void)addExampleDataToRealm:(void (^)(RLMRealm*))examplesData { NSURL *url = [self realmUrlFor:schemaVersion usingTemplate:false]; RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = url; configuration.schemaVersion = schemaVersion; [RLMRealmConfiguration setDefaultConfiguration:configuration]; NSError *error; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error]; if (error) { @throw [NSException exceptionWithName:@"RLMExampleException" reason:@"Could not open realm." userInfo:nil]; } [realm beginWriteTransaction]; exampleData(realm); [realm commitWriteTransaction]; } // Any version before the current versions will be migrated to check if all version combinations work. - (void)performMigration { for (NSInteger oldSchemaVersion = 0; oldSchemaVersion < schemaVersion; oldSchemaVersion++) { NSURL *realmUrl = [self realmUrlFor:oldSchemaVersion usingTemplate:true]; RLMRealmConfiguration *realmConfiguration = [RLMRealmConfiguration defaultConfiguration]; realmConfiguration.fileURL = realmUrl; realmConfiguration.schemaVersion = schemaVersion; realmConfiguration.migrationBlock = migrationBlock; [RLMRealmConfiguration setDefaultConfiguration:realmConfiguration]; NSError *error; [RLMRealm performMigrationForConfiguration:realmConfiguration error:&error]; if (error) { @throw [NSException exceptionWithName:@"RLMExampleException" reason:@"Could not migrate realm." userInfo:nil]; } RLMRealm *realm = [RLMRealm realmWithConfiguration:realmConfiguration error:&error]; if (error) { @throw [NSException exceptionWithName:@"RLMExampleException" reason:@"Could not open realm." userInfo:nil]; } migrationCheck(realm); } } - (NSURL*)realmUrlFor:(NSInteger)schemaVersion usingTemplate:(BOOL)usingTemplate { NSURL *defaultRealmURL = [RLMRealmConfiguration defaultConfiguration].fileURL; NSURL *defaultRealmParentURL = [defaultRealmURL URLByDeletingLastPathComponent]; NSString *fileName = [NSString stringWithFormat:@"default-v%ld", schemaVersion]; NSString *fileExtension = @"realm"; NSString *fileNameWithExtension = [NSString stringWithFormat:@"%@.%@", fileName, fileExtension]; NSURL *destinationUrl = [defaultRealmParentURL URLByAppendingPathComponent:fileNameWithExtension]; if ([[NSFileManager defaultManager] fileExistsAtPath:destinationUrl.path]) { NSError *error; [[NSFileManager defaultManager] removeItemAtPath:destinationUrl.path error:&error]; if (error) { @throw [NSException exceptionWithName:@"RLMExampleException" reason:@"Could not remove realm file." userInfo:nil]; } } if (usingTemplate) { NSURL *bundleUrl = [[NSBundle mainBundle] URLForResource:fileName withExtension:fileExtension]; NSError *error; [[NSFileManager defaultManager] copyItemAtPath:bundleUrl.path toPath:destinationUrl.path error:&error]; if (error) { @throw [NSException exceptionWithName:@"RLMExampleException" reason:@"Could not copy realm template to new path." userInfo:nil]; } } return destinationUrl; } @end ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v0.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_0 #define SCHEMA_VERSION_0 0 #endif #if SCHEMA_VERSION_0 #import #import #pragma mark - Schema NSInteger schemaVersion = 0; @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; @property NSInteger age; + (Person *)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(int)age; @end @implementation Person + (Person *)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(int)age { Person *person = [[self alloc] init]; person.firstName = firstName; person.lastName = lastName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"firstName", @"lastName", @"age"]; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration* migration, uint64_t schemaVersion) {}; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].firstName isEqualToString:@"John"]); assert([persons[0].lastName isEqualToString:@"Doe"]); assert(persons[0].age == 42); assert([persons[1].firstName isEqualToString:@"Jane"]); assert([persons[1].lastName isEqualToString:@"Doe"]); assert(persons[1].age == 43); assert([persons[2].firstName isEqualToString:@"John"]); assert([persons[2].lastName isEqualToString:@"Smith"]); assert(persons[2].age == 44); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFirstName:@"John" lastName:@"Doe" age:42]; Person *person2 = [Person personWithFirstName:@"Jane" lastName:@"Doe" age: 43]; Person *person3 = [Person personWithFirstName:@"John" lastName:@"Smith" age: 44]; [realm addObjects:@[person1, person2, person3]]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v1.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_1 #define SCHEMA_VERSION_1 0 #endif #if SCHEMA_VERSION_1 #import #import #pragma mark - Schema NSInteger schemaVersion = 1; // Changes from previous version: // - combine `firstName` and `lastName` into `fullName` @interface Person : RLMObject @property NSString *fullName; @property NSInteger age; + (Person *)personWithFullName:(NSString *)fullName age:(int)age; @end @implementation Person + (Person *)personWithFullName:(NSString *)fullName age:(int)age { Person *person = [[self alloc] init]; person.fullName = fullName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"fullName", @"age"]; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 1) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { if (oldSchemaVersion < 1) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; } }]; } }; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].fullName isEqualToString:@"John Doe"]); assert(persons[0].age == 42); assert([persons[1].fullName isEqualToString:@"Jane Doe"]); assert(persons[1].age == 43); assert([persons[2].fullName isEqualToString:@"John Smith"]); assert(persons[2].age == 44); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFullName:@"John Doe" age: 42]; Person *person2 = [Person personWithFullName:@"Jane Doe" age: 43]; Person *person3 = [Person personWithFullName:@"John Smith" age: 44]; [realm addObjects:@[person1, person2, person3]]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v2.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_2 #define SCHEMA_VERSION_2 0 #endif #if SCHEMA_VERSION_2 #import #import #pragma mark - Schema NSInteger schemaVersion = 2; // Changes from previous version: // add a `Dog` object // add a list of `dogs` to the `Person` object @interface Dog : RLMObject @property NSString *name; + (Dog *)dogWithName:(NSString *)name; @end RLM_COLLECTION_TYPE(Dog) @implementation Dog + (Dog *)dogWithName:(NSString *)name { Dog *dog = [[self alloc] init]; dog.name = name; return dog; } + (NSArray *)requiredProperties { return @[@"name"]; } @end @interface Person : RLMObject @property NSString *fullName; @property NSInteger age; @property RLMArray *dogs; + (Person *)personWithFullName:(NSString *)fullName age:(int)age; @end @implementation Person + (Person *)personWithFullName:(NSString *)fullName age:(int)age { Person *person = [[self alloc] init]; person.fullName = fullName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"fullName", @"age"]; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 1) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } if (oldSchemaVersion < 2) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // Add a pet to a specific person if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { Dog *marley = [Dog dogWithName:@"Marley"]; Dog *lassie = [Dog dogWithName:@"Lassie"]; RLMArray *dogs = newObject[@"dogs"]; [dogs addObject:marley]; [dogs addObject:lassie]; } else if ([newObject[@"fullName"] isEqualToString:@"Jane Doe"]) { Dog *toto = [Dog dogWithName:@"Toto"]; RLMArray *dogs = newObject[@"dogs"]; [dogs addObject:toto]; } }]; [migration createObject:Dog.className withValue:@[@"Slinkey"]]; } }; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].fullName isEqualToString:@"John Doe"]); assert(persons[0].age == 42); assert(persons[0].dogs.count == 2); assert([persons[0].dogs[0].name isEqualToString:@"Marley"]); assert([persons[0].dogs[1].name isEqualToString:@"Lassie"]); assert([persons[1].fullName isEqualToString:@"Jane Doe"]); assert(persons[1].age == 43); assert(persons[1].dogs.count == 1); assert([persons[1].dogs[0].name isEqualToString:@"Toto"]); assert([persons[2].fullName isEqualToString:@"John Smith"]); assert(persons[2].age == 44); RLMResults *dogs = [Dog allObjects]; assert(dogs.count == 4); assert([dogs objectsWithPredicate:[NSPredicate predicateWithFormat:@"name == 'Slinkey'"]].count == 1); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFullName:@"John Doe" age: 42]; Person *person2 = [Person personWithFullName:@"Jane Doe" age: 43]; Person *person3 = [Person personWithFullName:@"John Smith" age: 44]; Dog *pet1 = [Dog dogWithName:@"Marley"]; Dog *pet2 = [Dog dogWithName:@"Lassie"]; Dog *pet3 = [Dog dogWithName:@"Toto"]; Dog *pet4 = [Dog dogWithName:@"Slinkey"]; [realm addObjects:@[person1, person2, person3]]; // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. [realm addObject:pet4]; [person1.dogs addObject:pet1]; [person1.dogs addObject:pet2]; [person2.dogs addObject:pet3]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v3.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_3 #define SCHEMA_VERSION_3 0 #endif #if SCHEMA_VERSION_3 #import #import #pragma mark - Schema NSInteger schemaVersion = 3; // Changes from previous version: // rename the `Dog` object to `Pet` // add a `kind` property to `Pet` // change the `dogs` property on `Person`: // - rename to `pets` // - change type to `RLMArray` // Renaming tables is not supported yet: https://github.com/realm/realm-swift/issues/2491 // The recommended way is to create a new type instead and migrate the old type. // Here we create `Pet` and migrate its data from `Dog` so simulate renaming the table. @interface Pet : RLMObject typedef NS_ENUM(int, Kind) { unspecified, dog, chicken, cow }; @property NSString *name; @property NSInteger kindValue; @property enum Kind kind; + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind; @end RLM_COLLECTION_TYPE(Pet) @implementation Pet + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind { Pet *pet = [[self alloc] init]; pet.name = name; pet.kind = kind; return pet; } - (enum Kind)kind { return (Kind)_kindValue; } - (void)setKind:(enum Kind)kind { _kindValue = kind; } + (NSArray *)requiredProperties { return @[@"name", @"kindValue"]; } + (NSArray *)ignoredProperties { return @[@"kind"]; } @end @interface Person : RLMObject @property NSString *fullName; @property NSInteger age; @property RLMArray *pets; + (Person *)personWithFullName:(NSString *)fullName age:(int)age; @end @implementation Person + (Person *)personWithFullName:(NSString *)fullName age:(int)age { Person *person = [[self alloc] init]; person.fullName = fullName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"fullName", @"age"]; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 1) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } if (oldSchemaVersion < 2) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // Add a pet to a specific person if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. Pet *marley = [Pet petWithName:@"Marley" kind:dog]; Pet *lassie = [Pet petWithName:@"Lassie" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:marley]; [pets addObject:lassie]; } else if ([newObject[@"fullName"] isEqualToString:@"Jane Doe"]) { Pet *toto = [Pet petWithName:@"Toto" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:toto]; } }]; [migration createObject:Pet.className withValue:@[@"Slinkey", @1]]; } if (oldSchemaVersion == 2) { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { RLMArray *pets = newObject[@"pets"]; for (RLMObject *dog in oldObject[@"dogs"]) { Pet *pet = (Pet *)[migration createObject:Pet.className withValue:@[dog[@"name"], @1]]; [pets addObject:pet]; } }]; // We migrate over the old dog list to make sure all dogs get added, even those without // an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 [migration enumerateObjects:@"Dog" block:^(RLMObject *oldDogObject, RLMObject *newDogObject) { __block bool dogFound = false; [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { for (Pet *pet in newObject[@"pets"]) { if ([pet[@"name"] isEqualToString:oldDogObject[@"name"]]) { dogFound = true; break; } } }]; if (!dogFound) { [migration createObject:Pet.className withValue:@[oldDogObject[@"name"], @1]]; } }]; // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // [migration deleteDataForClassName:@"Dog"]; } }; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].fullName isEqualToString:@"John Doe"]); assert(persons[0].age == 42); assert(persons[0].pets.count == 2); assert([persons[0].pets[0].name isEqualToString:@"Marley"]); assert([persons[0].pets[1].name isEqualToString:@"Lassie"]); assert([persons[1].fullName isEqualToString:@"Jane Doe"]); assert(persons[1].age == 43); assert(persons[1].pets.count == 1); assert([persons[1].pets[0].name isEqualToString:@"Toto"]); assert([persons[2].fullName isEqualToString:@"John Smith"]); assert(persons[2].age == 44); RLMResults *pets = [Pet allObjects]; assert(pets.count == 4); assert([pets objectsWithPredicate:[NSPredicate predicateWithFormat:@"name == 'Slinkey'"]].count == 1); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFullName:@"John Doe" age: 42]; Person *person2 = [Person personWithFullName:@"Jane Doe" age: 43]; Person *person3 = [Person personWithFullName:@"John Smith" age: 44]; Pet *pet1 = [Pet petWithName:@"Marley" kind:dog]; Pet *pet2 = [Pet petWithName:@"Lassie" kind:dog]; Pet *pet3 = [Pet petWithName:@"Toto" kind:dog]; Pet *pet4 = [Pet petWithName:@"Slinkey" kind:dog]; [realm addObjects:@[person1, person2, person3]]; // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. [realm addObject:pet4]; [person1.pets addObject:pet1]; [person1.pets addObject:pet2]; [person2.pets addObject:pet3]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v4.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_4 #define SCHEMA_VERSION_4 0 #endif #if SCHEMA_VERSION_4 #import #import #pragma mark - Schema NSInteger schemaVersion = 4; // Changes from previous version: // Add an `Address` to the `Person`. @interface Pet : RLMObject typedef NS_ENUM(int, Kind) { unspecified, dog, chicken, cow }; @property NSString *name; @property NSInteger kindValue; @property enum Kind kind; + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind; @end RLM_COLLECTION_TYPE(Pet) @implementation Pet + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind { Pet *pet = [[self alloc] init]; pet.name = name; pet.kind = kind; return pet; } - (enum Kind)kind { return (Kind)_kindValue; } - (void)setKind:(enum Kind)kind { _kindValue = kind; } + (NSArray *)requiredProperties { return @[@"name", @"kindValue"]; } + (NSArray *)ignoredProperties { return @[@"kind"]; } @end @class Address; @interface Person : RLMObject @property NSString *fullName; @property NSInteger age; @property Address *address; @property RLMArray *pets; + (Person *)personWithFullName:(NSString *)fullName age:(int)age; @end @implementation Person + (Person *)personWithFullName:(NSString *)fullName age:(int)age { Person *person = [[self alloc] init]; person.fullName = fullName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"fullName", @"age"]; } @end @interface Address : RLMObject @property NSString *street; @property NSString *city; @property (readonly) RLMLinkingObjects *residents; + (Address *)addressWithStreet:(NSString *)street city:(NSString *)city; @end @implementation Address + (Address *)addressWithStreet:(NSString *)street city:(NSString *)city { Address *address = [[self alloc] init]; address.street = street; address.city = city; return address; } + (NSArray *)requiredProperties { return @[@"street", @"city"]; } + (NSDictionary *)linkingObjectsProperties { return @{ @"residents": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"address"], }; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 1) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } if (oldSchemaVersion < 2) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // Add a pet to a specific person if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. Pet *marley = [Pet petWithName:@"Marley" kind:dog]; Pet *lassie = [Pet petWithName:@"Lassie" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:marley]; [pets addObject:lassie]; } else if ([newObject[@"fullName"] isEqualToString:@"Jane Doe"]) { Pet *toto = [Pet petWithName:@"Toto" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:toto]; } }]; [migration createObject:Pet.className withValue:@[@"Slinkey", @1]]; } if (oldSchemaVersion == 2) { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { RLMArray *pets = newObject[@"pets"]; for (RLMObject *dog in oldObject[@"dogs"]) { Pet *pet = (Pet *)[migration createObject:Pet.className withValue:@[dog[@"name"], @1]]; [pets addObject:pet]; } }]; // We migrate over the old dog list to make sure all dogs get added, even those without // an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 [migration enumerateObjects:@"Dog" block:^(RLMObject *oldDogObject, RLMObject *newDogObject) { __block bool dogFound = false; [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { for (Pet *pet in newObject[@"pets"]) { if ([pet[@"name"] isEqualToString:oldDogObject[@"name"]]) { dogFound = true; break; } } }]; if (!dogFound) { [migration createObject:Pet.className withValue:@[oldDogObject[@"name"], @1]]; } }]; // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // [migration deleteDataForClassName:@"Dog"]; } if (oldSchemaVersion < 4) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { Address *address = (Address *)[migration createObject:Address.className withValue:@[@"Broadway", @"New York"]]; newObject[@"address"] = address; } }]; } }; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].fullName isEqualToString:@"John Doe"]); assert(persons[0].age == 42); assert(persons[0].address != nil); assert([persons[0].address.city isEqualToString:@"New York"]); assert([persons[0].address.street isEqualToString:@"Broadway"]); assert(persons[0].pets.count == 2); assert([persons[0].pets[0].name isEqualToString:@"Marley"]); assert([persons[0].pets[1].name isEqualToString:@"Lassie"]); assert([persons[1].fullName isEqualToString:@"Jane Doe"]); assert(persons[1].age == 43); assert(persons[1].address == nil); assert(persons[1].pets.count == 1); assert([persons[1].pets[0].name isEqualToString:@"Toto"]); assert([persons[2].fullName isEqualToString:@"John Smith"]); assert(persons[2].age == 44); assert(persons[2].address == nil); RLMResults *pets = [Pet allObjects]; assert(pets.count == 4); assert([pets objectsWithPredicate:[NSPredicate predicateWithFormat:@"name == 'Slinkey'"]].count == 1); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFullName:@"John Doe" age: 42]; Person *person2 = [Person personWithFullName:@"Jane Doe" age: 43]; Person *person3 = [Person personWithFullName:@"John Smith" age: 44]; Pet *pet1 = [Pet petWithName:@"Marley" kind:dog]; Pet *pet2 = [Pet petWithName:@"Lassie" kind:dog]; Pet *pet3 = [Pet petWithName:@"Toto" kind:dog]; Pet *pet4 = [Pet petWithName:@"Slinkey" kind:dog]; [realm addObjects:@[person1, person2, person3]]; // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. [realm addObject:pet4]; [person1.pets addObject:pet1]; [person1.pets addObject:pet2]; [person2.pets addObject:pet3]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Examples/Example_v5.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #ifndef SCHEMA_VERSION_5 #define SCHEMA_VERSION_5 1 #endif #if SCHEMA_VERSION_5 && !SCHEMA_VERSION_4 && !SCHEMA_VERSION_3 && !SCHEMA_VERSION_2 && !SCHEMA_VERSION_1 && !SCHEMA_VERSION_0 #import #import #pragma mark - Schema NSInteger schemaVersion = 5; // Changes from previous version: // - Change the `Address` from `Object` to `EmbeddedObject`. // // Be aware that this only works if there is only one `LinkingObject` per `Address`. // See https://github.com/realm/realm-swift/issues/7060 @interface Pet : RLMObject typedef NS_ENUM(int, Kind) { unspecified, dog, chicken, cow }; @property NSString *name; @property NSInteger kindValue; @property enum Kind kind; + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind; @end RLM_COLLECTION_TYPE(Pet) @implementation Pet + (Pet *)petWithName:(NSString *)name kind:(enum Kind)kind { Pet *pet = [[self alloc] init]; pet.name = name; pet.kind = kind; return pet; } - (enum Kind)kind { return (Kind)_kindValue; } - (void)setKind:(enum Kind)kind { _kindValue = kind; } + (NSArray *)requiredProperties { return @[@"name", @"kindValue"]; } + (NSArray *)ignoredProperties { return @[@"kind"]; } @end @class Address; @interface Person : RLMObject @property NSString *fullName; @property NSInteger age; @property Address *address; @property RLMArray *pets; + (Person *)personWithFullName:(NSString *)fullName age:(int)age; @end @implementation Person + (Person *)personWithFullName:(NSString *)fullName age:(int)age { Person *person = [[self alloc] init]; person.fullName = fullName; person.age = age; return person; } + (NSArray *)requiredProperties { return @[@"fullName", @"age"]; } @end @interface Address : RLMEmbeddedObject @property NSString *street; @property NSString *city; @property (readonly) RLMLinkingObjects *residents; + (Address *)addressWithStreet:(NSString *)street city:(NSString *)city; @end @implementation Address + (Address *)addressWithStreet:(NSString *)street city:(NSString *)city { Address *address = [[self alloc] init]; address.street = street; address.city = city; return address; } + (NSArray *)requiredProperties { return @[@"street", @"city"]; } + (NSDictionary *)linkingObjectsProperties { return @{ @"residents": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"address"], }; } @end #pragma mark - Migration // Migration block to migrate from *any* previous version to this version. RLMMigrationBlock migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 1) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } if (oldSchemaVersion < 2) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // Add a pet to a specific person if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. Pet *marley = [Pet petWithName:@"Marley" kind:dog]; Pet *lassie = [Pet petWithName:@"Lassie" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:marley]; [pets addObject:lassie]; } else if ([newObject[@"fullName"] isEqualToString:@"Jane Doe"]) { Pet *toto = [Pet petWithName:@"Toto" kind:dog]; RLMArray *pets = newObject[@"pets"]; [pets addObject:toto]; } }]; [migration createObject:Pet.className withValue:@[@"Slinkey", @1]]; } if (oldSchemaVersion == 2) { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { RLMArray *pets = newObject[@"pets"]; for (RLMObject *dog in oldObject[@"dogs"]) { Pet *pet = (Pet *)[migration createObject:Pet.className withValue:@[dog[@"name"], @1]]; [pets addObject:pet]; } }]; // We migrate over the old dog list to make sure all dogs get added, even those without // an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 [migration enumerateObjects:@"Dog" block:^(RLMObject *oldDogObject, RLMObject *newDogObject) { __block bool dogFound = false; [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { for (Pet *pet in newObject[@"pets"]) { if ([pet[@"name"] isEqualToString:oldDogObject[@"name"]]) { dogFound = true; break; } } }]; if (!dogFound) { [migration createObject:Pet.className withValue:@[oldDogObject[@"name"], @1]]; } }]; // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // [migration deleteDataForClassName:@"Dog"]; } if (oldSchemaVersion < 4) { [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { if ([newObject[@"fullName"] isEqualToString:@"John Doe"]) { Address *address = [Address addressWithStreet:@"Broadway" city:@"New York"]; newObject[@"address"] = address; } }]; } }; // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. typedef void (^MigrationCheck) (RLMRealm *realm); MigrationCheck migrationCheck = ^(RLMRealm *realm) { RLMResults *persons = [Person allObjects]; assert(persons.count == 3); assert([persons[0].fullName isEqualToString:@"John Doe"]); assert(persons[0].age == 42); assert(persons[0].address != nil); assert([persons[0].address.city isEqualToString:@"New York"]); assert([persons[0].address.street isEqualToString:@"Broadway"]); assert(persons[0].pets.count == 2); assert([persons[0].pets[0].name isEqualToString:@"Marley"]); assert([persons[0].pets[1].name isEqualToString:@"Lassie"]); assert([persons[1].fullName isEqualToString:@"Jane Doe"]); assert(persons[1].age == 43); assert(persons[1].address == nil); assert(persons[1].pets.count == 1); assert([persons[1].pets[0].name isEqualToString:@"Toto"]); assert([persons[2].fullName isEqualToString:@"John Smith"]); assert(persons[2].age == 44); assert(persons[2].address == nil); RLMResults *pets = [Pet allObjects]; assert(pets.count == 4); assert([pets objectsWithPredicate:[NSPredicate predicateWithFormat:@"name == 'Slinkey'"]].count == 1); }; #pragma mark - Example data // Example data for this schema version. typedef void (^ExampleData) (RLMRealm *realm); ExampleData exampleData = ^(RLMRealm *realm) { Person *person1 = [Person personWithFullName:@"John Doe" age: 42]; Person *person2 = [Person personWithFullName:@"Jane Doe" age: 43]; Person *person3 = [Person personWithFullName:@"John Smith" age: 44]; Pet *pet1 = [Pet petWithName:@"Marley" kind:dog]; Pet *pet2 = [Pet petWithName:@"Lassie" kind:dog]; Pet *pet3 = [Pet petWithName:@"Toto" kind:dog]; Pet *pet4 = [Pet petWithName:@"Slinkey" kind:dog]; [realm addObjects:@[person1, person2, person3]]; // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. [realm addObject:pet4]; [person1.pets addObject:pet1]; [person1.pets addObject:pet2]; [person2.pets addObject:pet3]; }; #endif ================================================ FILE: examples/ios/objc/Migration/Migration-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/Migration/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/REST/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/REST/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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. // //////////////////////////////////////////////////////////////////////////// // This app gives a simple example of retrieving data from the foursquare REST API // and persisting it in a Realm. To run this app, you will need to provide a foursquare // client ID and client secret. To get these, signup at https://developer.foursquare.com/ #import "AppDelegate.h" #import "Venue.h" #import #error Provide your foursquare client ID and client secret NSString *clientID = @"YOUR CLIENT ID"; NSString *clientSecret = @"YOUR CLIENT SECRET"; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UIViewController alloc] init]; [self.window makeKeyAndVisible]; // Ensure we start with an empty database [[NSFileManager defaultManager] removeItemAtURL:[RLMRealmConfiguration defaultConfiguration].fileURL error:nil]; // Query Foursquare API NSDictionary *foursquareVenues = [self getFoursquareVenues]; // Persist the results to Realm [self persistToDefaultRealm:foursquareVenues]; return YES; } -(NSDictionary*)getFoursquareVenues { // Call the foursquare API - here we use an NSData method for our API request, // but you could use anything that will allow you to call the API and serialize // the response as an NSDictionary or NSArray NSData *apiResponse = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:[NSString stringWithFormat:@"https://api.foursquare.com/v2/venues/search?near=San%@Francisco&client_id=%@&client_secret=%@&v=20140101&limit=50", @"%20", clientID, clientSecret]]]; // Serialize the NSData object from the response into an NSDictionary NSDictionary *serializedResponse = [[NSJSONSerialization JSONObjectWithData:apiResponse options:kNilOptions error:nil] objectForKey:@"response"]; // Extract the venues from the response as an NSDictionary return serializedResponse[@"venues"]; } - (void)persistToDefaultRealm:(NSDictionary*)foursquareVenues { // Open the default Realm file RLMRealm *defaultRealm = [RLMRealm defaultRealm]; // Begin a write transaction to save to the default Realm [defaultRealm beginWriteTransaction]; for (id venue in foursquareVenues) { // Store the foursquare venue name and id in a Realm Object Venue *newVenue = [[Venue alloc] init]; newVenue.foursquareID = venue[@"id"]; newVenue.name = venue[@"name"]; // Add the Venue object to the default Realm // (alternatively you could serialize the API response as an NSArray and call addObjectsFromArray) [defaultRealm addObject:newVenue]; } // Persist all the Venues with a single commit [defaultRealm commitWriteTransaction]; // Show all the venues that were persisted NSLog(@"Here are all the venues persisted to the default Realm: \n\n %@", [[Venue allObjects] description]); } @end ================================================ FILE: examples/ios/objc/REST/REST-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/REST/Venue.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import @interface Venue : RLMObject @property NSString * foursquareID; @property NSString * name; @end ================================================ FILE: examples/ios/objc/REST/Venue.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "Venue.h" @implementation Venue @end ================================================ FILE: examples/ios/objc/REST/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0295B8FF19D102880036D6C3 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; 0295B90019D102880036D6C3 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; 0295B90119D102880036D6C3 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; 0295B90219D102880036D6C3 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; 0295B90319D102880036D6C3 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; 227D20DE1CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20DF1CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E01CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E11CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E31CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E41CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E51CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 227D20E61CB6009D008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20DD1CB6009D008F641B /* LaunchScreen.xib */; }; 3F08725E27F3C95E007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08725F27F3C96A007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726027F3C971007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726127F3C977007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726227F3C97D007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726427F3C989007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726527F3C98E007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726627F3C993007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3F08726727F3C999007A1175 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F08725D27F3C8CF007A1175 /* libcompression.tbd */; }; 3FC898FD1A140F550067CBEC /* LabelViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FC898FC1A140F550067CBEC /* LabelViewController.m */; }; 7DD1D7E225DC2D3D00E63229 /* default-v2.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D225DC2C7400E63229 /* default-v2.realm */; }; 7DD1D7E325DC2D3D00E63229 /* default-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D325DC2C7400E63229 /* default-v3.realm */; }; 7DD1D7E425DC2D3D00E63229 /* default-v0.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D425DC2C7400E63229 /* default-v0.realm */; }; 7DD1D7E525DC2D3D00E63229 /* default-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D525DC2C7400E63229 /* default-v5.realm */; }; 7DD1D7E625DC2D3D00E63229 /* default-v4.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D125DC2C7400E63229 /* default-v4.realm */; }; 7DD1D7E725DC2D3D00E63229 /* default-v1.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DD1D7D025DC2C7400E63229 /* default-v1.realm */; }; C06134DF1A718BB800D22D12 /* TodayExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C0DC42061A7079D00067156A /* TodayExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C06134E41A71AA4E00D22D12 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; C06134E51A71AA5600D22D12 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; C06134E61A71AA5A00D22D12 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; C0DC41D41A7072680067156A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DC41D31A7072670067156A /* AppDelegate.m */; }; C0DC41F41A70729D0067156A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; C0DC41F51A70729D0067156A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; C0DC41F61A70729D0067156A /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; C0DC41F71A70729D0067156A /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; C0DC41F81A70729D0067156A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; C0DC41FC1A7072DE0067156A /* Tick.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DC41FB1A7072DE0067156A /* Tick.m */; }; C0DC42011A7078130067156A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DC41FD1A7073E40067156A /* main.m */; }; C0DC42081A7079D00067156A /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DC42071A7079D00067156A /* NotificationCenter.framework */; }; C0DC420E1A7079D00067156A /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DC420D1A7079D00067156A /* TodayViewController.m */; }; C0DC42101A7079D00067156A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0DC420F1A7079D00067156A /* MainInterface.storyboard */; }; C0DC42181A707B960067156A /* Tick.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DC41FB1A7072DE0067156A /* Tick.m */; }; C0DC42191A707BF80067156A /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E879D9CE1A12AA120035E2EB /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E879D9CF1A12AA120035E2EB /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E879D9D01A12AA120035E2EB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E879D9D11A12AA120035E2EB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E879D9D21A12AA120035E2EB /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; E879D9E21A12AA400035E2EB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E879D9DC1A12AA400035E2EB /* AppDelegate.m */; }; E879D9E31A12AA400035E2EB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E879D9DD1A12AA400035E2EB /* Images.xcassets */; }; E879D9E51A12AA400035E2EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E879D9DF1A12AA400035E2EB /* main.m */; }; E879D9E61A12AA400035E2EB /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E879D9E11A12AA400035E2EB /* TableViewController.m */; }; E8AB71DD19BA503500F3EDB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8AB71DE19BA503500F3EDB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8AB71DF19BA503500F3EDB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8AB720B19BA503B00F3EDB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8AB720C19BA503B00F3EDB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8AB720D19BA503B00F3EDB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8AB723919BA504000F3EDB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8AB723A19BA504000F3EDB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8AB723B19BA504000F3EDB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8AB726719BA504500F3EDB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8AB726819BA504500F3EDB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8AB726919BA504500F3EDB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8AB729519BA504900F3EDB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8AB729619BA504900F3EDB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8AB729719BA504900F3EDB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8BDBFCC1A116FCB00450CFF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8BDBFC91A116FCB00450CFF /* AppDelegate.m */; }; E8BDBFCE1A116FCB00450CFF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8BDBFCB1A116FCB00450CFF /* main.m */; }; E8BDBFD11A1172B200450CFF /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0295B8FE19D102880036D6C3 /* Realm.framework */; }; E8BDBFD21A1172B600450CFF /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8BDBFD31A1172BC00450CFF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */; }; E8BDBFD41A1172BF00450CFF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71AD19BA502500F3EDB4 /* UIKit.framework */; }; E8BDBFD51A1172C300450CFF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB71A919BA502500F3EDB4 /* Foundation.framework */; }; E8D5D6AA19BA53DF0053D333 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70E9319BA5083006F60D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70E9019BA5083006F60D5 /* AppDelegate.m */; }; E8F70E9419BA5083006F60D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70E9119BA5083006F60D5 /* main.m */; }; E8F70E9E19BA508B006F60D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70E9819BA508B006F60D5 /* AppDelegate.m */; }; E8F70E9F19BA508B006F60D5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E8F70E9919BA508B006F60D5 /* Images.xcassets */; }; E8F70EA019BA508B006F60D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70E9A19BA508B006F60D5 /* main.m */; }; E8F70EA219BA508B006F60D5 /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70E9D19BA508B006F60D5 /* TableViewController.m */; }; E8F70EAA19BA5093006F60D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EA519BA5093006F60D5 /* AppDelegate.m */; }; E8F70EAB19BA5093006F60D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EA619BA5093006F60D5 /* main.m */; }; E8F70EAD19BA5093006F60D5 /* Venue.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EA919BA5093006F60D5 /* Venue.m */; }; E8F70EBC19BA50A3006F60D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EB419BA50A3006F60D5 /* main.m */; }; E8F70EBE19BA50A3006F60D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EB719BA50A3006F60D5 /* AppDelegate.m */; }; E8F70EC419BA50AB006F60D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EC119BA50AB006F60D5 /* AppDelegate.m */; }; E8F70EC619BA50AB006F60D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E8F70EC319BA50AB006F60D5 /* main.m */; }; E8F70ED119BA52E8006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70ED519BA530C006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70ED919BA534E006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; E8F70EDD19BA5380006F60D5 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F70ED019BA52E8006F60D5 /* libc++.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ C06134E01A718BB800D22D12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E8AB719E19BA502500F3EDB4 /* Project object */; proxyType = 1; remoteGlobalIDString = C0DC42051A7079D00067156A; remoteInfo = TodayExtension; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 3F336E871DA2F8A4006CB5A0 /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; dstSubfolderSpec = 16; files = ( ); name = "Embed Watch Content"; runOnlyForDeploymentPostprocessing = 0; }; C06134E21A718BB800D22D12 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( C06134DF1A718BB800D22D12 /* TodayExtension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0295B8FE19D102880036D6C3 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0A73D4401B1442B200E1E8EE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = SOURCE_ROOT; }; 227D20DD1CB6009D008F641B /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 3F08725D27F3C8CF007A1175 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = usr/lib/libcompression.tbd; sourceTree = SDKROOT; }; 3FC898FB1A140F550067CBEC /* LabelViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LabelViewController.h; sourceTree = ""; }; 3FC898FC1A140F550067CBEC /* LabelViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LabelViewController.m; sourceTree = ""; }; 7D56426A25DD66160079F4C2 /* Example_v0.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v0.h; sourceTree = ""; }; 7D56427625DD6A7F0079F4C2 /* Example_v2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v2.h; sourceTree = ""; }; 7D56427725DD6A860079F4C2 /* Example_v3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v3.h; sourceTree = ""; }; 7D56427825DD6A8B0079F4C2 /* Example_v4.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v4.h; sourceTree = ""; }; 7D56427925DD6A920079F4C2 /* Example_v5.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Example_v5.h; sourceTree = ""; }; 7D718BC825DAADB500A74FDD /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../swift/Migration/README.md; sourceTree = ""; }; 7DD1D7D025DC2C7400E63229 /* default-v1.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v1.realm"; sourceTree = ""; }; 7DD1D7D125DC2C7400E63229 /* default-v4.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v4.realm"; sourceTree = ""; }; 7DD1D7D225DC2C7400E63229 /* default-v2.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v2.realm"; sourceTree = ""; }; 7DD1D7D325DC2C7400E63229 /* default-v3.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v3.realm"; sourceTree = ""; }; 7DD1D7D425DC2C7400E63229 /* default-v0.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v0.realm"; sourceTree = ""; }; 7DD1D7D525DC2C7400E63229 /* default-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v5.realm"; sourceTree = ""; }; C0DC41CC1A7072670067156A /* extension.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = extension.app; sourceTree = BUILT_PRODUCTS_DIR; }; C0DC41D21A7072670067156A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; C0DC41D31A7072670067156A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; C0DC41FA1A7072DE0067156A /* Tick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tick.h; sourceTree = ""; }; C0DC41FB1A7072DE0067156A /* Tick.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Tick.m; sourceTree = ""; }; C0DC41FD1A7073E40067156A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; C0DC41FF1A70775F0067156A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C0DC42061A7079D00067156A /* TodayExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TodayExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C0DC42071A7079D00067156A /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; C0DC420B1A7079D00067156A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C0DC420C1A7079D00067156A /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; C0DC420D1A7079D00067156A /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; C0DC420F1A7079D00067156A /* MainInterface.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainInterface.storyboard; sourceTree = ""; }; C0DC421B1A717B660067156A /* TodayExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = TodayExtension.entitlements; sourceTree = ""; }; C0DC421C1A717BD00067156A /* Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Extension.entitlements; sourceTree = ""; }; E879D9D81A12AA120035E2EB /* GroupedTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GroupedTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; E879D9DB1A12AA400035E2EB /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E879D9DC1A12AA400035E2EB /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E879D9DD1A12AA400035E2EB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; E879D9DE1A12AA400035E2EB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E879D9DF1A12AA400035E2EB /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E879D9E01A12AA400035E2EB /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; E879D9E11A12AA400035E2EB /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; E8AB71A919BA502500F3EDB4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; E8AB71AD19BA502500F3EDB4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; E8AB71C219BA502500F3EDB4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; E8AB71DC19BA503500F3EDB4 /* Simple.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Simple.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8AB720A19BA503B00F3EDB4 /* Migration.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Migration.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8AB723819BA504000F3EDB4 /* Encryption.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Encryption.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8AB726619BA504500F3EDB4 /* TableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8AB729419BA504900F3EDB4 /* REST.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = REST.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8BDBFA11A116FAC00450CFF /* Backlink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Backlink.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8BDBFC81A116FCB00450CFF /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8BDBFC91A116FCB00450CFF /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8BDBFCA1A116FCB00450CFF /* Backlink-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Backlink-Info.plist"; sourceTree = ""; }; E8BDBFCB1A116FCB00450CFF /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70E8F19BA5083006F60D5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8F70E9019BA5083006F60D5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8F70E9119BA5083006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70E9219BA5083006F60D5 /* Simple-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Simple-Info.plist"; sourceTree = ""; }; E8F70E9719BA508B006F60D5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8F70E9819BA508B006F60D5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8F70E9919BA508B006F60D5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; E8F70E9A19BA508B006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70E9B19BA508B006F60D5 /* TableView-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "TableView-Info.plist"; sourceTree = ""; }; E8F70E9C19BA508B006F60D5 /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; E8F70E9D19BA508B006F60D5 /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; E8F70EA419BA5093006F60D5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8F70EA519BA5093006F60D5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8F70EA619BA5093006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70EA719BA5093006F60D5 /* REST-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "REST-Info.plist"; sourceTree = ""; }; E8F70EA819BA5093006F60D5 /* Venue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Venue.h; sourceTree = ""; }; E8F70EA919BA5093006F60D5 /* Venue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Venue.m; sourceTree = ""; }; E8F70EAF19BA50A3006F60D5 /* Example_v1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Example_v1.h; sourceTree = ""; }; E8F70EB419BA50A3006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70EB519BA50A3006F60D5 /* Migration-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Migration-Info.plist"; sourceTree = ""; }; E8F70EB619BA50A3006F60D5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8F70EB719BA50A3006F60D5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8F70EC019BA50AB006F60D5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; E8F70EC119BA50AB006F60D5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; E8F70EC219BA50AB006F60D5 /* Encryption-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Encryption-Info.plist"; sourceTree = ""; }; E8F70EC319BA50AB006F60D5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E8F70ED019BA52E8006F60D5 /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; }; F18464511DC1551200DAB8B9 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ C0DC41C91A7072670067156A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C0DC41F41A70729D0067156A /* CoreGraphics.framework in Frameworks */, C0DC41F51A70729D0067156A /* Foundation.framework in Frameworks */, C0DC41F61A70729D0067156A /* libc++.dylib in Frameworks */, 3F08726027F3C971007A1175 /* libcompression.tbd in Frameworks */, C0DC41F71A70729D0067156A /* Realm.framework in Frameworks */, C0DC41F81A70729D0067156A /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C0DC42031A7079D00067156A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C06134E61A71AA5A00D22D12 /* CoreGraphics.framework in Frameworks */, C06134E51A71AA5600D22D12 /* Foundation.framework in Frameworks */, C0DC42191A707BF80067156A /* libc++.dylib in Frameworks */, 3F08726127F3C977007A1175 /* libcompression.tbd in Frameworks */, C0DC42081A7079D00067156A /* NotificationCenter.framework in Frameworks */, C06134E41A71AA4E00D22D12 /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E879D9CD1A12AA120035E2EB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E879D9CF1A12AA120035E2EB /* CoreGraphics.framework in Frameworks */, E879D9D11A12AA120035E2EB /* Foundation.framework in Frameworks */, E879D9CE1A12AA120035E2EB /* libc++.dylib in Frameworks */, 3F08726227F3C97D007A1175 /* libcompression.tbd in Frameworks */, E879D9D21A12AA120035E2EB /* Realm.framework in Frameworks */, E879D9D01A12AA120035E2EB /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB71D919BA503500F3EDB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8AB71DE19BA503500F3EDB4 /* CoreGraphics.framework in Frameworks */, E8AB71DD19BA503500F3EDB4 /* Foundation.framework in Frameworks */, E8F70ED119BA52E8006F60D5 /* libc++.dylib in Frameworks */, 3F08726627F3C993007A1175 /* libcompression.tbd in Frameworks */, 0295B8FF19D102880036D6C3 /* Realm.framework in Frameworks */, E8AB71DF19BA503500F3EDB4 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB720719BA503B00F3EDB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8AB720C19BA503B00F3EDB4 /* CoreGraphics.framework in Frameworks */, E8AB720B19BA503B00F3EDB4 /* Foundation.framework in Frameworks */, E8F70ED519BA530C006F60D5 /* libc++.dylib in Frameworks */, 3F08726427F3C989007A1175 /* libcompression.tbd in Frameworks */, 0295B90019D102880036D6C3 /* Realm.framework in Frameworks */, E8AB720D19BA503B00F3EDB4 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB723519BA504000F3EDB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8AB723A19BA504000F3EDB4 /* CoreGraphics.framework in Frameworks */, E8AB723919BA504000F3EDB4 /* Foundation.framework in Frameworks */, E8F70ED919BA534E006F60D5 /* libc++.dylib in Frameworks */, 3F08725F27F3C96A007A1175 /* libcompression.tbd in Frameworks */, 0295B90119D102880036D6C3 /* Realm.framework in Frameworks */, E8AB723B19BA504000F3EDB4 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB726319BA504500F3EDB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8AB726819BA504500F3EDB4 /* CoreGraphics.framework in Frameworks */, E8AB726719BA504500F3EDB4 /* Foundation.framework in Frameworks */, E8F70EDD19BA5380006F60D5 /* libc++.dylib in Frameworks */, 3F08726727F3C999007A1175 /* libcompression.tbd in Frameworks */, 0295B90219D102880036D6C3 /* Realm.framework in Frameworks */, E8AB726919BA504500F3EDB4 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB729119BA504900F3EDB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8AB729619BA504900F3EDB4 /* CoreGraphics.framework in Frameworks */, E8AB729519BA504900F3EDB4 /* Foundation.framework in Frameworks */, E8D5D6AA19BA53DF0053D333 /* libc++.dylib in Frameworks */, 3F08726527F3C98E007A1175 /* libcompression.tbd in Frameworks */, 0295B90319D102880036D6C3 /* Realm.framework in Frameworks */, E8AB729719BA504900F3EDB4 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8BDBF9E1A116FAC00450CFF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8BDBFD31A1172BC00450CFF /* CoreGraphics.framework in Frameworks */, E8BDBFD51A1172C300450CFF /* Foundation.framework in Frameworks */, E8BDBFD21A1172B600450CFF /* libc++.dylib in Frameworks */, 3F08725E27F3C95E007A1175 /* libcompression.tbd in Frameworks */, E8BDBFD11A1172B200450CFF /* Realm.framework in Frameworks */, E8BDBFD41A1172BF00450CFF /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 227D20DC1CB6009D008F641B /* Common */ = { isa = PBXGroup; children = ( 227D20DD1CB6009D008F641B /* LaunchScreen.xib */, ); path = Common; sourceTree = ""; }; 7D718BED25DAADE400A74FDD /* Examples */ = { isa = PBXGroup; children = ( 7D56426A25DD66160079F4C2 /* Example_v0.h */, E8F70EAF19BA50A3006F60D5 /* Example_v1.h */, 7D56427625DD6A7F0079F4C2 /* Example_v2.h */, 7D56427725DD6A860079F4C2 /* Example_v3.h */, 7D56427825DD6A8B0079F4C2 /* Example_v4.h */, 7D56427925DD6A920079F4C2 /* Example_v5.h */, ); path = Examples; sourceTree = ""; }; 7DD1D7D625DC2CC000E63229 /* RealmTemplates */ = { isa = PBXGroup; children = ( 7DD1D7D425DC2C7400E63229 /* default-v0.realm */, 7DD1D7D025DC2C7400E63229 /* default-v1.realm */, 7DD1D7D225DC2C7400E63229 /* default-v2.realm */, 7DD1D7D325DC2C7400E63229 /* default-v3.realm */, 7DD1D7D125DC2C7400E63229 /* default-v4.realm */, 7DD1D7D525DC2C7400E63229 /* default-v5.realm */, ); path = RealmTemplates; sourceTree = ""; }; C0DC41CD1A7072670067156A /* Extension */ = { isa = PBXGroup; children = ( C0DC41D21A7072670067156A /* AppDelegate.h */, C0DC41D31A7072670067156A /* AppDelegate.m */, C0DC421C1A717BD00067156A /* Extension.entitlements */, C0DC41FF1A70775F0067156A /* Info.plist */, C0DC41FD1A7073E40067156A /* main.m */, C0DC41FA1A7072DE0067156A /* Tick.h */, C0DC41FB1A7072DE0067156A /* Tick.m */, ); path = Extension; sourceTree = ""; }; C0DC42091A7079D00067156A /* TodayExtension */ = { isa = PBXGroup; children = ( C0DC420A1A7079D00067156A /* Supporting Files */, C0DC420F1A7079D00067156A /* MainInterface.storyboard */, C0DC421B1A717B660067156A /* TodayExtension.entitlements */, C0DC420C1A7079D00067156A /* TodayViewController.h */, C0DC420D1A7079D00067156A /* TodayViewController.m */, ); path = TodayExtension; sourceTree = ""; }; C0DC420A1A7079D00067156A /* Supporting Files */ = { isa = PBXGroup; children = ( C0DC420B1A7079D00067156A /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; E879D9DA1A12AA400035E2EB /* GroupedTableView */ = { isa = PBXGroup; children = ( E879D9DB1A12AA400035E2EB /* AppDelegate.h */, E879D9DC1A12AA400035E2EB /* AppDelegate.m */, E879D9DD1A12AA400035E2EB /* Images.xcassets */, E879D9DE1A12AA400035E2EB /* Info.plist */, E879D9DF1A12AA400035E2EB /* main.m */, E879D9E01A12AA400035E2EB /* TableViewController.h */, E879D9E11A12AA400035E2EB /* TableViewController.m */, ); path = GroupedTableView; sourceTree = ""; }; E8AB719D19BA502500F3EDB4 = { isa = PBXGroup; children = ( E8BDBFC71A116FCB00450CFF /* Backlink */, 227D20DC1CB6009D008F641B /* Common */, E8F70EBF19BA50AB006F60D5 /* Encryption */, C0DC41CD1A7072670067156A /* Extension */, E8AB71A819BA502500F3EDB4 /* Frameworks */, E879D9DA1A12AA400035E2EB /* GroupedTableView */, E8F70EAE19BA50A3006F60D5 /* Migration */, E8AB71A719BA502500F3EDB4 /* Products */, E8F70EA319BA5093006F60D5 /* REST */, E8F70E8E19BA5083006F60D5 /* Simple */, E8F70E9619BA508B006F60D5 /* TableView */, C0DC42091A7079D00067156A /* TodayExtension */, 0A73D4401B1442B200E1E8EE /* README.md */, ); sourceTree = ""; }; E8AB71A719BA502500F3EDB4 /* Products */ = { isa = PBXGroup; children = ( E8BDBFA11A116FAC00450CFF /* Backlink.app */, E8AB723819BA504000F3EDB4 /* Encryption.app */, C0DC41CC1A7072670067156A /* extension.app */, E879D9D81A12AA120035E2EB /* GroupedTableView.app */, E8AB720A19BA503B00F3EDB4 /* Migration.app */, E8AB729419BA504900F3EDB4 /* REST.app */, E8AB71DC19BA503500F3EDB4 /* Simple.app */, E8AB726619BA504500F3EDB4 /* TableView.app */, C0DC42061A7079D00067156A /* TodayExtension.appex */, ); name = Products; sourceTree = ""; }; E8AB71A819BA502500F3EDB4 /* Frameworks */ = { isa = PBXGroup; children = ( E8AB71AB19BA502500F3EDB4 /* CoreGraphics.framework */, E8AB71A919BA502500F3EDB4 /* Foundation.framework */, E8F70ED019BA52E8006F60D5 /* libc++.dylib */, F18464511DC1551200DAB8B9 /* libc++.tbd */, 3F08725D27F3C8CF007A1175 /* libcompression.tbd */, C0DC42071A7079D00067156A /* NotificationCenter.framework */, 0295B8FE19D102880036D6C3 /* Realm.framework */, E8AB71AD19BA502500F3EDB4 /* UIKit.framework */, E8AB71C219BA502500F3EDB4 /* XCTest.framework */, ); name = Frameworks; sourceTree = ""; }; E8BDBFC71A116FCB00450CFF /* Backlink */ = { isa = PBXGroup; children = ( E8BDBFC81A116FCB00450CFF /* AppDelegate.h */, E8BDBFC91A116FCB00450CFF /* AppDelegate.m */, E8BDBFCA1A116FCB00450CFF /* Backlink-Info.plist */, E8BDBFCB1A116FCB00450CFF /* main.m */, ); path = Backlink; sourceTree = ""; }; E8F70E8E19BA5083006F60D5 /* Simple */ = { isa = PBXGroup; children = ( E8F70E8F19BA5083006F60D5 /* AppDelegate.h */, E8F70E9019BA5083006F60D5 /* AppDelegate.m */, E8F70E9119BA5083006F60D5 /* main.m */, E8F70E9219BA5083006F60D5 /* Simple-Info.plist */, ); path = Simple; sourceTree = ""; }; E8F70E9619BA508B006F60D5 /* TableView */ = { isa = PBXGroup; children = ( E8F70E9719BA508B006F60D5 /* AppDelegate.h */, E8F70E9819BA508B006F60D5 /* AppDelegate.m */, E8F70E9919BA508B006F60D5 /* Images.xcassets */, E8F70E9A19BA508B006F60D5 /* main.m */, E8F70E9B19BA508B006F60D5 /* TableView-Info.plist */, E8F70E9C19BA508B006F60D5 /* TableViewController.h */, E8F70E9D19BA508B006F60D5 /* TableViewController.m */, ); path = TableView; sourceTree = SOURCE_ROOT; }; E8F70EA319BA5093006F60D5 /* REST */ = { isa = PBXGroup; children = ( E8F70EA419BA5093006F60D5 /* AppDelegate.h */, E8F70EA519BA5093006F60D5 /* AppDelegate.m */, E8F70EA619BA5093006F60D5 /* main.m */, E8F70EA719BA5093006F60D5 /* REST-Info.plist */, E8F70EA819BA5093006F60D5 /* Venue.h */, E8F70EA919BA5093006F60D5 /* Venue.m */, ); path = REST; sourceTree = ""; }; E8F70EAE19BA50A3006F60D5 /* Migration */ = { isa = PBXGroup; children = ( 7D718BED25DAADE400A74FDD /* Examples */, 7DD1D7D625DC2CC000E63229 /* RealmTemplates */, E8F70EB619BA50A3006F60D5 /* AppDelegate.h */, E8F70EB719BA50A3006F60D5 /* AppDelegate.m */, E8F70EB419BA50A3006F60D5 /* main.m */, E8F70EB519BA50A3006F60D5 /* Migration-Info.plist */, 7D718BC825DAADB500A74FDD /* README.md */, ); path = Migration; sourceTree = ""; }; E8F70EBF19BA50AB006F60D5 /* Encryption */ = { isa = PBXGroup; children = ( E8F70EC019BA50AB006F60D5 /* AppDelegate.h */, E8F70EC119BA50AB006F60D5 /* AppDelegate.m */, E8F70EC219BA50AB006F60D5 /* Encryption-Info.plist */, 3FC898FB1A140F550067CBEC /* LabelViewController.h */, 3FC898FC1A140F550067CBEC /* LabelViewController.m */, E8F70EC319BA50AB006F60D5 /* main.m */, ); path = Encryption; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ C0DC41CB1A7072670067156A /* extension */ = { isa = PBXNativeTarget; buildConfigurationList = C0DC41EC1A7072680067156A /* Build configuration list for PBXNativeTarget "extension" */; buildPhases = ( C0DC41C81A7072670067156A /* Sources */, C0DC41C91A7072670067156A /* Frameworks */, 227D20831CB4E4B9008F641B /* Resources */, 3F336E871DA2F8A4006CB5A0 /* Embed Watch Content */, C06134E21A718BB800D22D12 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( C06134E11A718BB800D22D12 /* PBXTargetDependency */, ); name = extension; productName = Extension; productReference = C0DC41CC1A7072670067156A /* extension.app */; productType = "com.apple.product-type.application"; }; C0DC42051A7079D00067156A /* TodayExtension */ = { isa = PBXNativeTarget; buildConfigurationList = C0DC42141A7079D00067156A /* Build configuration list for PBXNativeTarget "TodayExtension" */; buildPhases = ( C0DC42021A7079D00067156A /* Sources */, C0DC42031A7079D00067156A /* Frameworks */, C0DC42041A7079D00067156A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = TodayExtension; productName = TodayExtension; productReference = C0DC42061A7079D00067156A /* TodayExtension.appex */; productType = "com.apple.product-type.app-extension"; }; E879D9C61A12AA120035E2EB /* GroupedTableView */ = { isa = PBXNativeTarget; buildConfigurationList = E879D9D51A12AA120035E2EB /* Build configuration list for PBXNativeTarget "GroupedTableView" */; buildPhases = ( E879D9C91A12AA120035E2EB /* Sources */, E879D9CD1A12AA120035E2EB /* Frameworks */, E879D9D31A12AA120035E2EB /* Resources */, ); buildRules = ( ); dependencies = ( ); name = GroupedTableView; productName = TableView; productReference = E879D9D81A12AA120035E2EB /* GroupedTableView.app */; productType = "com.apple.product-type.application"; }; E8AB71DB19BA503500F3EDB4 /* Simple */ = { isa = PBXNativeTarget; buildConfigurationList = E8AB720019BA503500F3EDB4 /* Build configuration list for PBXNativeTarget "Simple" */; buildPhases = ( E8AB71D819BA503500F3EDB4 /* Sources */, E8AB71D919BA503500F3EDB4 /* Frameworks */, 227D20A51CB50248008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Simple; productName = Simple; productReference = E8AB71DC19BA503500F3EDB4 /* Simple.app */; productType = "com.apple.product-type.application"; }; E8AB720919BA503B00F3EDB4 /* Migration */ = { isa = PBXNativeTarget; buildConfigurationList = E8AB722E19BA503B00F3EDB4 /* Build configuration list for PBXNativeTarget "Migration" */; buildPhases = ( E8AB720619BA503B00F3EDB4 /* Sources */, E8AB720719BA503B00F3EDB4 /* Frameworks */, E8AB720819BA503B00F3EDB4 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Migration; productName = Migration; productReference = E8AB720A19BA503B00F3EDB4 /* Migration.app */; productType = "com.apple.product-type.application"; }; E8AB723719BA504000F3EDB4 /* Encryption */ = { isa = PBXNativeTarget; buildConfigurationList = E8AB725C19BA504000F3EDB4 /* Build configuration list for PBXNativeTarget "Encryption" */; buildPhases = ( E8AB723419BA504000F3EDB4 /* Sources */, E8AB723519BA504000F3EDB4 /* Frameworks */, 227D20801CB4E36D008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Encryption; productName = Encryption; productReference = E8AB723819BA504000F3EDB4 /* Encryption.app */; productType = "com.apple.product-type.application"; }; E8AB726519BA504500F3EDB4 /* TableView */ = { isa = PBXNativeTarget; buildConfigurationList = E8AB728A19BA504500F3EDB4 /* Build configuration list for PBXNativeTarget "TableView" */; buildPhases = ( E8AB726219BA504500F3EDB4 /* Sources */, E8AB726319BA504500F3EDB4 /* Frameworks */, E8AB726419BA504500F3EDB4 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = TableView; productName = TableView; productReference = E8AB726619BA504500F3EDB4 /* TableView.app */; productType = "com.apple.product-type.application"; }; E8AB729319BA504900F3EDB4 /* REST */ = { isa = PBXNativeTarget; buildConfigurationList = E8AB72B819BA504900F3EDB4 /* Build configuration list for PBXNativeTarget "REST" */; buildPhases = ( E8AB729019BA504900F3EDB4 /* Sources */, E8AB729119BA504900F3EDB4 /* Frameworks */, 227D208C1CB4E6C5008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = REST; productName = REST; productReference = E8AB729419BA504900F3EDB4 /* REST.app */; productType = "com.apple.product-type.application"; }; E8BDBFA01A116FAC00450CFF /* Backlink */ = { isa = PBXNativeTarget; buildConfigurationList = E8BDBFC51A116FAC00450CFF /* Build configuration list for PBXNativeTarget "Backlink" */; buildPhases = ( E8BDBF9D1A116FAC00450CFF /* Sources */, E8BDBF9E1A116FAC00450CFF /* Frameworks */, 227D20A21CB501A1008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Backlink; productName = Backlink; productReference = E8BDBFA11A116FAC00450CFF /* Backlink.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E8AB719E19BA502500F3EDB4 /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = RLM; LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { C0DC41CB1A7072670067156A = { CreatedOnToolsVersion = 6.2; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; }; }; }; C0DC42051A7079D00067156A = { CreatedOnToolsVersion = 6.2; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; }; }; }; E8BDBFA01A116FAC00450CFF = { CreatedOnToolsVersion = 6.1; }; }; }; buildConfigurationList = E8AB71A119BA502500F3EDB4 /* Build configuration list for PBXProject "RealmExamples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = E8AB719D19BA502500F3EDB4; productRefGroup = E8AB71A719BA502500F3EDB4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( E8BDBFA01A116FAC00450CFF /* Backlink */, E8AB723719BA504000F3EDB4 /* Encryption */, C0DC41CB1A7072670067156A /* extension */, C0DC42051A7079D00067156A /* TodayExtension */, E879D9C61A12AA120035E2EB /* GroupedTableView */, E8AB720919BA503B00F3EDB4 /* Migration */, E8AB729319BA504900F3EDB4 /* REST */, E8AB71DB19BA503500F3EDB4 /* Simple */, E8AB726519BA504500F3EDB4 /* TableView */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 227D20801CB4E36D008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20DF1CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D20831CB4E4B9008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20E01CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D208C1CB4E6C5008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20E41CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D20A21CB501A1008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20DE1CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D20A51CB50248008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20E51CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; C0DC42041A7079D00067156A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( C0DC42101A7079D00067156A /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; E879D9D31A12AA120035E2EB /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E879D9E31A12AA400035E2EB /* Images.xcassets in Resources */, 227D20E11CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB720819BA503B00F3EDB4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7DD1D7E425DC2D3D00E63229 /* default-v0.realm in Resources */, 7DD1D7E725DC2D3D00E63229 /* default-v1.realm in Resources */, 7DD1D7E225DC2D3D00E63229 /* default-v2.realm in Resources */, 7DD1D7E325DC2D3D00E63229 /* default-v3.realm in Resources */, 7DD1D7E625DC2D3D00E63229 /* default-v4.realm in Resources */, 7DD1D7E525DC2D3D00E63229 /* default-v5.realm in Resources */, 227D20E31CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB726419BA504500F3EDB4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70E9F19BA508B006F60D5 /* Images.xcassets in Resources */, 227D20E61CB6009D008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ C0DC41C81A7072670067156A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C0DC41D41A7072680067156A /* AppDelegate.m in Sources */, C0DC42011A7078130067156A /* main.m in Sources */, C0DC41FC1A7072DE0067156A /* Tick.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C0DC42021A7079D00067156A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C0DC42181A707B960067156A /* Tick.m in Sources */, C0DC420E1A7079D00067156A /* TodayViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E879D9C91A12AA120035E2EB /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E879D9E21A12AA400035E2EB /* AppDelegate.m in Sources */, E879D9E51A12AA400035E2EB /* main.m in Sources */, E879D9E61A12AA400035E2EB /* TableViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB71D819BA503500F3EDB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70E9319BA5083006F60D5 /* AppDelegate.m in Sources */, E8F70E9419BA5083006F60D5 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB720619BA503B00F3EDB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70EBE19BA50A3006F60D5 /* AppDelegate.m in Sources */, E8F70EBC19BA50A3006F60D5 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB723419BA504000F3EDB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70EC419BA50AB006F60D5 /* AppDelegate.m in Sources */, 3FC898FD1A140F550067CBEC /* LabelViewController.m in Sources */, E8F70EC619BA50AB006F60D5 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB726219BA504500F3EDB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70E9E19BA508B006F60D5 /* AppDelegate.m in Sources */, E8F70EA019BA508B006F60D5 /* main.m in Sources */, E8F70EA219BA508B006F60D5 /* TableViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8AB729019BA504900F3EDB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F70EAA19BA5093006F60D5 /* AppDelegate.m in Sources */, E8F70EAB19BA5093006F60D5 /* main.m in Sources */, E8F70EAD19BA5093006F60D5 /* Venue.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8BDBF9D1A116FAC00450CFF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8BDBFCC1A116FCB00450CFF /* AppDelegate.m in Sources */, E8BDBFCE1A116FCB00450CFF /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ C06134E11A718BB800D22D12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C0DC42051A7079D00067156A /* TodayExtension */; targetProxy = C06134E01A718BB800D22D12 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ AC1CA6392B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; CONFIGURATION_TEMP_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; OTHER_LDFLAGS = "-lz"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Static; }; AC1CA63A2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = "$(SRCROOT)/Backlink/Backlink-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Static; }; AC1CA63C2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Encryption/Encryption-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Static; }; AC1CA63D2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = Extension/Extension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = Extension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Static; }; AC1CA63E2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = TodayExtension/TodayExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.extension.$(PRODUCT_NAME:rfc1034identifier)-2"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SKIP_INSTALL = YES; }; name = Static; }; AC1CA63F2B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "$(SRCROOT)/GroupedTableView/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; OTHER_LDFLAGS = ( "-lz", "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = GroupedTableView; WRAPPER_EXTENSION = app; }; name = Static; }; AC1CA6412B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Migration/Migration-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Static; }; AC1CA6422B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "REST/REST-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Static; }; AC1CA6432B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Simple/Simple-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Static; }; AC1CA6442B1FF0BE002167B0 /* Static */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "TableView/TableView-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Static; }; C0DC41ED1A7072680067156A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = Extension/Extension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Extension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Debug; }; C0DC41EE1A7072680067156A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = Extension/Extension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = Extension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Release; }; C0DC42151A7079D00067156A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = TodayExtension/TodayExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.extension.$(PRODUCT_NAME:rfc1034identifier)-2"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SKIP_INSTALL = YES; }; name = Debug; }; C0DC42161A7079D00067156A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_ENTITLEMENTS = TodayExtension/TodayExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = TodayExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.examples.extension.$(PRODUCT_NAME:rfc1034identifier)-2"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SKIP_INSTALL = YES; }; name = Release; }; E879D9D61A12AA120035E2EB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/GroupedTableView/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; OTHER_LDFLAGS = ( "-lz", "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = GroupedTableView; WRAPPER_EXTENSION = app; }; name = Debug; }; E879D9D71A12AA120035E2EB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "$(SRCROOT)/GroupedTableView/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; OTHER_LDFLAGS = ( "-lz", "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = GroupedTableView; WRAPPER_EXTENSION = app; }; name = Release; }; E8AB71D019BA502500F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; CONFIGURATION_TEMP_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lz"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; E8AB71D119BA502500F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; CONFIGURATION_TEMP_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; OTHER_LDFLAGS = "-lz"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; E8AB720119BA503500F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "Simple/Simple-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; E8AB720219BA503500F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Simple/Simple-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; E8AB722F19BA503B00F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "Migration/Migration-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; E8AB723019BA503B00F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Migration/Migration-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; E8AB725D19BA504000F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "Encryption/Encryption-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; E8AB725E19BA504000F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "Encryption/Encryption-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; E8AB728B19BA504500F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "TableView/TableView-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; E8AB728C19BA504500F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "TableView/TableView-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; E8AB72B919BA504900F3EDB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "REST/REST-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; E8AB72BA19BA504900F3EDB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; INFOPLIST_FILE = "REST/REST-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; E8BDBFC11A116FAC00450CFF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/Backlink/Backlink-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8BDBFC21A116FAC00450CFF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNREACHABLE_CODE = YES; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; INFOPLIST_FILE = "$(SRCROOT)/Backlink/Backlink-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C0DC41EC1A7072680067156A /* Build configuration list for PBXNativeTarget "extension" */ = { isa = XCConfigurationList; buildConfigurations = ( C0DC41ED1A7072680067156A /* Debug */, C0DC41EE1A7072680067156A /* Release */, AC1CA63D2B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0DC42141A7079D00067156A /* Build configuration list for PBXNativeTarget "TodayExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( C0DC42151A7079D00067156A /* Debug */, C0DC42161A7079D00067156A /* Release */, AC1CA63E2B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E879D9D51A12AA120035E2EB /* Build configuration list for PBXNativeTarget "GroupedTableView" */ = { isa = XCConfigurationList; buildConfigurations = ( E879D9D61A12AA120035E2EB /* Debug */, E879D9D71A12AA120035E2EB /* Release */, AC1CA63F2B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB71A119BA502500F3EDB4 /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB71D019BA502500F3EDB4 /* Debug */, E8AB71D119BA502500F3EDB4 /* Release */, AC1CA6392B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB720019BA503500F3EDB4 /* Build configuration list for PBXNativeTarget "Simple" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB720119BA503500F3EDB4 /* Debug */, E8AB720219BA503500F3EDB4 /* Release */, AC1CA6432B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB722E19BA503B00F3EDB4 /* Build configuration list for PBXNativeTarget "Migration" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB722F19BA503B00F3EDB4 /* Debug */, E8AB723019BA503B00F3EDB4 /* Release */, AC1CA6412B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB725C19BA504000F3EDB4 /* Build configuration list for PBXNativeTarget "Encryption" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB725D19BA504000F3EDB4 /* Debug */, E8AB725E19BA504000F3EDB4 /* Release */, AC1CA63C2B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB728A19BA504500F3EDB4 /* Build configuration list for PBXNativeTarget "TableView" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB728B19BA504500F3EDB4 /* Debug */, E8AB728C19BA504500F3EDB4 /* Release */, AC1CA6442B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8AB72B819BA504900F3EDB4 /* Build configuration list for PBXNativeTarget "REST" */ = { isa = XCConfigurationList; buildConfigurations = ( E8AB72B919BA504900F3EDB4 /* Debug */, E8AB72BA19BA504900F3EDB4 /* Release */, AC1CA6422B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8BDBFC51A116FAC00450CFF /* Build configuration list for PBXNativeTarget "Backlink" */ = { isa = XCConfigurationList; buildConfigurations = ( E8BDBFC11A116FAC00450CFF /* Debug */, E8BDBFC21A116FAC00450CFF /* Release */, AC1CA63A2B1FF0BE002167B0 /* Static */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = E8AB719E19BA502500F3EDB4 /* Project object */; } ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Backlink.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Encryption.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Extension.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/GroupedTableView.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Migration.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/REST.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/Simple.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/TableView.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/TodayExtension.xcscheme ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/ios/objc/RealmExamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: examples/ios/objc/RealmExamples.xcworkspace/xcshareddata/RealmExamples.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : 9223372036854775807, "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "2AC1F783-2EB4-4770-9637-97862959857C", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : "realm-cocoa\/", "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-cocoa\/Realm\/ObjectStore\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "RealmExamples", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "examples\/ios\/objc\/RealmExamples.xcworkspace", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/realm\/realm-object-store.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/www.github.com\/realm\/realm-cocoa", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" } ] } ================================================ FILE: examples/ios/objc/RealmExamples.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: examples/ios/objc/Simple/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/Simple/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" #import // Define your models @interface Dog : RLMObject @property NSString *name; @property NSInteger age; @end @implementation Dog // No need for implementation @end RLM_COLLECTION_TYPE(Dog) @interface Person : RLMObject @property NSString *name; @property RLMArray *dogs; @end @implementation Person @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UIViewController alloc] init]; [self.window makeKeyAndVisible]; [[NSFileManager defaultManager] removeItemAtURL:[RLMRealmConfiguration defaultConfiguration].fileURL error:nil]; // Create a standalone object Dog *mydog = [[Dog alloc] init]; // Set & read properties mydog.name = @"Rex"; mydog.age = 9; NSLog(@"Name of dog: %@", mydog.name); // Realms are used to group data together RLMRealm *realm = [RLMRealm defaultRealm]; // Create realm pointing to default file // Save your object [realm beginWriteTransaction]; [realm addObject:mydog]; [realm commitWriteTransaction]; // Query RLMResults *results = [Dog objectsInRealm:realm where:@"name contains 'x'"]; // Queries are chainable! RLMResults *results2 = [results objectsWhere:@"age > 8"]; NSLog(@"Number of dogs: %li", (unsigned long)results2.count); // Link objects Person *person = [[Person alloc] init]; person.name = @"Tim"; [person.dogs addObject:mydog]; [realm beginWriteTransaction]; [realm addObject:person]; [realm commitWriteTransaction]; // Multi-threading dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @autoreleasepool { RLMRealm *otherRealm = [RLMRealm defaultRealm]; RLMResults *otherResults = [Dog objectsInRealm:otherRealm where:@"name contains 'Rex'"]; NSLog(@"Number of dogs: %li", (unsigned long)otherResults.count); } }); return YES; } @end ================================================ FILE: examples/ios/objc/Simple/Simple-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/Simple/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/TableView/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/ios/objc/TableView/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" #import "TableViewController.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: [[TableViewController alloc] initWithStyle:UITableViewStylePlain]]; [self.window makeKeyAndVisible]; return YES; } @end ================================================ FILE: examples/ios/objc/TableView/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/objc/TableView/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/objc/TableView/TableView-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/objc/TableView/TableViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface TableViewController : UITableViewController @end ================================================ FILE: examples/ios/objc/TableView/TableViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "TableViewController.h" #import // Realm model object @interface DemoObject : RLMObject @property NSString *title; @property NSDate *date; @end @implementation DemoObject // None needed @end static NSString * const kCellID = @"cell"; static NSString * const kTableName = @"table"; @interface TableViewController () @property (nonatomic, strong) RLMResults *array; @property (nonatomic, strong) RLMNotificationToken *notification; @end @implementation TableViewController #pragma mark - View Lifecycle - (void)viewDidLoad { [super viewDidLoad]; self.array = [[DemoObject allObjects] sortedResultsUsingKeyPath:@"date" ascending:YES]; [self setupUI]; // Set realm notification block __weak typeof(self) weakSelf = self; self.notification = [self.array addNotificationBlock:^(RLMResults *data, RLMCollectionChange *changes, NSError *error) { if (error) { NSLog(@"Failed to open Realm on background worker: %@", error); return; } UITableView *tv = weakSelf.tableView; // Initial run of the query will pass nil for the change information if (!changes) { [tv reloadData]; return; } // changes is non-nil, so we just need to update the tableview [tv beginUpdates]; [tv deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv endUpdates]; }]; } #pragma mark - UI - (void)setupUI { self.title = @"TableViewExample"; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"BG Add" style:UIBarButtonItemStylePlain target:self action:@selector(backgroundAdd)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)]; } #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.array.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kCellID]; } DemoObject *object = self.array[indexPath.row]; cell.textLabel.text = object.title; cell.detailTextLabel.text = object.date.description; return cell; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [realm deleteObject:self.array[indexPath.row]]; [realm commitWriteTransaction]; } } #pragma mark - Actions - (void)backgroundAdd { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Import many items in a background thread dispatch_async(queue, ^{ // Get new realm and table since we are in a new thread @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; for (NSInteger index = 0; index < 5; index++) { // Add row via dictionary. Order is ignored. [DemoObject createInRealm:realm withValue:@{@"title": [self randomString], @"date": [self randomDate]}]; } [realm commitWriteTransaction]; } }); } - (void)add { RLMRealm *realm = RLMRealm.defaultRealm; [realm beginWriteTransaction]; [DemoObject createInRealm:realm withValue:@[[self randomString], [self randomDate]]]; [realm commitWriteTransaction]; } #pragma - Helpers - (NSString *)randomString { return [NSString stringWithFormat:@"Title %d", arc4random()]; } - (NSDate *)randomDate { return [NSDate dateWithTimeIntervalSince1970:arc4random()]; } @end ================================================ FILE: examples/ios/objc/TableView/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/ios/objc/TodayExtension/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName TodayExtension CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType XPC! CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 NSExtension NSExtensionMainStoryboard MainInterface NSExtensionPointIdentifier com.apple.widget-extension ================================================ FILE: examples/ios/objc/TodayExtension/MainInterface.storyboard ================================================ ================================================ FILE: examples/ios/objc/TodayExtension/TodayExtension.entitlements ================================================ com.apple.security.application-groups group.io.realm.examples.extension ================================================ FILE: examples/ios/objc/TodayExtension/TodayViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 @interface TodayViewController : UIViewController @end ================================================ FILE: examples/ios/objc/TodayExtension/TodayViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 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 "TodayViewController.h" #import #import "Tick.h" @interface TodayViewController () @property (nonatomic, strong) UIButton *button; @property (nonatomic, strong) Tick *tick; @property (nonatomic, strong) RLMNotificationToken *notificationToken; @end @implementation TodayViewController - (void)viewDidLoad { [super viewDidLoad]; self.preferredContentSize = CGSizeMake(0, 200.0); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.io.realm.examples.extension"] URLByAppendingPathComponent:@"extension.realm"]; [RLMRealmConfiguration setDefaultConfiguration:configuration]; self.tick = [Tick allObjects].firstObject; if (!self.tick) { [[RLMRealm defaultRealm] transactionWithBlock:^{ self.tick = [Tick createInDefaultRealmWithValue:@[@"", @0]]; }]; } self.notificationToken = [self.tick.realm addNotificationBlock:^(NSString *notification, RLMRealm *realm) { // Occasionally, respond immediately to the notification by triggering a new notification. if (self.tick.count % 19 == 0) { [self tock]; } [self updateLabel]; }]; self.button = [UIButton buttonWithType:UIButtonTypeSystem]; self.button.frame = self.view.bounds; [self.button addTarget:self action:@selector(tock) forControlEvents:UIControlEventTouchUpInside]; self.button.titleLabel.textAlignment = NSTextAlignmentCenter; [self.button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.view addSubview:self.button]; [self updateLabel]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.button.frame = self.view.bounds; [self updateLabel]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self tock]; [self updateLabel]; } - (void)updateLabel { [self.button setTitle:@(self.tick.count).stringValue forState:UIControlStateNormal]; } - (void)tock { [[RLMRealm defaultRealm] transactionWithBlock:^{ self.tick.count++; }]; } @end ================================================ FILE: examples/ios/swift/.gitignore ================================================ Pods/ ================================================ FILE: examples/ios/swift/AppClip/AppClip.entitlements ================================================ com.apple.developer.associated-domains webcredentials:example.com appclips:example.com com.apple.developer.parent-application-identifiers $(AppIdentifierPrefix)io.realm.AppClip.AppClipParent com.apple.security.application-groups group.TEAM_ID.com.domain.APP_GROUP ================================================ FILE: examples/ios/swift/AppClip/AppClipApp.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 SwiftUI import RealmSwift @main struct AppClipApp: SwiftUI.App { var body: some Scene { WindowGroup { // This is ContentView.swift shared from AppClipParent ContentView(objects: demoObjects().list) } } private func demoObjects() -> DemoObjects { let config = Realm.Configuration(fileURL: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupId)!.appendingPathComponent("default.realm")) let realm = try! Realm(configuration: config) if let demoObjects = realm.object(ofType: DemoObjects.self, forPrimaryKey: 0) { return demoObjects } else { return try! realm.write { realm.create(DemoObjects.self, value: []) } } } } ================================================ FILE: examples/ios/swift/AppClip/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClip/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClip/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClip/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName AppClipParent CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UIApplicationSupportsIndirectInputEvents UILaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/swift/AppClip/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClipParent/AppClipParent.entitlements ================================================ com.apple.developer.associated-domains appclips:example.com webcredentials:example.com com.apple.security.application-groups group.TEAM_ID.com.domain.APP_GROUP ================================================ FILE: examples/ios/swift/AppClipParent/AppClipParentApp.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 SwiftUI import RealmSwift @main struct AppClipParentApp: SwiftUI.App { var body: some Scene { WindowGroup { ContentView(objects: demoObjects().list) } } private func demoObjects() -> DemoObjects { let config = Realm.Configuration(fileURL: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupId)!.appendingPathComponent("default.realm")) let realm = try! Realm(configuration: config) if let demoObjects = realm.object(ofType: DemoObjects.self, forPrimaryKey: 0) { return demoObjects } else { return try! realm.write { realm.create(DemoObjects.self, value: []) } } } } ================================================ FILE: examples/ios/swift/AppClipParent/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClipParent/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClipParent/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/AppClipParent/Constants.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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. // //////////////////////////////////////////////////////////////////////////// struct Constants { static let groupId = "group.TEAM_ID.com.domain.APP_GROUP" } ================================================ FILE: examples/ios/swift/AppClipParent/ContentView.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 SwiftUI import RealmSwift struct ContentView: View { @ObservedObject var objects: RealmSwift.List var body: some View { Section(header: Button("Add Object", action: addObject)) { List { ForEach(objects, id: \.uuid) { object in ContentViewRow(object: object) } } } } private func addObject() { /* The app clip and parent application share data by accessing a common realm file path within an App Group. */ let config = Realm.Configuration(fileURL: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupId)!.appendingPathComponent("default.realm")) let realm = try! Realm(configuration: config) try! realm.write { objects.append(DemoObject()) } } } struct ContentViewRow: View { var object: DemoObject var body: some View { VStack { Text(verbatim: object.uuid.uuidString).fixedSize() Text(object.date.description).font(.footnote).frame(alignment: .leading) } } } ================================================ FILE: examples/ios/swift/AppClipParent/DemoObject.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2020 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 Foundation import RealmSwift final class DemoObject: Object { @Persisted var uuid: UUID @Persisted var date: Date @Persisted var title: String } /* For a more detailed example of SwiftUI List updating, see the ListSwiftUI example target. */ final class DemoObjects: Object { @Persisted(primaryKey: true) var id: Int @Persisted var list: List } ================================================ FILE: examples/ios/swift/AppClipParent/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UIApplicationSupportsIndirectInputEvents UILaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/swift/AppClipParent/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/Backlink/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class Dog: Object { @Persisted var name: String @Persisted var age: Int // Define "owners" as the inverse relationship to Person.dogs @Persisted(originProperty: "dogs") var owners: LinkingObjects } class Person: Object { @Persisted var name: String @Persisted var dogs: List } @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UIViewController() window?.makeKeyAndVisible() _ = try! Realm.deleteFiles(for: Realm.Configuration.defaultConfiguration) let realm = try! Realm() try! realm.write { realm.create(Person.self, value: ["John", [["Fido", 1]]]) realm.create(Person.self, value: ["Mary", [["Rex", 2]]]) } // Log all dogs and their owners using the "owners" inverse relationship let allDogs = realm.objects(Dog.self) for dog in allDogs { let ownerNames = Array(dog.owners.map(\.name)) print("\(dog.name) has \(ownerNames.count) owners (\(ownerNames))") } return true } } ================================================ FILE: examples/ios/swift/Backlink/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/Common/LaunchScreen.xib ================================================ ================================================ FILE: examples/ios/swift/Encryption/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = ViewController() window?.makeKeyAndVisible() return true } } ================================================ FILE: examples/ios/swift/Encryption/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/Encryption/ViewController.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 Foundation import RealmSwift import Security import UIKit // Model definition class EncryptionObject: Object { @Persisted var stringProp: String } class ViewController: UIViewController { let textView = UITextView(frame: UIScreen.main.applicationFrame) // Create a view to display output in override func loadView() { super.loadView() view.addSubview(textView) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Use an autorelease pool to close the Realm at the end of the block, so // that we can try to reopen it with different keys autoreleasepool { let configuration = Realm.Configuration(encryptionKey: getKey() as Data) let realm = try! Realm(configuration: configuration) // Add an object try! realm.write { let obj = EncryptionObject() obj.stringProp = "abcd" realm.add(obj) } } // Opening with wrong key fails since it decrypts to the wrong thing autoreleasepool { do { let configuration = Realm.Configuration(encryptionKey: "1234567890123456789012345678901234567890123456789012345678901234".data(using: .utf8, allowLossyConversion: false)) _ = try Realm(configuration: configuration) } catch { log(text: "Open with wrong key: \(error)") } } // Opening without supplying a key at all fails autoreleasepool { do { _ = try Realm() } catch { log(text: "Open with no key: \(error)") } } // Reopening with the correct key works and can read the data autoreleasepool { let configuration = Realm.Configuration(encryptionKey: getKey() as Data) let realm = try! Realm(configuration: configuration) if let stringProp = realm.objects(EncryptionObject.self).first?.stringProp { log(text: "Saved object: \(stringProp)") } } } func log(text: String) { textView.text += "\(text)\n\n" } func getKey() -> NSData { // Identifier for our keychain entry - should be unique for your application let keychainIdentifier = "io.Realm.EncryptionExampleKey" let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)! // First check in the keychain for an existing key var query: [NSString: AnyObject] = [ kSecClass: kSecClassKey, kSecAttrApplicationTag: keychainIdentifierData as AnyObject, kSecAttrKeySizeInBits: 512 as AnyObject, kSecReturnData: true as AnyObject ] // To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item // See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328 var dataTypeRef: AnyObject? var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } if status == errSecSuccess { return dataTypeRef as! NSData } // No pre-existing key from this application, so generate a new one let keyData = NSMutableData(length: 64)! let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64)) assert(result == 0, "Failed to get random bytes") // Store the key in the keychain query = [ kSecClass: kSecClassKey, kSecAttrApplicationTag: keychainIdentifierData as AnyObject, kSecAttrKeySizeInBits: 512 as AnyObject, kSecValueData: keyData ] status = SecItemAdd(query as CFDictionary, nil) assert(status == errSecSuccess, "Failed to insert the new key in the keychain") return keyData } } ================================================ FILE: examples/ios/swift/GettingStarted.playground/Contents.swift ================================================ //: To get this Playground running do the following: //: //: 1) In the scheme selector choose RealmSwift > iPhone 6s //: 2) Press Cmd + B //: 3) If the Playground didn't already run press the ▶︎ button at the bottom import Foundation import RealmSwift //: I. Define the data entities class Person: Object { @Persisted var name: String @Persisted var age: Int @Persisted var spouse: Person? @Persisted var cars: List override var description: String { return "Person {\(name), \(age), \(spouse?.name ?? "nil")}" } } class Car: Object { @Persisted var brand: String @Persisted var name: String? @Persisted var year: Int override var description: String { return "Car {\(brand), \(name), \(year)}" } } //: II. Init the realm file let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm")) //: III. Create the objects let car1 = Car(value: ["brand": "BMW", "year": 1980]) let car2 = Car() car2.brand = "DeLorean" car2.name = "Outatime" car2.year = 1981 // people let wife = Person() wife.name = "Jennifer" wife.cars.append(objectsIn: [car1, car2]) wife.age = 47 let husband = Person(value: [ "name": "Marty", "age": 47, "spouse": wife ]) wife.spouse = husband //: IV. Write objects to the realm try! realm.write { realm.add(husband) } //: V. Read objects back from the realm let favorites = ["Jennifer"] let favoritePeopleWithSpousesAndCars = realm.objects(Person.self) .filter("cars.@count > 1 && spouse != nil && name IN %@", favorites) .sorted(byKeyPath: "age") for person in favoritePeopleWithSpousesAndCars { person.name person.age guard let car = person.cars.first else { continue } car.name car.brand //: VI. Update objects try! realm.write { car.year += 1 } car.year } //: VII. Delete objects try! realm.write { realm.deleteAll() } realm.objects(Person.self).count //: Thanks! To learn more about Realm go to https://www.mongodb.com/docs/realm/ ================================================ FILE: examples/ios/swift/GettingStarted.playground/contents.xcplayground ================================================ ================================================ FILE: examples/ios/swift/GroupedTableView/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UINavigationController(rootViewController: TableViewController(style: .plain)) window?.makeKeyAndVisible() return true } } ================================================ FILE: examples/ios/swift/GroupedTableView/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/GroupedTableView/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/GroupedTableView/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/GroupedTableView/TableViewController.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class DemoObject: Object { @Persisted var phoneNumber: String @Persisted var date: Date @Persisted var contactName: String var firstLetter: String { guard let char = contactName.first else { return "" } return String(char) } } class Cell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) } required init(coder: NSCoder) { fatalError("NSCoding not supported") } } class TableViewController: UITableViewController { var notificationToken: NotificationToken? var realm: Realm! var sectionedResults: SectionedResults! override func viewDidLoad() { super.viewDidLoad() setupUI() realm = try! Realm() sectionedResults = realm.objects(DemoObject.self) .sectioned(by: \.firstLetter, ascending: true) // Set realm notification block notificationToken = sectionedResults.observe { change in switch change { case .initial: break case let .update(_, deletions: deletions, insertions: insertions, modifications: modifications, sectionsToInsert: sectionsToInsert, sectionsToDelete: sectionsToDelete): self.tableView.performBatchUpdates { self.tableView.deleteRows(at: deletions, with: .automatic) self.tableView.insertRows(at: insertions, with: .automatic) self.tableView.reloadRows(at: modifications, with: .automatic) self.tableView.insertSections(sectionsToInsert, with: .automatic) self.tableView.deleteSections(sectionsToDelete, with: .automatic) } } } tableView.reloadData() } // UI func setupUI() { tableView.register(Cell.self, forCellReuseIdentifier: "cell") self.title = "GroupedTableView" self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "BG Add", style: .plain, target: self, action: #selector(TableViewController.backgroundAdd)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(TableViewController.add)) } // Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return sectionedResults.count } override func sectionIndexTitles(for tableView: UITableView) -> [String]? { return sectionedResults.allKeys } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionedResults[section].key } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sectionedResults[section].count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell let object = sectionedResults[indexPath] cell.textLabel?.text = "\(object.contactName): \(object.phoneNumber)" cell.detailTextLabel?.text = object.date.description return cell } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { try! realm.write { realm.delete(sectionedResults[indexPath]) } } } // MARK: Actions @objc func backgroundAdd() { // Import many items in a background thread DispatchQueue.global().async { // Get new realm and table since we are in a new thread autoreleasepool { let realm = try! Realm() realm.beginWrite() for _ in 0..<5 { // Add row via dictionary. Order is ignored. realm.create(DemoObject.self, value: ["contactName": randomName(), "date": NSDate(), "phoneNumber": randomPhoneNumber()]) } try! realm.commitWrite() } } } @objc func add() { try! realm.write { realm.create(DemoObject.self, value: ["contactName": randomName(), "date": NSDate(), "phoneNumber": randomPhoneNumber()]) } } } // MARK: Helpers func randomPhoneNumber() -> String { return "555-55\(Int.random(in: 0...9))5-55\(Int.random(in: 0...9))" } func randomName() -> String { return ["John", "Jane", "Mary", "Eric", "Sarah", "Sally"].randomElement()! } ================================================ FILE: examples/ios/swift/ListSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/ListSwiftUI/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/ListSwiftUI/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: examples/ios/swift/ListSwiftUI/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/ListSwiftUI/Views/App.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 Foundation import SwiftUI @main struct App: SwiftUI.App { var view: some View { ContentView() } var body: some Scene { WindowGroup { view } } } ================================================ FILE: examples/ios/swift/ListSwiftUI/Views/ContentView.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 RealmSwift import SwiftUI class Reminder: EmbeddedObject, ObjectKeyIdentifiable { enum Priority: Int, PersistableEnum, CaseIterable, Identifiable, CustomStringConvertible { var id: Int { self.rawValue } case low, medium, high var description: String { switch self { case .low: return "low" case .medium: return "medium" case .high: return "high" } } } @Persisted var title: String @Persisted var notes: String @Persisted var isFlagged: Bool @Persisted var date: Date @Persisted var isComplete: Bool @Persisted var priority: Priority = .low } class ReminderList: Object, ObjectKeyIdentifiable { @Persisted var name = "New List" @Persisted var icon: String = "list.bullet" @Persisted var reminders: RealmSwift.List } struct FocusableTextField: UIViewRepresentable { class Coordinator: NSObject, UITextFieldDelegate { @Binding var text: String var didBecomeFirstResponder = false init(text: Binding) { _text = text } func textFieldDidChangeSelection(_ textField: UITextField) { text = textField.text ?? "" } } let title: String @Binding var text: String @Binding var isFirstResponder: Bool init(_ title: String, text: Binding, isFirstResponder: Binding) { self.title = title self._text = text self._isFirstResponder = isFirstResponder } func makeUIView(context: UIViewRepresentableContext) -> UITextField { let textField = UITextField(frame: .zero) textField.placeholder = title textField.delegate = context.coordinator return textField } func makeCoordinator() -> Coordinator { return Coordinator(text: $text) } func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext) { uiView.text = text if isFirstResponder && !context.coordinator.didBecomeFirstResponder { uiView.becomeFirstResponder() context.coordinator.didBecomeFirstResponder = true } } } struct ReminderRowView: View { @ObservedRealmObject var list: ReminderList @ObservedRealmObject var reminder: Reminder @State var hasFocus: Bool @State var showReminderForm = false var body: some View { NavigationLink(destination: ReminderFormView(list: list, reminder: reminder, showReminderForm: $showReminderForm), isActive: $showReminderForm) { FocusableTextField("title", text: reminder.bind(\.title), isFirstResponder: $hasFocus).textCase(.lowercase) }.isDetailLink(true) } } struct ReminderFormView: View { @ObservedRealmObject var list: ReminderList @ObservedRealmObject var reminder: Reminder @Binding var showReminderForm: Bool var body: some View { Form { TextField("title", text: $reminder.title) DatePicker("date", selection: $reminder.date) Picker("priority", selection: $reminder.priority, content: { ForEach(Reminder.Priority.allCases) { priority in Text(priority.description).tag(priority) } }) } .navigationTitle(reminder.title) .navigationBarItems(trailing: Button("Save") { if reminder.realm == nil { $list.reminders.append(reminder) } showReminderForm.toggle() }.disabled(reminder.title.isEmpty)) } } struct ReminderListView: View { @ObservedRealmObject var list: ReminderList @State var newReminderAdded = false @State var showReminderForm = false func shouldFocusReminder(_ reminder: Reminder) -> Bool { return newReminderAdded && list.reminders.lastIndex(of: reminder) == (list.reminders.count - 1) } var body: some View { VStack { List { ForEach(list.reminders) { reminder in ReminderRowView(list: list, reminder: reminder, hasFocus: shouldFocusReminder(reminder)) } .onMove(perform: $list.reminders.move) .onDelete(perform: $list.reminders.remove) } }.navigationTitle(list.name) .navigationBarItems(trailing: HStack { EditButton() Button("add") { newReminderAdded = true $list.reminders.append(Reminder()) }.accessibility(identifier: "addReminder") }) } } struct ReminderListRowView: View { @ObservedRealmObject var list: ReminderList var body: some View { HStack { Image(systemName: list.icon) TextField("List Name", text: $list.name) Spacer() Text("\(list.reminders.count)") }.frame(minWidth: 100) } } struct ReminderListResultsView: View { @ObservedResults(ReminderList.self) var reminders @Binding var searchFilter: String var body: some View { let list = List { ForEach(reminders) { list in NavigationLink(destination: ReminderListView(list: list)) { ReminderListRowView(list: list).tag(list) }.accessibilityIdentifier(list.name) }.onDelete(perform: $reminders.remove) } if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { list .searchable(text: $searchFilter, collection: $reminders, keyPath: \.name) { ForEach(reminders) { remindersFiltered in Text(remindersFiltered.name).searchCompletion(remindersFiltered.name) } } } else { list .onChange(of: searchFilter) { value in $reminders.where = { $0.name.contains(value, options: .caseInsensitive) } } } } } public extension Color { static let lightText = Color(UIColor.lightText) static let darkText = Color(UIColor.darkText) static let label = Color(UIColor.label) static let secondaryLabel = Color(UIColor.secondaryLabel) static let tertiaryLabel = Color(UIColor.tertiaryLabel) static let quaternaryLabel = Color(UIColor.quaternaryLabel) static let systemBackground = Color(UIColor.systemBackground) static let secondarySystemBackground = Color(UIColor.secondarySystemBackground) static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground) } struct SearchView: View { @Binding var searchFilter: String var body: some View { VStack { Spacer() HStack { Image(systemName: "magnifyingglass").foregroundColor(.gray) .padding(.leading, 7) .padding(.top, 7) .padding(.bottom, 7) TextField("search", text: $searchFilter) .padding(.top, 7) .padding(.bottom, 7) }.background(RoundedRectangle(cornerRadius: 15) .fill(Color.secondarySystemBackground)) Spacer() }.frame(maxHeight: 40).padding() } } struct Footer: View { @ObservedResults(ReminderList.self) var lists var body: some View { HStack { Button(action: { $lists.append(ReminderList()) }, label: { HStack { Image(systemName: "plus.circle") Text("Add list") } }).buttonStyle(BorderlessButtonStyle()) .padding() .accessibility(identifier: "addList") Spacer() } } } struct ContentView: View { @State var searchFilter: String = "" var body: some View { NavigationView { VStack { if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { // Don't add a SearchView in case searchable is available } else { SearchView(searchFilter: $searchFilter) } ReminderListResultsView(searchFilter: $searchFilter) Spacer() Footer() } .navigationBarItems(trailing: EditButton()) .navigationTitle("reminders") } } } #if DEBUG struct Content_Preview: PreviewProvider { static var previews: some View { ContentView() } } #endif ================================================ FILE: examples/ios/swift/Migration/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UIViewController() window?.makeKeyAndVisible() #if CREATE_EXAMPLES addExampleDataToRealm(exampleData) #else performMigration() #endif return true } private func addExampleDataToRealm(_ exampleData: (Realm) -> Void) { let url = realmUrl(for: schemaVersion, usingTemplate: false) let configuration = Realm.Configuration(fileURL: url, schemaVersion: UInt64(schemaVersion)) let realm = try! Realm(configuration: configuration) try! realm.write { exampleData(realm) } } // Any version before the current versions will be migrated to check if all version combinations work. private func performMigration() { for oldSchemaVersion in 0.. URL { let defaultURL = Realm.Configuration.defaultConfiguration.fileURL! let defaultParentURL = defaultURL.deletingLastPathComponent() let fileName = "default-v\(schemaVersion)" let destinationUrl = defaultParentURL.appendingPathComponent(fileName + ".realm") if FileManager.default.fileExists(atPath: destinationUrl.path) { try! FileManager.default.removeItem(at: destinationUrl) } if usingTemplate { let bundleUrl = Bundle.main.url(forResource: fileName, withExtension: "realm")! try! FileManager.default.copyItem(at: bundleUrl, to: destinationUrl) } return destinationUrl } } ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v0.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if SCHEMA_VERSION_0 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 0 class Person: Object { @Persisted var firstName = "" @Persisted var lastName = "" @Persisted var age = 0 convenience init(firstName: String, lastName: String, age: Int) { self.init() self.firstName = firstName self.lastName = lastName self.age = age } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { _, _ in } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].firstName == "John") assert(persons[0].lastName == "Doe") assert(persons[0].age == 42) assert(persons[1].firstName == "Jane") assert(persons[1].lastName == "Doe") assert(persons[1].age == 43) assert(persons[2].firstName == "John") assert(persons[2].lastName == "Smith") assert(persons[2].age == 44) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let person1 = Person(firstName: "John", lastName: "Doe", age: 42) let person2 = Person(firstName: "Jane", lastName: "Doe", age: 43) let person3 = Person(firstName: "John", lastName: "Smith", age: 44) realm.add([person1, person2, person3]) } #endif ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v1.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if SCHEMA_VERSION_1 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 1 // Changes from previous version: // - combine `firstName` and `lastName` into `fullName` class Person: Object { @Persisted var fullName = "" @Persisted var age = 0 convenience init(fullName: String, age: Int) { self.init() self.fullName = fullName self.age = age } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].fullName == "John Doe") assert(persons[0].age == 42) assert(persons[1].fullName == "Jane Doe") assert(persons[1].age == 43) assert(persons[2].fullName == "John Smith") assert(persons[2].age == 44) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let person1 = Person(fullName: "John Doe", age: 42) let person2 = Person(fullName: "Jane Doe", age: 43) let person3 = Person(fullName: "John Smith", age: 44) realm.add([person1, person2, person3]) } #endif ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v2.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if SCHEMA_VERSION_2 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 2 // Changes from previous version: // add a `Dog` object // add a list of `dogs` to the `Person` object class Dog: Object { @Persisted var name = "" convenience init(name: String) { self.init() self.name = name } } class Person: Object { @Persisted var fullName = "" @Persisted var age = 0 @Persisted var dogs: List convenience init(fullName: String, age: Int) { self.init() self.fullName = fullName self.age = age } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } if oldSchemaVersion < 2 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in // Add a pet to a specific person if newObject!["fullName"] as! String == "John Doe" { let dogs = newObject!["dogs"] as! List let marley = migration.create(Dog.className(), value: ["Marley"]) let lassie = migration.create(Dog.className(), value: ["Lassie"]) dogs.append(marley) dogs.append(lassie) } else if newObject!["fullName"] as! String == "Jane Doe" { let dogs = newObject!["dogs"] as! List let toto = migration.create(Dog.className(), value: ["Toto"]) dogs.append(toto) } } let slinkey = migration.create(Dog.className(), value: ["Slinkey"]) } } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].fullName == "John Doe") assert(persons[0].age == 42) assert(persons[0].dogs.count == 2) assert(persons[0].dogs[0].name == "Marley") assert(persons[0].dogs[1].name == "Lassie") assert(persons[1].fullName == "Jane Doe") assert(persons[1].age == 43) assert(persons[1].dogs.count == 1) assert(persons[1].dogs[0].name == "Toto") assert(persons[2].fullName == "John Smith") assert(persons[2].age == 44) let dogs = realm.objects(Dog.self) assert(dogs.count == 4) assert(dogs.contains { $0.name == "Slinkey" }) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let person1 = Person(fullName: "John Doe", age: 42) let person2 = Person(fullName: "Jane Doe", age: 43) let person3 = Person(fullName: "John Smith", age: 44) let pet1 = Dog(name: "Marley") let pet2 = Dog(name: "Lassie") let pet3 = Dog(name: "Toto") let pet4 = Dog(name: "Slinkey") realm.add([person1, person2, person3]) // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. realm.add(pet4) person1.dogs.append(pet1) person1.dogs.append(pet2) person2.dogs.append(pet3) } #endif ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v3.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if SCHEMA_VERSION_3 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 3 // Changes from previous version: // rename the `Dog` object to `Pet` // add a `kind` property to `Pet` // change the `dogs` property on `Person`: // - rename to `pets` // - change type to `List` // Renaming tables is not supported yet: https://github.com/realm/realm-swift/issues/2491 // The recommended way is to create a new type instead and migrate the old type. // Here we create `Pet` and migrate its data from `Dog` so simulate renaming the table. class Pet: Object { enum Kind: Int, PersistableEnum { case unspecified case dog case chicken case cow } @Persisted var name = "" @Persisted var kind = Kind.unspecified convenience init(name: String, kind: Kind) { self.init() self.name = name self.kind = kind } } class Person: Object { @Persisted var fullName = "" @Persisted var age = 0 @Persisted var pets: List convenience init(fullName: String, age: Int) { self.init() self.fullName = fullName self.age = age } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } if oldSchemaVersion < 2 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in // Add a pet to a specific person if newObject!["fullName"] as! String == "John Doe" { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. let dogs = newObject!["pets"] as! List let marley = migration.create(Pet.className(), value: ["Marley", Pet.Kind.dog.rawValue]) let lassie = migration.create(Pet.className(), value: ["Lassie", Pet.Kind.dog.rawValue]) dogs.append(marley) dogs.append(lassie) } else if newObject!["fullName"] as! String == "Jane Doe" { let dogs = newObject!["pets"] as! List let toto = migration.create(Pet.className(), value: ["Toto", Pet.Kind.dog.rawValue]) dogs.append(toto) } } let slinkey = migration.create(Pet.className(), value: ["Slinkey", Pet.Kind.dog.rawValue]) } if oldSchemaVersion == 2 { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in let pets = newObject!["pets"] as! List for dog in oldObject!["dogs"] as! List { let pet = migration.create(Pet.className(), value: [dog["name"], Pet.Kind.dog.rawValue]) pets.append(pet) } } // We migrate over the old dog list to make sure all dogs get added, even those without // an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 migration.enumerateObjects(ofType: "Dog") { oldDogObject, _ in var dogFound = false migration.enumerateObjects(ofType: Person.className()) { _, newObject in for pet in newObject!["pets"] as! List where pet["name"] as! String == oldDogObject!["name"] as! String { dogFound = true break } } if !dogFound { migration.create(Pet.className(), value: [oldDogObject!["name"], Pet.Kind.dog.rawValue]) } } // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // migration.deleteData(forType: Pet.Kind.dog) } } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].fullName == "John Doe") assert(persons[0].age == 42) assert(persons[0].pets.count == 2) assert(persons[0].pets[0].name == "Marley") assert(persons[0].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[0].pets[1].name == "Lassie") assert(persons[0].pets[1].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[1].fullName == "Jane Doe") assert(persons[1].age == 43) assert(persons[1].pets.count == 1) assert(persons[1].pets[0].name == "Toto") assert(persons[1].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[2].fullName == "John Smith") assert(persons[2].age == 44) let pets = realm.objects(Pet.self) assert(pets.count == 4) assert(pets.contains { $0.name == "Slinkey" && $0.kind.rawValue == Pet.Kind.dog.rawValue }) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let person1 = Person(fullName: "John Doe", age: 42) let person2 = Person(fullName: "Jane Doe", age: 43) let person3 = Person(fullName: "John Smith", age: 44) let pet1 = Pet(name: "Marley", kind: .dog) let pet2 = Pet(name: "Lassie", kind: .dog) let pet3 = Pet(name: "Toto", kind: .dog) let pet4 = Pet(name: "Slinkey", kind: .dog) realm.add([person1, person2, person3]) person1.pets.append(pet1) person1.pets.append(pet2) person2.pets.append(pet3) // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. realm.add(pet4) } #endif ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v4.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if SCHEMA_VERSION_4 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 4 // Changes from previous version: // Add an `Address` to the `Person`. class Pet: Object { enum Kind: Int, PersistableEnum { case unspecified case dog case chicken case cow } @Persisted var name = "" @Persisted var kind = Kind.unspecified convenience init(name: String, kind: Kind) { self.init() self.name = name self.kind = kind } } class Person: Object { @Persisted var fullName = "" @Persisted var age = 0 @Persisted var address: Address? @Persisted var pets: List convenience init(fullName: String, age: Int, address: Address?) { self.init() self.fullName = fullName self.age = age self.address = address } } class Address: Object { @Persisted var street = "" @Persisted var city = "" @Persisted(originProperty: "adddress") var residents: LinkingObjects convenience init(street: String, city: String) { self.init() self.street = street self.city = city } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } if oldSchemaVersion < 2 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in // Add a pet to a specific person if newObject!["fullName"] as! String == "John Doe" { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. let dogs = newObject!["pets"] as! List let marley = migration.create(Pet.className(), value: ["Marley", Pet.Kind.dog.rawValue]) let lassie = migration.create(Pet.className(), value: ["Lassie", Pet.Kind.dog.rawValue]) dogs.append(marley) dogs.append(lassie) } else if newObject!["fullName"] as! String == "Jane Doe" { let dogs = newObject!["pets"] as! List let toto = migration.create(Pet.className(), value: ["Toto", Pet.Kind.dog.rawValue]) dogs.append(toto) } } let slinkey = migration.create(Pet.className(), value: ["Slinkey", Pet.Kind.dog.rawValue]) } if oldSchemaVersion == 2 { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in let pets = newObject!["pets"] as! List for dog in oldObject!["dogs"] as! List { let pet = migration.create(Pet.className(), value: [dog["name"], Pet.Kind.dog.rawValue]) pets.append(pet) } } // We migrate over the old dog list to make sure all dogs get added, even those without // an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 migration.enumerateObjects(ofType: "Dog") { oldDogObject, _ in var dogFound = false migration.enumerateObjects(ofType: Person.className()) { _, newObject in for pet in newObject!["pets"] as! List where pet["name"] as! String == oldDogObject!["name"] as! String { dogFound = true break } } if !dogFound { migration.create(Pet.className(), value: [oldDogObject!["name"], Pet.Kind.dog.rawValue]) } } // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // migration.deleteData(forType: Pet.Kind.dog) } if oldSchemaVersion < 4 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in if newObject!["fullName"] as! String == "John Doe" { let address = migration.create(Address.className(), value: ["Broadway", "New York"]) newObject!["address"] = address } } } } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].fullName == "John Doe") assert(persons[0].age == 42) assert(persons[0].address != nil) assert(persons[0].address?.city == "New York") assert(persons[0].address?.street == "Broadway") assert(persons[0].pets.count == 2) assert(persons[0].pets[0].name == "Marley") assert(persons[0].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[0].pets[1].name == "Lassie") assert(persons[0].pets[1].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[1].fullName == "Jane Doe") assert(persons[1].age == 43) assert(persons[1].address == nil) assert(persons[1].pets.count == 1) assert(persons[1].pets[0].name == "Toto") assert(persons[1].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[2].fullName == "John Smith") assert(persons[2].age == 44) assert(persons[2].address == nil) let pets = realm.objects(Pet.self) assert(pets.count == 4) assert(pets.contains { $0.name == "Slinkey" && $0.kind.rawValue == Pet.Kind.dog.rawValue }) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let address = Address(street: "Broadway", city: "New York") let person1 = Person(fullName: "John Doe", age: 42, address: address) let person2 = Person(fullName: "Jane Doe", age: 43, address: nil) let person3 = Person(fullName: "John Smith", age: 44, address: nil) let pet1 = Pet(name: "Marley", kind: .dog) let pet2 = Pet(name: "Lassie", kind: .dog) let pet3 = Pet(name: "Toto", kind: .dog) let pet4 = Pet(name: "Slinkey", kind: .dog) realm.add([person1, person2, person3]) person1.pets.append(pet1) person1.pets.append(pet2) person2.pets.append(pet3) // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. realm.add(pet4) } #endif ================================================ FILE: examples/ios/swift/Migration/Examples/Example_v5.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// #if !SCHEMA_VERSION_0 && !SCHEMA_VERSION_1 && !SCHEMA_VERSION_2 && !SCHEMA_VERSION_3 && !SCHEMA_VERSION_4 import Foundation import RealmSwift // MARK: - Schema let schemaVersion = 5 // Changes from previous version: // - Change the `Address` from `Object` to `EmbeddedObject`. // // Be aware that this only works if there is only one `LinkingObject` per `Address`. // See https://github.com/realm/realm-swift/issues/7060 // Renaming tables is not supported yet: https://github.com/realm/realm-swift/issues/2491 // The recommended way is to create a new type instead and migrate the old type. // Here we create `Pet` and migrate its data from `Dog` so simulate renaming the table. class Pet: Object { enum Kind: Int, PersistableEnum { case unspecified case dog case chicken case cow } @Persisted var name = "" @Persisted var kind = Kind.unspecified convenience init(name: String, kind: Kind) { self.init() self.name = name self.kind = kind } } class Person: Object { @Persisted var fullName = "" @Persisted var age = 0 @Persisted var address: Address? @Persisted var pets: List convenience init(fullName: String, age: Int, address: Address?) { self.init() self.fullName = fullName self.age = age self.address = address } } class Address: EmbeddedObject { @Persisted var street = "" @Persisted var city = "" @Persisted(originProperty: "address") var residents: LinkingObjects convenience init(street: String, city: String) { self.init() self.street = street self.city = city } } // MARK: - Migration // Migration block to migrate from *any* previous version to this version. let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } if oldSchemaVersion < 2 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in // Add a pet to a specific person if newObject!["fullName"] as! String == "John Doe" { // `Dog` was changed to `Pet` in v2 already, but we still need to account for this // if upgrading from pre v2 to v3. let dogs = newObject!["pets"] as! List let marley = migration.create(Pet.className(), value: ["Marley", Pet.Kind.dog.rawValue]) let lassie = migration.create(Pet.className(), value: ["Lassie", Pet.Kind.dog.rawValue]) dogs.append(marley) dogs.append(lassie) } else if newObject!["fullName"] as! String == "Jane Doe" { let dogs = newObject!["pets"] as! List let toto = migration.create(Pet.className(), value: ["Toto", Pet.Kind.dog.rawValue]) dogs.append(toto) } } let slinkey = migration.create(Pet.className(), value: ["Slinkey", Pet.Kind.dog.rawValue]) } if oldSchemaVersion == 2 { // This branch is only relevant for version 2. If we are migration from a previous // version, we would not be able to access `dogs` since they did not exist back there. // Migration from v0 and v1 to v3 is done in the previous blocks. // Related issue: https://github.com/realm/realm-swift/issues/6263 migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in let pets = newObject!["pets"] as! List for dog in oldObject!["dogs"] as! List { let pet = migration.create(Pet.className(), value: [dog["name"], Pet.Kind.dog.rawValue]) pets.append(pet) } } // We enumerate the old dog list to make sure all dogs get added, even those without an owner. // Related issue: https://github.com/realm/realm-swift/issues/6734 migration.enumerateObjects(ofType: "Dog") { oldDogObject, _ in var dogFound = false migration.enumerateObjects(ofType: Person.className()) { _, newObject in for pet in newObject!["pets"] as! List where pet["name"] as! String == oldDogObject!["name"] as! String { dogFound = true break } } if !dogFound { migration.create(Pet.className(), value: [oldDogObject!["name"], Pet.Kind.dog.rawValue]) } } // The data cannot be deleted just yet since the table is target of cross-table link columns. // See https://github.com/realm/realm-swift/issues/3686 // migration.deleteData(forType: "Dog") } if oldSchemaVersion < 4 { migration.enumerateObjects(ofType: Person.className()) { _, newObject in if newObject!["fullName"] as! String == "John Doe" { let address = Address(value: ["Broadway", "New York"]) newObject!["address"] = address } } } } // This block checks if the migration led to the expected result. // All older versions should have been migrated to the below stated `exampleData`. let migrationCheck: (Realm) -> Void = { realm in let persons = realm.objects(Person.self) assert(persons.count == 3) assert(persons[0].fullName == "John Doe") assert(persons[0].age == 42) assert(persons[0].address != nil) assert(persons[0].address?.city == "New York") assert(persons[0].address?.street == "Broadway") assert(persons[0].pets.count == 2) assert(persons[0].pets[0].name == "Marley") assert(persons[0].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[0].pets[1].name == "Lassie") assert(persons[0].pets[1].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[1].fullName == "Jane Doe") assert(persons[1].age == 43) assert(persons[1].address == nil) assert(persons[1].pets.count == 1) assert(persons[1].pets[0].name == "Toto") assert(persons[1].pets[0].kind.rawValue == Pet.Kind.dog.rawValue) assert(persons[2].fullName == "John Smith") assert(persons[2].age == 44) assert(persons[2].address == nil) let pets = realm.objects(Pet.self) assert(pets.count == 4) assert(pets.contains { $0.name == "Slinkey" && $0.kind.rawValue == Pet.Kind.dog.rawValue }) } // MARK: - Example data // Example data for this schema version. let exampleData: (Realm) -> Void = { realm in let address = Address(street: "Broadway", city: "New York") let person1 = Person(fullName: "John Doe", age: 42, address: address) let person2 = Person(fullName: "Jane Doe", age: 43, address: nil) let person3 = Person(fullName: "John Smith", age: 44, address: nil) let pet1 = Pet(name: "Marley", kind: .dog) let pet2 = Pet(name: "Lassie", kind: .dog) let pet3 = Pet(name: "Toto", kind: .dog) let pet4 = Pet(name: "Slinkey", kind: .dog) realm.add([person1, person2, person3]) person1.pets.append(pet1) person1.pets.append(pet2) person2.pets.append(pet3) // pet1, pet2 and pet3 get added automatically by adding them to a list. // pet4 has to be added manually though since it's not attached to a person yet. realm.add(pet4) } #endif ================================================ FILE: examples/ios/swift/Migration/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/Migration/Migration.xcconfig ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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. // //////////////////////////////////////////////////////////////////////////// // This file is used to create new examples. // To create a new example for a specific version you have to uncomment the // following line and the corresponding line with the schema version you // want to create (v5 being the default). //OTHER_SWIFT_FLAGS = $(inherited) -DCREATE_EXAMPLES; // If you want to test older versions, uncommene the corresponding version. //OTHER_SWIFT_FLAGS = $(inherited) -DSCHEMA_VERSION_0; //OTHER_SWIFT_FLAGS = $(inherited) -DSCHEMA_VERSION_1; //OTHER_SWIFT_FLAGS = $(inherited) -DSCHEMA_VERSION_2; //OTHER_SWIFT_FLAGS = $(inherited) -DSCHEMA_VERSION_3; //OTHER_SWIFT_FLAGS = $(inherited) -DSCHEMA_VERSION_4; ================================================ FILE: examples/ios/swift/Migration/README.md ================================================ # Migration Examples The Migration project shows several examples of migrations and migration blocks. The purpose of this example is to provide a more in depth view of certain problems and pitfalls users might face when migrations get more complex and the number of versions increases. ## How to use the example You can build and run the project as is. Migrations from all prior version to the current version will be checked: - v0 -> v5 - v1 -> v5 - v2 -> v5 - v3 -> v5 - v4 -> v5 The files to look at are located in the `Examples` folder. Every file contains an extract of everything necessary for this version (schema version, objects, migration and migration checks). If you want to compare older versions among each other (i.e. v2 -> v3) you can do so by enabling the schema version, for example: - Deactivate version 5 by setting `#define SCHEMA_VERSION_5 1` to `#define SCHEMA_VERSION_5 0` in `Example_v5.h`. - Activate version 3 by seting `#define SCHEMA_VERSION_3 0` to `#define SCHEMA_VERSION_3 1` in `Example_v3.h`. ================================================ FILE: examples/ios/swift/PlaygroundFrameworkWrapper/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: examples/ios/swift/PlaygroundFrameworkWrapper/PlaygroundFrameworkWrapper.swift ================================================ // The PlaygroundFrameworkWrapper framework enables // a binary release of RealmSwift.framework to be used // in Xcode Playgrounds. ================================================ FILE: examples/ios/swift/Projections/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/Projections/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/Projections/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/Projections/ContentView.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 SwiftUI import RealmSwift class Series: Object { @Persisted var title: String @Persisted var episodes: RealmSwift.List } class Movie: Object { @Persisted var title: String @Persisted var episodeNumber: Int @Persisted var length: Int @Persisted(originProperty: "episodes") var series: LinkingObjects } class SeriesModel: Projection { @Projected(\Series.title) var title @Projected(\Series.episodes.count) var epCount @Projected(\Series.episodes.first?.title.quoted) var firstEpisode @Projected(\Series.episodes) var episodes } extension String { var quoted: String { "\"\(self)\"" } } struct SeriesCellView: View { @ObservedRealmObject var series: SeriesModel var body: some View { VStack { HStack { Text(series.title) Text("\(series.epCount) \(series.epCount == 1 ? "episode" : "episodes")") .font(.footnote) } if let firstTitle = series.firstEpisode, !firstTitle.isEmpty { Text("start watch from " + firstTitle) .font(.footnote) } } } } struct EpisodeCellView: View { @ObservedRealmObject var episode: Movie var body: some View { Text(episode.title) .padding() } } struct SeriesView: View { @ObservedRealmObject var series: SeriesModel var body: some View { VStack { Text("Episodes") List($series.episodes) { episode in EpisodeCellView(episode: episode.wrappedValue) } } } } struct ContentView: View { @Environment(\.realm) var realm @ObservedResults(SeriesModel.self) var series var body: some View { NavigationView { List { ForEach(series) { series in NavigationLink(destination: SeriesView(series: series)) { SeriesCellView(series: series) } } } } .navigationTitle("Movies") .onAppear(perform: fillData) } // Add records to display in the view func fillData() { if realm.objects(Movie.self).count == 0 { let sw = Series(value: ["Space Shooter", [["Revived Beliefs", 4], ["The Tyrany Evens the Score", 5], ["Comeback of Magician", 6], ["The Mirage Hazard", 1], ["Offence of Siblings", 2], ["Vendetta of Bad Guys", 3]]]) try! realm.write { realm.add(sw) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ================================================ FILE: examples/ios/swift/Projections/Info.plist ================================================ UIApplicationSceneManifest UIApplicationSupportsMultipleScenes ================================================ FILE: examples/ios/swift/Projections/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/Projections/ProjectionsApp.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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 SwiftUI @main struct ProjectionsApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0CF50F83272FF6330048A358 /* ProjectionsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF50F82272FF6330048A358 /* ProjectionsApp.swift */; }; 0CF50F85272FF6330048A358 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF50F84272FF6330048A358 /* ContentView.swift */; }; 0CF50F87272FF6340048A358 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CF50F86272FF6340048A358 /* Assets.xcassets */; }; 0CF50F8A272FF6340048A358 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CF50F89272FF6340048A358 /* Preview Assets.xcassets */; }; 227D20F21CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 227D20F31CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 227D20F41CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 227D20F51CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 227D20F61CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 227D20F71CB609A1008F641B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 227D20F11CB609A1008F641B /* LaunchScreen.xib */; }; 3F7B506A2D120F3D004B792D /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; 3F7B506B2D120F3D004B792D /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B506C2D120F3D004B792D /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; 3F7B506D2D120F3D004B792D /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B506F2D120FFB004B792D /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; 3F7B50702D120FFB004B792D /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B50712D120FFB004B792D /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; 3F7B50722D120FFB004B792D /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B50742D121000004B792D /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; 3F7B50752D121000004B792D /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B50762D121000004B792D /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; 3F7B50772D121000004B792D /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B507D2D121019004B792D /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; 3F7B507E2D121019004B792D /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B507F2D121019004B792D /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; 3F7B50802D121019004B792D /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B50812D121063004B792D /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; 3F7B50822D121063004B792D /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3F7B50832D121063004B792D /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; 3F7B50842D121063004B792D /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3FA2F2FF268FCA380015062E /* DemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA2F2FD268FCA380015062E /* DemoObject.swift */; }; 3FA2F300268FCA380015062E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA2F2FE268FCA380015062E /* Constants.swift */; }; 3FA2F301268FCA960015062E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA2F2FE268FCA380015062E /* Constants.swift */; }; 3FA2F302268FCA960015062E /* DemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA2F2FD268FCA380015062E /* DemoObject.swift */; }; 3FA2F303268FCAAD0015062E /* AppClipApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53454CB924EAC3F900678E48 /* AppClipApp.swift */; }; 3FA2F304268FCAC80015062E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53454CCE24EAC43500678E48 /* ContentView.swift */; }; 3FC898FF1A1414110067CBEC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC898FE1A1414110067CBEC /* ViewController.swift */; }; 493759AB230D8CA60078C28E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493759AA230D8CA60078C28E /* ContentView.swift */; }; 493759AD230D8CAA0078C28E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 493759AC230D8CAA0078C28E /* Assets.xcassets */; }; 493759B0230D8CAA0078C28E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 493759AF230D8CAA0078C28E /* Preview Assets.xcassets */; }; 53379DB625CB0E020008A269 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53379DB525CB0E020008A269 /* App.swift */; }; 53454CCD24EAC43500678E48 /* AppClipParentApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53454CCC24EAC43500678E48 /* AppClipParentApp.swift */; }; 53454CCF24EAC43500678E48 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53454CCE24EAC43500678E48 /* ContentView.swift */; }; 53454CD124EAC43700678E48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53454CD024EAC43700678E48 /* Assets.xcassets */; }; 53454CD424EAC43700678E48 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53454CD324EAC43700678E48 /* Preview Assets.xcassets */; }; 53454CEC24EAC45700678E48 /* AppClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 53454CDD24EAC45500678E48 /* AppClip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7D46395025C2E2750089EFD9 /* Example_v3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D46394F25C2E2750089EFD9 /* Example_v3.swift */; }; 7D46395425C2E7140089EFD9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 7D46395325C2E7140089EFD9 /* README.md */; }; 7D46399825C45FF00089EFD9 /* Example_v4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D46399725C45FF00089EFD9 /* Example_v4.swift */; }; 7D4639A025C46C660089EFD9 /* Example_v5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D46399F25C46C660089EFD9 /* Example_v5.swift */; }; 7DADCBA525C816B600048287 /* default-v0.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCB9F25C816B500048287 /* default-v0.realm */; }; 7DADCBA625C816B600048287 /* default-v1.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBA025C816B500048287 /* default-v1.realm */; }; 7DADCBA725C816B600048287 /* default-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBA125C816B500048287 /* default-v3.realm */; }; 7DADCBA825C816B600048287 /* default-v4.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBA225C816B500048287 /* default-v4.realm */; }; 7DADCBA925C816B600048287 /* default-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBA325C816B500048287 /* default-v5.realm */; }; 7DADCBAA25C816B600048287 /* default-v2.realm in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBA425C816B600048287 /* default-v2.realm */; }; 7DADCBB225C8284500048287 /* Migration.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 7DADCBB125C8284500048287 /* Migration.xcconfig */; }; 7DFFB15E25C19F0700CA8AE5 /* Example_v0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB15D25C19F0700CA8AE5 /* Example_v0.swift */; }; 7DFFB16F25C1B65A00CA8AE5 /* Example_v1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB16E25C1B65A00CA8AE5 /* Example_v1.swift */; }; 7DFFB17725C1B84E00CA8AE5 /* Example_v2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFFB17625C1B84E00CA8AE5 /* Example_v2.swift */; }; C07E5D7A1B15003B00ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D7B1B15003B00ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D7C1B15004800ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D7D1B15004800ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D7E1B15004C00ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D7F1B15004C00ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D801B15004F00ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D811B15004F00ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D821B15005300ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D831B15005300ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C07E5D841B15005500ED625C /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; C07E5D851B15005500ED625C /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E886FB671A12A73E00CB2D0B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E886FB631A12A73E00CB2D0B /* AppDelegate.swift */; }; E886FB681A12A73E00CB2D0B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E886FB641A12A73E00CB2D0B /* Images.xcassets */; }; E886FB6A1A12A73E00CB2D0B /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E886FB661A12A73E00CB2D0B /* TableViewController.swift */; }; E8CA270B1CF9031300FF203A /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; }; E8CA270C1CF9031300FF203A /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8CA270D1CF9031600FF203A /* Realm.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C07E5D791B15001500ED625C /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8CA270E1CF9031600FF203A /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8CB088219BA6AEE0018434A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CB088019BA6AEE0018434A /* AppDelegate.swift */; }; E8CB088A19BA6AF70018434A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CB088519BA6AF70018434A /* AppDelegate.swift */; }; E8CB089219BA6B000018434A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CB089019BA6B000018434A /* AppDelegate.swift */; }; E8CB089919BA6B080018434A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CB089519BA6B080018434A /* AppDelegate.swift */; }; E8CB089A19BA6B080018434A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E8CB089619BA6B080018434A /* Images.xcassets */; }; E8CB089C19BA6B080018434A /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CB089819BA6B080018434A /* TableViewController.swift */; }; E8D212F11AF6A9E800DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212F21AF6A9E800DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D212F31AF6A9EF00DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212F41AF6A9EF00DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D212F51AF6A9FD00DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212F61AF6A9FD00DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D212F71AF6AA0000DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212F81AF6AA0000DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D212F91AF6AA0300DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212FA1AF6AA0300DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D212FB1AF6AA0600DAB243 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; }; E8D212FC1AF6AA0600DAB243 /* RealmSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C09C490F1A605A4800638C9F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E8D3F2781A11766A00620884 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D3F2761A11766A00620884 /* AppDelegate.swift */; }; E8F14D131CF8FD7F00564AF5 /* PlaygroundFrameworkWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F14D121CF8FD7F00564AF5 /* PlaygroundFrameworkWrapper.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 53454CEA24EAC45700678E48 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E805758D19BA55E600376620 /* Project object */; proxyType = 1; remoteGlobalIDString = 53454CDC24EAC45500678E48; remoteInfo = AppClip; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 3F7B506E2D120F3D004B792D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F7B506B2D120F3D004B792D /* Realm.framework in Embed Frameworks */, 3F7B506D2D120F3D004B792D /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 3F7B50732D120FFB004B792D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F7B50702D120FFB004B792D /* Realm.framework in Embed Frameworks */, 3F7B50722D120FFB004B792D /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 3F7B50782D121000004B792D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F7B50752D121000004B792D /* Realm.framework in Embed Frameworks */, 3F7B50772D121000004B792D /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 3F7B50852D121063004B792D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3F7B50822D121063004B792D /* Realm.framework in Embed Frameworks */, 3F7B50842D121063004B792D /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 53454CF024EAC45700678E48 /* Embed App Clips */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/AppClips"; dstSubfolderSpec = 16; files = ( 53454CEC24EAC45700678E48 /* AppClip.app in Embed App Clips */, ); name = "Embed App Clips"; runOnlyForDeploymentPostprocessing = 0; }; C01304E31A60A4A000EB3E1E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D7B1B15003B00ED625C /* Realm.framework in CopyFiles */, E8D212F21AF6A9E800DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; C01304E51A60A6CE00EB3E1E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D7F1B15004C00ED625C /* Realm.framework in CopyFiles */, E8D212F61AF6A9FD00DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; C01304E71A60A6EC00EB3E1E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D811B15004F00ED625C /* Realm.framework in CopyFiles */, E8D212F81AF6AA0000DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; C01304E91A60B77600EB3E1E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D7D1B15004800ED625C /* Realm.framework in CopyFiles */, E8D212F41AF6A9EF00DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; C09C490D1A6059BA00638C9F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D831B15005300ED625C /* Realm.framework in CopyFiles */, E8D212FA1AF6AA0300DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; C09C491C1A6068E500638C9F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( C07E5D851B15005500ED625C /* Realm.framework in CopyFiles */, E8D212FC1AF6AA0600DAB243 /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; E8F14D141CF900E500564AF5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( E8CA270D1CF9031600FF203A /* Realm.framework in CopyFiles */, E8CA270E1CF9031600FF203A /* RealmSwift.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0A73D4411B1443A700E1E8EE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = SOURCE_ROOT; }; 0CF50F80272FF6330048A358 /* Projections.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Projections.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0CF50F82272FF6330048A358 /* ProjectionsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectionsApp.swift; sourceTree = ""; }; 0CF50F84272FF6330048A358 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 0CF50F86272FF6340048A358 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0CF50F89272FF6340048A358 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0CF50F98273008BF0048A358 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 0CF50F99273008DF0048A358 /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0CF50F9A273008DF0048A358 /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 227D20F11CB609A1008F641B /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 3F7B50612D12042D004B792D /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3F7B50622D12042D004B792D /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3FA2F2FD268FCA380015062E /* DemoObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DemoObject.swift; path = AppClipParent/DemoObject.swift; sourceTree = SOURCE_ROOT; }; 3FA2F2FE268FCA380015062E /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = AppClipParent/Constants.swift; sourceTree = SOURCE_ROOT; }; 3FC898FE1A1414110067CBEC /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 493759A4230D8CA60078C28E /* ListSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ListSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 493759AA230D8CA60078C28E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 493759AC230D8CAA0078C28E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 493759AF230D8CAA0078C28E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 493759B4230D8CAA0078C28E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 493759B9230D9F9A0078C28E /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 493759BD230D9F9D0078C28E /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53379DB525CB0E020008A269 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 53454CB924EAC3F900678E48 /* AppClipApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipApp.swift; sourceTree = ""; }; 53454CBD24EAC3FA00678E48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53454CC024EAC3FA00678E48 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 53454CC224EAC3FA00678E48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53454CCA24EAC43500678E48 /* AppClipParent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppClipParent.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53454CCC24EAC43500678E48 /* AppClipParentApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipParentApp.swift; sourceTree = ""; }; 53454CCE24EAC43500678E48 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 53454CD024EAC43700678E48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53454CD324EAC43700678E48 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 53454CD524EAC43700678E48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53454CDD24EAC45500678E48 /* AppClip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppClip.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7D46394F25C2E2750089EFD9 /* Example_v3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v3.swift; sourceTree = ""; }; 7D46395325C2E7140089EFD9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; 7D46399725C45FF00089EFD9 /* Example_v4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v4.swift; sourceTree = ""; }; 7D46399F25C46C660089EFD9 /* Example_v5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v5.swift; sourceTree = ""; }; 7DADCB9F25C816B500048287 /* default-v0.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v0.realm"; sourceTree = ""; }; 7DADCBA025C816B500048287 /* default-v1.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v1.realm"; sourceTree = ""; }; 7DADCBA125C816B500048287 /* default-v3.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v3.realm"; sourceTree = ""; }; 7DADCBA225C816B500048287 /* default-v4.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v4.realm"; sourceTree = ""; }; 7DADCBA325C816B500048287 /* default-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v5.realm"; sourceTree = ""; }; 7DADCBA425C816B600048287 /* default-v2.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "default-v2.realm"; sourceTree = ""; }; 7DADCBB125C8284500048287 /* Migration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Migration.xcconfig; sourceTree = ""; }; 7DFFB15D25C19F0700CA8AE5 /* Example_v0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v0.swift; sourceTree = ""; }; 7DFFB16E25C1B65A00CA8AE5 /* Example_v1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v1.swift; sourceTree = ""; }; 7DFFB17625C1B84E00CA8AE5 /* Example_v2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_v2.swift; sourceTree = ""; }; 9C318E8C1CA42C7800879076 /* GettingStarted.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = GettingStarted.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; AC2D0C0E269C470900126DCF /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C0F269C470900126DCF /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C15269C471200126DCF /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC2D0C16269C471200126DCF /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C07E5D791B15001500ED625C /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C09C490F1A605A4800638C9F /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFEDF01D24B72B67007FF10A /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFEDF02124B72B6B007FF10A /* RealmSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFF1724924B7310D00A16A59 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CFF1724A24B7310E00A16A59 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E886FB601A12A6FC00CB2D0B /* GroupedTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GroupedTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; E886FB631A12A73E00CB2D0B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E886FB641A12A73E00CB2D0B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; E886FB651A12A73E00CB2D0B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E886FB661A12A73E00CB2D0B /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; E8CB07EF19BA6AB60018434A /* Encryption.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Encryption.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8CB081419BA6ABD0018434A /* Migration.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Migration.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8CB083919BA6AC60018434A /* TableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8CB085E19BA6ACA0018434A /* Simple.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Simple.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8CB088019BA6AEE0018434A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E8CB088119BA6AEE0018434A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8CB088519BA6AF70018434A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; }; E8CB088919BA6AF70018434A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8CB089019BA6B000018434A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E8CB089119BA6B000018434A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8CB089519BA6B080018434A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E8CB089619BA6B080018434A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; E8CB089719BA6B080018434A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8CB089819BA6B080018434A /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; E8D3F2531A11765200620884 /* Backlink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Backlink.app; sourceTree = BUILT_PRODUCTS_DIR; }; E8D3F2761A11766A00620884 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E8D3F2771A11766A00620884 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8F14D0A1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlaygroundFrameworkWrapper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E8F14D0E1CF8FD0C00564AF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8F14D121CF8FD7F00564AF5 /* PlaygroundFrameworkWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaygroundFrameworkWrapper.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0CF50F7D272FF6330048A358 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F7B506F2D120FFB004B792D /* Realm.framework in Frameworks */, 3F7B50712D120FFB004B792D /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 493759A1230D8CA60078C28E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F7B50812D121063004B792D /* Realm.framework in Frameworks */, 3F7B50832D121063004B792D /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 53454CC724EAC43500678E48 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 53454CDA24EAC45500678E48 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3F7B506A2D120F3D004B792D /* Realm.framework in Frameworks */, 3F7B506C2D120F3D004B792D /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E886FB581A12A6FC00CB2D0B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D7E1B15004C00ED625C /* Realm.framework in Frameworks */, E8D212F51AF6A9FD00DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB07EC19BA6AB60018434A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D7C1B15004800ED625C /* Realm.framework in Frameworks */, E8D212F31AF6A9EF00DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB081119BA6ABD0018434A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D801B15004F00ED625C /* Realm.framework in Frameworks */, E8D212F71AF6AA0000DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB083619BA6AC60018434A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D841B15005500ED625C /* Realm.framework in Frameworks */, E8D212FB1AF6AA0600DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB085B19BA6ACA0018434A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D821B15005300ED625C /* Realm.framework in Frameworks */, E8D212F91AF6AA0300DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8D3F2501A11765200620884 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C07E5D7A1B15003B00ED625C /* Realm.framework in Frameworks */, E8D212F11AF6A9E800DAB243 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E8F14D061CF8FD0C00564AF5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E8CA270B1CF9031300FF203A /* Realm.framework in Frameworks */, E8CA270C1CF9031300FF203A /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0295B90519D103180036D6C3 /* RealmSwift */ = { isa = PBXGroup; children = ( C07E5D791B15001500ED625C /* Realm.framework */, C09C490F1A605A4800638C9F /* RealmSwift.framework */, ); name = RealmSwift; sourceTree = ""; }; 0CF50F81272FF6330048A358 /* Projections */ = { isa = PBXGroup; children = ( 0CF50F88272FF6340048A358 /* Preview Content */, 0CF50F86272FF6340048A358 /* Assets.xcassets */, 0CF50F84272FF6330048A358 /* ContentView.swift */, 0CF50F98273008BF0048A358 /* Info.plist */, 0CF50F82272FF6330048A358 /* ProjectionsApp.swift */, ); path = Projections; sourceTree = ""; }; 0CF50F88272FF6340048A358 /* Preview Content */ = { isa = PBXGroup; children = ( 0CF50F89272FF6340048A358 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 227D20F01CB609A1008F641B /* Common */ = { isa = PBXGroup; children = ( 227D20F11CB609A1008F641B /* LaunchScreen.xib */, ); path = Common; sourceTree = ""; }; 493759A5230D8CA60078C28E /* ListSwiftUI */ = { isa = PBXGroup; children = ( 493759AE230D8CAA0078C28E /* Preview Content */, 49E693B22317FD3100189AAE /* Views */, 493759AC230D8CAA0078C28E /* Assets.xcassets */, 493759B4230D8CAA0078C28E /* Info.plist */, ); path = ListSwiftUI; sourceTree = ""; }; 493759AE230D8CAA0078C28E /* Preview Content */ = { isa = PBXGroup; children = ( 493759AF230D8CAA0078C28E /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 493759B8230D9F9A0078C28E /* Frameworks */ = { isa = PBXGroup; children = ( 3F7B50612D12042D004B792D /* Realm.framework */, 0CF50F99273008DF0048A358 /* Realm.framework */, AC2D0C15269C471200126DCF /* Realm.framework */, AC2D0C0E269C470900126DCF /* Realm.framework */, CFF1724924B7310D00A16A59 /* Realm.framework */, CFEDF01D24B72B67007FF10A /* Realm.framework */, 493759B9230D9F9A0078C28E /* Realm.framework */, 3F7B50622D12042D004B792D /* RealmSwift.framework */, 0CF50F9A273008DF0048A358 /* RealmSwift.framework */, AC2D0C16269C471200126DCF /* RealmSwift.framework */, AC2D0C0F269C470900126DCF /* RealmSwift.framework */, CFF1724A24B7310E00A16A59 /* RealmSwift.framework */, CFEDF02124B72B6B007FF10A /* RealmSwift.framework */, 493759BD230D9F9D0078C28E /* RealmSwift.framework */, ); name = Frameworks; sourceTree = ""; }; 49E693B22317FD3100189AAE /* Views */ = { isa = PBXGroup; children = ( 53379DB525CB0E020008A269 /* App.swift */, 493759AA230D8CA60078C28E /* ContentView.swift */, ); path = Views; sourceTree = ""; }; 53454CB824EAC3F900678E48 /* AppClip */ = { isa = PBXGroup; children = ( 53454CBF24EAC3FA00678E48 /* Preview Content */, 53454CB924EAC3F900678E48 /* AppClipApp.swift */, 53454CBD24EAC3FA00678E48 /* Assets.xcassets */, 3FA2F2FE268FCA380015062E /* Constants.swift */, 3FA2F2FD268FCA380015062E /* DemoObject.swift */, 53454CC224EAC3FA00678E48 /* Info.plist */, ); path = AppClip; sourceTree = ""; }; 53454CBF24EAC3FA00678E48 /* Preview Content */ = { isa = PBXGroup; children = ( 53454CC024EAC3FA00678E48 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 53454CCB24EAC43500678E48 /* AppClipParent */ = { isa = PBXGroup; children = ( 53454CD224EAC43700678E48 /* Preview Content */, 53454CCC24EAC43500678E48 /* AppClipParentApp.swift */, 53454CD024EAC43700678E48 /* Assets.xcassets */, 53454CCE24EAC43500678E48 /* ContentView.swift */, 53454CD524EAC43700678E48 /* Info.plist */, ); path = AppClipParent; sourceTree = ""; }; 53454CD224EAC43700678E48 /* Preview Content */ = { isa = PBXGroup; children = ( 53454CD324EAC43700678E48 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 7D46396525C30B580089EFD9 /* Examples */ = { isa = PBXGroup; children = ( 7DFFB15D25C19F0700CA8AE5 /* Example_v0.swift */, 7DFFB16E25C1B65A00CA8AE5 /* Example_v1.swift */, 7DFFB17625C1B84E00CA8AE5 /* Example_v2.swift */, 7D46394F25C2E2750089EFD9 /* Example_v3.swift */, 7D46399725C45FF00089EFD9 /* Example_v4.swift */, 7D46399F25C46C660089EFD9 /* Example_v5.swift */, ); path = Examples; sourceTree = ""; }; 7D46396A25C30B7F0089EFD9 /* RealmTemplates */ = { isa = PBXGroup; children = ( 7DADCB9F25C816B500048287 /* default-v0.realm */, 7DADCBA025C816B500048287 /* default-v1.realm */, 7DADCBA425C816B600048287 /* default-v2.realm */, 7DADCBA125C816B500048287 /* default-v3.realm */, 7DADCBA225C816B500048287 /* default-v4.realm */, 7DADCBA325C816B500048287 /* default-v5.realm */, ); path = RealmTemplates; sourceTree = ""; }; E805758C19BA55E600376620 = { isa = PBXGroup; children = ( 53454CB824EAC3F900678E48 /* AppClip */, 53454CCB24EAC43500678E48 /* AppClipParent */, E8D3F2751A11766A00620884 /* Backlink */, 227D20F01CB609A1008F641B /* Common */, E8CB087F19BA6AEE0018434A /* Encryption */, 493759B8230D9F9A0078C28E /* Frameworks */, E886FB621A12A73E00CB2D0B /* GroupedTableView */, 493759A5230D8CA60078C28E /* ListSwiftUI */, E8CB088419BA6AF70018434A /* Migration */, E8F14D0B1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper */, E805759619BA55E600376620 /* Products */, 0CF50F81272FF6330048A358 /* Projections */, 0295B90519D103180036D6C3 /* RealmSwift */, E8CB088F19BA6B000018434A /* Simple */, E8CB089419BA6B080018434A /* TableView */, 9C318E8C1CA42C7800879076 /* GettingStarted.playground */, 0A73D4411B1443A700E1E8EE /* README.md */, ); sourceTree = ""; }; E805759619BA55E600376620 /* Products */ = { isa = PBXGroup; children = ( 53454CDD24EAC45500678E48 /* AppClip.app */, 53454CCA24EAC43500678E48 /* AppClipParent.app */, E8D3F2531A11765200620884 /* Backlink.app */, E8CB07EF19BA6AB60018434A /* Encryption.app */, E886FB601A12A6FC00CB2D0B /* GroupedTableView.app */, 493759A4230D8CA60078C28E /* ListSwiftUI.app */, E8CB081419BA6ABD0018434A /* Migration.app */, E8F14D0A1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper.framework */, 0CF50F80272FF6330048A358 /* Projections.app */, E8CB085E19BA6ACA0018434A /* Simple.app */, E8CB083919BA6AC60018434A /* TableView.app */, ); name = Products; sourceTree = ""; }; E886FB621A12A73E00CB2D0B /* GroupedTableView */ = { isa = PBXGroup; children = ( E886FB631A12A73E00CB2D0B /* AppDelegate.swift */, E886FB641A12A73E00CB2D0B /* Images.xcassets */, E886FB651A12A73E00CB2D0B /* Info.plist */, E886FB661A12A73E00CB2D0B /* TableViewController.swift */, ); path = GroupedTableView; sourceTree = ""; }; E8CB087F19BA6AEE0018434A /* Encryption */ = { isa = PBXGroup; children = ( E8CB088019BA6AEE0018434A /* AppDelegate.swift */, E8CB088119BA6AEE0018434A /* Info.plist */, 3FC898FE1A1414110067CBEC /* ViewController.swift */, ); path = Encryption; sourceTree = ""; }; E8CB088419BA6AF70018434A /* Migration */ = { isa = PBXGroup; children = ( 7D46396525C30B580089EFD9 /* Examples */, 7D46396A25C30B7F0089EFD9 /* RealmTemplates */, E8CB088519BA6AF70018434A /* AppDelegate.swift */, E8CB088919BA6AF70018434A /* Info.plist */, 7DADCBB125C8284500048287 /* Migration.xcconfig */, 7D46395325C2E7140089EFD9 /* README.md */, ); path = Migration; sourceTree = ""; }; E8CB088F19BA6B000018434A /* Simple */ = { isa = PBXGroup; children = ( E8CB089019BA6B000018434A /* AppDelegate.swift */, E8CB089119BA6B000018434A /* Info.plist */, ); path = Simple; sourceTree = ""; }; E8CB089419BA6B080018434A /* TableView */ = { isa = PBXGroup; children = ( E8CB089519BA6B080018434A /* AppDelegate.swift */, E8CB089619BA6B080018434A /* Images.xcassets */, E8CB089719BA6B080018434A /* Info.plist */, E8CB089819BA6B080018434A /* TableViewController.swift */, ); path = TableView; sourceTree = ""; }; E8D3F2751A11766A00620884 /* Backlink */ = { isa = PBXGroup; children = ( E8D3F2761A11766A00620884 /* AppDelegate.swift */, E8D3F2771A11766A00620884 /* Info.plist */, ); path = Backlink; sourceTree = ""; }; E8F14D0B1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper */ = { isa = PBXGroup; children = ( E8F14D0E1CF8FD0C00564AF5 /* Info.plist */, E8F14D121CF8FD7F00564AF5 /* PlaygroundFrameworkWrapper.swift */, ); path = PlaygroundFrameworkWrapper; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0CF50F7F272FF6330048A358 /* Projections */ = { isa = PBXNativeTarget; buildConfigurationList = 0CF50F8B272FF6340048A358 /* Build configuration list for PBXNativeTarget "Projections" */; buildPhases = ( 0CF50F7C272FF6330048A358 /* Sources */, 0CF50F7D272FF6330048A358 /* Frameworks */, 0CF50F7E272FF6330048A358 /* Resources */, 3F7B50732D120FFB004B792D /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Projections; productName = Projections; productReference = 0CF50F80272FF6330048A358 /* Projections.app */; productType = "com.apple.product-type.application"; }; 493759A3230D8CA60078C28E /* ListSwiftUI */ = { isa = PBXNativeTarget; buildConfigurationList = 493759B7230D8CAA0078C28E /* Build configuration list for PBXNativeTarget "ListSwiftUI" */; buildPhases = ( 493759A0230D8CA60078C28E /* Sources */, 493759A1230D8CA60078C28E /* Frameworks */, 493759A2230D8CA60078C28E /* Resources */, 3F7B50852D121063004B792D /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = ListSwiftUI; productName = ListSwiftUI; productReference = 493759A4230D8CA60078C28E /* ListSwiftUI.app */; productType = "com.apple.product-type.application"; }; 53454CC924EAC43500678E48 /* AppClipParent */ = { isa = PBXNativeTarget; buildConfigurationList = 53454CD624EAC43700678E48 /* Build configuration list for PBXNativeTarget "AppClipParent" */; buildPhases = ( 53454CC624EAC43500678E48 /* Sources */, 53454CC724EAC43500678E48 /* Frameworks */, 53454CC824EAC43500678E48 /* Resources */, 53454CF024EAC45700678E48 /* Embed App Clips */, ); buildRules = ( ); dependencies = ( 53454CEB24EAC45700678E48 /* PBXTargetDependency */, ); name = AppClipParent; productName = AppClipParent; productReference = 53454CCA24EAC43500678E48 /* AppClipParent.app */; productType = "com.apple.product-type.application"; }; 53454CDC24EAC45500678E48 /* AppClip */ = { isa = PBXNativeTarget; buildConfigurationList = 53454CED24EAC45700678E48 /* Build configuration list for PBXNativeTarget "AppClip" */; buildPhases = ( 53454CD924EAC45500678E48 /* Sources */, 53454CDA24EAC45500678E48 /* Frameworks */, 53454CDB24EAC45500678E48 /* Resources */, 3F7B506E2D120F3D004B792D /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = AppClip; productName = AppClip; productReference = 53454CDD24EAC45500678E48 /* AppClip.app */; productType = "com.apple.product-type.application.on-demand-install-capable"; }; E886FB511A12A6FC00CB2D0B /* GroupedTableView */ = { isa = PBXNativeTarget; buildConfigurationList = E886FB5D1A12A6FC00CB2D0B /* Build configuration list for PBXNativeTarget "GroupedTableView" */; buildPhases = ( E886FB541A12A6FC00CB2D0B /* Sources */, E886FB581A12A6FC00CB2D0B /* Frameworks */, E886FB5B1A12A6FC00CB2D0B /* Resources */, C01304E51A60A6CE00EB3E1E /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = GroupedTableView; productName = TableView; productReference = E886FB601A12A6FC00CB2D0B /* GroupedTableView.app */; productType = "com.apple.product-type.application"; }; E8CB07EE19BA6AB60018434A /* Encryption */ = { isa = PBXNativeTarget; buildConfigurationList = E8CB080E19BA6AB70018434A /* Build configuration list for PBXNativeTarget "Encryption" */; buildPhases = ( E8CB07EB19BA6AB60018434A /* Sources */, E8CB07EC19BA6AB60018434A /* Frameworks */, C01304E91A60B77600EB3E1E /* CopyFiles */, 227D20941CB4EEE7008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Encryption; productName = Encryption; productReference = E8CB07EF19BA6AB60018434A /* Encryption.app */; productType = "com.apple.product-type.application"; }; E8CB081319BA6ABD0018434A /* Migration */ = { isa = PBXNativeTarget; buildConfigurationList = E8CB082F19BA6ABE0018434A /* Build configuration list for PBXNativeTarget "Migration" */; buildPhases = ( E8CB081019BA6ABD0018434A /* Sources */, E8CB081119BA6ABD0018434A /* Frameworks */, E8CB081219BA6ABD0018434A /* Resources */, C01304E71A60A6EC00EB3E1E /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = Migration; productName = Migration; productReference = E8CB081419BA6ABD0018434A /* Migration.app */; productType = "com.apple.product-type.application"; }; E8CB083819BA6AC60018434A /* TableView */ = { isa = PBXNativeTarget; buildConfigurationList = E8CB085419BA6AC70018434A /* Build configuration list for PBXNativeTarget "TableView" */; buildPhases = ( E8CB083519BA6AC60018434A /* Sources */, E8CB083619BA6AC60018434A /* Frameworks */, E8CB083719BA6AC60018434A /* Resources */, C09C491C1A6068E500638C9F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = TableView; productName = TableView; productReference = E8CB083919BA6AC60018434A /* TableView.app */; productType = "com.apple.product-type.application"; }; E8CB085D19BA6ACA0018434A /* Simple */ = { isa = PBXNativeTarget; buildConfigurationList = E8CB087919BA6ACB0018434A /* Build configuration list for PBXNativeTarget "Simple" */; buildPhases = ( E8CB085A19BA6ACA0018434A /* Sources */, E8CB085B19BA6ACA0018434A /* Frameworks */, C09C490D1A6059BA00638C9F /* CopyFiles */, 227D209D1CB4F451008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Simple; productName = Simple; productReference = E8CB085E19BA6ACA0018434A /* Simple.app */; productType = "com.apple.product-type.application"; }; E8D3F2521A11765200620884 /* Backlink */ = { isa = PBXNativeTarget; buildConfigurationList = E8D3F2731A11765200620884 /* Build configuration list for PBXNativeTarget "Backlink" */; buildPhases = ( E8D3F24F1A11765200620884 /* Sources */, E8D3F2501A11765200620884 /* Frameworks */, C01304E31A60A4A000EB3E1E /* CopyFiles */, 227D20911CB4EB18008F641B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Backlink; productName = Backlink; productReference = E8D3F2531A11765200620884 /* Backlink.app */; productType = "com.apple.product-type.application"; }; E8F14D091CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper */ = { isa = PBXNativeTarget; buildConfigurationList = E8F14D111CF8FD0C00564AF5 /* Build configuration list for PBXNativeTarget "PlaygroundFrameworkWrapper" */; buildPhases = ( E8F14D051CF8FD0C00564AF5 /* Sources */, E8F14D061CF8FD0C00564AF5 /* Frameworks */, E8F14D141CF900E500564AF5 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = PlaygroundFrameworkWrapper; productName = PlaygroundFrameworkWrapper; productReference = E8F14D0A1CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E805758D19BA55E600376620 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1310; LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { 0CF50F7F272FF6330048A358 = { CreatedOnToolsVersion = 13.1; DevelopmentTeam = QX5CR2FTN2; ProvisioningStyle = Automatic; }; 493759A3230D8CA60078C28E = { CreatedOnToolsVersion = 11.0; DevelopmentTeam = 2Z8M9MTEVV; ProvisioningStyle = Automatic; }; 53454CC924EAC43500678E48 = { CreatedOnToolsVersion = 12.0; ProvisioningStyle = Automatic; }; 53454CDC24EAC45500678E48 = { CreatedOnToolsVersion = 12.0; LastSwiftMigration = 1300; ProvisioningStyle = Automatic; }; E8CB07EE19BA6AB60018434A = { CreatedOnToolsVersion = 6.0; }; E8CB081319BA6ABD0018434A = { CreatedOnToolsVersion = 6.0; }; E8CB083819BA6AC60018434A = { CreatedOnToolsVersion = 6.0; }; E8CB085D19BA6ACA0018434A = { CreatedOnToolsVersion = 6.0; }; E8D3F2521A11765200620884 = { CreatedOnToolsVersion = 6.1; LastSwiftMigration = 1130; }; E8F14D091CF8FD0C00564AF5 = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = E805759019BA55E600376620 /* Build configuration list for PBXProject "RealmExamples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = E805758C19BA55E600376620; productRefGroup = E805759619BA55E600376620 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( E8D3F2521A11765200620884 /* Backlink */, E8CB07EE19BA6AB60018434A /* Encryption */, E886FB511A12A6FC00CB2D0B /* GroupedTableView */, E8CB081319BA6ABD0018434A /* Migration */, E8CB085D19BA6ACA0018434A /* Simple */, E8CB083819BA6AC60018434A /* TableView */, E8F14D091CF8FD0C00564AF5 /* PlaygroundFrameworkWrapper */, 493759A3230D8CA60078C28E /* ListSwiftUI */, 53454CC924EAC43500678E48 /* AppClipParent */, 53454CDC24EAC45500678E48 /* AppClip */, 0CF50F7F272FF6330048A358 /* Projections */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0CF50F7E272FF6330048A358 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0CF50F87272FF6340048A358 /* Assets.xcassets in Resources */, 0CF50F8A272FF6340048A358 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D20911CB4EB18008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20F21CB609A1008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D20941CB4EEE7008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20F31CB609A1008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 227D209D1CB4F451008F641B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 227D20F61CB609A1008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 493759A2230D8CA60078C28E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 493759AD230D8CAA0078C28E /* Assets.xcassets in Resources */, 493759B0230D8CAA0078C28E /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53454CC824EAC43500678E48 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 53454CD124EAC43700678E48 /* Assets.xcassets in Resources */, 53454CD424EAC43700678E48 /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53454CDB24EAC45500678E48 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; E886FB5B1A12A6FC00CB2D0B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E886FB681A12A73E00CB2D0B /* Images.xcassets in Resources */, 227D20F41CB609A1008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB081219BA6ABD0018434A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7DADCBA525C816B600048287 /* default-v0.realm in Resources */, 7DADCBA625C816B600048287 /* default-v1.realm in Resources */, 7DADCBAA25C816B600048287 /* default-v2.realm in Resources */, 7DADCBA725C816B600048287 /* default-v3.realm in Resources */, 7DADCBA825C816B600048287 /* default-v4.realm in Resources */, 7DADCBA925C816B600048287 /* default-v5.realm in Resources */, 227D20F51CB609A1008F641B /* LaunchScreen.xib in Resources */, 7DADCBB225C8284500048287 /* Migration.xcconfig in Resources */, 7D46395425C2E7140089EFD9 /* README.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB083719BA6AC60018434A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( E8CB089A19BA6B080018434A /* Images.xcassets in Resources */, 227D20F71CB609A1008F641B /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0CF50F7C272FF6330048A358 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0CF50F85272FF6330048A358 /* ContentView.swift in Sources */, 0CF50F83272FF6330048A358 /* ProjectionsApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 493759A0230D8CA60078C28E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 53379DB625CB0E020008A269 /* App.swift in Sources */, 493759AB230D8CA60078C28E /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53454CC624EAC43500678E48 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 53454CCD24EAC43500678E48 /* AppClipParentApp.swift in Sources */, 3FA2F301268FCA960015062E /* Constants.swift in Sources */, 53454CCF24EAC43500678E48 /* ContentView.swift in Sources */, 3FA2F302268FCA960015062E /* DemoObject.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 53454CD924EAC45500678E48 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3FA2F303268FCAAD0015062E /* AppClipApp.swift in Sources */, 3FA2F300268FCA380015062E /* Constants.swift in Sources */, 3FA2F304268FCAC80015062E /* ContentView.swift in Sources */, 3FA2F2FF268FCA380015062E /* DemoObject.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E886FB541A12A6FC00CB2D0B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E886FB671A12A73E00CB2D0B /* AppDelegate.swift in Sources */, E886FB6A1A12A73E00CB2D0B /* TableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB07EB19BA6AB60018434A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8CB088219BA6AEE0018434A /* AppDelegate.swift in Sources */, 3FC898FF1A1414110067CBEC /* ViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB081019BA6ABD0018434A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8CB088A19BA6AF70018434A /* AppDelegate.swift in Sources */, 7DFFB15E25C19F0700CA8AE5 /* Example_v0.swift in Sources */, 7DFFB16F25C1B65A00CA8AE5 /* Example_v1.swift in Sources */, 7DFFB17725C1B84E00CA8AE5 /* Example_v2.swift in Sources */, 7D46395025C2E2750089EFD9 /* Example_v3.swift in Sources */, 7D46399825C45FF00089EFD9 /* Example_v4.swift in Sources */, 7D4639A025C46C660089EFD9 /* Example_v5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB083519BA6AC60018434A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8CB089919BA6B080018434A /* AppDelegate.swift in Sources */, E8CB089C19BA6B080018434A /* TableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8CB085A19BA6ACA0018434A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8CB089219BA6B000018434A /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8D3F24F1A11765200620884 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8D3F2781A11766A00620884 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E8F14D051CF8FD0C00564AF5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E8F14D131CF8FD7F00564AF5 /* PlaygroundFrameworkWrapper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 53454CEB24EAC45700678E48 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 53454CDC24EAC45500678E48 /* AppClip */; targetProxy = 53454CEA24EAC45700678E48 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 0CF50F8C272FF6340048A358 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_ASSET_PATHS = "\"Projections/Preview Content\""; DEVELOPMENT_TEAM = QX5CR2FTN2; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Projections/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.Projections; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0CF50F8D272FF6340048A358 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Projections/Preview Content\""; DEVELOPMENT_TEAM = QX5CR2FTN2; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Projections/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.Projections; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 493759B5230D8CAA0078C28E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_ASSET_PATHS = "ListSwiftUI/Preview\\ Content"; DEVELOPMENT_TEAM = 2Z8M9MTEVV; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ListSwiftUI/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.ListSwiftUI; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 493759B6230D8CAA0078C28E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "ListSwiftUI/Preview\\ Content"; DEVELOPMENT_TEAM = 2Z8M9MTEVV; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ListSwiftUI/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.ListSwiftUI; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 53454CD724EAC43700678E48 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_ASSET_PATHS = "\"AppClipParent/Preview Content\""; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = AppClipParent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppClipParent; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 53454CD824EAC43700678E48 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"AppClipParent/Preview Content\""; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = AppClipParent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppClipParent; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 53454CEE24EAC45700678E48 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = AppClip/AppClip.entitlements; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_ASSET_PATHS = "\"AppClip/Preview Content\""; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = AppClip/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppClipParent.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 53454CEF24EAC45700678E48 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = AppClip/AppClip.entitlements; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"AppClip/Preview Content\""; ENABLE_PREVIEWS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = AppClip/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.realm.AppClipParent.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; E80575AF19BA55E700376620 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; E80575B019BA55E700376620 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; E886FB5E1A12A6FC00CB2D0B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/GroupedTableView/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = GroupedTableView; }; name = Debug; }; E886FB5F1A12A6FC00CB2D0B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/GroupedTableView/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = GroupedTableView; }; name = Release; }; E8CB080A19BA6AB70018434A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Encryption/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8CB080B19BA6AB70018434A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = Encryption/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8CB083019BA6ABE0018434A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7DADCBB125C8284500048287 /* Migration.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Migration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8CB083119BA6ABE0018434A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7DADCBB125C8284500048287 /* Migration.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = Migration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8CB085519BA6AC70018434A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = TableView/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8CB085619BA6AC70018434A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = TableView/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8CB087A19BA6ACB0018434A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Simple/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8CB087B19BA6ACB0018434A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = Simple/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8D3F26F1A11765200620884 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Backlink/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E8D3F2701A11765200620884 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Backlink/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.realm.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8F14D0F1CF8FD0C00564AF5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PlaygroundFrameworkWrapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PlaygroundFrameworkWrapper; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; E8F14D101CF8FD0C00564AF5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PlaygroundFrameworkWrapper/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PlaygroundFrameworkWrapper; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0CF50F8B272FF6340048A358 /* Build configuration list for PBXNativeTarget "Projections" */ = { isa = XCConfigurationList; buildConfigurations = ( 0CF50F8C272FF6340048A358 /* Debug */, 0CF50F8D272FF6340048A358 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 493759B7230D8CAA0078C28E /* Build configuration list for PBXNativeTarget "ListSwiftUI" */ = { isa = XCConfigurationList; buildConfigurations = ( 493759B5230D8CAA0078C28E /* Debug */, 493759B6230D8CAA0078C28E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 53454CD624EAC43700678E48 /* Build configuration list for PBXNativeTarget "AppClipParent" */ = { isa = XCConfigurationList; buildConfigurations = ( 53454CD724EAC43700678E48 /* Debug */, 53454CD824EAC43700678E48 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 53454CED24EAC45700678E48 /* Build configuration list for PBXNativeTarget "AppClip" */ = { isa = XCConfigurationList; buildConfigurations = ( 53454CEE24EAC45700678E48 /* Debug */, 53454CEF24EAC45700678E48 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E805759019BA55E600376620 /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( E80575AF19BA55E700376620 /* Debug */, E80575B019BA55E700376620 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E886FB5D1A12A6FC00CB2D0B /* Build configuration list for PBXNativeTarget "GroupedTableView" */ = { isa = XCConfigurationList; buildConfigurations = ( E886FB5E1A12A6FC00CB2D0B /* Debug */, E886FB5F1A12A6FC00CB2D0B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8CB080E19BA6AB70018434A /* Build configuration list for PBXNativeTarget "Encryption" */ = { isa = XCConfigurationList; buildConfigurations = ( E8CB080A19BA6AB70018434A /* Debug */, E8CB080B19BA6AB70018434A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8CB082F19BA6ABE0018434A /* Build configuration list for PBXNativeTarget "Migration" */ = { isa = XCConfigurationList; buildConfigurations = ( E8CB083019BA6ABE0018434A /* Debug */, E8CB083119BA6ABE0018434A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8CB085419BA6AC70018434A /* Build configuration list for PBXNativeTarget "TableView" */ = { isa = XCConfigurationList; buildConfigurations = ( E8CB085519BA6AC70018434A /* Debug */, E8CB085619BA6AC70018434A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8CB087919BA6ACB0018434A /* Build configuration list for PBXNativeTarget "Simple" */ = { isa = XCConfigurationList; buildConfigurations = ( E8CB087A19BA6ACB0018434A /* Debug */, E8CB087B19BA6ACB0018434A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8D3F2731A11765200620884 /* Build configuration list for PBXNativeTarget "Backlink" */ = { isa = XCConfigurationList; buildConfigurations = ( E8D3F26F1A11765200620884 /* Debug */, E8D3F2701A11765200620884 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8F14D111CF8FD0C00564AF5 /* Build configuration list for PBXNativeTarget "PlaygroundFrameworkWrapper" */ = { isa = XCConfigurationList; buildConfigurations = ( E8F14D0F1CF8FD0C00564AF5 /* Debug */, E8F14D101CF8FD0C00564AF5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = E805758D19BA55E600376620 /* Project object */; } ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/AppClip.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/Backlink.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/Encryption.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/GroupedTableView.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/Migration.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/Simple.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/TableView.xcscheme ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/ios/swift/RealmExamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: examples/ios/swift/RealmExamples.xcworkspace/xcshareddata/RealmExamples.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : 9223372036854775807, "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "382D912A-B3A9-46DF-8DD9-1DF0D7888DE7", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : "realm-cocoa\/", "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-cocoa\/Realm\/ObjectStore\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "RealmExamples", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "examples\/ios\/swift-3.0\/RealmExamples.xcworkspace", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:realm\/realm-object-store-private.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:realm\/realm-cocoa.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" } ] } ================================================ FILE: examples/ios/swift/RealmExamples.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: examples/ios/swift/Simple/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class Dog: Object { @Persisted var name: String @Persisted var age: Int } class Person: Object { @Persisted var name: String @Persisted var dogs: List } @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UIViewController() window?.makeKeyAndVisible() _ = try! Realm.deleteFiles(for: Realm.Configuration.defaultConfiguration) // Create a standalone object let mydog = Dog() // Set & read properties mydog.name = "Rex" mydog.age = 9 print("Name of dog: \(mydog.name)") // Realms are used to group data together let realm = try! Realm() // Create realm pointing to default file // Save your object realm.beginWrite() realm.add(mydog) try! realm.commitWrite() // Query let results = realm.objects(Dog.self).filter("name contains 'x'") // Queries are chainable! let results2 = results.filter("age > 8") print("Number of dogs: \(results.count)") print("Dogs older than eight: \(results2.count)") // Link objects let person = Person() person.name = "Tim" person.dogs.append(mydog) try! realm.write { realm.add(person) } // Multi-threading DispatchQueue.global().async { autoreleasepool { let otherRealm = try! Realm() let otherResults = otherRealm.objects(Dog.self).filter("name contains 'Rex'") print("Number of dogs \(otherResults.count)") } } return true } } ================================================ FILE: examples/ios/swift/Simple/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/TableView/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UINavigationController(rootViewController: TableViewController(style: .plain)) window?.makeKeyAndVisible() return true } } ================================================ FILE: examples/ios/swift/TableView/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: examples/ios/swift/TableView/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/ios/swift/TableView/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen ================================================ FILE: examples/ios/swift/TableView/TableViewController.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class DemoObject: Object { @Persisted var title: String @Persisted var date: Date } class Cell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) } required init(coder: NSCoder) { fatalError("NSCoding not supported") } } class TableViewController: UITableViewController { let realm = try! Realm() let results = try! Realm().objects(DemoObject.self).sorted(byKeyPath: "date") var notificationToken: NotificationToken? override func viewDidLoad() { super.viewDidLoad() setupUI() // Set results notification block self.notificationToken = results.observe { (changes: RealmCollectionChange) in switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI self.tableView.reloadData() case .update(_, let deletions, let insertions, let modifications): // Query results have changed, so apply them to the TableView self.tableView.beginUpdates() self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic) self.tableView.endUpdates() case .error(let err): // An error occurred while opening the Realm file on the background worker thread fatalError("\(err)") } } } // UI func setupUI() { tableView.register(Cell.self, forCellReuseIdentifier: "cell") self.title = "TableView" self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "BG Add", style: .plain, target: self, action: #selector(backgroundAdd)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add)) } // Table view data source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return results.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell let object = results[indexPath.row] cell.textLabel?.text = object.title cell.detailTextLabel?.text = object.date.description return cell } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { realm.beginWrite() realm.delete(results[indexPath.row]) try! realm.commitWrite() } } // Actions @objc func backgroundAdd() { // Import many items in a background thread DispatchQueue.global().async { // Get new realm and table since we are in a new thread autoreleasepool { let realm = try! Realm() realm.beginWrite() for _ in 0..<5 { // Add row via dictionary. Order is ignored. realm.create(DemoObject.self, value: ["title": TableViewController.randomString(), "date": TableViewController.randomDate()]) } try! realm.commitWrite() } } } @objc func add() { realm.beginWrite() realm.create(DemoObject.self, value: [TableViewController.randomString(), TableViewController.randomDate()]) try! realm.commitWrite() } // Helpers class func randomString() -> String { return "Title \(Int.random(in: 0..<100))" } class func randomDate() -> NSDate { return NSDate(timeIntervalSince1970: TimeInterval.random(in: 0.. @interface Person : RLMObject // Add properties here to define the model @property NSString *fullName; @property NSDate *birthdate; @property NSInteger numberOfFriends; @end // This protocol enables typed collections. i.e.: // RLMArray RLM_COLLECTION_TYPE(Person) ================================================ FILE: examples/osx/objc/JSONImport/Person.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "Person.h" @implementation Person // Specify default values for properties //+ (NSDictionary *)defaultPropertyValues //{ // return @{}; //} // Specify properties to ignore (Realm won't persist these) //+ (NSArray *)ignoredProperties //{ // return @[]; //} - (NSString *)description { return [NSString stringWithFormat:@"%@ was born on %@ and had %ld friends", self.fullName, self.birthdate, (long)self.numberOfFriends]; } @end ================================================ FILE: examples/osx/objc/JSONImport/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Import JSON NSString *jsonFilePath = [[NSBundle mainBundle] pathForResource:@"persons" ofType:@"json"]; NSData *jsonData = [NSData dataWithContentsOfFile:jsonFilePath]; NSError *error = nil; NSArray *personDicts = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (error) { NSLog(@"There was an error reading the JSON file: %@", error.localizedDescription); return 1; } NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; dateFormatter.dateFormat = @"MMMM dd, yyyy"; dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; [[NSFileManager defaultManager] removeItemAtURL:[RLMRealmConfiguration defaultConfiguration].fileURL error:nil]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // Add Person objects in realm for every person dictionary in JSON array for (NSDictionary *personDict in personDicts) { Person *person = [[Person alloc] init]; person.fullName = personDict[@"name"]; person.birthdate = [dateFormatter dateFromString:personDict[@"birthdate"]]; person.numberOfFriends = [(NSNumber *)personDict[@"friendCount"] integerValue]; [realm addObject:person]; } [realm commitWriteTransaction]; // Print all persons from realm for (Person *person in [Person allObjects]) { NSLog(@"person persisted to realm: %@", person); } // Realm file saved at default path (~/Documents/default.realm) } return 0; } ================================================ FILE: examples/osx/objc/JSONImport/persons.json ================================================ [ { "name": "John Coltrane", "birthdate": "September 23, 1926", "friendCount": 123456 }, { "name": "Miles Davis", "birthdate": "May 26, 1926", "friendCount": 234567 }, { "name": "Duke Ellington", "birthdate": "April 29, 1899", "friendCount": 345678 } ] ================================================ FILE: examples/osx/objc/RealmExamples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ E823C33D19BA4A5F00D2FF5F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F1E82019BA4A3800FAD64E /* Foundation.framework */; }; E823C34D19BA4A7600D2FF5F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E823C34919BA4A7600D2FF5F /* main.m */; }; E823C34E19BA4A7600D2FF5F /* Person.m in Sources */ = {isa = PBXBuildFile; fileRef = E823C34B19BA4A7600D2FF5F /* Person.m */; }; E823C35119BA4B7500D2FF5F /* persons.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = E823C34C19BA4A7600D2FF5F /* persons.json */; }; E823C35319BA4B8600D2FF5F /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E823C35219BA4B8600D2FF5F /* libc++.dylib */; }; E85A517119BA76B7006C63CB /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E85A517019BA76B7006C63CB /* Realm.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ E823C33A19BA4A5F00D2FF5F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 16; files = ( E823C35119BA4B7500D2FF5F /* persons.json in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0ADF47111B1578CF00F67B16 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = SOURCE_ROOT; }; E823C33C19BA4A5F00D2FF5F /* JSONImport */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = JSONImport; sourceTree = BUILT_PRODUCTS_DIR; }; E823C34919BA4A7600D2FF5F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; E823C34A19BA4A7600D2FF5F /* Person.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = ""; }; E823C34B19BA4A7600D2FF5F /* Person.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = ""; }; E823C34C19BA4A7600D2FF5F /* persons.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = persons.json; sourceTree = ""; }; E823C35219BA4B8600D2FF5F /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; }; E85A517019BA76B7006C63CB /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E8F1E82019BA4A3800FAD64E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ E823C33919BA4A5F00D2FF5F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E823C33D19BA4A5F00D2FF5F /* Foundation.framework in Frameworks */, E823C35319BA4B8600D2FF5F /* libc++.dylib in Frameworks */, E85A517119BA76B7006C63CB /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ E823C34819BA4A7600D2FF5F /* JSONImport */ = { isa = PBXGroup; children = ( E823C34919BA4A7600D2FF5F /* main.m */, E823C34A19BA4A7600D2FF5F /* Person.h */, E823C34B19BA4A7600D2FF5F /* Person.m */, E823C34C19BA4A7600D2FF5F /* persons.json */, ); path = JSONImport; sourceTree = ""; }; E8F1E81419BA4A3800FAD64E = { isa = PBXGroup; children = ( E8F1E81F19BA4A3800FAD64E /* Frameworks */, E823C34819BA4A7600D2FF5F /* JSONImport */, E8F1E81E19BA4A3800FAD64E /* Products */, 0ADF47111B1578CF00F67B16 /* README.md */, ); sourceTree = ""; }; E8F1E81E19BA4A3800FAD64E /* Products */ = { isa = PBXGroup; children = ( E823C33C19BA4A5F00D2FF5F /* JSONImport */, ); name = Products; sourceTree = ""; }; E8F1E81F19BA4A3800FAD64E /* Frameworks */ = { isa = PBXGroup; children = ( E8F1E82019BA4A3800FAD64E /* Foundation.framework */, E823C35219BA4B8600D2FF5F /* libc++.dylib */, E85A517019BA76B7006C63CB /* Realm.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ E823C33B19BA4A5F00D2FF5F /* JSONImport */ = { isa = PBXNativeTarget; buildConfigurationList = E823C34719BA4A5F00D2FF5F /* Build configuration list for PBXNativeTarget "JSONImport" */; buildPhases = ( E823C33819BA4A5F00D2FF5F /* Sources */, E823C33919BA4A5F00D2FF5F /* Frameworks */, E823C33A19BA4A5F00D2FF5F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = JSONImport; productName = JSONImport; productReference = E823C33C19BA4A5F00D2FF5F /* JSONImport */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E8F1E81519BA4A3800FAD64E /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; }; buildConfigurationList = E8F1E81819BA4A3800FAD64E /* Build configuration list for PBXProject "RealmExamples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, ); mainGroup = E8F1E81419BA4A3800FAD64E; productRefGroup = E8F1E81E19BA4A3800FAD64E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( E823C33B19BA4A5F00D2FF5F /* JSONImport */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ E823C33819BA4A5F00D2FF5F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E823C34D19BA4A7600D2FF5F /* main.m in Sources */, E823C34E19BA4A7600D2FF5F /* Person.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ E823C34519BA4A5F00D2FF5F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; E823C34619BA4A5F00D2FF5F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; E8F1E82919BA4A3900FAD64E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; E8F1E82A19BA4A3900FAD64E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ E823C34719BA4A5F00D2FF5F /* Build configuration list for PBXNativeTarget "JSONImport" */ = { isa = XCConfigurationList; buildConfigurations = ( E823C34519BA4A5F00D2FF5F /* Debug */, E823C34619BA4A5F00D2FF5F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8F1E81819BA4A3800FAD64E /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( E8F1E82919BA4A3900FAD64E /* Debug */, E8F1E82A19BA4A3900FAD64E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = E8F1E81519BA4A3800FAD64E /* Project object */; } ================================================ FILE: examples/osx/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/JSONImport.xcscheme ================================================ ================================================ FILE: examples/osx/objc/RealmExamples.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/osx/objc/RealmExamples.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: examples/tvos/objc/DownloadCache/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/tvos/objc/DownloadCache/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } @end ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json ================================================ { "assets" : [ { "size" : "1280x768", "idiom" : "tv", "filename" : "App Icon - Large.imagestack", "role" : "primary-app-icon" }, { "size" : "400x240", "idiom" : "tv", "filename" : "App Icon - Small.imagestack", "role" : "primary-app-icon" }, { "size" : "1920x720", "idiom" : "tv", "filename" : "Top Shelf Image.imageset", "role" : "top-shelf-image" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "landscape", "idiom" : "tv", "extent" : "full-screen", "minimum-system-version" : "9.0", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/DownloadCache/Base.lproj/Main.storyboard ================================================ ================================================ FILE: examples/tvos/objc/DownloadCache/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 ================================================ FILE: examples/tvos/objc/DownloadCache/RepositoriesViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface RepositoriesViewController : UICollectionViewController @end ================================================ FILE: examples/tvos/objc/DownloadCache/RepositoriesViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RepositoriesViewController.h" #import "RepositoryCell.h" #import "Repository.h" @interface RepositoriesViewController () @property (nonatomic, weak) IBOutlet UISegmentedControl *sortOrderControl; @property (nonatomic, weak) IBOutlet UITextField *searchField; @property (nonatomic) RLMResults *results; @property (nonatomic) RLMNotificationToken *token; @end @implementation RepositoriesViewController - (void)dealloc { [self.token invalidate]; } - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.token = [[RLMRealm defaultRealm] addNotificationBlock:^(NSString * _Nonnull notification, RLMRealm * _Nonnull realm) { [weakSelf reloadData]; }]; NSURLComponents *components = [NSURLComponents componentsWithString:@"https://api.github.com/search/repositories"]; components.queryItems = @[[NSURLQueryItem queryItemWithName:@"q" value:@"language:objc"], [NSURLQueryItem queryItemWithName:@"sort" value:@"stars"], [NSURLQueryItem queryItemWithName:@"order" value:@"desc"]]; [[[NSURLSession sharedSession] dataTaskWithURL:components.URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { NSError *jsonError = nil; NSDictionary *repositories = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError]; if (!jsonError) { NSArray *items = repositories[@"items"]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ for (NSDictionary *item in items) { Repository *repository = [Repository new]; repository.identifier = [NSString stringWithFormat:@"%@", item[@"id"]]; repository.name = item[@"name"]; repository.avatarURL = item[@"owner"][@"avatar_url"]; [realm addOrUpdateObject:repository]; } }]; } else { NSLog(@"%@", jsonError); } } else { NSLog(@"%@", error); } }] resume]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.results.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { RepositoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; Repository *repository = self.results[indexPath.item]; cell.titleLabel.text = repository.name; [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:repository.avatarURL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { dispatch_async(dispatch_get_main_queue(), ^{ UIImage *image = [UIImage imageWithData:data]; cell.avatarImageView.image = image; }); } else { NSLog(@"%@", error); } }] resume]; return cell; } - (void)reloadData { self.results = [Repository allObjects]; if (self.searchField.text.length > 0) { self.results = [self.results objectsWhere:@"name contains[c] %@", self.searchField.text]; } self.results = [self.results sortedResultsUsingKeyPath:@"name" ascending:self.sortOrderControl.selectedSegmentIndex == 0]; [self.collectionView reloadData]; } - (IBAction)valueChanged:(id)sender { [self reloadData]; } - (IBAction)clearSearchField:(id)sender { self.searchField.text = nil; [self reloadData]; } - (void)textFieldDidEndEditing:(UITextField *)textField { [self reloadData]; } @end ================================================ FILE: examples/tvos/objc/DownloadCache/Repository.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface Repository : RLMObject @property NSString *identifier; @property NSString *name; @property NSString *avatarURL; @end ================================================ FILE: examples/tvos/objc/DownloadCache/Repository.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "Repository.h" @implementation Repository + (NSString *)primaryKey { return @"identifier"; } + (NSArray *)requiredProperties { return @[@"identifier"]; } @end ================================================ FILE: examples/tvos/objc/DownloadCache/RepositoryCell.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface RepositoryCell : UICollectionViewCell @property (nonatomic, weak) IBOutlet UIImageView *avatarImageView; @property (nonatomic, weak) IBOutlet UILabel *titleLabel; @end ================================================ FILE: examples/tvos/objc/DownloadCache/RepositoryCell.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RepositoryCell.h" @implementation RepositoryCell - (void)prepareForReuse { self.avatarImageView.image = nil; self.titleLabel.text = nil; } @end ================================================ FILE: examples/tvos/objc/DownloadCache/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/tvos/objc/PreloadedData/AppDelegate.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: examples/tvos/objc/PreloadedData/AppDelegate.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } @end ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json ================================================ { "assets" : [ { "size" : "1280x768", "idiom" : "tv", "filename" : "App Icon - Large.imagestack", "role" : "primary-app-icon" }, { "size" : "400x240", "idiom" : "tv", "filename" : "App Icon - Small.imagestack", "role" : "primary-app-icon" }, { "size" : "1920x720", "idiom" : "tv", "filename" : "Top Shelf Image.imageset", "role" : "top-shelf-image" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "landscape", "idiom" : "tv", "extent" : "full-screen", "minimum-system-version" : "9.0", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/objc/PreloadedData/Base.lproj/Main.storyboard ================================================ ================================================ FILE: examples/tvos/objc/PreloadedData/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 ================================================ FILE: examples/tvos/objc/PreloadedData/Place.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface Place : RLMObject @property NSString *postalCode; @property NSString *placeName; @property NSString *state; @property NSString *stateAbbreviation; @property NSString *county; @property double latitude; @property double longitude; @end ================================================ FILE: examples/tvos/objc/PreloadedData/Place.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "Place.h" @implementation Place @end ================================================ FILE: examples/tvos/objc/PreloadedData/PlacesViewController.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface PlacesViewController : UITableViewController @end ================================================ FILE: examples/tvos/objc/PreloadedData/PlacesViewController.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "PlacesViewController.h" #import "Place.h" @interface PlacesViewController () @property (nonatomic, weak) IBOutlet UITextField *searchField; @property (nonatomic) RLMResults *results; @end @implementation PlacesViewController - (void)viewDidLoad { [super viewDidLoad]; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.readOnly = YES; config.fileURL = [[NSBundle mainBundle] URLForResource:@"Places" withExtension:@"realm"]; [RLMRealmConfiguration setDefaultConfiguration:config]; [self reloadData]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.results.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; Place *place = self.results[indexPath.row]; cell.textLabel.text = place.postalCode; if (place.county) { cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@, %@", place.placeName, place.state, place.county]; } else { cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@", place.placeName, place.state]; } return cell; } - (void)reloadData { self.results = [Place allObjects]; if (self.searchField.text.length > 0) { self.results = [self.results objectsWhere:@"postalCode beginswith %@", self.searchField.text]; } self.results = [self.results sortedResultsUsingKeyPath:@"postalCode" ascending:YES]; [self.tableView reloadData]; } - (void)textFieldDidEndEditing:(UITextField *)textField { [self reloadData]; } @end ================================================ FILE: examples/tvos/objc/PreloadedData/main.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: examples/tvos/objc/RealmExamples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1403AF6F1BFDC8D300C1FBB4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403AF6E1BFDC8D300C1FBB4 /* main.m */; }; 1403AF721BFDC8D300C1FBB4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403AF711BFDC8D300C1FBB4 /* AppDelegate.m */; }; 1403AF751BFDC8D300C1FBB4 /* RepositoriesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403AF741BFDC8D300C1FBB4 /* RepositoriesViewController.m */; }; 1403AF781BFDC8D300C1FBB4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1403AF761BFDC8D300C1FBB4 /* Main.storyboard */; }; 1403AF7A1BFDC8D300C1FBB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1403AF791BFDC8D300C1FBB4 /* Assets.xcassets */; }; 1403AF831BFDCA8200C1FBB4 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1403AF811BFDCA8200C1FBB4 /* Realm.framework */; }; 1403AF841BFDCA8200C1FBB4 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1403AF811BFDCA8200C1FBB4 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 1403AF8E1BFDDDCF00C1FBB4 /* Repository.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403AF8D1BFDDDCE00C1FBB4 /* Repository.m */; }; 1403AF911BFDDE0B00C1FBB4 /* RepositoryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403AF901BFDDE0B00C1FBB4 /* RepositoryCell.m */; }; 14835DF51BFE5AAB00B9A267 /* Places.realm in Resources */ = {isa = PBXBuildFile; fileRef = 14835DF41BFE5AAB00B9A267 /* Places.realm */; }; 14835DF81BFE5D4100B9A267 /* Place.m in Sources */ = {isa = PBXBuildFile; fileRef = 14835DF71BFE5D4100B9A267 /* Place.m */; }; 1493911B1BFE50940036B420 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1493911A1BFE50940036B420 /* main.m */; }; 1493911E1BFE50940036B420 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1493911D1BFE50940036B420 /* AppDelegate.m */; }; 149391211BFE50940036B420 /* PlacesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 149391201BFE50940036B420 /* PlacesViewController.m */; }; 149391241BFE50940036B420 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 149391221BFE50940036B420 /* Main.storyboard */; }; 149391261BFE50940036B420 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 149391251BFE50940036B420 /* Assets.xcassets */; }; 1493912B1BFE52880036B420 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1403AF811BFDCA8200C1FBB4 /* Realm.framework */; }; 1493912C1BFE52880036B420 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1403AF811BFDCA8200C1FBB4 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 1403AF871BFDCA8200C1FBB4 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 1403AF841BFDCA8200C1FBB4 /* Realm.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 1493912F1BFE52880036B420 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 1493912C1BFE52880036B420 /* Realm.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1403AF6B1BFDC8D300C1FBB4 /* DownloadCache.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DownloadCache.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1403AF6E1BFDC8D300C1FBB4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1403AF701BFDC8D300C1FBB4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 1403AF711BFDC8D300C1FBB4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 1403AF731BFDC8D300C1FBB4 /* RepositoriesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RepositoriesViewController.h; sourceTree = ""; }; 1403AF741BFDC8D300C1FBB4 /* RepositoriesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RepositoriesViewController.m; sourceTree = ""; }; 1403AF771BFDC8D300C1FBB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 1403AF791BFDC8D300C1FBB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1403AF7B1BFDC8D300C1FBB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1403AF811BFDCA8200C1FBB4 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1403AF8C1BFDDDCE00C1FBB4 /* Repository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Repository.h; sourceTree = ""; }; 1403AF8D1BFDDDCE00C1FBB4 /* Repository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Repository.m; sourceTree = ""; }; 1403AF8F1BFDDE0B00C1FBB4 /* RepositoryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RepositoryCell.h; sourceTree = ""; }; 1403AF901BFDDE0B00C1FBB4 /* RepositoryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RepositoryCell.m; sourceTree = ""; }; 14835DF41BFE5AAB00B9A267 /* Places.realm */ = {isa = PBXFileReference; lastKnownFileType = file; name = Places.realm; path = "Seed Data/Places.realm"; sourceTree = ""; }; 14835DF61BFE5D4100B9A267 /* Place.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Place.h; sourceTree = ""; }; 14835DF71BFE5D4100B9A267 /* Place.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Place.m; sourceTree = ""; }; 149391171BFE50930036B420 /* PreloadedData.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PreloadedData.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1493911A1BFE50940036B420 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1493911C1BFE50940036B420 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 1493911D1BFE50940036B420 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 1493911F1BFE50940036B420 /* PlacesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlacesViewController.h; sourceTree = ""; }; 149391201BFE50940036B420 /* PlacesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlacesViewController.m; sourceTree = ""; }; 149391231BFE50940036B420 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 149391251BFE50940036B420 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 149391271BFE50940036B420 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1403AF681BFDC8D300C1FBB4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1403AF831BFDCA8200C1FBB4 /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 149391141BFE50930036B420 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1493912B1BFE52880036B420 /* Realm.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1403AF3F1BFDC60A00C1FBB4 = { isa = PBXGroup; children = ( 1403AF6C1BFDC8D300C1FBB4 /* DownloadCache */, 149391181BFE50930036B420 /* PreloadedData */, 1403AF491BFDC60A00C1FBB4 /* Products */, 1403AF881BFDCAC800C1FBB4 /* Realm */, ); sourceTree = ""; }; 1403AF491BFDC60A00C1FBB4 /* Products */ = { isa = PBXGroup; children = ( 1403AF6B1BFDC8D300C1FBB4 /* DownloadCache.app */, 149391171BFE50930036B420 /* PreloadedData.app */, ); name = Products; sourceTree = ""; }; 1403AF6C1BFDC8D300C1FBB4 /* DownloadCache */ = { isa = PBXGroup; children = ( 1403AF6D1BFDC8D300C1FBB4 /* Supporting Files */, 1403AF701BFDC8D300C1FBB4 /* AppDelegate.h */, 1403AF711BFDC8D300C1FBB4 /* AppDelegate.m */, 1403AF791BFDC8D300C1FBB4 /* Assets.xcassets */, 1403AF7B1BFDC8D300C1FBB4 /* Info.plist */, 1403AF761BFDC8D300C1FBB4 /* Main.storyboard */, 1403AF731BFDC8D300C1FBB4 /* RepositoriesViewController.h */, 1403AF741BFDC8D300C1FBB4 /* RepositoriesViewController.m */, 1403AF8C1BFDDDCE00C1FBB4 /* Repository.h */, 1403AF8D1BFDDDCE00C1FBB4 /* Repository.m */, 1403AF8F1BFDDE0B00C1FBB4 /* RepositoryCell.h */, 1403AF901BFDDE0B00C1FBB4 /* RepositoryCell.m */, ); path = DownloadCache; sourceTree = ""; }; 1403AF6D1BFDC8D300C1FBB4 /* Supporting Files */ = { isa = PBXGroup; children = ( 1403AF6E1BFDC8D300C1FBB4 /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; 1403AF881BFDCAC800C1FBB4 /* Realm */ = { isa = PBXGroup; children = ( 1403AF811BFDCA8200C1FBB4 /* Realm.framework */, ); name = Realm; sourceTree = ""; }; 14835DF11BFE582C00B9A267 /* Seed Data */ = { isa = PBXGroup; children = ( 14835DF41BFE5AAB00B9A267 /* Places.realm */, ); name = "Seed Data"; sourceTree = ""; }; 149391181BFE50930036B420 /* PreloadedData */ = { isa = PBXGroup; children = ( 14835DF11BFE582C00B9A267 /* Seed Data */, 149391191BFE50940036B420 /* Supporting Files */, 1493911C1BFE50940036B420 /* AppDelegate.h */, 1493911D1BFE50940036B420 /* AppDelegate.m */, 149391251BFE50940036B420 /* Assets.xcassets */, 149391271BFE50940036B420 /* Info.plist */, 149391221BFE50940036B420 /* Main.storyboard */, 14835DF61BFE5D4100B9A267 /* Place.h */, 14835DF71BFE5D4100B9A267 /* Place.m */, 1493911F1BFE50940036B420 /* PlacesViewController.h */, 149391201BFE50940036B420 /* PlacesViewController.m */, ); path = PreloadedData; sourceTree = ""; }; 149391191BFE50940036B420 /* Supporting Files */ = { isa = PBXGroup; children = ( 1493911A1BFE50940036B420 /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 1403AF6A1BFDC8D300C1FBB4 /* DownloadCache */ = { isa = PBXNativeTarget; buildConfigurationList = 1403AF7E1BFDC8D300C1FBB4 /* Build configuration list for PBXNativeTarget "DownloadCache" */; buildPhases = ( 1403AF671BFDC8D300C1FBB4 /* Sources */, 1403AF681BFDC8D300C1FBB4 /* Frameworks */, 1403AF691BFDC8D300C1FBB4 /* Resources */, 1403AF871BFDCA8200C1FBB4 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = DownloadCache; productName = DownloadCache; productReference = 1403AF6B1BFDC8D300C1FBB4 /* DownloadCache.app */; productType = "com.apple.product-type.application"; }; 149391161BFE50930036B420 /* PreloadedData */ = { isa = PBXNativeTarget; buildConfigurationList = 1493912A1BFE50940036B420 /* Build configuration list for PBXNativeTarget "PreloadedData" */; buildPhases = ( 149391131BFE50930036B420 /* Sources */, 149391141BFE50930036B420 /* Frameworks */, 149391151BFE50930036B420 /* Resources */, 1493912F1BFE52880036B420 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = PreloadedData; productName = PreloadedData; productReference = 149391171BFE50930036B420 /* PreloadedData.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 1403AF401BFDC60A00C1FBB4 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { 1403AF6A1BFDC8D300C1FBB4 = { CreatedOnToolsVersion = 7.2; }; 149391161BFE50930036B420 = { CreatedOnToolsVersion = 7.2; }; }; }; buildConfigurationList = 1403AF431BFDC60A00C1FBB4 /* Build configuration list for PBXProject "RealmExamples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 1403AF3F1BFDC60A00C1FBB4; productRefGroup = 1403AF491BFDC60A00C1FBB4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 1403AF6A1BFDC8D300C1FBB4 /* DownloadCache */, 149391161BFE50930036B420 /* PreloadedData */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 1403AF691BFDC8D300C1FBB4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 1403AF7A1BFDC8D300C1FBB4 /* Assets.xcassets in Resources */, 1403AF781BFDC8D300C1FBB4 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 149391151BFE50930036B420 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 149391261BFE50940036B420 /* Assets.xcassets in Resources */, 149391241BFE50940036B420 /* Main.storyboard in Resources */, 14835DF51BFE5AAB00B9A267 /* Places.realm in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 1403AF671BFDC8D300C1FBB4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 1403AF721BFDC8D300C1FBB4 /* AppDelegate.m in Sources */, 1403AF6F1BFDC8D300C1FBB4 /* main.m in Sources */, 1403AF751BFDC8D300C1FBB4 /* RepositoriesViewController.m in Sources */, 1403AF8E1BFDDDCF00C1FBB4 /* Repository.m in Sources */, 1403AF911BFDDE0B00C1FBB4 /* RepositoryCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 149391131BFE50930036B420 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 1493911E1BFE50940036B420 /* AppDelegate.m in Sources */, 1493911B1BFE50940036B420 /* main.m in Sources */, 14835DF81BFE5D4100B9A267 /* Place.m in Sources */, 149391211BFE50940036B420 /* PlacesViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 1403AF761BFDC8D300C1FBB4 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 1403AF771BFDC8D300C1FBB4 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 149391221BFE50940036B420 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 149391231BFE50940036B420 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 1403AF5D1BFDC60B00C1FBB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; 1403AF5E1BFDC60B00C1FBB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; }; name = Release; }; 1403AF7C1BFDC8D300C1FBB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = DownloadCache/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.DownloadCache; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 1403AF7D1BFDC8D300C1FBB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = DownloadCache/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.DownloadCache; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 149391281BFE50940036B420 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = PreloadedData/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PreloadedData; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 149391291BFE50940036B420 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = PreloadedData/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PreloadedData; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1403AF431BFDC60A00C1FBB4 /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( 1403AF5D1BFDC60B00C1FBB4 /* Debug */, 1403AF5E1BFDC60B00C1FBB4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1403AF7E1BFDC8D300C1FBB4 /* Build configuration list for PBXNativeTarget "DownloadCache" */ = { isa = XCConfigurationList; buildConfigurations = ( 1403AF7C1BFDC8D300C1FBB4 /* Debug */, 1403AF7D1BFDC8D300C1FBB4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1493912A1BFE50940036B420 /* Build configuration list for PBXNativeTarget "PreloadedData" */ = { isa = XCConfigurationList; buildConfigurations = ( 149391281BFE50940036B420 /* Debug */, 149391291BFE50940036B420 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 1403AF401BFDC60A00C1FBB4 /* Project object */; } ================================================ FILE: examples/tvos/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/DownloadCache.xcscheme ================================================ ================================================ FILE: examples/tvos/objc/RealmExamples.xcodeproj/xcshareddata/xcschemes/PreloadedData.xcscheme ================================================ ================================================ FILE: examples/tvos/objc/RealmExamples.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/tvos/objc/RealmExamples.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: examples/tvos/swift/DownloadCache/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { return true } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json ================================================ { "assets" : [ { "size" : "1280x768", "idiom" : "tv", "filename" : "App Icon - Large.imagestack", "role" : "primary-app-icon" }, { "size" : "400x240", "idiom" : "tv", "filename" : "App Icon - Small.imagestack", "role" : "primary-app-icon" }, { "size" : "1920x720", "idiom" : "tv", "filename" : "Top Shelf Image.imageset", "role" : "top-shelf-image" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "landscape", "idiom" : "tv", "extent" : "full-screen", "minimum-system-version" : "9.0", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/DownloadCache/Base.lproj/Main.storyboard ================================================ ================================================ FILE: examples/tvos/swift/DownloadCache/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 ================================================ FILE: examples/tvos/swift/DownloadCache/RepositoriesViewController.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class RepositoriesViewController: UICollectionViewController, UITextFieldDelegate { @IBOutlet weak var sortOrderControl: UISegmentedControl! @IBOutlet weak var searchField: UITextField! var results: Results? var token: NotificationToken? deinit { token?.invalidate() } override func viewDidLoad() { super.viewDidLoad() let realm = try! Realm() token = realm.observe { [weak self] _, _ in self?.reloadData() } var components = URLComponents(string: "https://api.github.com/search/repositories")! components.queryItems = [ URLQueryItem(name: "q", value: "language:objc"), URLQueryItem(name: "sort", value: "stars"), URLQueryItem(name: "order", value: "desc") ] URLSession.shared.dataTask(with: URLRequest(url: components.url!)) { data, _, error in if let error = error { print(error) return } do { let repositories = try JSONSerialization.jsonObject(with: data!, options: []) as! [String: AnyObject] let items = repositories["items"] as! [[String: AnyObject]] let realm = try Realm() try realm.write { for item in items { let repository = Repository() repository.identifier = String(item["id"] as! Int) repository.name = item["name"] as? String repository.avatarURL = item["owner"]!["avatar_url"] as? String realm.add(repository, update: .modified) } } } catch { print(error.localizedDescription) } }.resume() } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return results?.count ?? 0 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! RepositoryCell let repository = results![indexPath.item] cell.titleLabel.text = repository.name URLSession.shared.dataTask(with: URLRequest(url: URL(string: repository.avatarURL!)!)) { (data, _, error) in if let error = error { print(error.localizedDescription) return } DispatchQueue.main.async { let image = UIImage(data: data!)! cell.avatarImageView!.image = image } }.resume() return cell } func reloadData() { let realm = try! Realm() results = realm.objects(Repository.self) if let text = searchField.text, !text.isEmpty { results = results?.filter("name contains[c] %@", text) } results = results?.sorted(byKeyPath: "name", ascending: sortOrderControl!.selectedSegmentIndex == 0) collectionView?.reloadData() } @IBAction func valueChanged(sender: AnyObject) { reloadData() } @IBAction func clearSearchField(sender: AnyObject) { searchField.text = nil reloadData() } func textFieldDidEndEditing(_ textField: UITextField) { reloadData() } } ================================================ FILE: examples/tvos/swift/DownloadCache/Repository.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class Repository: Object { @objc dynamic var identifier = "" @objc dynamic var name: String? @objc dynamic var avatarURL: String? override static func primaryKey() -> String? { return "identifier" } } ================================================ FILE: examples/tvos/swift/DownloadCache/RepositoryCell.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit class RepositoryCell: UICollectionViewCell { @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var titleLabel: UILabel! override func prepareForReuse() { avatarImageView.image = nil titleLabel.text = nil } } ================================================ FILE: examples/tvos/swift/PreloadedData/AppDelegate.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { return true } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json ================================================ { "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json ================================================ { "assets" : [ { "size" : "1280x768", "idiom" : "tv", "filename" : "App Icon - Large.imagestack", "role" : "primary-app-icon" }, { "size" : "400x240", "idiom" : "tv", "filename" : "App Icon - Small.imagestack", "role" : "primary-app-icon" }, { "size" : "1920x720", "idiom" : "tv", "filename" : "Top Shelf Image.imageset", "role" : "top-shelf-image" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "landscape", "idiom" : "tv", "extent" : "full-screen", "minimum-system-version" : "9.0", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: examples/tvos/swift/PreloadedData/Base.lproj/Main.storyboard ================================================ ================================================ FILE: examples/tvos/swift/PreloadedData/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 ================================================ FILE: examples/tvos/swift/PreloadedData/Place.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class Place: Object { @objc dynamic var postalCode: String? @objc dynamic var placeName: String? @objc dynamic var state: String? @objc dynamic var stateAbbreviation: String? @objc dynamic var county: String? @objc dynamic var latitude = 0.0 @objc dynamic var longitude = 0.0 } ================================================ FILE: examples/tvos/swift/PreloadedData/PlacesViewController.swift ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 UIKit import RealmSwift class PlacesViewController: UITableViewController, UITextFieldDelegate { @IBOutlet weak var searchField: UITextField! var results: Results? override func viewDidLoad() { super.viewDidLoad() let seedFileURL = Bundle.main.url(forResource: "Places", withExtension: "realm") let config = Realm.Configuration(fileURL: seedFileURL, readOnly: true) Realm.Configuration.defaultConfiguration = config reloadData() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return results?.count ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let place = results![indexPath.row] cell.textLabel!.text = place.postalCode cell.detailTextLabel!.text = "\(place.placeName!), \(place.state!)" if let county = place.county { cell.detailTextLabel!.text = cell.detailTextLabel!.text! + ", \(county)" } return cell } func reloadData() { let realm = try! Realm() results = realm.objects(Place.self) if let text = searchField.text, !text.isEmpty { results = results?.filter("postalCode beginswith %@", text) } results = results?.sorted(byKeyPath: "postalCode") tableView?.reloadData() } func textFieldDidEndEditing(_ textField: UITextField) { reloadData() } } ================================================ FILE: examples/tvos/swift/RealmExamples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 14835E1B1BFE5E4000B9A267 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14835E1A1BFE5E4000B9A267 /* AppDelegate.swift */; }; 14835E1D1BFE5E4000B9A267 /* RepositoriesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14835E1C1BFE5E4000B9A267 /* RepositoriesViewController.swift */; }; 14835E201BFE5E4000B9A267 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 14835E1E1BFE5E4000B9A267 /* Main.storyboard */; }; 14835E221BFE5E4100B9A267 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14835E211BFE5E4100B9A267 /* Assets.xcassets */; }; 14835E451BFE5F5900B9A267 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E431BFE5F5900B9A267 /* Realm.framework */; }; 14835E461BFE5F5900B9A267 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E431BFE5F5900B9A267 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14835E471BFE5F5900B9A267 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E441BFE5F5900B9A267 /* RealmSwift.framework */; }; 14835E481BFE5F5900B9A267 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E441BFE5F5900B9A267 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14835E531BFE603300B9A267 /* RepositoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14835E521BFE603300B9A267 /* RepositoryCell.swift */; }; 14835E551BFE605000B9A267 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14835E541BFE605000B9A267 /* Repository.swift */; }; 14AACA891BFE7B740046BD85 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AACA881BFE7B740046BD85 /* AppDelegate.swift */; }; 14AACA8B1BFE7B740046BD85 /* PlacesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AACA8A1BFE7B740046BD85 /* PlacesViewController.swift */; }; 14AACA8E1BFE7B740046BD85 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 14AACA8C1BFE7B740046BD85 /* Main.storyboard */; }; 14AACA901BFE7B740046BD85 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14AACA8F1BFE7B740046BD85 /* Assets.xcassets */; }; 14AACA961BFE7C260046BD85 /* Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AACA951BFE7C260046BD85 /* Place.swift */; }; 14AACA981BFE7C420046BD85 /* Places.realm in Resources */ = {isa = PBXBuildFile; fileRef = 14AACA971BFE7C420046BD85 /* Places.realm */; }; 14AACA9A1BFE7D0A0046BD85 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E431BFE5F5900B9A267 /* Realm.framework */; }; 14AACA9B1BFE7D0A0046BD85 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E431BFE5F5900B9A267 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14AACA9C1BFE7D0A0046BD85 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E441BFE5F5900B9A267 /* RealmSwift.framework */; }; 14AACA9D1BFE7D0A0046BD85 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14835E441BFE5F5900B9A267 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 14835E491BFE5F5A00B9A267 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 14835E461BFE5F5900B9A267 /* Realm.framework in Embed Frameworks */, 14835E481BFE5F5900B9A267 /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 14AACA9E1BFE7D0A0046BD85 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 14AACA9B1BFE7D0A0046BD85 /* Realm.framework in Embed Frameworks */, 14AACA9D1BFE7D0A0046BD85 /* RealmSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 14835E181BFE5E4000B9A267 /* DownloadCache.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DownloadCache.app; sourceTree = BUILT_PRODUCTS_DIR; }; 14835E1A1BFE5E4000B9A267 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 14835E1C1BFE5E4000B9A267 /* RepositoriesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoriesViewController.swift; sourceTree = ""; }; 14835E1F1BFE5E4000B9A267 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 14835E211BFE5E4100B9A267 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 14835E231BFE5E4100B9A267 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 14835E431BFE5F5900B9A267 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 14835E441BFE5F5900B9A267 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RealmSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 14835E521BFE603300B9A267 /* RepositoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryCell.swift; sourceTree = ""; }; 14835E541BFE605000B9A267 /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 14AACA861BFE7B740046BD85 /* PreloadedData.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PreloadedData.app; sourceTree = BUILT_PRODUCTS_DIR; }; 14AACA881BFE7B740046BD85 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 14AACA8A1BFE7B740046BD85 /* PlacesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacesViewController.swift; sourceTree = ""; }; 14AACA8D1BFE7B740046BD85 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 14AACA8F1BFE7B740046BD85 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 14AACA911BFE7B740046BD85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 14AACA951BFE7C260046BD85 /* Place.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Place.swift; sourceTree = ""; }; 14AACA971BFE7C420046BD85 /* Places.realm */ = {isa = PBXFileReference; lastKnownFileType = file; name = Places.realm; path = "Seed Data/Places.realm"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 14835E151BFE5E4000B9A267 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 14835E451BFE5F5900B9A267 /* Realm.framework in Frameworks */, 14835E471BFE5F5900B9A267 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 14AACA831BFE7B740046BD85 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 14AACA9A1BFE7D0A0046BD85 /* Realm.framework in Frameworks */, 14AACA9C1BFE7D0A0046BD85 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 14835DF91BFE5E0B00B9A267 = { isa = PBXGroup; children = ( 14835E191BFE5E4000B9A267 /* DownloadCache */, 14AACA871BFE7B740046BD85 /* PreloadedData */, 14835E031BFE5E0B00B9A267 /* Products */, 14835E511BFE5FBD00B9A267 /* RealmSwift */, ); sourceTree = ""; }; 14835E031BFE5E0B00B9A267 /* Products */ = { isa = PBXGroup; children = ( 14835E181BFE5E4000B9A267 /* DownloadCache.app */, 14AACA861BFE7B740046BD85 /* PreloadedData.app */, ); name = Products; sourceTree = ""; }; 14835E191BFE5E4000B9A267 /* DownloadCache */ = { isa = PBXGroup; children = ( 14835E1A1BFE5E4000B9A267 /* AppDelegate.swift */, 14835E211BFE5E4100B9A267 /* Assets.xcassets */, 14835E231BFE5E4100B9A267 /* Info.plist */, 14835E1E1BFE5E4000B9A267 /* Main.storyboard */, 14835E1C1BFE5E4000B9A267 /* RepositoriesViewController.swift */, 14835E541BFE605000B9A267 /* Repository.swift */, 14835E521BFE603300B9A267 /* RepositoryCell.swift */, ); path = DownloadCache; sourceTree = ""; }; 14835E511BFE5FBD00B9A267 /* RealmSwift */ = { isa = PBXGroup; children = ( 14835E431BFE5F5900B9A267 /* Realm.framework */, 14835E441BFE5F5900B9A267 /* RealmSwift.framework */, ); name = RealmSwift; sourceTree = ""; }; 14AACA871BFE7B740046BD85 /* PreloadedData */ = { isa = PBXGroup; children = ( 14AACA991BFE7C450046BD85 /* Seed Data */, 14AACA881BFE7B740046BD85 /* AppDelegate.swift */, 14AACA8F1BFE7B740046BD85 /* Assets.xcassets */, 14AACA911BFE7B740046BD85 /* Info.plist */, 14AACA8C1BFE7B740046BD85 /* Main.storyboard */, 14AACA951BFE7C260046BD85 /* Place.swift */, 14AACA8A1BFE7B740046BD85 /* PlacesViewController.swift */, ); path = PreloadedData; sourceTree = ""; }; 14AACA991BFE7C450046BD85 /* Seed Data */ = { isa = PBXGroup; children = ( 14AACA971BFE7C420046BD85 /* Places.realm */, ); name = "Seed Data"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 14835E171BFE5E4000B9A267 /* DownloadCache */ = { isa = PBXNativeTarget; buildConfigurationList = 14835E241BFE5E4100B9A267 /* Build configuration list for PBXNativeTarget "DownloadCache" */; buildPhases = ( 14835E141BFE5E4000B9A267 /* Sources */, 14835E151BFE5E4000B9A267 /* Frameworks */, 14835E161BFE5E4000B9A267 /* Resources */, 14835E491BFE5F5A00B9A267 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = DownloadCache; productName = DownloadCache; productReference = 14835E181BFE5E4000B9A267 /* DownloadCache.app */; productType = "com.apple.product-type.application"; }; 14AACA851BFE7B740046BD85 /* PreloadedData */ = { isa = PBXNativeTarget; buildConfigurationList = 14AACA921BFE7B740046BD85 /* Build configuration list for PBXNativeTarget "PreloadedData" */; buildPhases = ( 14AACA821BFE7B740046BD85 /* Sources */, 14AACA831BFE7B740046BD85 /* Frameworks */, 14AACA841BFE7B740046BD85 /* Resources */, 14AACA9E1BFE7D0A0046BD85 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = PreloadedData; productName = PreloadedData; productReference = 14AACA861BFE7B740046BD85 /* PreloadedData.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 14835DFA1BFE5E0B00B9A267 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; TargetAttributes = { 14835E171BFE5E4000B9A267 = { CreatedOnToolsVersion = 7.2; LastSwiftMigration = 1130; }; 14AACA851BFE7B740046BD85 = { CreatedOnToolsVersion = 7.2; }; }; }; buildConfigurationList = 14835DFD1BFE5E0B00B9A267 /* Build configuration list for PBXProject "RealmExamples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 14835DF91BFE5E0B00B9A267; productRefGroup = 14835E031BFE5E0B00B9A267 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 14835E171BFE5E4000B9A267 /* DownloadCache */, 14AACA851BFE7B740046BD85 /* PreloadedData */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 14835E161BFE5E4000B9A267 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 14835E221BFE5E4100B9A267 /* Assets.xcassets in Resources */, 14835E201BFE5E4000B9A267 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 14AACA841BFE7B740046BD85 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 14AACA901BFE7B740046BD85 /* Assets.xcassets in Resources */, 14AACA8E1BFE7B740046BD85 /* Main.storyboard in Resources */, 14AACA981BFE7C420046BD85 /* Places.realm in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 14835E141BFE5E4000B9A267 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 14835E1B1BFE5E4000B9A267 /* AppDelegate.swift in Sources */, 14835E1D1BFE5E4000B9A267 /* RepositoriesViewController.swift in Sources */, 14835E551BFE605000B9A267 /* Repository.swift in Sources */, 14835E531BFE603300B9A267 /* RepositoryCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 14AACA821BFE7B740046BD85 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 14AACA891BFE7B740046BD85 /* AppDelegate.swift in Sources */, 14AACA961BFE7C260046BD85 /* Place.swift in Sources */, 14AACA8B1BFE7B740046BD85 /* PlacesViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 14835E1E1BFE5E4000B9A267 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 14835E1F1BFE5E4000B9A267 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 14AACA8C1BFE7B740046BD85 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 14AACA8D1BFE7B740046BD85 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 14835E0F1BFE5E0B00B9A267 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; 14835E101BFE5E0B00B9A267 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; }; name = Release; }; 14835E251BFE5E4100B9A267 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DownloadCache/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.DownloadCache; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 14835E261BFE5E4100B9A267 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DownloadCache/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.DownloadCache; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 14AACA931BFE7B740046BD85 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PreloadedData/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PreloadedData; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 14AACA941BFE7B740046BD85 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PreloadedData/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.realm.PreloadedData; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 14835DFD1BFE5E0B00B9A267 /* Build configuration list for PBXProject "RealmExamples" */ = { isa = XCConfigurationList; buildConfigurations = ( 14835E0F1BFE5E0B00B9A267 /* Debug */, 14835E101BFE5E0B00B9A267 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 14835E241BFE5E4100B9A267 /* Build configuration list for PBXNativeTarget "DownloadCache" */ = { isa = XCConfigurationList; buildConfigurations = ( 14835E251BFE5E4100B9A267 /* Debug */, 14835E261BFE5E4100B9A267 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 14AACA921BFE7B740046BD85 /* Build configuration list for PBXNativeTarget "PreloadedData" */ = { isa = XCConfigurationList; buildConfigurations = ( 14AACA931BFE7B740046BD85 /* Debug */, 14AACA941BFE7B740046BD85 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 14835DFA1BFE5E0B00B9A267 /* Project object */; } ================================================ FILE: examples/tvos/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/DownloadCache.xcscheme ================================================ ================================================ FILE: examples/tvos/swift/RealmExamples.xcodeproj/xcshareddata/xcschemes/PreloadedData.xcscheme ================================================ ================================================ FILE: examples/tvos/swift/RealmExamples.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: examples/tvos/swift/RealmExamples.xcworkspace/xcshareddata/RealmExamples.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : 0, "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : 0 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1704DB1C-4304-4E23-A31F-7898B29FB132", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" : "realm-cocoa\/", "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-cocoa\/Realm\/ObjectStore\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "RealmExamples", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "examples\/tvos\/swift-3.0\/RealmExamples.xcworkspace", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:realm\/realm-object-store-private.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:realm\/realm-cocoa.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9FB1FDDBA011002795A1FF5BD3CABFA2F79E6A59" } ] } ================================================ FILE: examples/tvos/swift/RealmExamples.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: plugin/README.md ================================================ # Realm Plugin The Realm Plugin for Xcode adds several useful features for developing with Realm: 1. A LLDB script which adds support for inspecting the property values of persisted RLMObjects in the debugger pane. 2. File templates for RLMObject subclasses. 3. A menu item in Xcode's 'File' menu to quickly launch the Realm Browser. Note that this item will only appear in Xcode 7 and in unsigned versions of Xcode 8 or later (not recommended). To install the Realm Plugin, open `RealmPlugin.xcodeproj` and Build. This will prompt for your password. After building the plugin, restart Xcode. ================================================ FILE: plugin/RealmPlugin/RLMPRealmPlugin.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 @interface RLMPRealmPlugin : NSObject @end ================================================ FILE: plugin/RealmPlugin/RLMPRealmPlugin.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMPRealmPlugin.h" #import "RLMPSimulatorManager.h" static RLMPRealmPlugin *sharedPlugin; static NSString *const RootDeviceSimulatorPath = @"Library/Developer/CoreSimulator/Devices"; static NSString *const DeviceSimulatorApplicationPath = @"data/Containers/Data/Application"; static NSString *const RLMPErrorDomain = @"io.Realm.error"; static NSArray * RLMPGlobFilesAtDirectoryURLWithPredicate(NSFileManager *fileManager, NSURL *directoryURL, NSPredicate *filteredPredicate, BOOL (^handler)(NSURL *URL, NSError *error)) { NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:directoryURL includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:handler]; NSMutableArray *fileURLs = [NSMutableArray array]; for (NSURL *fileURL in directoryEnumerator) { NSString *fileName; [fileURL getResourceValue:&fileName forKey:NSURLNameKey error:nil]; NSString *isDirectory; [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; // Check whether it is a directory or not if (![isDirectory boolValue]) { [fileURLs addObject:fileURL]; } } return [fileURLs filteredArrayUsingPredicate:filteredPredicate]; } @interface RLMPRealmPlugin() @property (nonatomic, strong) NSBundle *bundle; @property (nonatomic, strong) NSURL *browserUrl; @end @implementation RLMPRealmPlugin + (void)pluginDidLoad:(NSBundle *)plugin { static dispatch_once_t onceToken; NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; if ([currentApplicationName isEqual:@"Xcode"]) { dispatch_once(&onceToken, ^{ sharedPlugin = [[self alloc] initWithBundle:plugin]; }); } } - (id)initWithBundle:(NSBundle *)plugin { if (self = [super init]) { // Save reference to plugin's bundle, for resource acccess self.bundle = plugin; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didApplicationFinishLaunchingNotification:) name:NSApplicationDidFinishLaunchingNotification object:nil]; } return self; } - (void)didApplicationFinishLaunchingNotification:(NSNotification *)notification { // Look for the Realm Browser NSString *urlString = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"Realm Browser"]; if (urlString) { self.browserUrl = [NSURL fileURLWithPath:urlString]; // Create menu item to open Browser under File: NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"File"]; if (menuItem) { [[menuItem submenu] addItem:[NSMenuItem separatorItem]]; NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Realm..." action:@selector(openBrowser) keyEquivalent:@""]; [actionMenuItem setTarget:self]; [[menuItem submenu] addItem:actionMenuItem]; } } else { NSLog(@"Realm Plugin: Couldn't find Realm Browser. Will not show 'Open Realm...' menu item."); } } - (void)openBrowser { // This shouldn't be possible to call without having the Browser installed if (!self.browserUrl) { NSString *title = @"Please install the Realm Browser"; NSString *message = @"You need to install the Realm Browser in order to use it from this plugin. Please visit realm.io for more information."; NSError *error = [NSError errorWithDomain:RLMPErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : title, NSLocalizedRecoverySuggestionErrorKey : message }]; [self showError:error]; return; } // Find Device UUID NSString *bootedSimulatorUUID = [RLMPSimulatorManager bootedSimulatorUUID]; // Find Realm File URL NSArray *realmFileURLs = [self realmFilesURLWithDeviceUUID:bootedSimulatorUUID]; if (realmFileURLs.count == 0) { NSString *title = @"Unable to find Realm file"; NSString *message = @"You must launch iOS Simulator with app that uses Realm"; NSError *error = [NSError errorWithDomain:RLMPErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : title, NSLocalizedRecoverySuggestionErrorKey : message }]; [self showError:error]; return; } NSMutableArray *arguments = [NSMutableArray array]; for (NSURL *realmFileURL in realmFileURLs) { [arguments addObject:realmFileURL.path]; } NSDictionary *configuration = @{ NSWorkspaceLaunchConfigurationArguments : arguments }; // Show confirmation alert if 2 or more files are detected if (arguments.count > 1) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Realm Browser"]; [alert setInformativeText:[NSString stringWithFormat:@"%ld Realm files are detected. Are you sure to open them at once?", arguments.count]]; [alert setAlertStyle:NSInformationalAlertStyle]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertFirstButtonReturn) { [self openRealmBrowserWithConfiguration:configuration]; } }]; } else { [self openRealmBrowserWithConfiguration:configuration]; } } - (void)openRealmBrowserWithConfiguration:(NSDictionary *)configuration { NSError *error; if (![[NSWorkspace sharedWorkspace] launchApplicationAtURL:self.browserUrl options:NSWorkspaceLaunchNewInstance configuration:configuration error:&error]) { // This will happen if the Browser was present at Xcode launch and then was deleted [self showError:error]; } } - (NSArray *)realmFilesURLWithDeviceUUID:(NSString *)deviceUUID { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *homeURL = [NSURL URLWithString:NSHomeDirectory()]; NSMutableString *fullPath = [NSMutableString string]; [fullPath appendFormat:@"%@/%@/%@", RootDeviceSimulatorPath, deviceUUID, DeviceSimulatorApplicationPath]; NSURL *bootedDeviceDirectoryURL = [homeURL URLByAppendingPathComponent:fullPath]; NSArray* fileURLs = RLMPGlobFilesAtDirectoryURLWithPredicate(fileManager, bootedDeviceDirectoryURL, [NSPredicate predicateWithFormat:@"pathExtension == 'realm'"], ^BOOL(NSURL *URL, NSError *error) { if (error) { NSLog(@"%@", error); return NO; } return YES; }); return fileURLs; } - (void)showError:(NSError *)error { NSAlert *alert = [NSAlert alertWithError:error]; [alert runModal]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end ================================================ FILE: plugin/RealmPlugin/RLMPSimulatorManager.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 /** RLMPSimulatorManager is a helper class to monitor iOS Simulator status and corresponding UUID. The only usage is to return UUID of booted Simulator which is found by using command xcrun. NSString *bootedUUID = [RLMPSimulatorManager bootedSimulatorUUID]; */ @interface RLMPSimulatorManager : NSObject /** UUID of booted Simulator */ + (NSString *)bootedSimulatorUUID; @end ================================================ FILE: plugin/RealmPlugin/RLMPSimulatorManager.m ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 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 "RLMPSimulatorManager.h" static NSString *const RLMPBootedSimulatorKey = @"Booted"; static NSTask *RLMPLaunchedTaskSynchonouslyWithProperty(NSString *path, NSArray *arguments, NSString *__autoreleasing *output) { // Setup task with given parameters NSTask *task = [[NSTask alloc] init]; task.launchPath = path; task.arguments = arguments; // Setup output Pipe to created Task NSPipe *outputPipe = [NSPipe pipe]; task.standardOutput = outputPipe; [task launch]; [task waitUntilExit]; NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; *output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; return task; } @interface RLMPSimulatorManager () @end @implementation RLMPSimulatorManager + (NSString *)bootedSimulatorUUID { NSString *deviceData = [self readDeviceData]; __block NSString *bootedDeviceUUID; if (deviceData) { // Process output NSDictionary *deviceStatuses = [self processDeviceData:deviceData]; [deviceStatuses enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { if ([value isEqualToString:RLMPBootedSimulatorKey]) { bootedDeviceUUID = key; // Stop when we found single booted device *stop = YES; } }]; } return bootedDeviceUUID; } + (NSString *)readDeviceData { // Find out Xcode path from mainBundle NSURL *bundleURL = [[NSBundle mainBundle] infoDictionary][@"CFBundleInfoPlistURL"]; // Append with xcrun path NSString *pathToXcrun = @"Contents/Developer/usr/bin/xcrun"; NSURL *fullURL = [[NSURL alloc] initWithString:pathToXcrun relativeToURL:bundleURL.baseURL]; // Set parameters to get device detail NSArray *args = @[@"simctl", @"list", @"devices"]; NSString *output; RLMPLaunchedTaskSynchonouslyWithProperty(fullURL.path, args, &output); return output; } + (NSDictionary *)processDeviceData:(NSString *)data { NSMutableDictionary *device = [NSMutableDictionary dictionary]; NSScanner *scanner = [NSScanner scannerWithString:data]; // Skip punctuation ( ) as we only want status inside scanner.charactersToBeSkipped = [NSCharacterSet punctuationCharacterSet]; while (![scanner isAtEnd]) { NSString *deviceKey; NSString *deviceStatus; // Scan up to ( [scanner scanUpToString:@"(" intoString:nil]; [scanner scanUpToString:@")" intoString:&deviceKey]; // Scan up to ( [scanner scanUpToString:@"(" intoString:nil]; [scanner scanUpToString:@")" intoString:&deviceStatus]; [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:nil]; if (deviceKey && deviceStatus) { [device setValue:deviceStatus forKey:deviceKey]; } } return device; } @end ================================================ FILE: plugin/RealmPlugin/RealmPlugin-Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier io.realm.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 DVTPlugInCompatibilityUUIDs FEC992CC-CA4A-4CFD-8881-77300FCB848A C4A681B0-4A26-480E-93EC-1218098B9AA0 A2E4D43F-41F4-4FB9-BB94-7177011C9AED AD68E85B-441B-4301-B564-A45E4919A6AD 63FC1C47-140D-42B0-BB4D-A10B2D225574 37B30044-3B14-46BA-ABAA-F01000C27B63 640F884E-CE55-4B40-87C0-8869546CAB7A 992275C1-432A-4CF7-B659-D84ED6D42D3F A16FF353-8441-459E-A50C-B071F53F51B7 9F75337B-21B4-4ADC-B558-F9CADF7073A7 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 E969541F-E6F9-4D25-8158-72DC3545A6C6 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 AABB7188-E14E-4433-AD3B-5CD791EAD9A3 CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 7265231C-39B4-402C-89E1-16167C4CC990 9AFF134A-08DC-4096-8CEE-62A4BB123046 F41BD31E-2683-44B8-AE7F-5F09E919790E ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C NSPrincipalClass RLMPRealmPlugin XC4Compatible XC5Compatible XCPluginHasUI ================================================ FILE: plugin/RealmPlugin/RealmPlugin-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #ifdef __OBJC__ #import #endif ================================================ FILE: plugin/RealmPlugin/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: plugin/RealmPlugin.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 2263AABA19B0A1E6007240D9 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2263AAB919B0A1E6007240D9 /* AppKit.framework */; }; 2263AABC19B0A1E6007240D9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2263AABB19B0A1E6007240D9 /* Foundation.framework */; }; 2263AAC219B0A1E6007240D9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2263AAC019B0A1E6007240D9 /* InfoPlist.strings */; }; 2263AAC519B0A1E6007240D9 /* RLMPRealmPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 2263AAC419B0A1E6007240D9 /* RLMPRealmPlugin.m */; }; 3CF8FC5D1B494F3B0066DC23 /* RLMPSimulatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CF8FC5C1B494F3B0066DC23 /* RLMPSimulatorManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2263AAB619B0A1E6007240D9 /* RealmPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 2263AAB919B0A1E6007240D9 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 2263AABB19B0A1E6007240D9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 2263AABF19B0A1E6007240D9 /* RealmPlugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RealmPlugin-Info.plist"; sourceTree = ""; }; 2263AAC119B0A1E6007240D9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 2263AAC319B0A1E6007240D9 /* RLMPRealmPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLMPRealmPlugin.h; sourceTree = ""; }; 2263AAC419B0A1E6007240D9 /* RLMPRealmPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RLMPRealmPlugin.m; sourceTree = ""; }; 2263AAC619B0A1E6007240D9 /* RealmPlugin-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RealmPlugin-Prefix.pch"; sourceTree = ""; }; 22B78F1F19B0A47600968525 /* ___FILEBASENAME___.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "___FILEBASENAME___.h"; sourceTree = ""; }; 22B78F2019B0A47600968525 /* ___FILEBASENAME___.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "___FILEBASENAME___.m"; sourceTree = ""; }; 22B78F2119B0A47600968525 /* TemplateIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = TemplateIcon.icns; sourceTree = ""; }; 22B78F2219B0A47600968525 /* TemplateInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TemplateInfo.plist; sourceTree = ""; }; 22B78F2319B0A47600968525 /* install_templates.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = install_templates.sh; sourceTree = ""; }; 3CF8FC5B1B494F3B0066DC23 /* RLMPSimulatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMPSimulatorManager.h; sourceTree = ""; }; 3CF8FC5C1B494F3B0066DC23 /* RLMPSimulatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RLMPSimulatorManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2263AAB319B0A1E6007240D9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2263AABA19B0A1E6007240D9 /* AppKit.framework in Frameworks */, 2263AABC19B0A1E6007240D9 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2263AAAD19B0A1E5007240D9 = { isa = PBXGroup; children = ( 2263AAB819B0A1E6007240D9 /* Frameworks */, 2263AAB719B0A1E6007240D9 /* Products */, 2263AABD19B0A1E6007240D9 /* RealmPlugin */, 22B78F1319B0A47600968525 /* Templates */, ); sourceTree = ""; }; 2263AAB719B0A1E6007240D9 /* Products */ = { isa = PBXGroup; children = ( 2263AAB619B0A1E6007240D9 /* RealmPlugin.xcplugin */, ); name = Products; sourceTree = ""; }; 2263AAB819B0A1E6007240D9 /* Frameworks */ = { isa = PBXGroup; children = ( 2263AAB919B0A1E6007240D9 /* AppKit.framework */, 2263AABB19B0A1E6007240D9 /* Foundation.framework */, ); name = Frameworks; sourceTree = ""; }; 2263AABD19B0A1E6007240D9 /* RealmPlugin */ = { isa = PBXGroup; children = ( 2263AABE19B0A1E6007240D9 /* Supporting Files */, 2263AAC319B0A1E6007240D9 /* RLMPRealmPlugin.h */, 2263AAC419B0A1E6007240D9 /* RLMPRealmPlugin.m */, 3CF8FC5B1B494F3B0066DC23 /* RLMPSimulatorManager.h */, 3CF8FC5C1B494F3B0066DC23 /* RLMPSimulatorManager.m */, ); path = RealmPlugin; sourceTree = ""; }; 2263AABE19B0A1E6007240D9 /* Supporting Files */ = { isa = PBXGroup; children = ( 2263AAC019B0A1E6007240D9 /* InfoPlist.strings */, 2263AABF19B0A1E6007240D9 /* RealmPlugin-Info.plist */, 2263AAC619B0A1E6007240D9 /* RealmPlugin-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 22B78F1319B0A47600968525 /* Templates */ = { isa = PBXGroup; children = ( 22B78F1D19B0A47600968525 /* file_templates */, 22B78F2319B0A47600968525 /* install_templates.sh */, ); path = Templates; sourceTree = ""; }; 22B78F1D19B0A47600968525 /* file_templates */ = { isa = PBXGroup; children = ( 22B78F1E19B0A47600968525 /* Realm Model Object.xctemplate */, ); path = file_templates; sourceTree = ""; }; 22B78F1E19B0A47600968525 /* Realm Model Object.xctemplate */ = { isa = PBXGroup; children = ( 22B78F1F19B0A47600968525 /* ___FILEBASENAME___.h */, 22B78F2019B0A47600968525 /* ___FILEBASENAME___.m */, 22B78F2119B0A47600968525 /* TemplateIcon.icns */, 22B78F2219B0A47600968525 /* TemplateInfo.plist */, ); path = "Realm Model Object.xctemplate"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2263AAB519B0A1E6007240D9 /* RealmPlugin */ = { isa = PBXNativeTarget; buildConfigurationList = 2263AAC919B0A1E6007240D9 /* Build configuration list for PBXNativeTarget "RealmPlugin" */; buildPhases = ( 22B78EF819B0A3AF00968525 /* Install Templates */, 3F527BDF1A16C34300CA8B97 /* Install LLDB Script */, 2263AAB219B0A1E6007240D9 /* Sources */, 2263AAB319B0A1E6007240D9 /* Frameworks */, 2263AAB419B0A1E6007240D9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = RealmPlugin; productName = RealmPlugin; productReference = 2263AAB619B0A1E6007240D9 /* RealmPlugin.xcplugin */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2263AAAE19B0A1E5007240D9 /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = RLMP; LastUpgradeCheck = 1430; ORGANIZATIONNAME = Realm; }; buildConfigurationList = 2263AAB119B0A1E5007240D9 /* Build configuration list for PBXProject "RealmPlugin" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 2263AAAD19B0A1E5007240D9; productRefGroup = 2263AAB719B0A1E6007240D9 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2263AAB519B0A1E6007240D9 /* RealmPlugin */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2263AAB419B0A1E6007240D9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2263AAC219B0A1E6007240D9 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 22B78EF819B0A3AF00968525 /* Install Templates */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Install Templates"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "cd Templates && sh install_templates.sh"; }; 3F527BDF1A16C34300CA8B97 /* Install LLDB Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Install LLDB Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "python rlm_lldb.py"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2263AAB219B0A1E6007240D9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2263AAC519B0A1E6007240D9 /* RLMPRealmPlugin.m in Sources */, 3CF8FC5D1B494F3B0066DC23 /* RLMPSimulatorManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 2263AAC019B0A1E6007240D9 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 2263AAC119B0A1E6007240D9 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 2263AAC719B0A1E6007240D9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 2263AAC819B0A1E6007240D9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; }; name = Release; }; 2263AACA19B0A1E6007240D9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; DEPLOYMENT_LOCATION = YES; DSTROOT = "$(HOME)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RealmPlugin/RealmPlugin-Prefix.pch"; INFOPLIST_FILE = "RealmPlugin/RealmPlugin-Info.plist"; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xcplugin; }; name = Debug; }; 2263AACB19B0A1E6007240D9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; DEPLOYMENT_LOCATION = YES; DSTROOT = "$(HOME)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RealmPlugin/RealmPlugin-Prefix.pch"; INFOPLIST_FILE = "RealmPlugin/RealmPlugin-Info.plist"; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xcplugin; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2263AAB119B0A1E5007240D9 /* Build configuration list for PBXProject "RealmPlugin" */ = { isa = XCConfigurationList; buildConfigurations = ( 2263AAC719B0A1E6007240D9 /* Debug */, 2263AAC819B0A1E6007240D9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2263AAC919B0A1E6007240D9 /* Build configuration list for PBXNativeTarget "RealmPlugin" */ = { isa = XCConfigurationList; buildConfigurations = ( 2263AACA19B0A1E6007240D9 /* Debug */, 2263AACB19B0A1E6007240D9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 2263AAAE19B0A1E5007240D9 /* Project object */; } ================================================ FILE: plugin/Templates/file_templates/Realm Model Object.xctemplate/Objective-C/___FILEBASENAME___.h ================================================ // // ___FILENAME___ // ___PROJECTNAME___ // // Created by ___FULLUSERNAME___ on ___DATE___. //___COPYRIGHT___ // #import @interface ___FILEBASENAMEASIDENTIFIER___ : RLMObject <# Add properties here to define the model #> @end // This protocol enables typed collections. i.e.: // RLMArray<___FILEBASENAMEASIDENTIFIER___ *><___FILEBASENAMEASIDENTIFIER___> RLM_COLLECTION_TYPE(___FILEBASENAMEASIDENTIFIER___) ================================================ FILE: plugin/Templates/file_templates/Realm Model Object.xctemplate/Objective-C/___FILEBASENAME___.m ================================================ // // ___FILENAME___ // ___PROJECTNAME___ // // Created by ___FULLUSERNAME___ on ___DATE___. //___COPYRIGHT___ // #import "___FILEBASENAME___.h" @implementation ___FILEBASENAMEASIDENTIFIER___ // Specify default values for properties //+ (NSDictionary *)defaultPropertyValues //{ // return @{}; //} // Specify properties to ignore (Realm won't persist these) //+ (NSArray *)ignoredProperties //{ // return @[]; //} @end ================================================ FILE: plugin/Templates/file_templates/Realm Model Object.xctemplate/Swift/___FILEBASENAME___.swift ================================================ // // ___FILENAME___ // ___PROJECTNAME___ // // Created by ___FULLUSERNAME___ on ___DATE___. // ___COPYRIGHT___ // import Foundation import RealmSwift class ___FILEBASENAMEASIDENTIFIER___: Object { // Specify properties to ignore (Realm won't persist these) // override static func ignoredProperties() -> [String] { // return [] // } } ================================================ FILE: plugin/Templates/file_templates/Realm Model Object.xctemplate/TemplateInfo.plist ================================================ AllowedTypes public.objective-c-source public.objective-c-plus-plus-source DefaultCompletionName MyClass Description A Realm model object class, with implementation and header files. Kind Xcode.IDEKit.TextSubstitutionFileTemplateKind Options Description The name of the class to create Identifier productName Name Model Object Class NotPersisted Required Type text AllowedTypes Objective-C public.objective-c-source public.objective-c-plus-plus-source Swift public.swift-source Default Objective-C Description The implementation language Identifier languageChoice MainTemplateFiles Swift ___FILEBASENAME___.swift Objective-C ___FILEBASENAME___.m Name Language Required Type popup Values Objective-C Swift Platforms com.apple.platform.iphoneos com.apple.platform.macosx Summary A Realm model object ================================================ FILE: plugin/Templates/install_templates.sh ================================================ #!/bin/sh PATH=/bin:/usr/bin:/usr/libexec FILE_TEMPLATES_DIR="$HOME/Library/Developer/Xcode/Templates/File Templates/Realm" mkdir -p "$FILE_TEMPLATES_DIR" for dir in "file_templates/*/" do cp -R ${dir%*/} "$FILE_TEMPLATES_DIR" done ================================================ FILE: plugin/rlm_lldb.py ================================================ #!/usr/bin/python ############################################################################## # # Copyright 2014 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. # ############################################################################## # In the lldb shell, load with: # command script import [Realm path]/plugin/rlm_lldb.py --allow-reload # To load automatically, add that line to your ~/.lldbinit file (which you will # have to create if you have not set up any previous lldb scripts), or run this # file as a Python script outside of Xcode to install it automatically if __name__ == '__main__': # Script is being run directly, so install it import errno import shutil import os source = os.path.realpath(__file__) destination = os.path.expanduser("~/Library/Application Support/Realm") # Copy the file into place try: os.makedirs(destination, 0o744) except os.error as e: # It's fine if the directory already exists if e.errno != errno.EEXIST: raise shutil.copy2(source, destination + '/rlm_lldb.py') # Add it to ~/.lldbinit load_line = 'command script import "~/Library/Application Support/Realm/rlm_lldb.py" --allow-reload\n' is_installed = False try: with open(os.path.expanduser('~/.lldbinit')) as f: for line in f: if line == load_line: is_installed = True break except IOError as e: if e.errno != errno.ENOENT: raise # File not existing yet is fine if not is_installed: with open(os.path.expanduser('~/.lldbinit'), 'a') as f: f.write('\n' + load_line) exit(0) import lldb property_types = { 0: 'int64_t', 10: 'double', 1: 'bool', 9: 'float', } def cache_lookup(cache, key, generator): value = cache.get(key, None) if not value: value = generator(key) cache[key] = value return value ivar_cache = {} def get_ivar_info(obj, ivar): def get_offset(ivar): class_name, ivar_name = ivar.split('.') frame = obj.GetThread().GetSelectedFrame() ptr = frame.EvaluateExpression("&(({} *)0)->{}".format(class_name, ivar_name)) return (ptr.GetValueAsUnsigned(), ptr.deref.type, ptr.deref.size) return cache_lookup(ivar_cache, ivar, get_offset) def get_ivar(obj, addr, ivar): offset, _, size = get_ivar_info(obj, ivar) if isinstance(addr, lldb.SBAddress): addr = addr.GetFileAddress() return obj.GetProcess().ReadUnsignedFromMemory(addr + offset, size, lldb.SBError()) object_table_ptr_offset = None def is_object_deleted(obj): addr = obj.GetAddress().GetFileAddress() global object_table_ptr_offset if not object_table_ptr_offset: row, _, _ = get_ivar_info(obj, 'RLMObject._row') table, _, _ = get_ivar_info(obj, 'realm::Row.m_table') ptr, _, _ = get_ivar_info(obj, 'realm::TableRef.m_ptr') object_table_ptr_offset = row + table + ptr ptr = obj.GetProcess().ReadUnsignedFromMemory(addr + object_table_ptr_offset, obj.target.addr_size, lldb.SBError()) return ptr == 0 class SyntheticChildrenProvider(object): def __init__(self, class_name): self._class_name = class_name def _eval(self, expr): frame = self.obj.GetThread().GetSelectedFrame() return frame.EvaluateExpression(expr) def _get_ivar(self, addr, ivar): return get_ivar(self.obj, addr, ivar) def _to_str(self, val): return self.obj.GetProcess().ReadCStringFromMemory(val, 1024, lldb.SBError()) def _value_from_ivar(self, ivar): offset, ivar_type, _ = get_ivar_info(self.obj, '{}._{}'.format(self._class_name, ivar)) return self.obj.CreateChildAtOffset(ivar, offset, ivar_type) def RLMObject_SummaryProvider(obj, _): if is_object_deleted(obj): return '[Deleted object]' return None schema_cache = {} class RLMObject_SyntheticChildrenProvider(SyntheticChildrenProvider): def __init__(self, obj, _): super(RLMObject_SyntheticChildrenProvider, self).__init__('RLMObject') self.obj = obj if not obj.GetAddress() or is_object_deleted(obj): self.props = [] return object_schema = self._get_ivar(self.obj.GetAddress(), 'RLMObject._objectSchema') def get_schema(object_schema): properties = self._get_ivar(object_schema, 'RLMObjectSchema._properties') if not properties: return None count = self._eval("(NSUInteger)[((NSArray *){}) count]".format(properties)).GetValueAsUnsigned() return [self._get_prop(properties, i) for i in range(count)] self.props = cache_lookup(schema_cache, object_schema, get_schema) def num_children(self): return len(self.props) + 2 def has_children(self): return not is_object_deleted(self.obj) def get_child_index(self, name): if name == 'realm': return 0 if name == 'objectSchema': return 1 return next(i for i, (prop_name, _) in enumerate(self.props) if prop_name == name) def get_child_at_index(self, index): if index == 0: return self._value_from_ivar('realm') if index == 1: return self._value_from_ivar('objectSchema') name, getter = self.props[index - 2] value = self._eval(getter) return self.obj.CreateValueFromData(name, value.GetData(), value.GetType()) def update(self): pass def _get_prop(self, props, i): prop = self._eval("(NSUInteger)[((NSArray *){}) objectAtIndex:{}]".format(props, i)).GetValueAsUnsigned() name = self._to_str(self._eval('[(NSString *){} UTF8String]'.format(self._get_ivar(prop, "RLMProperty._name"))).GetValueAsUnsigned()) type = self._get_ivar(prop, 'RLMProperty._type') getter = "({})[(id){} {}]".format(property_types.get(type, 'id'), self.obj.GetAddress(), name) return name, getter class_name_cache = {} def get_object_class_name(frame, obj, addr, ivar): class_name_ptr = get_ivar(obj, addr, ivar) def get_class_name(ptr): utf8_addr = frame.EvaluateExpression('(const char *)[(NSString *){} UTF8String]'.format(class_name_ptr)).GetValueAsUnsigned() return obj.GetProcess().ReadCStringFromMemory(utf8_addr, 1024, lldb.SBError()) return cache_lookup(class_name_cache, class_name_ptr, get_class_name) def RLMArray_SummaryProvider(obj, _): frame = obj.GetThread().GetSelectedFrame() class_name = get_object_class_name(frame, obj, obj.GetAddress(), 'RLMArray._objectClassName') count = frame.EvaluateExpression('(NSUInteger)[(RLMArray *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned() return "({}[{}])".format(class_name, count) results_mode_offset = None mode_type = None mode_query_value = None def is_results_evaluated(obj): global results_mode_offset, mode_type, mode_query_value if not results_mode_offset: results_offset, _, _ = get_ivar_info(obj, 'RLMResults._results') mode_offset, mode_type, _ = get_ivar_info(obj, 'Results.m_mode') results_mode_offset = results_offset + mode_offset mode_query_value = next(m for m in mode_type.enum_members if m.name == 'Query').GetValueAsUnsigned() addr = obj.GetAddress().GetFileAddress() mode = obj.GetProcess().ReadUnsignedFromMemory(addr + results_mode_offset, mode_type.size, lldb.SBError()) return mode != mode_query_value def results_object_class_name(obj): class_info = get_ivar(obj, obj.GetAddress(), 'RLMResults._info') object_schema = get_ivar(obj, class_info, 'RLMClassInfo.rlmObjectSchema') return get_object_class_name(obj.GetThread().GetSelectedFrame(), obj, object_schema, 'RLMObjectSchema._className') def RLMResults_SummaryProvider(obj, _): class_name = results_object_class_name(obj) if not is_results_evaluated(obj): return 'Unevaluated query on ' + class_name frame = obj.GetThread().GetSelectedFrame() count = frame.EvaluateExpression('(NSUInteger)[(RLMResults *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned() return "({}[{}])".format(class_name, count) class RLMCollection_SyntheticChildrenProvider(SyntheticChildrenProvider): def __init__(self, valobj, _): super(RLMCollection_SyntheticChildrenProvider, self).__init__(valobj.deref.type.name) self.obj = valobj self.addr = self.obj.GetAddress() def num_children(self): if not self.count: self.count = self._eval("(NSUInteger)[(id){} count]".format(self.addr)).GetValueAsUnsigned() return self.count + 1 def has_children(self): return True def get_child_index(self, name): if name == 'realm': return 0 if not name.startswith('['): return None return int(name.lstrip('[').rstrip(']')) + 1 def get_child_at_index(self, index): if index == 0: return self._value_from_ivar('realm') value = self._eval('(id)[(id){} objectAtIndex:{}]'.format(self.addr, index - 1)) return self.obj.CreateValueFromData('[' + str(index - 1) + ']', value.GetData(), value.GetType()) def update(self): self.count = None def __lldb_init_module(debugger, _): debugger.HandleCommand('type summary add RLMArray -F rlm_lldb.RLMArray_SummaryProvider') debugger.HandleCommand('type summary add RLMArrayLinkView -F rlm_lldb.RLMArray_SummaryProvider') debugger.HandleCommand('type summary add RLMResults -F rlm_lldb.RLMResults_SummaryProvider') debugger.HandleCommand('type summary add -x RLMAccessor_ -F rlm_lldb.RLMObject_SummaryProvider') debugger.HandleCommand('type synthetic add RLMArray --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add RLMArrayLinkView --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add RLMResults --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add -x RLMAccessor_.* --python-class rlm_lldb.RLMObject_SyntheticChildrenProvider') ================================================ FILE: scripts/create-release-package.rb ================================================ #!/usr/bin/env ruby require 'fileutils' require 'pathname' require 'tmpdir' raise 'usage: create-release-package.rb destination_path version [xcode_versions]' unless ARGV.length >= 3 DESTINATION = Pathname(ARGV[0]) VERSION = ARGV[1] XCODE_VERSIONS = ARGV[2..] ROOT = Pathname(__FILE__).+('../..').expand_path BUILD_SH = Pathname(__FILE__).+('../../build.sh').expand_path VERBOSE = false OBJC_XCODE_VERSION = XCODE_VERSIONS.reduce { |a, e| e.include?('beta') ? a : e } def sh(*args) puts "executing: #{args.join(' ')}" if VERBOSE system(*args, VERBOSE ? {} : {:out => '/dev/null'}) || exit(1) end def platforms(xcode_version) if xcode_version.to_f >= 15.2 %w{osx ios watchos tvos catalyst visionos} else %w{osx ios watchos tvos catalyst} end end def create_xcframework(root, xcode_version, configuration, name) prefix = "#{root}/#{xcode_version}" output = "#{prefix}/#{configuration}/#{name}.xcframework" files = Dir.glob "#{prefix}/build/#{configuration}/*/#{name}.xcframework/*/#{name}.framework" sh 'xcodebuild', '-create-xcframework', '-allow-internal-distribution', '-output', output, *files.flat_map {|f| ['-framework', f]} end def zip(name, *files) path = (DESTINATION + name).to_path FileUtils.rm_f path sh 'zip', '--symlinks', '-r', path, *files end puts "Packaging version #{VERSION} for Xcode versions #{XCODE_VERSIONS.join(', ')}" FileUtils.mkdir_p DESTINATION Dir.mktmpdir do |tmp| # The default temp directory is in /var, which is a symlink to /private/var # xcodebuild's relative path resolution breaks due to this and we need to # give it the fully resolved path tmp = File.realpath tmp for version in XCODE_VERSIONS puts "Extracting source binaries for Xcode #{version}" FileUtils.mkdir_p "#{tmp}/#{version}" Dir.chdir("#{tmp}/#{version}") do for platform in platforms(version) sh 'tar', 'xf', "#{ROOT}/build-#{platform}-#{version}-swift/build.tar" end sh 'tar', 'xf', "#{ROOT}/build-ios-#{version}-static/build.tar" end end for version in XCODE_VERSIONS puts "Creating Swift XCFrameworks for Xcode #{version}" create_xcframework tmp, version, 'Release', 'RealmSwift' end puts 'Creating Obj-C XCFrameworks' create_xcframework tmp, OBJC_XCODE_VERSION, 'Release', 'Realm' create_xcframework tmp, OBJC_XCODE_VERSION, 'Static', 'Realm' puts 'Creating release package' package_dir = "#{tmp}/realm-swift-#{VERSION}" FileUtils.mkdir_p package_dir sh 'cp', "#{ROOT}/LICENSE", package_dir sh 'unzip', "#{ROOT}/realm-examples/realm-examples.zip", '-d', package_dir for lang in %w(objc swift) File.write "#{package_dir}/#{lang}-docs.webloc", %Q{ URL https://www.mongodb.com/docs/realm-sdks/${lang}/${version} } end sh 'cp', '-Rca', "#{tmp}/#{OBJC_XCODE_VERSION}/Release/Realm.xcframework", "#{package_dir}" FileUtils.mkdir_p "#{package_dir}/static" sh 'cp', '-Rca', "#{tmp}/#{OBJC_XCODE_VERSION}/Static/Realm.xcframework", "#{package_dir}/static" for version in XCODE_VERSIONS FileUtils.mkdir_p "#{package_dir}/#{version}" sh 'cp', '-Rca', "#{tmp}/#{version}/Release/RealmSwift.xcframework", "#{package_dir}/#{version}" end Dir.chdir(tmp) do zip "realm-swift-#{VERSION}.zip", "realm-swift-#{VERSION}" end puts 'Creating SPM release zips' Dir.chdir "#{tmp}/#{OBJC_XCODE_VERSION}/Release" do zip 'Realm.spm.zip', "Realm.xcframework" end for version in XCODE_VERSIONS Dir.chdir "#{tmp}/#{version}/Release" do zip "RealmSwift@#{version}.spm.zip", 'RealmSwift.xcframework' end end end puts 'Creating Carthage release zip' Dir.mktmpdir do |tmp| tmp = File.realpath tmp Dir.chdir(tmp) do for platform in platforms('15.1') sh 'tar', 'xf', "#{ROOT}/build-#{platform}-#{OBJC_XCODE_VERSION}-swift/build.tar" end create_xcframework tmp, '', 'Release', 'RealmSwift' create_xcframework tmp, '', 'Release', 'Realm' Dir.chdir('Release') do zip 'Carthage.xcframework.zip', 'Realm.xcframework', 'RealmSwift.xcframework' end end end ================================================ FILE: scripts/download-core.sh ================================================ #!/usr/bin/env bash set -euo pipefail source_root="$(dirname "$0")/.." readonly source_root # override this env variable if you need to download from a private mirror : "${REALM_BASE_URL:="https://static.realm.io/downloads/core"}" # set to "current" to always use the current build : "${REALM_CORE_VERSION:=$(sed -n 's/^REALM_CORE_VERSION=\(.*\)$/\1/p' "${source_root}/dependencies.list")}" # Provide a fallback value for TMPDIR, relevant for Xcode Bots : "${TMPDIR:=$(getconf DARWIN_USER_TEMP_DIR)}" readonly dst="$source_root/core" copy_core() { local src="$1" rm -rf "$dst" mkdir "$dst" ditto "$src" "$dst" # XCFramework processing only copies the "realm" headers, so put the third-party ones in a known location mkdir -p "$dst/include" find "$src" -name external -exec ditto "{}" "$dst/include/external" \; -quit # Remove the C API header to avoid accidentally pulling it in find "$dst" -name realm.h -delete } tries_left=3 readonly version="$REALM_CORE_VERSION" readonly url="${REALM_BASE_URL}/${version}/cocoa/realm-monorepo-xcframework-${version}.tar.xz" # First check if we need to do anything if [ -e "$dst" ]; then if [ -e "$dst/version.txt" ]; then if [ "$(cat "$dst/version.txt")" == "$version" ]; then echo "Version ${version} already present" exit 0 else echo "Switching from version $(cat "$dst/version.txt") to ${version}" fi else if [ "$(find "$dst" -name librealm-monorepo.a)" ]; then echo 'Using existing custom core build without checking version' exit 0 fi fi fi # We may already have this version downloaded and just need to set it as # the active one readonly versioned_name="realm-core-${version}-xcframework" readonly versioned_dir="$source_root/$versioned_name" if [ -e "$versioned_dir/version.txt" ]; then echo "Setting ${version} as the active version" copy_core "$versioned_dir" exit 0 fi echo "Downloading dependency: ${version} from ${url}" if [ -z "$TMPDIR" ]; then TMPDIR='/tmp' fi temp_dir=$(dirname "$TMPDIR/waste")/realm-core-tmp readonly temp_dir mkdir -p "$temp_dir" readonly tar_path="${temp_dir}/${versioned_name}.tar.xz" readonly temp_path="${tar_path}.tmp" while [ 0 -lt $tries_left ] && [ ! -f "$tar_path" ]; do if ! error=$(/usr/bin/curl --fail --silent --show-error --location "$url" --output "$temp_path" 2>&1); then tries_left=$((tries_left-1)) else mv "$temp_path" "$tar_path" fi done if [ ! -f "$tar_path" ]; then printf "Downloading core failed:\n\t%s\n\t%s\n" "$url" "$error" exit 1 fi ( cd "$temp_dir" rm -rf core tar xf "$tar_path" --xz if [ ! -f core/version.txt ]; then printf %s "${version}" > core/version.txt fi mv core "${versioned_name}" ) rm -rf "${versioned_dir}" mv "${temp_dir}/${versioned_name}" "$source_root" copy_core "$versioned_dir" ================================================ FILE: scripts/generate-rlmversion.sh ================================================ #!/bin/sh : ${SRCROOT:?"generate-rlmversion.sh must be invoked as part of an Xcode script phase"} TEMPORARY_FILE="${TARGET_TEMP_DIR}/RLMVersion.h" DESTINATION_FILE="${DERIVED_FILE_DIR}/RLMVersion.h" echo "#define REALM_COCOA_VERSION @\"$(sh build.sh get-version)\"" > ${TEMPORARY_FILE} if ! cmp -s "${TEMPORARY_FILE}" "${DESTINATION_FILE}"; then echo "Updating ${DESTINATION_FILE}" cp "${TEMPORARY_FILE}" "${DESTINATION_FILE}" fi ================================================ FILE: scripts/github_release.rb ================================================ #!/usr/bin/env ruby require 'pathname' require 'octokit' require 'fileutils' BUILD_SH = Pathname(__FILE__).+('../../build.sh').expand_path REPOSITORY = 'realm/realm-swift' def sh(*args) puts "executing: #{args.join(' ')}" if false system(*args, false ? {} : {:out => '/dev/null'}) || exit(1) end def release_notes(version) changelog = BUILD_SH.parent.+('CHANGELOG.md').readlines current_version_index = changelog.find_index { |line| line =~ (/^#{Regexp.escape version}/) } unless current_version_index raise "Update the changelog for the last version (#{version})" end current_version_index += 2 previous_version_lines = changelog[(current_version_index+1)...-1] previous_version_index = current_version_index + (previous_version_lines.find_index { |line| line =~ /^\d+\.\d+\.\d+(-(alpha|beta|rc)(\.\d+)?)?\s+/ } || changelog.count) relevant = changelog[current_version_index..previous_version_index] relevant.join.strip end def create_release(version) access_token = ENV['GITHUB_ACCESS_TOKEN'] raise 'GITHUB_ACCESS_TOKEN must be set to create GitHub releases' unless access_token release_notes = release_notes(version) github = Octokit::Client.new github.access_token = ENV['GITHUB_ACCESS_TOKEN'] puts 'Creating GitHub release' prerelease = (version =~ /alpha|beta|rc|preview/) ? true : false release = "v#{version}" response = github.create_release(REPOSITORY, release, name: release, body: release_notes, prerelease: prerelease) release_url = response[:url] Dir.glob 'release-package/*.zip' do |upload| puts "Uploading #{upload} to GitHub" github.upload_asset(release_url, upload, content_type: 'application/zip') end end def package_release_notes(version) release_notes = release_notes(version) FileUtils.mkdir_p("ExtractedChangelog") out_file = File.new("ExtractedChangelog/CHANGELOG.md", "w") out_file.puts(release_notes) end def download_artifacts(key, sha) access_token = ENV['GITHUB_ACCESS_TOKEN'] raise 'GITHUB_ACCESS_TOKEN must be set to create GitHub releases' unless access_token github = Octokit::Client.new github.auto_paginate = true github.access_token = ENV['GITHUB_ACCESS_TOKEN'] response = github.repository_artifacts(REPOSITORY) sha_artifacts = response[:artifacts].filter { |artifact| artifact[:workflow_run][:head_sha] == sha && artifact[:name] == key } sha_artifacts.each { |artifact| download_url = github.artifact_download_url(REPOSITORY, artifact[:id]) download(artifact[:name], download_url) } end def download(name, url) sh 'curl', '--output', "#{name}.zip", "#{url}" end if ARGV[0] == 'create-release' version = ARGV[1] create_release(version) elsif ARGV[0] == 'package-release-notes' version = ARGV[1] package_release_notes(version) elsif ARGV[0] == 'download-artifacts' key = ARGV[1] sha = ARGV[2] download_artifacts(key, sha) end ================================================ FILE: scripts/package_examples.rb ================================================ #!/usr/bin/env ruby require 'fileutils' require 'xcodeproj' ########################## # Helpers ########################## def remove_reference_to_realm_xcode_project(workspace_path) workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path) file_references = workspace.file_references.reject do |file_reference| file_reference.path == '../../../Realm.xcodeproj' end workspace = Xcodeproj::Workspace.new(nil) file_references.each { |ref| workspace << ref } workspace.save_as(workspace_path) end def replace_in_file(filepath, pattern, replacement) contents = File.read(filepath) File.open(filepath, "w") do |file| file.puts contents.gsub(pattern, replacement) end end def replace_framework(example, framework, path) project_path = "#{example}/RealmExamples.xcodeproj" replace_in_file("#{project_path}/project.pbxproj", /lastKnownFileType = wrapper.framework; path = (#{framework}).framework; sourceTree = BUILT_PRODUCTS_DIR;/, "lastKnownFileType = wrapper.xcframework; name = \\1.xcframework; path = \"#{path}/\\1.xcframework\"; sourceTree = \"\";") replace_in_file("#{project_path}/project.pbxproj", /(#{framework}).framework/, "\\1.xcframework") end ########################## # Script ########################## base_examples = [ "examples/ios/objc", "examples/osx/objc", "examples/tvos/objc", "examples/ios/swift", "examples/tvos/swift", ] xcode_versions = %w(26.1 26.2 26.3 26.4) # Remove reference to Realm.xcodeproj from all example workspaces. base_examples.each do |example| remove_reference_to_realm_xcode_project("#{example}/RealmExamples.xcworkspace") end # Make a copy of each Swift example for each Swift version. base_examples.each do |example| if example =~ /\/swift$/ xcode_versions.each do |xcode_version| FileUtils.cp_r example, "#{example}-#{xcode_version}" end FileUtils.rm_r example end end # Update the paths to the prebuilt frameworks replace_framework('examples/ios/objc', 'Realm', '../../../static') replace_framework('examples/osx/objc', 'Realm', '../../..') replace_framework('examples/tvos/objc', 'Realm', '../../..') xcode_versions.each do |xcode_version| replace_framework("examples/ios/swift-#{xcode_version}", 'Realm', "../../..") replace_framework("examples/tvos/swift-#{xcode_version}", 'Realm', "../../..") replace_framework("examples/ios/swift-#{xcode_version}", 'RealmSwift', "../../../#{xcode_version}") replace_framework("examples/tvos/swift-#{xcode_version}", 'RealmSwift', "../../../#{xcode_version}") end # Update Playground imports and instructions xcode_versions.each do |xcode_version| playground_file = "examples/ios/swift-#{xcode_version}/GettingStarted.playground/Contents.swift" replace_in_file(playground_file, 'choose RealmSwift', 'choose PlaygroundFrameworkWrapper') replace_in_file(playground_file, "import Foundation\n", "import Foundation\nimport PlaygroundFrameworkWrapper // only necessary to use a binary release of Realm Swift in this playground.\n") end ================================================ FILE: scripts/pr-ci-matrix.rb ================================================ #!/usr/bin/env ruby XCODE_VERSIONS = %w(26.1 26.2 26.3 26.4) DOC_VERSION = '26.3' all = ->(v) { true } latest_only = ->(v) { v == XCODE_VERSIONS.last } oldest_and_latest = ->(v) { v == XCODE_VERSIONS.first or v == XCODE_VERSIONS.last } def minimum_version(major) ->(v) { v.split('.').first.to_i >= major } end targets = { 'osx' => all, 'osx-encryption' => latest_only, 'swiftpm' => oldest_and_latest, 'swiftpm-debug' => all, 'swiftpm-address' => latest_only, 'swiftpm-thread' => latest_only, 'ios-static' => oldest_and_latest, 'ios' => oldest_and_latest, 'watchos' => oldest_and_latest, 'tvos' => oldest_and_latest, 'visionos' => oldest_and_latest, 'osx-swift' => all, 'ios-swift' => oldest_and_latest, 'tvos-swift' => oldest_and_latest, 'osx-swift-evolution' => latest_only, 'ios-swift-evolution' => latest_only, 'tvos-swift-evolution' => latest_only, 'catalyst' => oldest_and_latest, 'catalyst-swift' => oldest_and_latest, 'xcframework' => latest_only, 'cocoapods-osx' => all, 'cocoapods-ios-static' => latest_only, 'cocoapods-ios' => latest_only, 'cocoapods-watchos' => latest_only, 'cocoapods-tvos' => latest_only, 'cocoapods-catalyst' => latest_only, 'ios-swiftui' => latest_only, } output_file = """ # This is a generated file produced by scripts/pr-ci-matrix.rb. name: Pull request build and test on: pull_request: paths-ignore: - '**.md' workflow_dispatch: jobs: docs: runs-on: macos-26 name: Test docs steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: sudo xcode-select -switch /Applications/Xcode_#{DOC_VERSION}.app - run: bundle exec sh build.sh verify-docs swiftlint: runs-on: macos-26 name: Check swiftlint steps: - uses: actions/checkout@v4 - run: sudo xcode-select -switch /Applications/Xcode_#{DOC_VERSION}.app - run: brew install swiftlint - run: sh build.sh verify-swiftlint """ targets.each { |name, filter| XCODE_VERSIONS.each { |version| if not filter.call(version) next end image = 'macos-26' output_file << """ #{name}-#{version.gsub(' ', '_').gsub('.', '_')}: runs-on: #{image} name: Test #{name} on Xcode #{version} env: DEVELOPER_DIR: '/Applications/Xcode_#{version}.app/Contents/Developer' steps: - uses: actions/checkout@v4 - run: sh -x build.sh ci-pr #{name} """ } } File.open('.github/workflows/build-pr.yml', "w") do |file| file.puts output_file end ================================================ FILE: scripts/release-matrix.rb ================================================ #!/usr/bin/env ruby # Matrix of resulting build actions for each release workflow BuildDestination = Struct.new(:build_platform, :destination) do |cls| def cls.macOS BuildDestination.new('MACOS', 'ANY_MAC') end def cls.catalyst BuildDestination.new('MACOS', 'ANY_MAC_CATALYST') end def cls.iOS BuildDestination.new('IOS', 'ANY_IOS_DEVICE') end def cls.iOSSimulator BuildDestination.new('IOS', 'ANY_IOS_SIMULATOR') end def cls.tvOS BuildDestination.new('TVOS', 'ANY_TVOS_DEVICE') end def cls.tvOSSimulator BuildDestination.new('TVOS', 'ANY_TVOS_SIMULATOR') end def cls.watchOS BuildDestination.new('WATCHOS', 'ANY_WATCHOS_DEVICE') end def cls.watchOSSimulator BuildDestination.new('WATCHOS', 'ANY_WATCHOS_SIMULATOR') end def cls.visionOS BuildDestination.new('VISIONOS', 'ANY_VISIONOS_DEVICE') end def cls.visionOSSimulator BuildDestination.new('VISIONOS', 'ANY_VISIONOS_SIMULATOR') end def cls.generic BuildDestination.new('MACOS', nil) end end RELEASE_DESTINATIONS = { "osx" => BuildDestination.macOS, "catalyst" => BuildDestination.catalyst, "ios" => BuildDestination.iOS, "iossimulator" => BuildDestination.iOSSimulator, "tvos" => BuildDestination.tvOS, "tvossimulator" => BuildDestination.tvOSSimulator, "watchos" => BuildDestination.watchOS, "watchossimulator" => BuildDestination.watchOSSimulator, "visionos" => BuildDestination.visionOS, "visionossimulator" => BuildDestination.visionOSSimulator } ReleaseTarget = Struct.new(:name, :scheme, :platform) do def action build_destination = RELEASE_DESTINATIONS[platform] action = { name: self.name, actionType: 'BUILD', destination: build_destination.destination, buildDistributionAudience: nil, scheme: self.scheme, platform: build_destination.build_platform, isRequiredToPass: true } return action end end ================================================ FILE: scripts/setup-cocoapods.sh ================================================ #!/usr/bin/env bash set -euo pipefail source_root="$(dirname "$0")/.." readonly source_root if [ ! -f "$source_root/core/version.txt" ]; then sh "$source_root/scripts/download-core.sh" fi rm -rf "$source_root/include" mkdir -p "$source_root/include" cp -R "$source_root/core/realm-monorepo.xcframework/ios-arm64/Headers" "$source_root/include/core" mkdir -p "$source_root/include" cp "$source_root/Realm/"*.h "$source_root/Realm/"*.hpp "$source_root/include" echo "#define REALM_IOPLATFORMUUID @\"$(sh $source_root/build.sh get-ioplatformuuid)\"" >> "$source_root/Realm/RLMAnalytics.hpp" ================================================ FILE: scripts/swift-version.sh ================================================ #!/usr/bin/env bash : "${REALM_XCODE_VERSION:=}" : "${DEVELOPER_DIR:=}" get_xcode_version() { "$1" -version 2>/dev/null | sed -ne 's/^Xcode \([^\b ]*\).*/\1/p' } is_xcode_version() { test "$(get_xcode_version "$1")" = "$2" } find_xcode_with_version() { local path required_version if [ -z "$1" ]; then echo "find_xcode_with_version requires an Xcode version" >&2 exit 1 fi required_version=$1 # First check if the currently active one is fine, unless we are in a CI run if [ -z "$JENKINS_HOME" ] && is_xcode_version xcodebuild "$required_version"; then xcode-select -p return 0 fi # Check the spot where we install it on CI machines path="/Applications/Xcode-${required_version}.app/Contents/Developer" if [ -d "$path" ]; then if is_xcode_version "$path/usr/bin/xcodebuild" "$required_version"; then echo "$path" return 0 fi fi # Check all of the items in /Applications that look promising per #4534 for path in /Applications/Xcode*.app/Contents/Developer; do if is_xcode_version "$path/usr/bin/xcodebuild" "$required_version"; then echo "$path" return 0 fi done # Use Spotlight to see if we can find others installed copies of Xcode for path in $(/usr/bin/mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null); do path="$path/Contents/Developer" if [ ! -d "$path" ]; then continue fi if is_xcode_version "$path/usr/bin/xcodebuild" "$required_version"; then echo "$path" return 0 fi done echo "No Xcode found with version $required_version" >&2 exit 1 } test_xcode_for_swift_version() { if [ -z "$1" ] || [ -z "$2" ]; then echo "test_xcode_for_swift_version called with empty parameter(s): '$1' or '$2'" >&2 exit 1 fi local path=$1 local required_version=$2 for swift in "$path"/Toolchains/*.xctoolchain/usr/bin/swift; do if [ "$(get_swift_version "$swift")" = "$required_version" ]; then return 0 fi done return 1 } find_xcode_for_swift() { local path required_version if [ -z "$1" ]; then echo "find_xcode_for_swift requires a Swift version" >&2 exit 1 fi required_version=$1 # First check if the currently active one is fine, unless we are in a CI run if [ -z "$JENKINS_HOME" ] && test_xcode_for_swift_version "$(xcode-select -p)" "$required_version"; then DEVELOPER_DIR=$(xcode-select -p) return 0 fi # Check all of the items in /Applications that look promising per #4534 for path in /Applications/Xcode*.app/Contents/Developer; do if test_xcode_for_swift_version "$path" "$required_version"; then DEVELOPER_DIR=$path return 0 fi done # Use Spotlight to see if we can find others installed copies of Xcode for path in $(/usr/bin/mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null); do path="$path/Contents/Developer" if [ ! -d "$path" ]; then continue fi if test_xcode_for_swift_version "$path" "$required_version"; then DEVELOPER_DIR=$path return 0 fi done echo "No version of Xcode found that supports Swift $required_version" >&2 exit 1 } find_default_xcode_version() { DEVELOPER_DIR="$(xcode-select -p)" # Verify that DEVELOPER_DIR points to an Xcode installation, rather than the Xcode command-line tools. if [ -x "$DEVELOPER_DIR/usr/bin/xcodebuild" ]; then # It's an Xcode installation so we're good to go. return 0 fi echo "WARNING: The active Xcode command line tools, as returned by 'xcode-select -p', are not from Xcode." >&2 echo " The newest version of Xcode will be used instead." >&2 # Find the newest version of Xcode available on the system, based on CFBundleVersion. local xcode_version newest_xcode_version newest_xcode_path newest_xcode_version=0 for path in $(/usr/bin/mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null); do xcode_version=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$path/Contents/Info.plist") if echo "$xcode_version" "$newest_xcode_version" | awk '{exit !( $1 > $2)}'; then newest_xcode_version="$xcode_version" newest_xcode_path="$path" fi done if [ -z "$newest_xcode_path" ]; then echo "No version of Xcode could be found" >&2 exit 1 fi DEVELOPER_DIR="$newest_xcode_path/Contents/Developer" } set_xcode_version() { if [ -n "$REALM_XCODE_VERSION" ]; then DEVELOPER_DIR=$(find_xcode_with_version "$REALM_XCODE_VERSION") elif [ -z "$DEVELOPER_DIR" ]; then find_default_xcode_version fi export DEVELOPER_DIR # Setting this silences some seemingly spurious warnings export XCODE_DEVELOPER_DIR_PATH="$DEVELOPER_DIR" REALM_XCODE_VERSION="$(get_xcode_version "$DEVELOPER_DIR/usr/bin/xcodebuild")" export REALM_XCODE_VERSION } return 2>/dev/null || { # only run if called directly set_xcode_version echo "Found Xcode version $REALM_XCODE_VERSION at $DEVELOPER_DIR" }