Compare commits

...

12 Commits

Author SHA1 Message Date
Nora Trapp
e7e46253bb Reduce logging 2020-12-22 17:02:35 -08:00
Michael Kirk
b72c2d1e61 Merge branch 'mkirk/type-annotations' into signal-master 2019-05-30 09:29:02 -06:00
Michael Kirk
0ad65248f1 type annotations 2019-05-29 17:49:01 -06:00
Matthew Chen
9599b1d979 Merge remote-tracking branch 'origin/mkirk/perf-2' into signal-master 2019-01-04 16:24:54 -05:00
Michael Kirk
cd39677318 [PERF] fixup "merge" related perf work 2018-12-21 12:46:32 -07:00
Michael Kirk
97f94a0ea4 [PERF] fix up validation perf changes 2018-12-21 12:46:29 -07:00
Michael Kirk
86be628090 Merge branch 'mkirk/perf' into signal-master 2018-12-19 13:30:42 -07:00
Michael Kirk
59b5685fd5 [PERF] save ~1.5% on large migrations by caching behavior reflection 2018-12-19 13:27:36 -07:00
Michael Kirk
876c193270 [PERF] save ~3% on large migrations by caching behavior reflection 2018-12-19 13:27:36 -07:00
Michael Kirk
edf68e32c7 [PERF] save ~5% on large migrations by removing validation reflection
The validation NSCoding validation reflection used by Mantle is expensive, and
we've never used it.  If we later want to use this feature, we'll need to
carefully evaluate the perf implications on large migrations.
2018-12-19 13:27:36 -07:00
Michael Kirk
4a60daec18 [PERF] save ~2% on large migrations by removing MTLSelectorWithCapitalizedKeyPattern reflection
The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
and we're aren't using the reflective features that require it.  If we later
want to use this feature, we'll need to carefully evaluate the perf
implications on large migrations.
2018-12-19 13:27:36 -07:00
Michael Kirk
5902172226 podspec 2018-12-17 15:38:53 -07:00
7 changed files with 329 additions and 148 deletions

30
Mantle.podspec.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "Mantle",
"version": "2.1.0",
"summary": "Model framework for Cocoa and Cocoa Touch.",
"homepage": "https://github.com/Mantle/Mantle",
"license": "MIT",
"authors": {
"Github": "support@github.com"
},
"source": {
"git": "https://github.com/github/Mantle.git",
"tag": "2.1.0"
},
"platforms": {
"ios": "5.0",
"osx": "10.7",
"watchos": "2.0",
"tvos": "9.0"
},
"requires_arc": true,
"frameworks": "Foundation",
"source_files": "Mantle",
"subspecs": [
{
"name": "extobjc",
"source_files": "Mantle/extobjc",
"private_header_files": "Mantle/extobjc/*.h"
}
]
}

View File

@ -359,7 +359,18 @@ NSString * const MTLJSONAdapterThrownExceptionErrorKey = @"MTLJSONAdapterThrownE
id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
return [model validate:error] ? model : nil;
// BEGIN ORM-PERF-2
// Commented out by mkirk as part of ORM perf optimizations.
//
// The validation NSCoding validation reflection used by Mantle is expensive, and
// we've never used it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
// return [model validate:error] ? model : nil;
//
return model;
// END ORM-PERF-2
}
+ (NSDictionary *)valueTransformersForModelClass:(Class)modelClass {

View File

@ -17,6 +17,15 @@ static NSString * const MTLModelVersionKey = @"MTLModelVersion";
// Used to cache the reflection performed in +allowedSecureCodingClassesByPropertyKey.
static void *MTLModelCachedAllowedClassesKey = &MTLModelCachedAllowedClassesKey;
// BEGIN ORM-PERF-3
// Added by mkirk as part of ORM perf optimizations.
//
// +encodingBehaviorsByPropertyKey is somewhat expensive, so we follow existing library patterns
// to cache the computed reflection on the class via an associated object
// Used to cache the reflection performed in +encodingBehaviorsByPropertyKey.
static void *MTLModelCachedEncodingBehaviorsByPropertyKeyKey = &MTLModelCachedEncodingBehaviorsByPropertyKeyKey;
// END ORM-PERF-3
// Returns whether the given NSCoder requires secure coding.
static BOOL coderRequiresSecureCoding(NSCoder *coder) {
SEL requiresSecureCodingSelector = @selector(requiresSecureCoding);
@ -63,6 +72,15 @@ static void verifyAllowedClassesByPropertyKey(Class modelClass) {
#pragma mark Encoding Behaviors
+ (NSDictionary *)encodingBehaviorsByPropertyKey {
// BEGIN ORM-PERF-3
// Added by mkirk as part of ORM perf optimizations.
//
// +encodingBehaviorsByPropertyKey is somewhat expensive, so we follow existing library patterns
// to cache the computed reflection on the class via an associated object
NSDictionary *cachedBehaviors = objc_getAssociatedObject(self, MTLModelCachedEncodingBehaviorsByPropertyKeyKey);
if (cachedBehaviors != nil) return cachedBehaviors;
// END ORM-PERF-3
NSSet *propertyKeys = self.propertyKeys;
NSMutableDictionary *behaviors = [[NSMutableDictionary alloc] initWithCapacity:propertyKeys.count];
@ -79,6 +97,14 @@ static void verifyAllowedClassesByPropertyKey(Class modelClass) {
behaviors[key] = @(behavior);
}
// BEGIN ORM-PERF-3
// Added by mkirk as part of ORM perf optimizations.
//
// +encodingBehaviorsByPropertyKey is somewhat expensive, so we follow existing library patterns
// to cache the computed reflection on the class via an associated object
objc_setAssociatedObject(self, MTLModelCachedEncodingBehaviorsByPropertyKeyKey, behaviors, OBJC_ASSOCIATION_COPY);
// END ORM-PERF-3
return behaviors;
}
@ -126,14 +152,21 @@ static void verifyAllowedClassesByPropertyKey(Class modelClass) {
NSParameterAssert(key != nil);
NSParameterAssert(coder != nil);
SEL selector = MTLSelectorWithCapitalizedKeyPattern("decode", key, "WithCoder:modelVersion:");
if ([self respondsToSelector:selector]) {
IMP imp = [self methodForSelector:selector];
id (*function)(id, SEL, NSCoder *, NSUInteger) = (__typeof__(function))imp;
id result = function(self, selector, coder, modelVersion);
return result;
}
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
// SEL selector = MTLSelectorWithCapitalizedKeyPattern("decode", key, "WithCoder:modelVersion:");
// if ([self respondsToSelector:selector]) {
// IMP imp = [self methodForSelector:selector];
// id (*function)(id, SEL, NSCoder *, NSUInteger) = (__typeof__(function))imp;
// id result = function(self, selector, coder, modelVersion);
//
// return result;
// }
// END ORM-PERF-1
@try {
if (coderRequiresSecureCoding(coder)) {

View File

@ -48,7 +48,7 @@ typedef enum : NSUInteger {
/// (like a KVC validation error).
///
/// Returns an initialized model object, or nil if validation failed.
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
+ (instancetype)modelWithDictionary:(NSDictionary<NSString *, id> *)dictionaryValue error:(NSError **)error;
/// A dictionary representing the properties of the receiver.
///
@ -56,7 +56,7 @@ typedef enum : NSUInteger {
/// with any nil values represented by NSNull.
///
/// This property must never be nil.
@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *dictionaryValue;
/// Initializes the receiver using key-value coding, setting the keys and values
/// in the given dictionary.
@ -74,15 +74,22 @@ typedef enum : NSUInteger {
/// (like a KVC validation error).
///
/// Returns an initialized model object, or nil if validation failed.
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)dictionaryValue error:(NSError **)error;
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
/// Merges the value of the given key on the receiver with the value of the same
/// key from the given model object, giving precedence to the other model object.
- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model;
//- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model;
// END ORM-PERF-1
/// Returns the keys for all @property declarations, except for `readonly`
/// properties without ivars, or properties on MTLModel itself.
+ (NSSet *)propertyKeys;
+ (NSSet<NSString *> *)propertyKeys;
/// Validates the model.
///
@ -90,7 +97,7 @@ typedef enum : NSUInteger {
/// validation
///
/// Returns YES if the model is valid, or NO if the validation failed.
- (BOOL)validate:(NSError **)error;
//- (BOOL)validate:(NSError **)error;
@end
@ -113,23 +120,31 @@ typedef enum : NSUInteger {
/// (like a KVC validation error).
///
/// Returns an initialized model object, or nil if validation failed.
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)dictionaryValue error:(NSError **)error;
/// Initializes the receiver with default values.
///
/// This is the designated initializer for this class.
- (instancetype)init;
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
/// By default, this method looks for a `-merge<Key>FromModel:` method on the
/// receiver, and invokes it if found. If not found, and `model` is not nil, the
/// value for the given key is taken from `model`.
- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model;
//- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model;
//
/// Merges the values of the given model object into the receiver, using
/// -mergeValueForKey:fromModel: for each key in +propertyKeys.
///
/// `model` must be an instance of the receiver's class or a subclass thereof.
- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model;
//- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model;
//
// END ORM-PERF-1
/// The storage behavior of a given key.
///
@ -161,19 +176,29 @@ typedef enum : NSUInteger {
@end
/// Implements validation logic for MTLModel.
@interface MTLModel (Validation)
/// Validates the model.
///
/// The default implementation simply invokes -validateValue:forKey:error: with
/// all +propertyKeys and their current value. If -validateValue:forKey:error:
/// returns a new value, the property is set to that new value.
///
/// error - If not NULL, this may be set to any error that occurs during
/// validation
///
/// Returns YES if the model is valid, or NO if the validation failed.
- (BOOL)validate:(NSError **)error;
@end
// BEGIN ORM-PERF-2
// Commented out by mkirk as part of ORM perf optimizations.
//
// The validation NSCoding validation reflection used by Mantle is expensive, and
// we've never used it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
///// Implements validation logic for MTLModel.
//@interface MTLModel (Validation)
//
///// Validates the model.
/////
///// The default implementation simply invokes -validateValue:forKey:error: with
///// all +propertyKeys and their current value. If -validateValue:forKey:error:
///// returns a new value, the property is set to that new value.
/////
///// error - If not NULL, this may be set to any error that occurs during
///// validation
/////
///// Returns YES if the model is valid, or NO if the validation failed.
//- (BOOL)validate:(NSError **)error;
//
//@end
//
// END ORM-PERF-2

View File

@ -24,6 +24,24 @@ static void *MTLModelCachedTransitoryPropertyKeysKey = &MTLModelCachedTransitory
// property keys.
static void *MTLModelCachedPermanentPropertyKeysKey = &MTLModelCachedPermanentPropertyKeysKey;
// BEGIN ORM-PERF-4
// Added by mkirk as part of ORM perf optimizations.
//
// +dictionaryValueKeys is somewhat expensive, so we follow existing library patterns
// to cache the computed reflection on the class via an associated object
//
// Used to cache the reflection performed in +dictionaryValueKeys
static void *MTLModelCachedDictionaryValueKeysKey = &MTLModelCachedDictionaryValueKeysKey;
// END ORM-PERF-4
// BEGIN ORM-PERF-2
// Commented out by mkirk as part of ORM perf optimizations.
//
// The validation NSCoding validation reflection used by Mantle is expensive, and
// we've never used it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
// Validates a value for an object and sets it if necessary.
//
// obj - The object for which the value is being validated. This value
@ -38,35 +56,36 @@ static void *MTLModelCachedPermanentPropertyKeysKey = &MTLModelCachedPermanentPr
//
// Returns YES if `value` could be validated and set, or NO if an error
// occurred.
static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
// Mark this as being autoreleased, because validateValue may return
// a new object to be stored in this variable (and we don't want ARC to
// double-free or leak the old or new values).
__autoreleasing id validatedValue = value;
@try {
if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
if (forceUpdate || value != validatedValue) {
[obj setValue:validatedValue forKey:key];
}
return YES;
} @catch (NSException *ex) {
NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
// Fail fast in Debug builds.
#if DEBUG
@throw ex;
#else
if (error != NULL) {
*error = [NSError mtl_modelErrorWithException:ex];
}
return NO;
#endif
}
}
//static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
// // Mark this as being autoreleased, because validateValue may return
// // a new object to be stored in this variable (and we don't want ARC to
// // double-free or leak the old or new values).
// __autoreleasing id validatedValue = value;
//
// @try {
// if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
//
// if (forceUpdate || value != validatedValue) {
// [obj setValue:validatedValue forKey:key];
// }
//
// return YES;
// } @catch (NSException *ex) {
// NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
//
// // Fail fast in Debug builds.
// #if DEBUG
// @throw ex;
// #else
// if (error != NULL) {
// *error = [NSError mtl_modelErrorWithException:ex];
// }
//
// return NO;
// #endif
// }
//}
// END ORM-PERF-2
@interface MTLModel ()
@ -76,11 +95,11 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
// Returns a set of all property keys for which
// +storageBehaviorForPropertyWithKey returned MTLPropertyStorageTransitory.
+ (NSSet *)transitoryPropertyKeys;
+ (NSSet<NSString *> *)transitoryPropertyKeys;
// Returns a set of all property keys for which
// +storageBehaviorForPropertyWithKey returned MTLPropertyStoragePermanent.
+ (NSSet *)permanentPropertyKeys;
+ (NSSet<NSString *> *)permanentPropertyKeys;
// Enumerates all properties of the receiver's class hierarchy, starting at the
// receiver, and continuing up until (but not including) MTLModel.
@ -96,8 +115,8 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
#pragma mark Lifecycle
+ (void)generateAndCacheStorageBehaviors {
NSMutableSet *transitoryKeys = [NSMutableSet set];
NSMutableSet *permanentKeys = [NSMutableSet set];
NSMutableSet<NSString *> *transitoryKeys = [NSMutableSet set];
NSMutableSet<NSString *> *permanentKeys = [NSMutableSet set];
for (NSString *propertyKey in self.propertyKeys) {
switch ([self storageBehaviorForPropertyWithKey:propertyKey]) {
@ -120,7 +139,7 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
objc_setAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey, permanentKeys, OBJC_ASSOCIATION_COPY);
}
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
+ (instancetype)modelWithDictionary:(NSDictionary<NSString *, id> *)dictionary error:(NSError **)error {
return [[self alloc] initWithDictionary:dictionary error:error];
}
@ -129,7 +148,7 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
return [super init];
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)dictionary error:(NSError **)error {
self = [self init];
if (self == nil) return nil;
@ -140,9 +159,18 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
__autoreleasing id value = [dictionary objectForKey:key];
if ([value isEqual:NSNull.null]) value = nil;
BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
if (!success) return nil;
// BEGIN ORM-PERF-2
// Commented out by mkirk as part of ORM perf optimizations.
//
// The validation NSCoding validation reflection used by Mantle is expensive, and
// we've never used it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
// BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
// if (!success) return nil;
[self setValue:value forKey:key];
// END ORM-PERF-2
}
return self;
@ -172,11 +200,11 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
}
}
+ (NSSet *)propertyKeys {
NSSet *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey);
+ (NSSet<NSString *> *)propertyKeys {
NSSet<NSString *> *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey);
if (cachedKeys != nil) return cachedKeys;
NSMutableSet *keys = [NSMutableSet set];
NSMutableSet<NSString *> *keys = [NSMutableSet set];
[self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
NSString *key = @(property_getName(property));
@ -193,8 +221,8 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
return keys;
}
+ (NSSet *)transitoryPropertyKeys {
NSSet *transitoryPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedTransitoryPropertyKeysKey);
+ (NSSet<NSString *> *)transitoryPropertyKeys {
NSSet<NSString *> *transitoryPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedTransitoryPropertyKeysKey);
if (transitoryPropertyKeys == nil) {
[self generateAndCacheStorageBehaviors];
@ -204,8 +232,8 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
return transitoryPropertyKeys;
}
+ (NSSet *)permanentPropertyKeys {
NSSet *permanentPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey);
+ (NSSet<NSString *> *)permanentPropertyKeys {
NSSet<NSString *> *permanentPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey);
if (permanentPropertyKeys == nil) {
[self generateAndCacheStorageBehaviors];
@ -215,12 +243,29 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
return permanentPropertyKeys;
}
- (NSDictionary *)dictionaryValue {
NSSet *keys = [self.class.transitoryPropertyKeys setByAddingObjectsFromSet:self.class.permanentPropertyKeys];
return [self dictionaryWithValuesForKeys:keys.allObjects];
// BEGIN ORM-PERF-4
// Added by mkirk as part of ORM perf optimizations.
//
// +dictionaryValueKeys is somewhat expensive, so we follow existing library patterns
// to cache the computed reflection on the class via an associated object
//
// Used to cache the reflection performed in +dictionaryValueKeys
+ (NSArray<NSString *> *)dictionaryValueKeys {
NSArray<NSString *> *dictionaryValueKeys = objc_getAssociatedObject(self, MTLModelCachedDictionaryValueKeysKey);
if (!dictionaryValueKeys) {
NSSet<NSString *> *keys = [self.class.transitoryPropertyKeys setByAddingObjectsFromSet:self.permanentPropertyKeys];
dictionaryValueKeys = keys.allObjects;
objc_setAssociatedObject(self, MTLModelCachedDictionaryValueKeysKey, dictionaryValueKeys, OBJC_ASSOCIATION_COPY);
}
return dictionaryValueKeys;
}
- (NSDictionary<NSString *, id> *)dictionaryValue {
NSArray<NSString *> *keys = self.class.dictionaryValueKeys;
return [self dictionaryWithValuesForKeys:keys];
}
// END ORM-PERF-4
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey {
objc_property_t property = class_getProperty(self.class, propertyKey.UTF8String);
@ -250,45 +295,61 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
#pragma mark Merging
- (void)mergeValueForKey:(NSString *)key fromModel:(NSObject<MTLModel> *)model {
NSParameterAssert(key != nil);
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
//- (void)mergeValueForKey:(NSString *)key fromModel:(NSObject<MTLModel> *)model {
// NSParameterAssert(key != nil);
//
//// SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
//// if (![self respondsToSelector:selector]) {
//// if (model != nil) {
//// [self setValue:[model valueForKey:key] forKey:key];
//// }
////
//// return;
//// }
//
// IMP imp = [self methodForSelector:selector];
// void (*function)(id, SEL, id<MTLModel>) = (__typeof__(function))imp;
// function(self, selector, model);
//}
//
//- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model {
// NSSet<NSString *> *propertyKeys = model.class.propertyKeys;
//
// for (NSString *key in self.class.propertyKeys) {
// if (![propertyKeys containsObject:key]) continue;
//
// [self mergeValueForKey:key fromModel:model];
// }
//}
// END ORM-PERF-1
SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
if (![self respondsToSelector:selector]) {
if (model != nil) {
[self setValue:[model valueForKey:key] forKey:key];
}
return;
}
IMP imp = [self methodForSelector:selector];
void (*function)(id, SEL, id<MTLModel>) = (__typeof__(function))imp;
function(self, selector, model);
}
- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model {
NSSet *propertyKeys = model.class.propertyKeys;
for (NSString *key in self.class.propertyKeys) {
if (![propertyKeys containsObject:key]) continue;
[self mergeValueForKey:key fromModel:model];
}
}
#pragma mark Validation
- (BOOL)validate:(NSError **)error {
for (NSString *key in self.class.propertyKeys) {
id value = [self valueForKey:key];
BOOL success = MTLValidateAndSetValue(self, key, value, NO, error);
if (!success) return NO;
}
return YES;
}
// BEGIN ORM-PERF-2
// Commented out by mkirk as part of ORM perf optimizations.
//
// The validation NSCoding validation reflection used by Mantle is expensive, and
// we've never used it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//#pragma mark Validation
//
//- (BOOL)validate:(NSError **)error {
// for (NSString *key in self.class.propertyKeys) {
// id value = [self valueForKey:key];
//
// BOOL success = MTLValidateAndSetValue(self, key, value, NO, error);
// if (!success) return NO;
// }
//
// return YES;
//}
// END ORM-PERF-2
#pragma mark NSCopying
@ -301,9 +362,13 @@ static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUp
#pragma mark NSObject
- (NSString *)description {
NSDictionary *permanentProperties = [self dictionaryWithValuesForKeys:self.class.permanentPropertyKeys.allObjects];
#if DEBUG
NSDictionary<NSString *, id> *permanentProperties = [self dictionaryWithValuesForKeys:self.class.permanentPropertyKeys.allObjects];
return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, permanentProperties];
#else
return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
#endif
}
- (NSUInteger)hash {

View File

@ -18,6 +18,14 @@
/// selector.
SEL MTLSelectorWithKeyPattern(NSString *key, const char *suffix) __attribute__((pure, nonnull(1, 2)));
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//
/// Creates a selector from a key and a constant prefix and suffix.
///
/// prefix - A string to prepend to the key as part of the selector.
@ -28,4 +36,6 @@ SEL MTLSelectorWithKeyPattern(NSString *key, const char *suffix) __attribute__((
///
/// Returns a selector, or NULL if the input strings cannot form a valid
/// selector.
SEL MTLSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) __attribute__((pure, nonnull(1, 2, 3)));
//SEL MTLSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) __attribute__((pure, nonnull(1, 2, 3)));
//
// END ORM-PERF-1

View File

@ -24,27 +24,34 @@ SEL MTLSelectorWithKeyPattern(NSString *key, const char *suffix) {
return sel_registerName(selector);
}
SEL MTLSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) {
NSUInteger prefixLength = strlen(prefix);
NSUInteger suffixLength = strlen(suffix);
NSString *initial = [key substringToIndex:1].uppercaseString;
NSUInteger initialLength = [initial maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSString *rest = [key substringFromIndex:1];
NSUInteger restLength = [rest maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char selector[prefixLength + initialLength + restLength + suffixLength + 1];
memcpy(selector, prefix, prefixLength);
BOOL success = [initial getBytes:selector + prefixLength maxLength:initialLength usedLength:&initialLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, initial.length) remainingRange:NULL];
if (!success) return NULL;
success = [rest getBytes:selector + prefixLength + initialLength maxLength:restLength usedLength:&restLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, rest.length) remainingRange:NULL];
if (!success) return NULL;
memcpy(selector + prefixLength + initialLength + restLength, suffix, suffixLength);
selector[prefixLength + initialLength + restLength + suffixLength] = '\0';
return sel_registerName(selector);
}
// BEGIN ORM-PERF-1
// Commented out by mkirk as part of ORM perf optimizations.
// The `MTLSelectorWithCapitalizedKeyPattern` can be quite expensive in aggregate
// and we're not using the reflective features that require it.
// If we later want to use this feature, we'll need to carefully evaluate the perf
// implications on large migrations.
//SEL MTLSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) {
// NSUInteger prefixLength = strlen(prefix);
// NSUInteger suffixLength = strlen(suffix);
//
// NSString *initial = [key substringToIndex:1].uppercaseString;
// NSUInteger initialLength = [initial maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
//
// NSString *rest = [key substringFromIndex:1];
// NSUInteger restLength = [rest maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
//
// char selector[prefixLength + initialLength + restLength + suffixLength + 1];
// memcpy(selector, prefix, prefixLength);
//
// BOOL success = [initial getBytes:selector + prefixLength maxLength:initialLength usedLength:&initialLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, initial.length) remainingRange:NULL];
// if (!success) return NULL;
//
// success = [rest getBytes:selector + prefixLength + initialLength maxLength:restLength usedLength:&restLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, rest.length) remainingRange:NULL];
// if (!success) return NULL;
//
// memcpy(selector + prefixLength + initialLength + restLength, suffix, suffixLength);
// selector[prefixLength + initialLength + restLength + suffixLength] = '\0';
//
// return sel_registerName(selector);
//}
// END ORM-PERF-1