diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfc82920e..e66ef4a135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ x.x.x Release notes (yyyy-MM-dd) 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`. 1.0.0 Release notes (2016-05-25) diff --git a/Realm/RLMObjectSchema.mm b/Realm/RLMObjectSchema.mm index 036cc5e9ca..a122657031 100644 --- a/Realm/RLMObjectSchema.mm +++ b/Realm/RLMObjectSchema.mm @@ -171,6 +171,16 @@ + (instancetype)schemaForObjectClass:(Class)objectClass { return schema; } ++ (nullable NSString *)baseNameForLazySwiftProperty:(NSString *)propertyName { + // 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 'x.storage'. Note that '.' is illegal in either a Swift or Objective-C property name. + NSString *const storageSuffix = @".storage"; + if ([propertyName hasSuffix:storageSuffix]) { + return [propertyName substringToIndex:propertyName.length - storageSuffix.length]; + } + return nil; +} + + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass { Class objectUtil = [objectClass objectUtilClass:isSwiftClass]; NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass]; @@ -267,6 +277,18 @@ + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass { } if (auto type = RLMCoerceToNil(propertyType)) { if (existing == NSNotFound) { + // Check to see if this optional property is an underlying storage property for a Swift lazy var. + // Managed lazy vars are't allowed. + // NOTE: Revisit this once property behaviors are implemented in Swift. + if (NSString *lazyPropertyBaseName = [self baseNameForLazySwiftProperty:propertyName]) { + if ([ignoredProperties containsObject:lazyPropertyBaseName]) { + // This property is the storage property for a ignored lazy Swift property. Just continue. + return; + } else { + @throw RLMException(@"Lazy managed property '%@' is not allowed on a Realm Swift object class. Either add the property to the ignored properties list or make it non-lazy.", lazyPropertyBaseName); + } + } + // The current property isn't a storage property for a lazy Swift property. property = [[RLMProperty alloc] initSwiftOptionalPropertyWithName:propertyName indexed:[indexed containsObject:propertyName] ivar:class_getInstanceVariable(objectClass, propertyName.UTF8String) diff --git a/Realm/Tests/Swift2.2/SwiftPropertyTypeTest.swift b/Realm/Tests/Swift2.2/SwiftPropertyTypeTest.swift index a28c55c5ec..71d508e2dd 100644 --- a/Realm/Tests/Swift2.2/SwiftPropertyTypeTest.swift +++ b/Realm/Tests/Swift2.2/SwiftPropertyTypeTest.swift @@ -103,4 +103,20 @@ class SwiftPropertyTypeTest: RLMTestCase { XCTAssertEqual(obj.int32, v32) XCTAssertEqual(obj.int64, v64) } + + func testLazyVarProperties() { + let realm = realmWithTestPath() + let succeeded : Void? = try? realm.transactionWithBlock() { + realm.addObject(SwiftLazyVarObject()) + } + XCTAssertNotNil(succeeded, "Writing an NSObject-based object with an lazy property should work.") + } + + func testIgnoredLazyVarProperties() { + let realm = realmWithTestPath() + let succeeded : Void? = try? realm.transactionWithBlock() { + realm.addObject(SwiftIgnoredLazyVarObject()) + } + XCTAssertNotNil(succeeded, "Writing an object with an ignored lazy property should work.") + } } diff --git a/Realm/Tests/Swift2.2/SwiftTestObjects.swift b/Realm/Tests/Swift2.2/SwiftTestObjects.swift index 258e78ece4..228166f935 100644 --- a/Realm/Tests/Swift2.2/SwiftTestObjects.swift +++ b/Realm/Tests/Swift2.2/SwiftTestObjects.swift @@ -141,3 +141,12 @@ class SwiftLinkTargetObject: RLMObject { return ["backlinks": RLMPropertyDescriptor(withClass: SwiftLinkSourceObject.self, propertyName: "link")] } } + +class SwiftLazyVarObject : RLMObject { + dynamic lazy var lazyProperty : String = "hello world" +} + +class SwiftIgnoredLazyVarObject : RLMObject { + dynamic lazy var ignoredVar : String = "hello world" + override class func ignoredProperties() -> [String] { return ["ignoredVar"] } +} diff --git a/RealmSwift-swift2.2/Tests/ObjectSchemaInitializationTests.swift b/RealmSwift-swift2.2/Tests/ObjectSchemaInitializationTests.swift index aed8fb9cb2..b51a9da530 100644 --- a/RealmSwift-swift2.2/Tests/ObjectSchemaInitializationTests.swift +++ b/RealmSwift-swift2.2/Tests/ObjectSchemaInitializationTests.swift @@ -122,6 +122,8 @@ class ObjectSchemaInitializationTests: TestCase { "Should throw when not ignoring a property of a type we can't persist") assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithOptionalStringArray.self), "Should throw when not ignoring a property of a type we can't persist") + assertThrows(RLMObjectSchema(forObjectClass: RealmObjectWithLazyVar.self), + "Should throw when not ignoring a property marked as 'dynamic lazy var'") // Shouldn't throw when not ignoring a property of a type we can't persist if it's not dynamic _ = RLMObjectSchema(forObjectClass: SwiftObjectWithEnum.self) @@ -147,6 +149,10 @@ class ObjectSchemaInitializationTests: TestCase { 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") + + assertSucceeds("Should not throw when ignoring a lazy property.") { + _ = RLMObjectSchema(forObjectClass: RealmObjectWithIgnoredLazyVar.self) + } } func testIndexedProperties() { @@ -272,3 +278,12 @@ extension Set: RealmOptionalType { } class SwiftObjectWithNonRealmOptionalType: SwiftFakeObject { let set = RealmOptional>() } + +class RealmObjectWithLazyVar: SwiftFakeObject { + dynamic lazy var someVar = "" +} + +class RealmObjectWithIgnoredLazyVar: Object { + dynamic lazy var ignoredVar = "" + dynamic override class func ignoredProperties() -> [String] { return ["ignoredVar"] } +}