You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
20 KiB
587 lines
20 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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"
|
|
|
|
// See -countByEnumeratingWithState:objects:count
|
|
@interface RLMArrayHolder : NSObject {
|
|
@public
|
|
std::unique_ptr<id[]> items;
|
|
}
|
|
@end
|
|
@implementation RLMArrayHolder
|
|
@end
|
|
|
|
@interface RLMArray () <RLMThreadConfined_Private>
|
|
@end
|
|
|
|
@implementation RLMArray {
|
|
@public
|
|
// Backing array when this instance is unmanaged
|
|
NSMutableArray *_backingArray;
|
|
}
|
|
|
|
#pragma mark - Initializers
|
|
|
|
- (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;
|
|
}
|
|
|
|
#pragma mark - Convenience wrappers used for all RLMArray types
|
|
|
|
- (void)addObjects:(id<NSFastEnumeration>)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]];
|
|
}
|
|
|
|
#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 [_backingArray objectAtIndex:index];
|
|
}
|
|
|
|
- (NSUInteger)count {
|
|
return _backingArray.count;
|
|
}
|
|
|
|
- (BOOL)isInvalidated {
|
|
return NO;
|
|
}
|
|
|
|
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
|
|
objects:(__unused __unsafe_unretained id [])buffer
|
|
count:(__unused NSUInteger)len {
|
|
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<id[]>(self.count);
|
|
|
|
NSUInteger i = 0;
|
|
for (id object in _backingArray) {
|
|
copy->items[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;
|
|
}
|
|
|
|
|
|
template<typename IndexSetFactory>
|
|
static void changeArray(__unsafe_unretained RLMArray *const ar,
|
|
NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
|
|
if (!ar->_backingArray) {
|
|
ar->_backingArray = [NSMutableArray new];
|
|
}
|
|
|
|
if (RLMObjectBase *parent = ar->_parentObject) {
|
|
NSIndexSet *indexes = is();
|
|
[parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
|
|
f();
|
|
[parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
|
|
}
|
|
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<RLMObjectBase>(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->_backingArray.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(_backingArray.count, array.count), ^{
|
|
[_backingArray addObjectsFromArray:array];
|
|
});
|
|
}
|
|
|
|
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
|
|
RLMArrayValidateMatchingObjectType(self, anObject);
|
|
validateArrayBounds(self, index, true);
|
|
changeArray(self, NSKeyValueChangeInsertion, index, ^{
|
|
[_backingArray insertObject:anObject atIndex:index];
|
|
});
|
|
}
|
|
|
|
- (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
|
|
changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
|
|
NSUInteger currentIndex = [indexes firstIndex];
|
|
for (RLMObject *obj in objects) {
|
|
RLMArrayValidateMatchingObjectType(self, obj);
|
|
[_backingArray insertObject:obj atIndex:currentIndex];
|
|
currentIndex = [indexes indexGreaterThanIndex:currentIndex];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)removeObjectAtIndex:(NSUInteger)index {
|
|
validateArrayBounds(self, index);
|
|
changeArray(self, NSKeyValueChangeRemoval, index, ^{
|
|
[_backingArray removeObjectAtIndex:index];
|
|
});
|
|
}
|
|
|
|
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
|
|
changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
|
|
[_backingArray removeObjectsAtIndexes:indexes];
|
|
});
|
|
}
|
|
|
|
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
|
|
RLMArrayValidateMatchingObjectType(self, anObject);
|
|
validateArrayBounds(self, index);
|
|
changeArray(self, NSKeyValueChangeReplacement, index, ^{
|
|
[_backingArray replaceObjectAtIndex:index withObject:anObject];
|
|
});
|
|
}
|
|
|
|
- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
|
|
validateArrayBounds(self, sourceIndex);
|
|
validateArrayBounds(self, destinationIndex);
|
|
id original = _backingArray[sourceIndex];
|
|
|
|
auto start = std::min(sourceIndex, destinationIndex);
|
|
auto len = std::max(sourceIndex, destinationIndex) - start + 1;
|
|
changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
|
|
[_backingArray removeObjectAtIndex:sourceIndex];
|
|
[_backingArray insertObject:original atIndex:destinationIndex];
|
|
});
|
|
}
|
|
|
|
- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
|
|
validateArrayBounds(self, index1);
|
|
validateArrayBounds(self, index2);
|
|
|
|
changeArray(self, NSKeyValueChangeReplacement, ^{
|
|
[_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
|
|
}, [=] {
|
|
NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
|
|
[set addIndex:index2];
|
|
return set;
|
|
});
|
|
}
|
|
|
|
- (NSUInteger)indexOfObject:(id)object {
|
|
RLMArrayValidateMatchingObjectType(self, object);
|
|
if (!_backingArray) {
|
|
return NSNotFound;
|
|
}
|
|
if (_type != RLMPropertyTypeObject) {
|
|
return [_backingArray indexOfObject:object];
|
|
}
|
|
|
|
NSUInteger index = 0;
|
|
for (RLMObjectBase *cmp in _backingArray) {
|
|
if (RLMObjectBaseAreEqual(object, cmp)) {
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return NSNotFound;
|
|
}
|
|
|
|
- (void)removeAllObjects {
|
|
changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
|
|
[_backingArray removeAllObjects];
|
|
});
|
|
}
|
|
|
|
- (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]];
|
|
}
|
|
|
|
static bool canAggregate(RLMPropertyType type, bool allowDate) {
|
|
switch (type) {
|
|
case RLMPropertyTypeInt:
|
|
case RLMPropertyTypeFloat:
|
|
case RLMPropertyTypeDouble:
|
|
return true;
|
|
case RLMPropertyTypeDate:
|
|
return allowDate;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
- (RLMPropertyType)typeForProperty:(NSString *)propertyName {
|
|
if ([propertyName isEqualToString:@"self"]) {
|
|
return _type;
|
|
}
|
|
|
|
RLMObjectSchema *objectSchema;
|
|
if (_backingArray.count) {
|
|
objectSchema = [_backingArray[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 [_backingArray 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"] ? _backingArray : [_backingArray 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 _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath];
|
|
}
|
|
|
|
if (!_backingArray) {
|
|
_backingArray = [NSMutableArray new];
|
|
}
|
|
|
|
NSUInteger dot = [keyPath rangeOfString:@"."].location;
|
|
if (dot == NSNotFound) {
|
|
return [_backingArray 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 (!_backingArray) {
|
|
_backingArray = [NSMutableArray new];
|
|
}
|
|
return [_backingArray valueForKey:key];
|
|
}
|
|
|
|
- (void)setValue:(id)value forKey:(NSString *)key {
|
|
if ([key isEqualToString:@"self"]) {
|
|
RLMArrayValidateMatchingObjectType(self, value);
|
|
for (NSUInteger i = 0, count = _backingArray.count; i < count; ++i) {
|
|
_backingArray[i] = value;
|
|
}
|
|
return;
|
|
}
|
|
else if (_type == RLMPropertyTypeObject) {
|
|
[_backingArray 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 (!_backingArray) {
|
|
return NSNotFound;
|
|
}
|
|
return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
|
|
return [predicate evaluateWithObject:obj];
|
|
}];
|
|
}
|
|
|
|
- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
|
|
if (!_backingArray) {
|
|
_backingArray = [NSMutableArray new];
|
|
}
|
|
return [_backingArray objectsAtIndexes:indexes];
|
|
}
|
|
|
|
- (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<RLMSortDescriptor *> *)properties {
|
|
@throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
|
|
}
|
|
|
|
// 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 ignored "-Wmismatched-parameter-types"
|
|
- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
|
|
@throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
|
|
}
|
|
|
|
#pragma mark - Thread Confined Protocol Conformance
|
|
|
|
- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
|
|
REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
|
|
}
|
|
|
|
- (id)objectiveCMetadata {
|
|
REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
|
|
}
|
|
|
|
+ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)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);
|
|
}
|
|
@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
|
|
|