Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for arrays of more types #5274

Merged
merged 32 commits into from
Sep 23, 2017
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1b239e7
Fix more cases where assigning an RLMArray property to itself would c…
tgoyne Aug 25, 2017
9b58677
Merge branch 'tg/list-self-assignment' into master-3.0
tgoyne Aug 25, 2017
fa96e2a
Deliver notifications for deleted RLMArrays
tgoyne Aug 22, 2017
ed6d9ac
Remove a use of containsString:
tgoyne Aug 22, 2017
4b4f331
Add support for arrays of more types
tgoyne Jun 21, 2017
4301d21
Delete some unreachable code
tgoyne Aug 24, 2017
df5d94b
Update objectstore
tgoyne Sep 7, 2017
b87840d
Update to core 3.1/sync 2.0-rc18
tgoyne Sep 7, 2017
fb1e11f
Change the optional property on collections to BOOL
tgoyne Sep 7, 2017
623348f
Merge remote-tracking branch 'origin/master-3.0' into tg/primitive-array
bdash Sep 18, 2017
bfee691
Duplicate the protocol `RealmCollection` extensions related to arrays…
bdash Sep 19, 2017
7260ee4
Remove some duplicated file references from the Xcode project.
bdash Sep 19, 2017
93fb306
Add missing `@objc` annotations to `SwiftPrimitiveArrayObject`.
bdash Sep 19, 2017
f004e85
Don't use -compare: to compare NSData objects.
bdash Sep 19, 2017
781b2c3
Fix primitive list tests with Swift 4.
bdash Sep 19, 2017
6cb5edf
Don't hard-code the type of the string in the expected error.
bdash Sep 19, 2017
df38d3a
Disable a portion of `testReplaceRange` that hits issues with Swift 4's
bdash Sep 19, 2017
a5ca2b5
Rename `PrimitiveListTestsBase.assertEqual`.
bdash Sep 19, 2017
1465f3e
Tweak some tests to be compatible with Swift 3.0.
bdash Sep 19, 2017
9557147
Fix or suppress SwiftLint violations.
bdash Sep 19, 2017
aa4d784
Fix tests with Swift 3.0.
bdash Sep 19, 2017
454110c
Compile Util.swift into RealmSwift Tests rather than using `@testable`.
bdash Sep 19, 2017
6531f8d
Increase the timeout for the interprocess tests.
bdash Sep 19, 2017
699c298
Add documentation for extension methods on RealmCollection, and
bdash Sep 19, 2017
fac6ff9
Remove an unnecessary changelog section.
bdash Sep 19, 2017
072d391
Swap the order of SwiftLint and documentation suppression comments.
bdash Sep 19, 2017
39d5b7a
Fix some compiler warnings in PrimitiveListTests.swift.
bdash Sep 21, 2017
aab7f5f
Throw an exception when querying arrays of primitives rather than sil…
tgoyne Sep 22, 2017
dc976ca
Add a changelog entry
tgoyne Sep 22, 2017
307946f
Merge remote-tracking branch 'origin/master-3.0' into tg/primitive-array
tgoyne Sep 22, 2017
e1b6c8d
Invert the sense of the define that Util.swift looks for when
bdash Sep 22, 2017
4124acd
Merge remote-tracking branch 'origin/master-3.0' into tg/primitive-array
tgoyne Sep 23, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,22 @@ x.x.x Release notes (yyyy-MM-dd)
`false` from `Object.shouldIncludeInDefaultSchema()`.
* Don't clear RLMArrays on self-assignment.

x.x.x Release notes (yyyy-MM-dd)
=============================================================

### API Breaking Changes

* None.

### Enhancements

* None.

### Bugfixes

* Fix more cases where assigning an RLMArray property to itself would clear the
RLMArray.

2.10.0 Release notes (2017-08-21)
=============================================================

Expand Down
125 changes: 78 additions & 47 deletions Realm.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Realm/ObjectServerTests/SwiftObjectServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase {
let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
var theError: SyncError?

try autoreleasepool { _ in
try autoreleasepool {
let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
let ex = expectation(description: "Waiting for error handler to be called...")
SyncManager.shared.errorHandler = { (error, session) in
Expand Down
16 changes: 9 additions & 7 deletions Realm/RLMAccessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ class RLMAccessorContext {
id box(realm::Object&&);
id box(realm::RowExpr);

id box(realm::BinaryData v) { return RLMBinaryDataToNSData(v); }
id box(bool v) { return @(v); }
id box(double v) { return @(v); }
id box(float v) { return @(v); }
id box(long long v) { return @(v); }
id box(realm::StringData v) { return RLMStringDataToNSString(v); }
id box(realm::Timestamp v) { return RLMTimestampToNSDate(v); }
id box(realm::StringData v) { return RLMStringDataToNSString(v) ?: NSNull.null; }
id box(realm::BinaryData v) { return RLMBinaryDataToNSData(v) ?: NSNull.null; }
id box(realm::Timestamp v) { return RLMTimestampToNSDate(v) ?: NSNull.null; }
id box(realm::Mixed v) { return RLMMixedToObjc(v); }

id box(realm::util::Optional<bool> v) { return v ? @(*v) : nil; }
id box(realm::util::Optional<double> v) { return v ? @(*v) : nil; }
id box(realm::util::Optional<float> v) { return v ? @(*v) : nil; }
id box(realm::util::Optional<int64_t> v) { return v ? @(*v) : nil; }
id box(realm::util::Optional<bool> v) { return v ? @(*v) : NSNull.null; }
id box(realm::util::Optional<double> v) { return v ? @(*v) : NSNull.null; }
id box(realm::util::Optional<float> v) { return v ? @(*v) : NSNull.null; }
id box(realm::util::Optional<int64_t> v) { return v ? @(*v) : NSNull.null; }

void will_change(realm::Row const&, realm::Property const&);
void will_change(realm::Object& obj, realm::Property const& prop) { will_change(obj.row(), prop); }
Expand All @@ -68,6 +68,8 @@ class RLMAccessorContext {
RLMOptionalId default_value_for_property(realm::ObjectSchema const&,
std::string const& prop);

bool is_same_list(realm::List const& list, id v) const noexcept;

template<typename Func>
void enumerate_list(__unsafe_unretained const id v, Func&& func) {
for (id value in v) {
Expand Down
147 changes: 46 additions & 101 deletions Realm/RLMAccessor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -135,24 +135,16 @@ void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained id<NSFastEnumeration> const value) {
RLMVerifyInWriteTransaction(obj);
auto prop = obj->_info->propertyForTableColumn(colIndex);
RLMValidateValueForProperty(value, obj->_info->rlmObjectSchema, prop, true);

realm::List list(obj->_realm->_realm, obj->_row.get_linklist(colIndex));
if ([(id)value respondsToSelector:@selector(isBackedByList:)] && [(id)value isBackedByList:list]) {
return; // self-assignment is a no-op
realm::List list(obj->_realm->_realm, *obj->_row.get_table(), colIndex, obj->_row.get_index());
RLMClassInfo *info = obj->_info;
if (list.get_type() == realm::PropertyType::Object) {
info = &obj->_info->linkTargetType(prop.index);
}

list.remove_all();
if (!value || (id)value == NSNull.null) {
return;
}

RLMAccessorContext ctx(obj->_realm,
obj->_info->linkTargetType(obj->_info->propertyForTableColumn(colIndex).index));
translateError([&] {
for (id element in value) {
list.add(ctx, element);
}
});
RLMAccessorContext ctx(obj->_realm, *info);
translateError([&] { list.assign(ctx, value, false); });
}

void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
Expand Down Expand Up @@ -214,10 +206,6 @@ void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
}

// any getter/setter
void setValue(__unsafe_unretained RLMObjectBase *const, NSUInteger, __unsafe_unretained id) {
@throw RLMException(@"Modifying Mixed properties is not supported");
}

template<typename Type, typename StorageType=Type>
id makeGetter(NSUInteger index) {
return ^(__unsafe_unretained RLMObjectBase *const obj) {
Expand Down Expand Up @@ -352,7 +340,7 @@ id managedSetter(RLMProperty *prop, const char *type) {
case RLMPropertyTypeString: return makeSetter<NSString *>(prop);
case RLMPropertyTypeDate: return makeSetter<NSDate *>(prop);
case RLMPropertyTypeData: return makeSetter<NSData *>(prop);
case RLMPropertyTypeAny: return makeSetter<id>(prop);
case RLMPropertyTypeAny: return nil;
case RLMPropertyTypeLinkingObjects: return nil;
case RLMPropertyTypeObject: return makeSetter<RLMObjectBase *>(prop);
}
Expand Down Expand Up @@ -384,11 +372,23 @@ id unmanagedGetter(RLMProperty *prop, const char *) {
}
if (prop.array) {
NSString *propName = prop.name;
NSString *objectClassName = prop.objectClassName;
if (prop.type == RLMPropertyTypeObject) {
NSString *objectClassName = prop.objectClassName;
return ^(RLMObjectBase *obj) {
id val = superGet(obj, propName);
if (!val) {
val = [[RLMArray alloc] initWithObjectClassName:objectClassName];
superSet(obj, propName, val);
}
return val;
};
}
auto type = prop.type;
auto optional = prop.optional;
return ^(RLMObjectBase *obj) {
id val = superGet(obj, propName);
if (!val) {
val = [[RLMArray alloc] initWithObjectClassName:objectClassName];
val = [[RLMArray alloc] initWithObjectType:type optional:optional];
superSet(obj, propName, val);
}
return val;
Expand All @@ -398,17 +398,24 @@ id unmanagedGetter(RLMProperty *prop, const char *) {
}

id unmanagedSetter(RLMProperty *prop, const char *) {
// Only RLMArray needs special handling for the unmanaged setter
if (!prop.array) {
return nil;
}

NSString *propName = prop.name;
NSString *objectClassName = prop.objectClassName;
return ^(RLMObjectBase *obj, id<NSFastEnumeration> ar) {
return ^(RLMObjectBase *obj, id<NSFastEnumeration> values) {
auto prop = obj->_objectSchema[propName];
RLMValidateValueForProperty(values, obj->_objectSchema, prop, true);

// make copy when setting (as is the case for all other variants)
RLMArray *standaloneAr = [[RLMArray alloc] initWithObjectClassName:objectClassName];
[standaloneAr addObjects:ar];
superSet(obj, propName, standaloneAr);
RLMArray *ar;
if (prop.type == RLMPropertyTypeObject)
ar = [[RLMArray alloc] initWithObjectClassName:prop.objectClassName];
else
ar = [[RLMArray alloc] initWithObjectType:prop.type optional:prop.optional];
[ar addObjects:values];
superSet(obj, propName, ar);
};
}

Expand Down Expand Up @@ -527,11 +534,7 @@ void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id val) {
if (prop.isPrimary) {
@throw RLMException(@"Primary key can't be changed to '%@' after an object is inserted.", val);
}
if (!RLMIsObjectValidForProperty(val, prop)) {
@throw RLMException(@"Invalid property value '%@' for property '%@' of class '%@'",
val, propName, obj->_objectSchema.className);
}

RLMValidateValueForProperty(val, schema, prop, true);
RLMDynamicSet(obj, prop, RLMCoerceToNil(val));
}

Expand Down Expand Up @@ -574,7 +577,7 @@ id RLMDynamicGetByName(__unsafe_unretained RLMObjectBase *const obj,

RLMAccessorContext::RLMAccessorContext(RLMAccessorContext& parent, realm::Property const& property)
: _realm(parent._realm)
, _info(parent._info.linkTargetType(property))
, _info(property.type == realm::PropertyType::Object ? parent._info.linkTargetType(property) : parent._info)
, _promote_existing(parent._promote_existing)
{
}
Expand All @@ -600,38 +603,6 @@ id RLMDynamicGetByName(__unsafe_unretained RLMObjectBase *const obj,
return _defaultValues[key];
}

static void validateValueForProperty(__unsafe_unretained id const obj,
__unsafe_unretained RLMProperty *const prop,
RLMClassInfo const& info) {
if (prop.array) {
if (obj && obj != NSNull.null && ![obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
@throw RLMException(@"Array property value (%@) is not enumerable.", obj);
}
return;
}
switch (prop.type) {
case RLMPropertyTypeString:
case RLMPropertyTypeBool:
case RLMPropertyTypeDate:
case RLMPropertyTypeInt:
case RLMPropertyTypeFloat:
case RLMPropertyTypeDouble:
case RLMPropertyTypeData:
if (!RLMIsObjectValidForProperty(obj, prop)) {
@throw RLMException(@"Invalid value '%@' for property '%@.%@'",
obj, info.rlmObjectSchema.className, prop.name);
}
break;
case RLMPropertyTypeObject:
break;
case RLMPropertyTypeAny:
case RLMPropertyTypeLinkingObjects:
@throw RLMException(@"Invalid value '%@' for property '%@.%@'",
obj, info.rlmObjectSchema.className, prop.name);
}
}


id RLMAccessorContext::propertyValue(__unsafe_unretained id const obj, size_t propIndex,
__unsafe_unretained RLMProperty *const prop) {
// Property value from an NSArray
Expand Down Expand Up @@ -685,54 +656,36 @@ static void validateValueForProperty(__unsafe_unretained id const obj,
results:std::move(r)];
}

static void checkType(bool cond, __unsafe_unretained id v,
__unsafe_unretained NSString *const expected) {
if (__builtin_expect(!cond, 0)) {
@throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@'",
v, [v class], expected);
}
}

template<>
realm::Timestamp RLMAccessorContext::unbox(__unsafe_unretained id const value, bool, bool) {
id v = RLMCoerceToNil(value);
checkType(!v || [v respondsToSelector:@selector(timeIntervalSinceReferenceDate)], v, @"date");
return RLMTimestampForNSDate(v);
}

// Checking for NSNumber here rather than the selectors like the other ones
// because NSString implements the same selectors and we don't want implicit
// conversions from string
template<>
bool RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
checkType([v isKindOfClass:[NSNumber class]], v, @"bool");
return [v boolValue];
}
template<>
double RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
checkType([v isKindOfClass:[NSNumber class]], v, @"double");
return [v doubleValue];
}
template<>
float RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
checkType([v isKindOfClass:[NSNumber class]], v, @"float");
return [v floatValue];
}
template<>
long long RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
checkType([v isKindOfClass:[NSNumber class]], v, @"int");
return [v longLongValue];
}
template<>
realm::BinaryData RLMAccessorContext::unbox(id v, bool, bool) {
v = RLMCoerceToNil(v);
checkType(!v || [v respondsToSelector:@selector(bytes)], v, @"data");
return RLMBinaryDataForNSData(v);
}
template<>
realm::StringData RLMAccessorContext::unbox(id v, bool, bool) {
v = RLMCoerceToNil(v);
checkType(!v || [v respondsToSelector:@selector(UTF8String)], v, @"string");
return RLMStringDataWithNSString(v);
}

Expand All @@ -744,31 +697,19 @@ static auto to_optional(__unsafe_unretained id const value, Fn&& fn) {

template<>
realm::util::Optional<bool> RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
return to_optional(v, [&](__unsafe_unretained id v) {
checkType([v respondsToSelector:@selector(boolValue)], v, @"bool?");
return (bool)[v boolValue];
});
return to_optional(v, [&](__unsafe_unretained id v) { return (bool)[v boolValue]; });
}
template<>
realm::util::Optional<double> RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
return to_optional(v, [&](__unsafe_unretained id v) {
checkType([v respondsToSelector:@selector(doubleValue)], v, @"double?");
return [v doubleValue];
});
return to_optional(v, [&](__unsafe_unretained id v) { return [v doubleValue]; });
}
template<>
realm::util::Optional<float> RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
return to_optional(v, [&](__unsafe_unretained id v) {
checkType([v respondsToSelector:@selector(floatValue)], v, @"float?");
return [v floatValue];
});
return to_optional(v, [&](__unsafe_unretained id v) { return [v floatValue]; });
}
template<>
realm::util::Optional<int64_t> RLMAccessorContext::unbox(__unsafe_unretained id const v, bool, bool) {
return to_optional(v, [&](__unsafe_unretained id v) {
checkType([v respondsToSelector:@selector(longLongValue)], v, @"int?");
return [v longLongValue];
});
return to_optional(v, [&](__unsafe_unretained id v) { return [v longLongValue]; });
}

template<>
Expand Down Expand Up @@ -831,7 +772,7 @@ static auto to_optional(__unsafe_unretained id const value, Fn&& fn) {
auto prop = _info.rlmObjectSchema.properties[propIndex];
id value = propertyValue(obj, propIndex, prop);
if (value) {
validateValueForProperty(value, prop, _info);
RLMValidateValueForProperty(value, _info.rlmObjectSchema, prop);
}

if (_promote_existing && [obj isKindOfClass:_info.rlmObjectSchema.objectClass] && !prop.swiftIvar) {
Expand All @@ -853,3 +794,7 @@ static auto to_optional(__unsafe_unretained id const value, Fn&& fn) {
{
return RLMOptionalId{defaultValue(@(prop.c_str()))};
}

bool RLMAccessorContext::is_same_list(realm::List const& list, __unsafe_unretained id const v) const noexcept {
return [v respondsToSelector:@selector(isBackedByList:)] && [v isBackedByList:list];
}
20 changes: 15 additions & 5 deletions Realm/RLMArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@
//
////////////////////////////////////////////////////////////////////////////

#import <Foundation/Foundation.h>

#import <Realm/RLMCollection.h>

NS_ASSUME_NONNULL_BEGIN

@class RLMObject, RLMRealm, RLMResults<RLMObjectType>, RLMNotificationToken;
@class RLMObject, RLMResults<RLMObjectType>;

/**
`RLMArray` is the container type in Realm used to define to-many relationships.
Expand Down Expand Up @@ -67,9 +65,21 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, assign) NSUInteger count;

/**
The class name (i.e. type) of the objects contained in the array.
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) bool optional;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be BOOL, and should likely also have getter=isOptional to follow Cocoa's naming conventions.


/**
The class name of the objects contained in the array.

Will be `nil` if `type` is not RLMPropertyTypeObject.
*/
@property (nonatomic, readonly, copy) NSString *objectClassName;
@property (nonatomic, readonly, copy, nullable) NSString *objectClassName;

/**
The Realm which manages the array. Returns `nil` for unmanaged arrays.
Expand Down
Loading