Skip to content

Instantly share code, notes, and snippets.

@jparishy
Created July 20, 2015 03:08
Show Gist options
  • Save jparishy/a6110f4202f8fb2a0202 to your computer and use it in GitHub Desktop.
Save jparishy/a6110f4202f8fb2a0202 to your computer and use it in GitHub Desktop.
//
// JLEInstagramAPI.m
// WhenToPost
//
// Created by Julius Parishy on 6/27/15.
// Copyright (c) 2015 Julius Parishy. All rights reserved.
//
#import "JLEInstagramAPI.h"
#import <SSKeychain/SSKeychain.h>
static NSString *const JLEKeychainInstagramService = @"Instagram";
static NSString *const JLEKeychainInstagramAccount = @"StoredAccessToken";
NSString *const JLEInstagramAPIErrorDomain = @"JLEInstagramAPIErrorDomain";
NSString *const JLEInstagramAPIErrorUserInfoURLRequestKey = @"JLEInstagramAPIErrorUserInfoURLRequestKey";
NSString *const JLEInstagramAPIErrorUserInfoHTTPResponseKey = @"JLEInstagramAPIErrorUserInfoHTTPResponseKey";
NSString *const JLEInstagramAPIErrorUserInfoResponseDataKey = @"JLEInstagramAPIErrorUserInfoResponseDataKey";
typedef NS_ENUM(NSInteger, JLEHTTPMethod)
{
JLEHTTPMethodGET,
JLEHTTPMethodPOST,
};
static NSString *JLEHTTPMethodStringForMethod(JLEHTTPMethod method);
@interface JLERequestResponse : NSObject
@property (nonatomic, strong, readonly) NSHTTPURLResponse *URLResponse;
@property (nonatomic, strong, readonly) id responseObject;
- (instancetype)initWithURLResponse:(NSHTTPURLResponse *)URLResponse responseObject:(id)responseObject;
@end
@interface JLEInstagramAPI ()
@property (nonatomic, strong) NSURL *baseURL;
@end
@implementation JLEInstagramAPI
- (instancetype)initWithAccessToken:(NSString *)accessToken
{
if((self = [super init]))
{
_accessToken = accessToken;
_queue = [[NSOperationQueue alloc] init];
_baseURL = [NSURL URLWithString:@"https://api.instagram.com/v1/"];
}
return self;
}
+ (NSString *)storedAccessToken
{
return [SSKeychain passwordForService:JLEKeychainInstagramService account:JLEKeychainInstagramAccount];
}
+ (void)setStoredAccessToken:(NSString *)accessToken
{
[SSKeychain setPassword:accessToken forService:JLEKeychainInstagramService account:JLEKeychainInstagramAccount];
}
- (NSDictionary *)defaultHTTPHeaders
{
return @{
@"Accept" : @"application/json",
@"Content-Type" : @"application/json"
};
}
- (NSString *)queryParametersStringFromParameters:(NSDictionary *)parameters
{
NSMutableDictionary *allParameters = [parameters mutableCopy] ?: [NSMutableDictionary dictionary];
allParameters[@"access_token"] = self.accessToken;
NSString *string = @"";
NSArray *keys = [[allParameters allKeys] sortedArrayUsingDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES] ]];
for(NSString *key in keys)
{
id value = allParameters[key];
NSString *maybeAmp = (keys.lastObject == key) ? @"" : @"&";
string = [string stringByAppendingFormat:@"%@=%@%@", key, value, maybeAmp];
}
return string;
}
- (NSURL *)APIEndpointURLWithPath:(NSString *)path
method:(JLEHTTPMethod)method
parameters:(NSDictionary *)parameters
{
switch(method)
{
case JLEHTTPMethodGET:
{
NSString *queryParamtersString = [self queryParametersStringFromParameters:parameters];
NSString *pathWithParameters = [path stringByAppendingFormat:@"?%@", queryParamtersString];
return [NSURL URLWithString:pathWithParameters relativeToURL:self.baseURL];
}
case JLEHTTPMethodPOST:
{
return [NSURL URLWithString:path relativeToURL:self.baseURL];
}
};
}
- (NSURLRequest *)requestWithPath:(NSString *)path
method:(JLEHTTPMethod)method
parameters:(NSDictionary *)parameters
error:(NSError **)error
{
NSURL *URL = [self APIEndpointURLWithPath:path method:method parameters:parameters];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.HTTPMethod = JLEHTTPMethodStringForMethod(method);
NSDictionary *defaultHTTPHeaders = [self defaultHTTPHeaders];
for(NSString *name in defaultHTTPHeaders)
{
NSString *value = defaultHTTPHeaders[name];
[request setValue:value forHTTPHeaderField:name];
}
switch (method)
{
case JLEHTTPMethodPOST:
{
NSError *JSONError = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:parameters options:kNilOptions error:&JSONError];
if(bodyData == nil)
{
*error = JSONError;
return nil;
}
request.HTTPBody = bodyData;
};
default:
break;
};
return [request copy];
}
- (BFTask *)beginRequestWithPath:(NSString *)path method:(JLEHTTPMethod)method parameters:(NSDictionary *)parameters
{
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
NSError *error = nil;
NSURLRequest *request = [self requestWithPath:path method:method parameters:parameters error:&error];
if(request == nil)
{
[source setError:error];
}
else
{
[NSURLConnection sendAsynchronousRequest:request queue:self.queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if(connectionError != nil)
{
[source setError:connectionError];
return;
}
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if((HTTPResponse.statusCode / 100) != 2)
{
NSError *statusCodeError = [NSError errorWithDomain:JLEInstagramAPIErrorDomain code:JLEInstagramAPIErrorCodeInvalidResponseStatusCode userInfo:@{
JLEInstagramAPIErrorUserInfoURLRequestKey : request,
JLEInstagramAPIErrorUserInfoHTTPResponseKey : HTTPResponse,
JLEInstagramAPIErrorUserInfoResponseDataKey : data
}];
[source setError:statusCodeError];
return;
}
NSString *mimeType = HTTPResponse.MIMEType;
if([mimeType isEqualToString:@"application/json"] == NO)
{
NSError *contentTypeError = [NSError errorWithDomain:JLEInstagramAPIErrorDomain code:JLEInstagramAPIErrorCodeInvalidContentType userInfo:@{
JLEInstagramAPIErrorUserInfoURLRequestKey : request,
JLEInstagramAPIErrorUserInfoHTTPResponseKey : HTTPResponse,
JLEInstagramAPIErrorUserInfoResponseDataKey : data
}];
[source setError:contentTypeError];
return;
}
NSError *JSONError = nil;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
if(responseObject == nil)
{
[source setError:JSONError];
return;
}
[source setResult:@[ HTTPResponse, responseObject ]];
}];
}
return source.task;
}
- (BFTask *)GET:(NSString *)path parameters:(NSDictionary *)parameters
{
return [self beginRequestWithPath:path method:JLEHTTPMethodGET parameters:parameters];
}
- (BFTask *)POST:(NSString *)path parameters:(NSDictionary *)parameters
{
return [self beginRequestWithPath:path method:JLEHTTPMethodPOST parameters:parameters];
}
- (BFTask *)requestAllMedia
{
return [self requestMediaWithPath:@"users/self/media/recent" nextMaxID:nil models:nil];
}
- (BFTask *)requestMediaWithPath:(NSString *)path nextMaxID:(NSString *)nextMaxID models:(NSArray *)models
{
NSDictionary *parameters = nil;
if(nextMaxID != nil)
{
parameters = @{
@"max_id" : nextMaxID
};
}
return [[self GET:path parameters:parameters] continueWithBlock:^id(BFTask *task) {
if(task.error != nil)
{
return [BFTask taskWithError:task.error];
}
NSDictionary *rootObject = [self JSONObjectFromTask:task];
NSArray *JSONArray = rootObject[@"data"];
NSError *serializationError = nil;
NSArray *newModels = [MTLJSONAdapter modelsOfClass:[JLEInstagramMedia class] fromJSONArray:JSONArray error:&serializationError];
if(newModels == nil)
{
return [BFTask taskWithError:serializationError];
}
NSArray *allModels = [models arrayByAddingObjectsFromArray:newModels] ?: newModels;
NSDictionary *pagination = rootObject[@"pagination"];
NSString *nextURLString = pagination[@"next_url"];
NSURL *nextURL = [NSURL URLWithString:nextURLString];
if(nextURL != nil)
{
NSString *path = nextURL.path;
NSString *nextMaxID = nil;
NSURLComponents *components = [NSURLComponents componentsWithURL:nextURL resolvingAgainstBaseURL:NO];
NSArray *queryItems = components.queryItems;
for(NSURLQueryItem *queryItem in queryItems)
{
if([queryItem.name isEqualToString:@"max_id"])
{
nextMaxID = queryItem.value;
break;
}
}
return [self requestMediaWithPath:path nextMaxID:nextMaxID models:allModels];
}
else
{
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
[source setResult:allModels];
return source.task;
}
}];
}
- (NSHTTPURLResponse *)responseFromTask:(BFTask *)task
{
NSArray *tuple = task.result;
return tuple[0];
}
- (id)JSONObjectFromTask:(BFTask *)task
{
NSArray *tuple = task.result;
return tuple[1];
}
@end
@implementation JLERequestResponse
- (instancetype)initWithURLResponse:(NSHTTPURLResponse *)URLResponse responseObject:(id)responseObject
{
if((self = [super init]))
{
_URLResponse = URLResponse;
_responseObject = responseObject;
}
return self;
}
@end
static NSString *JLEHTTPMethodStringForMethod(JLEHTTPMethod method)
{
switch(method)
{
case JLEHTTPMethodGET:
{
return @"GET";
}
case JLEHTTPMethodPOST:
{
return @"POST";
}
}
}
__weak typeof(self) weakSelf = self;
[[self.API requestAllMedia] continueWithBlock:^id(BFTask *task) {
__weak typeof(self) self = weakSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__weak typeof(self) self = weakSelf;
[UIView animateWithDuration:0.4 animations:^{
self.analyzingLabel.alpha = 0.0f;
}];
[UIView animateWithDuration:1.2 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:1.0f options:kNilOptions animations:^{
self.separatorView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
__weak typeof(self) self = weakSelf;
self.analyzing = NO;
}];
}];
NSArray *unsorted = task.result;
NSDictionary *byWeekday = [JLEInstagramMedia groupedByWeekday:unsorted];
[self handleGroupedByWeekday:byWeekday];
NSDictionary *byHour = [JLEInstagramMedia groupedByHour:unsorted];
[self handleGroupedByHour:byHour];
return nil;
}];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment