Skip to content

Instantly share code, notes, and snippets.

@siyu6974
Created August 12, 2018 07:29
Show Gist options
  • Save siyu6974/0f6b8273b76ee0d7fe939e8b5a982553 to your computer and use it in GitHub Desktop.
Save siyu6974/0f6b8273b76ee0d7fe939e8b5a982553 to your computer and use it in GitHub Desktop.
Use Realm with mantle
#import <Realm/Realm.h>
#import <Mantle/Mantle.h>
@interface ModelBase : RLMObject <MTLJSONSerializing, MTLModel>
@end
#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
@siyu6974
Copy link
Author

To use it, make your model class a subclass of this Base class.
For example,

#import "ModelBase.h"
@interface User : ModelBase
@property NSInteger uid;
@property NSString *username;
@property NSString *email;
@property NSString *first_name;
@property NSString *last_name;
# pragma mark - Calculated properties
@property (readonly) NSString *fullname;
@end

@implementation User

+ (NSString *)primaryKey {
    return @"uid";
}

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"uid": @"id",
             @"username": @"username",
             @"email": @"email",
             @"first_name": @"first_name",
             @"last_name": @"last_name"
             };
}

# pragma mark - Calculated properties
- (NSString *)fullname {
    return [NSString stringWithFormat:@"%@ %@", self.first_name, self.last_name];
}
@end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment