Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
RFC on MTLModel+MTLNSUbiquitousKeyValueStore

MTLModel+MTLNSUbiquitousKeyValueStore

Motivation

To create a method to persist and re-instantiate Mantle MTLModel objects to NSUbiquitousKeyValueStore (iCloud key-value store).

Background

NSUbiquitousKeyValueStore can only store values of class NSNumber, NSString, NSDate, NSData, NSArray, and NSDictionary ("plist types"). The MTLNSUbiquitousKeyValueStore category on MTLModel has been designed to create an NSDictionary representation of a model object in a deeply recursive way, making it suitable for storage in an NSUbiquitousKeyValueStore.

Likewise a model can be instantiated from such a dictionary representation.

Objects that have properties of types that are not either a "plist type" nor a MTLModel subclass will fail to persist in this way (e.g. a property that is an NSSet cannot (as of yet) be successfully stored in an NSUbiquitousKeyValueStore).

Keep in mind that the per-app quota is 1MB, so root objects that represent a large object graph may not be suitable to store this way, but for smaller state object graphs this may be sufficient.

Request For Comment

I am proposing the following files as such an implementation. I am interested in community comment or feedback on this approach (or whether I have overlooked something drastically simpler and have overcomplicated things).

//
// MTLModel+MTLNSUbiquitousKeyValueStore.h
//
// Created by Charles Etzel on 11/11/13.
// Copyright (c) 2013 Charles Etzel. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MTLModel.h"
@interface MTLModel (MTLNSUbiquitousKeyValueStore)
- (instancetype)initWithUbiquitousKeyValueStoreDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)ubiquitousKeyValueStoreDictionaryRepresentation;
- (void)persistToUbiquitousKeyValueStoreForKey:(NSString *)key;
@end
//
// MTLModel+MTLNSUbiquitousKeyValueStore.m
//
// Created by Charles Etzel on 11/11/13.
// Copyright (c) 2013 Charles Etzel. All rights reserved.
//
#import <objc/runtime.h>
#import "MTLModel+MTLNSUbiquitousKeyValueStore.h"
#import "MTLModel+NSCoding.h"
#define MTLMODEL_UKVS_CLASSNAME_KEY @"__MTLModel_NSUbiquitousKeyValueStore_className__"
@implementation MTLModel (MTLNSUbiquitousKeyValueStore)
- (instancetype)initWithUbiquitousKeyValueStoreDictionary:(NSDictionary *)dictionary
{
if (self = [super init]) {
for (NSString *key in self.class.propertyKeys) {
id val = dictionary[key];
if (!val || val == [NSNull null] || [val isEqual:[NSNull null]]) {
continue;
}
id finalVal = [self __resolvedUbiquitousKeyValueStoreValueForValue:val];
[self setValue:finalVal forKey:key];
}
}
return self;
}
- (id)__resolvedUbiquitousKeyValueStoreValueForValue:(id)val
{
if ([val isKindOfClass:[NSDictionary class]]) {
if ([val objectForKey:MTLMODEL_UKVS_CLASSNAME_KEY]) {
Class cls = NSClassFromString([val objectForKey:MTLMODEL_UKVS_CLASSNAME_KEY]);
if ([[cls class] isSubclassOfClass:[MTLModel class]]) {
id object = [[cls alloc] initWithUbiquitousKeyValueStoreDictionary:val];
return object;
} else {
return val;
}
} else {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *key in val) {
id resolvedVal = [self __resolvedUbiquitousKeyValueStoreValueForValue:[val valueForKey:key]];
[dict setValue:resolvedVal forKey:key];
}
return dict;
}
} else if ([val isKindOfClass:[NSArray class]]) {
NSMutableArray *array = [NSMutableArray array];
for (id arrVal in val) {
id object = [self __resolvedUbiquitousKeyValueStoreValueForValue:arrVal];
[array addObject:object];
}
return array;
} else {
return val;
}
}
- (NSDictionary *)ubiquitousKeyValueStoreDictionaryRepresentation
{
NSMutableDictionary *returnDict = [NSMutableDictionary dictionary];
NSDictionary *encodingBehaviors = self.class.encodingBehaviorsByPropertyKey;
[self.dictionaryValue enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
// Skip nil values.
if ([value isEqual:NSNull.null]) return;
switch ([encodingBehaviors[key] unsignedIntegerValue]) {
// This will also match a nil behavior.
case MTLModelEncodingBehaviorExcluded:
break;
case MTLModelEncodingBehaviorUnconditional:
case MTLModelEncodingBehaviorConditional: {
id transformedVal = [self __transformedUbiquitousKeyValueStoreValueForValue:value];
[returnDict setValue:transformedVal forKey:key];
}
break;
default:
NSAssert(NO, @"Unrecognized encoding behavior %@ for key \"%@\"", encodingBehaviors[key], key);
}
}];
[returnDict setValue:NSStringFromClass([self class]) forKey:MTLMODEL_UKVS_CLASSNAME_KEY];
return returnDict;
}
- (id)__transformedUbiquitousKeyValueStoreValueForValue:(id)val
{
if ([val respondsToSelector:@selector(ubiquitousKeyValueStoreDictionaryRepresentation)]) {
return [val ubiquitousKeyValueStoreDictionaryRepresentation];
} else if ([val isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *key in val) {
id transformedVal = [self __transformedUbiquitousKeyValueStoreValueForValue:[val valueForKey:key]];
[dict setValue:transformedVal forKey:key];
}
return dict;
} else if ([val isKindOfClass:[NSArray class]]) {
NSMutableArray *array = [NSMutableArray array];
for (id obj in val) {
if ([obj respondsToSelector:@selector(ubiquitousKeyValueStoreDictionaryRepresentation)]) {
[array addObject:[obj ubiquitousKeyValueStoreDictionaryRepresentation]];
} else {
[array addObject:obj];
}
}
return array;
} else {
return val;
}
}
- (void)persistToUbiquitousKeyValueStoreForKey:(NSString *)key
{
if (!key) {
return;
}
[[NSUbiquitousKeyValueStore defaultStore] setDictionary:[self ubiquitousKeyValueStoreDictionaryRepresentation] forKey:key];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.