Skip to content

Instantly share code, notes, and snippets.

@zadr
Created Mar 16, 2017
Embed
What would you like to do?
NS_ASSUME_NONNULL_BEGIN
@interface WObject : NSObject <NSCopying, NSSecureCoding>
+ (instancetype) objectWithJSONRepresentation:(NSDictionary *) JSONRepresentation;
+ (instancetype) objectWithJSONRepresentation:(NSDictionary *) JSONRepresentation excludingKeys:(NSArray *__nullable) keys;
+ (NSString *__nullable) replacementKeyForKey:(NSString *__nonnull) key;
+ (NSFormatter *__nullable) formatterForKey:(NSString *__nonnull) key;
+ (NSValueTransformer *__nullable) transformerForKey:(NSString *__nonnull) key;
@end
NS_ASSUME_NONNULL_END
#import "WObject.h"
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
@implementation WObject
+ (instancetype) objectWithJSONRepresentation:(NSDictionary *) JSONRepresentation {
return [self objectWithJSONRepresentation:JSONRepresentation excludingKeys:nil];
}
+ (instancetype) objectWithJSONRepresentation:(NSDictionary *) JSONRepresentation excludingKeys:(NSArray *__nullable) excludingKeys {
WObject *object = [[[self class] alloc] init];
NSMutableDictionary *workingJSONRepresentation = [JSONRepresentation mutableCopy];
for (id key in excludingKeys) {
[workingJSONRepresentation removeObjectForKey:key];
}
[[workingJSONRepresentation copy] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
id actualKey = [self replacementKeyForKey:key];
if (actualKey && ![actualKey isEqual:key]) {
workingJSONRepresentation[actualKey] = workingJSONRepresentation[key];
[workingJSONRepresentation removeObjectForKey:key];
}
}];
[object setValuesForKeysWithDictionary:workingJSONRepresentation];
return object;
}
+ (NSString *__nullable) replacementKeyForKey:(NSString *) key {
if ([key isEqual:@"id"]) {
return @"identifier";
}
if ([key isEqual:@"description"]) {
return @"objectDescription";
}
return nil;
}
+ (NSFormatter *__nullable) formatterForKey:(NSString *) key {
return nil;
}
+ (NSValueTransformer *__nullable) transformerForKey:(NSString *) key {
return nil;
}
#pragma mark -
+ (BOOL)supportsSecureCoding {
return YES;
}
- (__nullable id) initWithCoder:(NSCoder *) aDecoder {
if (!(self = [super init])) {
return nil;
}
[self _enumeratePropertiesWithPropertyStartBlock:nil propertyBlock:^(NSString *key, id __nullable value) {
objc_property_t property = class_getProperty([self class], key.UTF8String);
NSString *attributes = [NSString stringWithUTF8String:property_getAttributes(property)];
NSArray *attributesComponents = [attributes componentsSeparatedByString:@","];
NSString *encodedClassString = attributesComponents.firstObject;
if (encodedClassString) {
NSArray *encodedClassComponents = [encodedClassString componentsSeparatedByString:@"\""];
NSString *classString = encodedClassComponents.firstObject;
if (classString) {
Class klass = NSClassFromString(classString);
[self setValue:[aDecoder decodeObjectOfClass:klass forKey:key] forKey:key];
}
}
} completionBlock:nil];
return self;
}
- (void) encodeWithCoder:(NSCoder *) aCoder {
[self _enumeratePropertiesWithPropertyStartBlock:nil propertyBlock:^(NSString *key, id __nullable value) {
[aCoder encodeObject:value forKey:key];
} completionBlock:nil];
}
#pragma mark -
- (id) copyWithZone:(nullable NSZone *) zone {
id copy = [[[self class] alloc] init];
[self _enumeratePropertiesWithPropertyStartBlock:nil propertyBlock:^(NSString *key, id __nullable value) {
[copy setValue:value forKey:key];
} completionBlock:nil];
return copy;
}
#pragma mark -
- (void) setValue:(nullable id) value forKey:(NSString *) key {
NSError *error = nil;
if ([self validateValue:&value forKey:key error:&error]) {
[super setValue:value forKey:key];
}
}
- (void) setNilValueForKey:(NSString *) key {
NSLog(@"Refusing to set nil value for key");
}
- (void) setValue:(nullable id) value forUndefinedKey:(NSString *) key {
NSLog(@"%@: Refusing to set value \"%@\" for unknown key \"%@\"", NSStringFromClass([self class]), value, key);
}
- (BOOL) validateValue:(inout id *) ioValue forKey:(NSString *) inKey error:(out NSError **) outError {
NSString *value = *ioValue;
if (![value isKindOfClass:[NSString class]]) {
return YES;
}
BOOL validValue = YES;
NSValueTransformer *transformer = [[self class] transformerForKey:inKey];
if (transformer) {
*ioValue = [transformer transformedValue:*ioValue];
}
NSFormatter *formatter = [[self class] formatterForKey:inKey];
if (formatter) {
validValue = [formatter getObjectValue:ioValue forString:value errorDescription:nil];
}
return validValue;
}
#pragma mark -
- (NSString *) description {
NSMutableString *propertyDescription = [[super description] mutableCopy];
[self _enumeratePropertiesWithPropertyStartBlock:^(NSUInteger propertyCount) {
if (propertyCount) {
[propertyDescription appendString:@"\t{"];
}
} propertyBlock:^(NSString *__nonnull key, id value) {
[propertyDescription appendFormat: @"\n\t%@: %@,", key, value];
} completionBlock:^(NSUInteger propertyCount) {
if (propertyCount) {
[propertyDescription deleteCharactersInRange:NSMakeRange(propertyDescription.length - 1, 1)];
[propertyDescription appendString:@"\n}"];
}
}];
return [propertyDescription copy];
}
#pragma mark -
- (void) _enumeratePropertiesWithPropertyStartBlock:(void(^__nullable)(NSUInteger)) startBlock propertyBlock:(void(^)(NSString *, id __nullable)) propertyBlock completionBlock:(void(^__nullable)(NSUInteger)) completionBlock {
unsigned int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
if (startBlock) {
startBlock(propertyCount);
}
if (propertyCount) {
for (unsigned int i = 0; i < propertyCount; i++) {
objc_property_t property = propertyList[i];
const char *propertyNameCString = property_getName(property);
NSString *propertyNameString = [NSString stringWithUTF8String:propertyNameCString];
id value = [self valueForKey:propertyNameString];
propertyBlock(propertyNameString, value);
}
free(propertyList);
}
if (completionBlock) {
completionBlock(propertyCount);
}
}
@end
NS_ASSUME_NONNULL_END
#import "WObject.h"
@interface WFlight : WObject
@property (copy) NSDate *actTime;
@property (copy) NSString *arrCityName1;
@property (copy) NSString *arrCityName2;
@property (copy) NSString *arrCityName3;
@property (copy) NSString *bagClaim;
@property (copy) NSString *baseFlight;
@property (copy) NSString *baseFlightCarrier;
@property (copy) NSString *carrier;
@property (copy) NSString *carrierFull;
@property (copy) NSString *city1;
@property (copy) NSString *city2;
@property (copy) NSString *city3;
@property (copy) NSString *cityCode;
@property (copy) NSString *cityFull;
@property (copy) NSString *depCityName1;
@property (copy) NSString *depCityName2;
@property (copy) NSString *depCityName3;
@property (copy) NSDate *estTime;
@property (copy) NSDate *estTimeMili;
@property (copy) NSNumber *flightNum;
@property (copy) NSString *flightNumFull;
@property (copy) NSString *flightType;
@property (copy) NSString *gate;
@property (copy) NSString *origin;
@property (copy) NSString *remark;
@property (copy) NSDate *schedTime;
@property (copy) NSDate *schedTimeMili;
@property (copy) NSString *terminal;
@property (copy) NSDate *addedTimestamp;
@property (copy) NSString *iGate;
@property (copy) NSString *inc;
@property (copy) NSDate *lastUpdated;
@end
#import "WFlight.h"
NS_ASSUME_NONNULL_BEGIN
@interface WEpochDateTransformer : NSValueTransformer
@end
@implementation WEpochDateTransformer
+ (BOOL) allowsReverseTransformation {
return YES;
}
+ (Class)transformedValueClass {
return [NSDate class];
}
- (nullable id) transformedValue:(nullable id) value {
return [NSDate dateWithTimeIntervalSince1970:[value doubleValue]];
}
- (nullable id) reverseTransformedValue:(nullable id) value {
return @([value timeIntervalSince1970]);
}
@end
@implementation WFlight
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[NSValueTransformer setValueTransformer:[[self alloc] init] forName:NSStringFromClass([self class])];
});
}
+ (NSString *__nullable) replacementKeyForKey:(NSString *__nonnull) key {
NSString *replacementKey = nil;
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[key characterAtIndex:0]]) {
NSString *lowercaseString = [key substringToIndex:1].lowercaseString;
replacementKey = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:lowercaseString];
}
if ([key isEqual:@"added_ts"]) {
replacementKey = NSStringFromSelector(@selector(addedTimestamp));
} else if ([key isEqual:@"lastupdated"]) {
replacementKey = NSStringFromSelector(@selector(lastUpdated));
}
return replacementKey ?: [super replacementKeyForKey:key];
}
+ (NSFormatter *__nullable) formatterForKey:(NSString *__nonnull) key {
NSDateFormatter *(^dateFormatterWithFormat)(NSString *) = ^NSDateFormatter *(NSString *format) {
static NSMutableDictionary *dateFormatters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatters = [NSMutableDictionary dictionary];
});
NSDateFormatter *dateFormatter = dateFormatters[format];
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = format;
dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"PST"]; // todo: this means PST // todo: don't assume SFO/OAK
dateFormatters[format] = dateFormatter;
}
return dateFormatter;
};
static NSArray *hhmmaProperties = nil;
if (!hhmmaProperties) {
hhmmaProperties = @[ NSStringFromSelector(@selector(estTime)), NSStringFromSelector(@selector(schedTime)) ];
}
if ([hhmmaProperties containsObject:key]) {
return dateFormatterWithFormat(@"hh:mm a");
}
static NSArray *HHmmProperties = nil;
if (!HHmmProperties) {
HHmmProperties = @[ NSStringFromSelector(@selector(estTimeMili)), NSStringFromSelector(@selector(schedTimeMili)) ];
}
if ([HHmmProperties containsObject:key]) {
return dateFormatterWithFormat(@"HH:mm");
}
static NSArray *MMddyyyyhhmma = nil;
if (!MMddyyyyhhmma) {
MMddyyyyhhmma = @[ NSStringFromSelector(@selector(lastUpdated)) ];
}
if ([MMddyyyyhhmma containsObject:key]) {
return dateFormatterWithFormat(@"MM/dd/yyyy HH:mm a");
}
return [super formatterForKey:key];
}
+ (NSValueTransformer *__nullable)transformerForKey:(NSString *) key {
NSArray *epochTimestamp = @[ NSStringFromSelector(@selector(addedTimestamp)) ];
if ([epochTimestamp containsObject:key]) {
return [NSValueTransformer valueTransformerForName:NSStringFromClass([WEpochDateTransformer class])];
}
return [super transformerForKey:key];
}
- (BOOL) isEqual:(id) object {
if (!object || ![object isKindOfClass:[self class]]) {
return NO;
}
return [self.flightNumFull isEqual:[object flightNumFull]];
}
- (NSUInteger) hash {
return self.flightNumFull.hash;
}
@end
NS_ASSUME_NONNULL_END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment