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

Extract the MTLModel protocol #219

Merged
merged 14 commits into from Mar 14, 2014
@@ -8,10 +8,10 @@

#import <Foundation/Foundation.h>

@class MTLModel;
@protocol MTLModel;

// A MTLModel object that supports being parsed from and serialized to JSON.
@protocol MTLJSONSerializing
@protocol MTLJSONSerializing <MTLModel>
@required

// Specifies how to map property keys to different key paths in JSON.
@@ -66,7 +66,7 @@ extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;

// The model object that the receiver was initialized with, or that the receiver
// parsed from a JSON dictionary.
@property (nonatomic, strong, readonly) MTLModel<MTLJSONSerializing> *model;
@property (nonatomic, strong, readonly) id<MTLJSONSerializing> model;

// Attempts to parse a JSON dictionary into a model object.
//
@@ -91,7 +91,7 @@ extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
// serializing.
//
// Returns a JSON dictionary, or nil if a serialization error occurred.
+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model error:(NSError **)error;
+ (NSDictionary *)JSONDictionaryFromModel:(id<MTLJSONSerializing>)model error:(NSError **)error;

// Initializes the receiver by attempting to parse a JSON dictionary into
// a model object.
@@ -114,7 +114,7 @@ extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
//
// model - The model to use for JSON serialization. This argument must not be
// nil.
- (id)initWithModel:(MTLModel<MTLJSONSerializing> *)model;
- (id)initWithModel:(id<MTLJSONSerializing>)model;

// Serializes the receiver's `model` into JSON.
//
@@ -176,6 +176,8 @@ extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;

@end

@class MTLModel;

@interface MTLJSONAdapter (Deprecated)

+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model __attribute__((deprecated("Replaced by +JSONDictionaryFromModel:error:")));
@@ -59,7 +59,7 @@ + (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDict
return adapter.model;
}

+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model error:(NSError **)error {
+ (NSDictionary *)JSONDictionaryFromModel:(id<MTLJSONSerializing>)model error:(NSError **)error {
MTLJSONAdapter *adapter = [[self alloc] initWithModel:model];

return [adapter serializeToJSONDictionary:error];
@@ -74,7 +74,6 @@ - (id)init {

- (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass error:(NSError **)error {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass isSubclassOfClass:MTLModel.class]);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);

if (JSONDictionary == nil || ![JSONDictionary isKindOfClass:NSDictionary.class]) {
@@ -103,7 +102,6 @@ - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)mo
return nil;
}

NSAssert([modelClass isSubclassOfClass:MTLModel.class], @"Class %@ returned from +classForParsingJSONDictionary: is not a subclass of MTLModel", modelClass);
NSAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)], @"Class %@ returned from +classForParsingJSONDictionary: does not conform to <MTLJSONSerializing>", modelClass);
}

@@ -190,7 +188,7 @@ - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)mo
return self;
}

- (id)initWithModel:(MTLModel<MTLJSONSerializing> *)model {
- (id)initWithModel:(id<MTLJSONSerializing>)model {
NSParameterAssert(model != nil);

self = [super init];
@@ -272,7 +270,6 @@ - (NSString *)JSONKeyPathForPropertyKey:(NSString *)key {

- (NSDictionary *)valueTransformersForModelClass:(Class)modelClass {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass isSubclassOfClass:MTLModel.class]);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);

NSMutableDictionary *result = [NSMutableDictionary dictionary];
@@ -8,11 +8,11 @@

#import <CoreData/CoreData.h>

@class MTLModel;
@protocol MTLModel;

// A MTLModel object that supports being serialized to and from Core Data as an
// NSManagedObject.
@protocol MTLManagedObjectSerializing
@protocol MTLManagedObjectSerializing <MTLModel>
@required

// The name of the Core Data entity that the receiver serializes to and
@@ -171,7 +171,7 @@ extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass;
// argument must not be nil.
// error - If not NULL, this may be set to an error that occurs during
// serialization or insertion.
+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error;
+ (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error;

// An optional value transformer that should be used for properties of the given
// class.
@@ -82,15 +82,15 @@ + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)manage
// Invoked from
// +managedObjectFromModel:insertingIntoContext:processedObjects:error: after
// the receiver's properties have been initialized.
- (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
- (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;

// Performs the actual work of serialization. This method is also invoked when
// processing relationships, to create a new adapter (if needed) to handle them.
//
// `processedObjects` is a dictionary mapping MTLModels to the NSManagedObjects
// that have been created so far. It should remain alive for the full process
// of serializing the top-level MTLModel.
+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;
+ (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error;

// Looks at propertyKeysForManagedObjectUniquing and forms an NSPredicate
// using the uniquing keys and the provided model.
@@ -104,7 +104,7 @@ + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
// Returns a predicate, or nil if no predicate is needed or if an error
// occurred. Clients should inspect the success parameter to decide how to
// proceed with the result.
- (NSPredicate *)uniquingPredicateForModel:(MTLModel<MTLManagedObjectSerializing> *)model success:(BOOL *)success error:(NSError **)error;
- (NSPredicate *)uniquingPredicateForModel:(id<MTLManagedObjectSerializing>)model success:(BOOL *)success error:(NSError **)error;

@end

@@ -146,7 +146,7 @@ - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(
NSManagedObjectContext *context = managedObject.managedObjectContext;

NSDictionary *managedObjectProperties = entity.propertiesByName;
MTLModel *model = [[self.modelClass alloc] init];
NSObject<MTLModel> *model = [[self.modelClass alloc] init];

// Pre-emptively consider this object processed, so that we don't get into
// any cycles when processing its relationships.
@@ -199,7 +199,7 @@ - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(
NSMutableArray *models = [NSMutableArray arrayWithCapacity:[relationshipCollection count]];

for (NSManagedObject *nestedObject in relationshipCollection) {
MTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
id<MTLManagedObjectSerializing> model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
if (model == nil) return nil;

[models addObject:model];
@@ -219,7 +219,7 @@ - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(

if (nestedObject == nil) return YES;

MTLModel *model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
id<MTLManagedObjectSerializing> model = [self.class modelOfClass:nestedClass fromManagedObject:nestedObject processedObjects:processedObjects error:error];
if (model == nil) return NO;

return setValueForKey(propertyKey, model);
@@ -312,7 +312,7 @@ + (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)manage
return [adapter modelFromManagedObject:managedObject processedObjects:processedObjects error:error];
}

- (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
- (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
NSParameterAssert(model != nil);
NSParameterAssert(context != nil);
NSParameterAssert(processedObjects != nil);
@@ -431,7 +431,7 @@ - (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
};

NSManagedObject * (^objectForRelationshipFromModel)(id) = ^ id (id model) {
if (![model isKindOfClass:MTLModel.class] || ![model conformsToProtocol:@protocol(MTLManagedObjectSerializing)]) {
if (![model conformsToProtocol:@protocol(MTLManagedObjectSerializing)]) {
NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"Property of class %@ cannot be encoded into an NSManagedObject.", @""), [model class]];

NSDictionary *userInfo = @{
@@ -471,7 +471,7 @@ - (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
relationshipCollection = [NSMutableSet set];
}

for (MTLModel *model in value) {
for (id<MTLModel> model in value) {
NSManagedObject *nestedObject = objectForRelationshipFromModel(model);
if (nestedObject == nil) return NO;

@@ -548,7 +548,7 @@ - (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
return managedObject;
}

+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error {
+ (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error {
CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks;

// Compare MTLModel keys using pointer equality, not -isEqual:.
@@ -564,7 +564,7 @@ + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
return [self managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error];
}

+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
+ (id)managedObjectFromModel:(id<MTLManagedObjectSerializing>)model insertingIntoContext:(NSManagedObjectContext *)context processedObjects:(CFMutableDictionaryRef)processedObjects error:(NSError **)error {
NSParameterAssert(model != nil);
NSParameterAssert(context != nil);
NSParameterAssert(processedObjects != nil);
@@ -578,7 +578,29 @@ + (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model inse
return [adapter managedObjectFromModel:model insertingIntoContext:context processedObjects:processedObjects error:error];
}

- (NSPredicate *)uniquingPredicateForModel:(MTLModel<MTLManagedObjectSerializing> *)model success:(BOOL *)success error:(NSError **)error {
- (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key {
NSParameterAssert(key != nil);

SEL selector = MTLSelectorWithKeyPattern(key, "EntityAttributeTransformer");
if ([self.modelClass respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self.modelClass methodSignatureForSelector:selector]];
invocation.target = self.modelClass;
invocation.selector = selector;
[invocation invoke];

__unsafe_unretained id result = nil;
[invocation getReturnValue:&result];
return result;
}

if ([self.modelClass respondsToSelector:@selector(entityAttributeTransformerForKey:)]) {
return [self.modelClass entityAttributeTransformerForKey:key];
}

return nil;
}

- (NSPredicate *)uniquingPredicateForModel:(NSObject<MTLManagedObjectSerializing> *)model success:(BOOL *)success error:(NSError **)error {
if (![self.modelClass respondsToSelector:@selector(propertyKeysForManagedObjectUniquing)]) return nil;

NSSet *propertyKeys = [self.modelClass propertyKeysForManagedObjectUniquing];
@@ -10,7 +10,6 @@
#import "EXTRuntimeExtensions.h"
#import "EXTScope.h"
#import "MTLReflection.h"
#import <objc/runtime.h>

// Used in archives to store the modelVersion of the archived instance.
static NSString * const MTLModelVersionKey = @"MTLModelVersion";
@@ -21,26 +21,42 @@
// (like `NSCoding`) and equality, since it can
// be expected to stick around.
typedef enum : NSUInteger {
MTLPropertyStorageNone,
MTLPropertyStorageTransitory,
MTLPropertyStoragePermanent,
MTLPropertyStorageNone,
MTLPropertyStorageTransitory,
MTLPropertyStoragePermanent,
} MTLPropertyStorage;

// An abstract base class for model objects, using reflection to provide
// sensible default behaviors.
// This protocol defines the minimal interface that classes need to implement to
// interact with Mantle adapters.
//
// The default implementations of <NSCopying>, -hash, and -isEqual: make use of
// the +propertyKeys method.
@interface MTLModel : NSObject <NSCopying>
// It is intended for scenarios where inheriting from MTLModel is not feasible.
// However, clients are encouraged to subclass the MTLModel class if they can.
//
// Clients that wish to implement their own adapters should target classes
// conforming to this protocol rather than subclasses of MTLModel to ensure
// maximum compatibility.
@protocol MTLModel <NSObject, NSCopying>

This comment has been minimized.

Copy link
@jspahrsummers

jspahrsummers Mar 13, 2014

Member

Can you document the purpose of the protocol (and, specifically, the split between it and the class)?


// Returns a new instance of the receiver initialized using
// -initWithDictionary:error:.
// Initializes a new instance of the receiver using key-value coding, setting
// the keys and values in the given dictionary.
//
// dictionaryValue - Property keys and values to set on the instance. Any NSNull
// values will be converted to nil before being used. KVC
// validation methods will automatically be invoked for all of
// the properties given.
// error - If not NULL, this may be set to any error that occurs
// (like a KVC validation error).
//
// Returns an initialized model object, or nil if validation failed.
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;

// Initializes the receiver with default values.
// A dictionary representing the properties of the receiver.
//
// This is the designated initializer for this class.
- (instancetype)init;
// Combines the values corresponding to all +propertyKeys into a dictionary,
// with any nil values represented by NSNull.
//
// This property must never be nil.
@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;

// Initializes the receiver using key-value coding, setting the keys and values
// in the given dictionary.
@@ -56,31 +72,52 @@ typedef enum : NSUInteger {
// Returns an initialized model object, or nil if validation failed.
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;

// 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;

// Returns the keys for all @property declarations, except for `readonly`
// properties without ivars, or properties on MTLModel itself.
+ (NSSet *)propertyKeys;

// A dictionary representing the properties of the receiver.
@end

// An abstract base class for model objects, using reflection to provide
// sensible default behaviors.
//
// The default implementation combines the values corresponding to all
// +propertyKeys into a dictionary, with any nil values represented by NSNull.
// The default implementations of <NSCopying>, -hash, and -isEqual: make use of
// the +propertyKeys method.
@interface MTLModel : NSObject <MTLModel>

// Initializes the receiver using key-value coding, setting the keys and values
// in the given dictionary.
//
// This property must never be nil.
@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;
// dictionaryValue - Property keys and values to set on the receiver. Any NSNull
// values will be converted to nil before being used. KVC
// validation methods will automatically be invoked for all of
// the properties given. If nil, this method is equivalent to
// -init.
// error - If not NULL, this may be set to any error that occurs
// (like a KVC validation error).
//
// Returns an initialized model object, or nil if validation failed.
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;

// 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.
// Initializes the receiver with default values.
//
// This is the designated initializer for this class.
- (instancetype)init;

// 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:(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:(MTLModel *)model;
- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model;

// The storage behavior of a given key.
//
@@ -240,7 +240,7 @@ + (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey

#pragma mark Merging

- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model {
- (void)mergeValueForKey:(NSString *)key fromModel:(NSObject<MTLModel> *)model {
NSParameterAssert(key != nil);

SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
@@ -260,7 +260,7 @@ - (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model {
[invocation invoke];
}

- (void)mergeValuesForKeysFromModel:(MTLModel *)model {
- (void)mergeValuesForKeysFromModel:(id<MTLModel>)model {
for (NSString *key in self.class.propertyKeys) {
[self mergeValueForKey:key fromModel:model];
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.