This file contains 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
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 |
This file contains 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 "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 |
This file contains 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 "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 |
This file contains 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 "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