Created
August 12, 2018 07:29
-
-
Save siyu6974/0f6b8273b76ee0d7fe939e8b5a982553 to your computer and use it in GitHub Desktop.
Use Realm with mantle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Realm/Realm.h> | |
#import <Mantle/Mantle.h> | |
@interface ModelBase : RLMObject <MTLJSONSerializing, MTLModel> | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "ModelBase.h" | |
#import "NSError+MTLModelException.h" | |
#import "MTLModel.h" | |
#import <Mantle/EXTRuntimeExtensions.h> | |
#import <Mantle/EXTScope.h> | |
#import "MTLReflection.h" | |
// Used to cache the reflection performed in +propertyKeys. | |
static void *MTLModelCachedPropertyKeysKey = &MTLModelCachedPropertyKeysKey; | |
static void *MTLModelCachedPermanentPropertyKeysKey = &MTLModelCachedPermanentPropertyKeysKey; | |
static void *MTLModelCachedTransitoryPropertyKeysKey = &MTLModelCachedTransitoryPropertyKeysKey; | |
@interface ModelBase () | |
@end | |
@implementation ModelBase | |
- (nonnull id)copyWithZone:(nullable NSZone *)zone { | |
return [[self.class allocWithZone:zone] initWithDictionary:self.dictionaryValue error:NULL]; | |
} | |
# pragma mark - Protocol MTLJSONSerializing | |
+ (NSDictionary *)JSONKeyPathsByPropertyKey { | |
// Must be overriden by subclass | |
@throw [NSException exceptionWithName:NSInternalInconsistencyException | |
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] | |
userInfo:nil]; | |
} | |
# pragma mark - Protocol MTLModel | |
@synthesize dictionaryValue; | |
- (NSDictionary *)dictionaryValue { | |
NSSet *keys = [self.class.transitoryPropertyKeys setByAddingObjectsFromSet:self.class.permanentPropertyKeys]; | |
return [self dictionaryWithValuesForKeys:keys.allObjects]; | |
} | |
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary error:(NSError **)error { | |
return [[self alloc] initWithDictionary:dictionary error:error]; | |
} | |
- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error { | |
self = [self init]; | |
if (self == nil) return nil; | |
for (NSString *key in dictionary) { | |
// 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 value = [dictionary objectForKey:key]; | |
if ([value isEqual:NSNull.null]) value = nil; | |
BOOL success = MTLValidateAndSetValue(self, key, value, YES, error); | |
if (!success) return nil; | |
} | |
return self; | |
} | |
- (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 *propertyKeys = model.class.propertyKeys; | |
for (NSString *key in self.class.propertyKeys) { | |
if (![propertyKeys containsObject:key]) continue; | |
[self mergeValueForKey:key fromModel:model]; | |
} | |
} | |
- (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; | |
} | |
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 | |
} | |
} | |
+ (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block { | |
Class cls = self; | |
BOOL stop = NO; | |
while (!stop && ![cls isEqual:ModelBase.class]) { | |
unsigned count = 0; | |
objc_property_t *properties = class_copyPropertyList(cls, &count); | |
cls = cls.superclass; | |
if (properties == NULL) continue; | |
@onExit { | |
free(properties); | |
}; | |
for (unsigned i = 0; i < count; i++) { | |
block(properties[i], &stop); | |
if (stop) break; | |
} | |
} | |
} | |
+ (NSSet *)propertyKeys { | |
NSSet *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey); | |
if (cachedKeys != nil) return cachedKeys; | |
NSMutableSet *keys = [NSMutableSet set]; | |
[self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) { | |
NSString *key = @(property_getName(property)); | |
if ([self storageBehaviorForPropertyWithKey:key] != MTLPropertyStorageNone) { | |
[keys addObject:key]; | |
} | |
}]; | |
// It doesn't really matter if we replace another thread's work, since we do | |
// it atomically and the result should be the same. | |
objc_setAssociatedObject(self, MTLModelCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY); | |
return keys; | |
} | |
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey { | |
objc_property_t property = class_getProperty(self.class, propertyKey.UTF8String); | |
if (property == NULL) return MTLPropertyStorageNone; | |
mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property); | |
@onExit { | |
free(attributes); | |
}; | |
BOOL hasGetter = [self instancesRespondToSelector:attributes->getter]; | |
BOOL hasSetter = [self instancesRespondToSelector:attributes->setter]; | |
if (!attributes->dynamic && attributes->ivar == NULL && !hasGetter && !hasSetter) { | |
return MTLPropertyStorageNone; | |
} else if (attributes->readonly && attributes->ivar == NULL) { | |
if ([self isEqual:MTLModel.class]) { | |
return MTLPropertyStorageNone; | |
} else { | |
// Check superclass in case the subclass redeclares a property that | |
// falls through | |
return [self.superclass storageBehaviorForPropertyWithKey:propertyKey]; | |
} | |
} else { | |
return MTLPropertyStoragePermanent; | |
} | |
} | |
# pragma mark - Helper func from MTLModel | |
+ (NSSet *)transitoryPropertyKeys { | |
NSSet *transitoryPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedTransitoryPropertyKeysKey); | |
if (transitoryPropertyKeys == nil) { | |
[self generateAndCacheStorageBehaviors]; | |
transitoryPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedTransitoryPropertyKeysKey); | |
} | |
return transitoryPropertyKeys; | |
} | |
+ (NSSet *)permanentPropertyKeys { | |
NSSet *permanentPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey); | |
if (permanentPropertyKeys == nil) { | |
[self generateAndCacheStorageBehaviors]; | |
permanentPropertyKeys = objc_getAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey); | |
} | |
return permanentPropertyKeys; | |
} | |
+ (void)generateAndCacheStorageBehaviors { | |
NSMutableSet *transitoryKeys = [NSMutableSet set]; | |
NSMutableSet *permanentKeys = [NSMutableSet set]; | |
for (NSString *propertyKey in self.propertyKeys) { | |
switch ([self storageBehaviorForPropertyWithKey:propertyKey]) { | |
case MTLPropertyStorageNone: | |
break; | |
case MTLPropertyStorageTransitory: | |
[transitoryKeys addObject:propertyKey]; | |
break; | |
case MTLPropertyStoragePermanent: | |
[permanentKeys addObject:propertyKey]; | |
break; | |
} | |
} | |
// It doesn't really matter if we replace another thread's work, since we do | |
// it atomically and the result should be the same. | |
objc_setAssociatedObject(self, MTLModelCachedTransitoryPropertyKeysKey, transitoryKeys, OBJC_ASSOCIATION_COPY); | |
objc_setAssociatedObject(self, MTLModelCachedPermanentPropertyKeysKey, permanentKeys, OBJC_ASSOCIATION_COPY); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To use it, make your model class a subclass of this Base class.
For example,