Skip to content

Commit

Permalink
Adding tons of introspection code to parse attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
kreeger committed Mar 5, 2014
1 parent dbb067a commit 1f98cd8
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 5 deletions.
6 changes: 3 additions & 3 deletions BDKUntappd.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ Pod::Spec.new do |s|
s.osx.deployment_target = '10.8'
s.requires_arc = true

s.source_files = 'Classes'
s.resources = 'Assets'

s.source_files = 'Classes/**/*.{h,m}'
# s.resources = 'Assets'
s.ios.exclude_files = 'Classes/osx'
s.osx.exclude_files = 'Classes/ios'
# s.public_header_files = 'Classes/**/*.h'
# s.frameworks = 'SomeFramework', 'AnotherFramework'
s.dependency 'AFNetworking/Serialization', '>= 2.0.0'
s.dependency 'AFNetworking/NSURLSession', '>= 2.0.0'
s.dependency 'TransformerKit'
end
9 changes: 8 additions & 1 deletion Classes/BDKUntappd.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#import "BDKUntappd.h"

#import "BDKUntappdModels.h"

NSString * const BDKUntappdBaseURL = @"http://api.untappd.com/v4";
NSString * const BDKUntappdAuthenticateURL = @"https://untappd.com/oauth/authenticate";
NSString * const BDKUntappdAuthorizeURL = @"https://untappd.com/oauth/authorize";
Expand Down Expand Up @@ -71,7 +73,12 @@ - (void)checkinsForUser:(NSString *)username completion:(BDKUntappdResultBlock)c

NSString *url = [NSString stringWithFormat:@"/v4/user/checkins%@%@", username ? @"/" : @"", username ?: @""];
[self GET:url parameters:[self authorizationParams] success:^(NSURLSessionDataTask *task, id responseObject) {
completion(responseObject, nil);
NSMutableArray *checkins = [NSMutableArray array];
[responseObject[@"response"][@"checkins"][@"items"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
BDKUntappdCheckin *checkin = [BDKUntappdCheckin modelWithDictionary:obj];
[checkins addObject:checkin];
}];
completion(checkins, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
completion(nil, error);
}];
Expand Down
24 changes: 24 additions & 0 deletions Classes/BDKUntappdCheckin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// BDKUntappdCheckin.h
//
// Created by Ben Kreeger on 3/4/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

#import "BDKUntappdModel.h"

@interface BDKUntappdCheckin : BDKUntappdModel

@property (strong, nonatomic) NSArray *badges;
@property (strong, nonatomic) NSDictionary *beer;
@property (strong, nonatomic) NSDictionary *brewery;
@property (strong, nonatomic) NSArray *comments;
@property (strong, nonatomic) NSDate *createdAt;
@property (strong, nonatomic) NSArray *media;
@property (strong, nonatomic) NSNumber *ratingScore;
@property (strong, nonatomic) NSDictionary *source;
@property (strong, nonatomic) NSArray *toasts;
@property (strong, nonatomic) NSDictionary *user;
@property (strong, nonatomic) NSDictionary *venue;

@end
18 changes: 18 additions & 0 deletions Classes/BDKUntappdCheckin.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// BDKUntappdCheckin.h
//
// Created by Ben Kreeger on 3/4/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

#import "BDKUntappdCheckin.h"

@implementation BDKUntappdCheckin

#pragma mark - BDKUntappdModel

- (NSString *)remoteIdentifierName {
return @"checkin_id";
}

@end
17 changes: 17 additions & 0 deletions Classes/BDKUntappdModel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// BDKUntappdModel.h
//
// Created by Ben Kreeger on 3/4/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

@interface BDKUntappdModel : NSObject <NSCoding>

@property (strong, nonatomic) NSString *identifier;
@property (readonly, strong, nonatomic) NSString *remoteIdentifierName;

+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (void)updateWithDictionary:(NSDictionary *)dictionary;

@end
187 changes: 187 additions & 0 deletions Classes/BDKUntappdModel.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//
// BDKUntappdModel.m
//
// Created by Ben Kreeger on 3/4/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

#import "BDKUntappdModel.h"

#import "NSObject+BDKUntappd.h"

#import <TransformerKit/TransformerKit.h>
#import <TransformerKit/NSValueTransformer+TransformerKit.h>
#import <objc/runtime.h>

// Praise be to Jastor. https://github.com/elado/jastor
static const char *property_getTypeName(objc_property_t property) {
const char *attributes = property_getAttributes(property);
char buffer[1 + strlen(attributes)];
strcpy(buffer, attributes);
char *state = buffer, *attribute;
while ((attribute = strsep(&state, ",")) != NULL) {
if (attribute[0] == 'T') {
size_t len = strlen(attribute);
attribute[len - 1] = '\0';
return (const char *)[[NSData dataWithBytes:(attribute + 3) length:len - 2] bytes];
}
}
return "@";
}

@implementation BDKUntappdModel

+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
return [[self alloc] initWithDictionary:dictionary];
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (!self) return nil;
[self updateWithDictionary:dictionary];
return self;
}

- (void)updateWithDictionary:(NSDictionary *)dictionary {
NSArray *properties = [[self class] propertyNamesForClass:[self class]];
[properties enumerateObjectsUsingBlock:^(NSString *propertyName, NSUInteger idx, BOOL *stop) {
NSString *remoteName = [[self class] remotePropertyNameForLocalPropertyName:propertyName];
id val = dictionary[remoteName];
if (![val bdk_isPresent]) return;
if ([[self class] property:propertyName isReadOnlyForClass:[self class]]) return;

if ([val isKindOfClass:[NSDictionary class]]) {
// Untappd's API has arrays nested in dictionaries with some metadata; this pulls it out and rights it.
if (val[@"items"]) {
val = val[@"items"];
} else {
Class klass = [[self class] classForPropertyName:propertyName inClass:[self class]];
val = [[klass alloc] initWithDictionary:val];
}

}

if ([val isKindOfClass:[NSArray class]]) {
NSArray *valuesForVal = (NSArray *)val;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[valuesForVal count]];
[valuesForVal enumerateObjectsUsingBlock:^(id subVal, NSUInteger idx, BOOL *stop) {
if ([[subVal class] isSubclassOfClass:[NSDictionary class]]) {
SEL classSelector = NSSelectorFromString([NSString stringWithFormat:@"%@_class", propertyName]);
if ([[self class] respondsToSelector:classSelector]) {
Class klass = [[self class] performSelector:classSelector];
if ([klass isSubclassOfClass:[BDKUntappdModel class]]) {
BDKUntappdModel *model = [[klass alloc] initWithDictionary:subVal];
[objects addObject:model];
return;
}
}
[objects addObject:subVal];
}
}];
val = [objects copy];

}

[self setValue:val forKey:propertyName];
}];

id identifierValue = [dictionary objectForKey:self.remoteIdentifierName];
if ([identifierValue bdk_isPresent]) {
if (![identifierValue isKindOfClass:[NSString class]]) {
identifierValue = [NSString stringWithFormat:@"%@", identifierValue];
}
[self setValue:identifierValue forKey:@"identifier"];
}
}

- (void)dealloc {
self.identifier = nil;
}

- (NSString *)description {
return [NSString stringWithFormat:@"<%@ %p> { id: %@ }", NSStringFromClass([self class]), self, self.identifier];
}

#pragma mark - Properties

- (NSString *)remoteIdentifierName {
return @"id";
}

#pragma mark - NSCoding

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (!self) return nil;

id decoded = [aDecoder decodeObjectForKey:@"identifier"];
[self setValue:decoded forKey:@"identifier"];

NSArray *propertyNames = [[self class] propertyNamesForClass:[self class]];
[propertyNames enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
if ([[self class] property:key isReadOnlyForClass:[self class]]) return;
id decoded = [aDecoder decodeObjectForKey:key];
if (![decoded bdk_isPresent]) return;
[self setValue:decoded forKey:key];
}];
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.identifier forKey:@"identifier"];
NSArray *propertyNames = [[self class] propertyNamesForClass:[self class]];
[propertyNames enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}];
}

#pragma mark - Private methods

+ (NSArray *)propertyNamesForClass:(Class)klass {
if (klass == [BDKUntappdModel class]) {
return [NSArray array];
}

int numberOfProperties = 0;
objc_property_t *propertyTypes = class_copyPropertyList(klass, &numberOfProperties);

NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:numberOfProperties];
for (int idx = 0; idx < numberOfProperties; idx++) {
objc_property_t property = propertyTypes[idx];
[propertyList addObject:[NSString stringWithUTF8String:property_getName(property)]];
}

free(propertyTypes);
return propertyList;
}

+ (NSString *)remotePropertyNameForLocalPropertyName:(NSString *)localPropertyName {
NSString *unLlamad = [[NSValueTransformer valueTransformerForName:TTTLlamaCaseStringTransformerName]
reverseTransformedValue:localPropertyName];
return [[NSValueTransformer valueTransformerForName:TTTTrainCaseStringTransformerName] transformedValue:unLlamad];
}

+ (BOOL)property:(NSString *)propertyName isReadOnlyForClass:(Class)klass {
const char *type = property_getAttributes(class_getProperty(klass, [propertyName UTF8String]));
NSString *typeAttribute = [[[NSString stringWithUTF8String:type] componentsSeparatedByString:@","] objectAtIndex:1];
return [typeAttribute rangeOfString:@"R"].length > 0;
}

+ (Class)classForPropertyName:(NSString *)propertyName inClass:(Class)klass {
int numberOfProperties = 0;
objc_property_t *propertyTypes = class_copyPropertyList(klass, &numberOfProperties);
const char *cPropertyName = [propertyName UTF8String];

for (int idx = 0; idx < numberOfProperties; idx++) {
objc_property_t property = propertyTypes[idx];
if (strcmp(cPropertyName, property_getName(property)) == 0) {
free(propertyTypes);
return NSClassFromString([NSString stringWithUTF8String:property_getTypeName(property)]);
}
}

free(propertyTypes);
return [self classForPropertyName:propertyName inClass:class_getSuperclass(klass)];
}

@end
2 changes: 2 additions & 0 deletions Classes/BDKUntappdModels.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#import "BDKUntappdModel.h"
#import "BDKUntappdCheckin.h"
12 changes: 12 additions & 0 deletions Classes/Categories/NSObject+BDKUntappd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// NSObject+BDKUntappd.h
//
// Created by Ben Kreeger on 3/5/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

@interface NSObject (BDKUntappd)

- (BOOL)bdk_isPresent;

@end
16 changes: 16 additions & 0 deletions Classes/Categories/NSObject+BDKUntappd.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// NSObject+BDKUntappd.m
//
// Created by Ben Kreeger on 3/5/14.
// Copyright (c) 2014 Ben Kreeger. All rights reserved.
//

#import "NSObject+BDKUntappd.h"

@implementation NSObject (BDKUntappd)

- (BOOL)bdk_isPresent {
return ![self isEqual:[NSNull null]];
}

@end
5 changes: 4 additions & 1 deletion Project/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ PODS:
- BDKUntappd (0.1.0):
- AFNetworking/NSURLSession (>= 2.0.0)
- AFNetworking/Serialization (>= 2.0.0)
- TransformerKit
- TransformerKit (0.3.1)

DEPENDENCIES:
- BDKUntappd (from `../BDKUntappd.podspec`)
Expand All @@ -21,6 +23,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
AFNetworking: e8cfd8f3fe3bfe19124a0cc63f4d45752d892a56
BDKUntappd: f904c5255674ad5ff6260be467db8d08075c8af9
BDKUntappd: 527ef70470c6071031237e38b2d89db2928fa1b4
TransformerKit: 612d3966b31e345f2abc8221fe6657379bb5d8b6

COCOAPODS: 0.29.0

0 comments on commit 1f98cd8

Please sign in to comment.