Skip to content

Instantly share code, notes, and snippets.

@TosinAF
Last active August 29, 2015 14:09
Show Gist options
  • Save TosinAF/176e04e43b30d55b4d90 to your computer and use it in GitHub Desktop.
Save TosinAF/176e04e43b30d55b4d90 to your computer and use it in GitHub Desktop.
Designer News API iOS Wrapper. Full Source Code can be found at https://github.com/TosinAF/thenews

I released TheNews iOS (bit.ly/thenews-ios) over the past summer. A required part of the app was to communicate with the designer news api. The api had only just been recently released so there was no wrapper for iOS apps.

As such, I had to create an API wrapper. Having never attempted this before, I studied a few examples on Github and modeled my solution like those I had seen.

It was a really interesting project to build. It's important to create the wrapper as a singleton to maintain a user session & prevent multiple calls of the same requests. I also built the wrapper with the consideration that others might find it useful (https://github.com/TosinAF/DNManager) .

As such I had to consider what methods should be public and make them easy for anyone to get familiar with it.

I leveraged the power of the AFNetworking Library to build the api wrapper & used blocks so as to allow the user to specify what actions should be taken if the request succeeds or if it fails.

API Spec can be found at http://developers.news.layervault.com/

//
// DesignerNewsAPIClient.h
// DesignerNewsAPI
//
// Created by Tosin Afolabi on 21/03/2014.
// Copyright (c) 2014 Tosin Afolabi. All rights reserved.
//
#import "DNMOTD.h"
#import "DNUser.h"
#import "DNStory.h"
#import "DNComment.h"
#import "AFHTTPSessionManager.h"
typedef NS_ENUM (NSInteger, DNFeedType) {
DNFeedTypeTop,
DNFeedTypeRecent
};
typedef NS_ENUM (NSInteger, DNActionType) {
DNActionTypeUpvote,
DNActionTypeReply,
};
typedef NS_ENUM (NSInteger, DNObjectType) {
DNObjectTypeStory,
DNObjectTypeComment,
};
typedef void (^ActionSuccessBlock) ();
typedef void (^RequestFailureBlock) (NSURLSessionDataTask *task, NSError *error);
@interface DNManager : AFHTTPSessionManager
#pragma mark - Singleton Method
+ (instancetype)sharedManager;
#pragma mark - Authentication methods
- (void)authenticateUser:(NSString *)username
password:(NSString *)password
success:(void (^) (NSString *accessToken))success
failure:(RequestFailureBlock)failure;
- (BOOL)isUserAuthenticated;
- (void)logout;
#pragma mark - DN User Methods
- (void)getUserInfo:(void (^) (DNUser *userInfo))success
failure:(RequestFailureBlock)failure;
#pragma mark - DN Search Methods
- (void)search:(NSString *)searchQuery
success:(void (^) (NSArray *dnStories))success
failure:(RequestFailureBlock)failure;
#pragma mark - DN Story Methods
- (void)getStoriesOnPage:(NSString *)page
feedType:(DNFeedType)feedType
success:(void (^) (NSArray *dnStories))success
failure:(RequestFailureBlock)failure;
- (void)getStoryWithID:(NSString *)storyID
success:(void (^) (DNStory *story))success
failure:(RequestFailureBlock)failure;
- (void)upvoteStoryWithID:(NSString *)storyID
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure;
- (void)replyStoryWithID:(NSString *)storyID
comment:(NSString *)comment
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure;
- (void)addStoryToReadList:(NSNumber *)storyID;
- (BOOL)hasUserReadStory:(NSNumber *)storyID;
#pragma mark - DN Comment Methods
- (void)getCommentsForStoryWithID:(NSString *)storyID
success:(void (^) (NSArray *comments))success
failure:(RequestFailureBlock)failure;
- (void)upvoteCommentWithID:(NSString *)commentID
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure;
- (void)replyCommentWithID:(NSString *)commentID
comment:(NSString *)comment
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure;
#pragma mark - DN MOTD Methods
- (void)getMOTD:(void (^) (DNMOTD *motd))success
failure:(RequestFailureBlock)failure;
- (void)upvoteMOTD:(void (^) (NSURLSessionDataTask *task, id responseObject))success
failure:(RequestFailureBlock)failure;
- (void)downvoteMOTD:(void (^) (NSURLSessionDataTask *task, id responseObject))success
failure:(RequestFailureBlock)failure;
#pragma mark - Auxillary Methods
- (void)cancelAllRequests;
@end
//
// DesignerNewsAPIClient.m
// DesignerNewsAPI
//
// Created by Tosin Afolabi on 21/03/2014.
// Copyright (c) 2014 Tosin Afolabi. All rights reserved.
//
#import "DNManager.h"
static NSString * const DNAPIAccessTokenKey = @"DNAPIAccessTokenKey";
static NSString * const DNReadPostsKey = @"DNReadPostsKey";
static NSString * const DNReadPostsCacheDateKey = @"DNReadPostsCacheDateKey";
static NSString * const DNAPIBaseURLString = @"https://api-news.layervault.com/api/v1";
static NSString * const DNAPIClientID = @"";
static NSString * const DNAPIClientSecret = @"";
@interface DNManager ()
@property (nonatomic, strong) NSMutableArray *allCommentsForStory;
@end
@implementation DNManager
#pragma mark - Singleton Method
+ (instancetype)sharedManager {
static DNManager *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[DNManager alloc] initWithBaseURL:[NSURL URLWithString:DNAPIBaseURLString]];
});
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([defaults arrayForKey:DNReadPostsKey] == nil) {
[defaults setObject:[NSArray new] forKey:DNReadPostsKey];
[defaults synchronize];
}
[_sharedClient checkReadPostsCache];
return _sharedClient;
}
#pragma mark - Authentication methods
- (void)authenticateUser:(NSString *)username
password:(NSString *)password
success:(void (^) (NSString *accessToken))success
failure:(RequestFailureBlock)failure
{
NSString *authURL = @"https://api-news.layervault.com/oauth/token";
NSDictionary *parameters = @{@"grant_type" : @"password",
@"username" : username,
@"password" : password,
@"client_id": DNAPIClientID,
@"client_secret" : DNAPIClientSecret };
[self POST:authURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
NSString *accessToken = responseObject[@"access_token"];
// Store Access Token
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:accessToken forKey:DNAPIAccessTokenKey];
[defaults synchronize];
success(accessToken);
} failure:failure];
}
- (BOOL)isUserAuthenticated
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *accessToken = [defaults objectForKey:DNAPIAccessTokenKey];
if ( accessToken == Nil ) {
return false;
}
return true;
}
- (void)logout
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:nil forKey:DNAPIAccessTokenKey];
[defaults synchronize];
}
#pragma mark - DN User Methods
- (void)getUserInfo:(void (^) (DNUser *userInfo))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL = [NSString stringWithFormat:@"%@/me", DNAPIBaseURLString];
NSString *accessToken = [self getAccessToken];
[self.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"];
[self GET:resourceURL parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
DNUser *userInfo = [[DNUser alloc] initWithDictionary:responseObject[@"user"]];
success(userInfo);
} failure:failure];
}
#pragma mark - DN Search Methods
- (void)search:(NSString *)searchQuery
success:(void (^) (NSArray *dnStories))success
failure:(RequestFailureBlock)failure
{
NSMutableArray *stories = [NSMutableArray new];
NSString *resourceURL = [NSString stringWithFormat:@"%@/stories/search", DNAPIBaseURLString];
NSDictionary *parameters = @{@"query":searchQuery, @"client_id":DNAPIClientID};
NSLog(@"%@", resourceURL);
[self GET:resourceURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
for (NSDictionary *story in responseObject) {
[stories addObject:[[DNStory alloc] initWithDictionaryForSearch:story]];
}
success([NSArray arrayWithArray:stories]);
} failure:failure];
}
#pragma mark - DN Story Methods
- (void)getStoriesOnPage:(NSString *)page
feedType:(DNFeedType)feedType
success:(void (^) (NSArray *dnStories))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL;
switch (feedType) {
case DNFeedTypeTop:
default:
resourceURL = [NSString stringWithFormat:@"%@/stories", DNAPIBaseURLString];
break;
case DNFeedTypeRecent:
resourceURL = [NSString stringWithFormat:@"%@/stories/recent", DNAPIBaseURLString];
break;
}
NSDictionary *parameters = @{@"page":page, @"client_id": DNAPIClientID};
[self getStories:resourceURL parameters:parameters success:success failure:failure];
}
- (void)getStoryWithID:(NSString *)storyID
success:(void (^) (DNStory *story))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL = [NSString stringWithFormat:@"%@/stories/%@", DNAPIBaseURLString, storyID];
NSDictionary *parameters = @{@"client_id": DNAPIClientID};
[self GET:resourceURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
DNStory *story = [[DNStory alloc] initWithDictionary:responseObject[@"story"]];
success(story);
} failure:failure];
}
- (void)upvoteStoryWithID:(NSString *)storyID
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure
{
[self performActionOnObjectWithID:storyID
object:DNObjectTypeStory
action:DNActionTypeUpvote
comment:nil
success:success
failure:failure];
}
- (void)replyStoryWithID:(NSString *)storyID
comment:(NSString *)comment
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure
{
[self performActionOnObjectWithID:storyID
object:DNObjectTypeStory
action:DNActionTypeReply
comment:comment
success:success
failure:failure];
}
- (void)addStoryToReadList:(NSNumber *)storyID
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentReadArticles = [defaults arrayForKey:DNReadPostsKey];
if ([self hasUserReadStory:storyID]) {
return;
}
NSMutableArray *newReadArticles = [NSMutableArray arrayWithArray:currentReadArticles];
[newReadArticles addObject:storyID];
[defaults setObject:[NSArray arrayWithArray:newReadArticles] forKey:DNReadPostsKey];
[defaults synchronize];
}
- (BOOL)hasUserReadStory:(NSNumber *)storyID
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentReadArticles = [defaults arrayForKey:DNReadPostsKey];
return [currentReadArticles containsObject:storyID];
}
- (void)checkReadPostsCache
{
NSDate *cacheDate = [[NSUserDefaults standardUserDefaults] objectForKey:DNReadPostsCacheDateKey];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// If this is the first time the app has been launched we record right now as the first time the app was launched.
if (!cacheDate) {
[defaults setObject:[NSDate date] forKey:DNReadPostsCacheDateKey];
return;
}
int diff = abs([cacheDate timeIntervalSinceNow]);
if (diff > 60 * 60 * 24 * 3) {
[defaults setObject:[NSArray new] forKey:DNReadPostsKey];
}
}
#pragma mark - DN Comment Methods
- (void)getCommentsForStoryWithID:(NSString *)storyID
success:(void (^) (NSArray *comments))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL = [NSString stringWithFormat:@"%@/stories/%@", DNAPIBaseURLString, storyID];
NSDictionary *parameters = @{@"client_id": DNAPIClientID};
self.allCommentsForStory = [[NSMutableArray alloc] init];
[self GET:resourceURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
[self collapseNestedComments:[responseObject[@"story"] objectForKey:@"comments"]];
success([NSArray arrayWithArray:self.allCommentsForStory]);
self.allCommentsForStory = nil;
} failure:failure];
}
- (void)upvoteCommentWithID:(NSString *)commentID
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure
{
[self performActionOnObjectWithID:commentID
object:DNObjectTypeComment
action:DNActionTypeUpvote
comment:nil
success:success
failure:failure];
}
- (void)replyCommentWithID:(NSString *)commentID
comment:(NSString *)comment
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure
{
[self performActionOnObjectWithID:commentID
object:DNObjectTypeComment
action:DNActionTypeReply
comment:comment
success:success
failure:failure];
}
#pragma mark - DN MOTD Methods
- (void)getMOTD:(void (^) (DNMOTD *motd))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL = [NSString stringWithFormat:@"%@/motd", DNAPIBaseURLString];
NSDictionary *parameters = @{@"client_id": DNAPIClientID};
[self GET:resourceURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
DNMOTD *motd = [[DNMOTD alloc] initWithDictionary:responseObject[@"motd"]];
success(motd);
} failure:failure];
}
- (void)upvoteMOTD:(void (^) (NSURLSessionDataTask *task, id responseObject))success
failure:(RequestFailureBlock)failure
{
[self performActionOnMOTD:@"upvote" success:success failure:failure];
}
- (void)downvoteMOTD:(void (^) (NSURLSessionDataTask *task, id responseObject))success
failure:(RequestFailureBlock)failure
{
[self performActionOnMOTD:@"downvote" success:success failure:failure];
}
#pragma mark - Auxillary Methods
- (void)cancelAllRequests
{
[self.operationQueue cancelAllOperations];
}
#pragma mark - Private Methods
- (NSString *)getAccessToken
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *accessToken = [defaults objectForKey:DNAPIAccessTokenKey];
return accessToken;
}
- (void)getStories:(NSString *)resourceURL
parameters:(NSDictionary *)parameters
success:(void (^) (NSArray *dnStories))success
failure:(RequestFailureBlock)failure
{
NSMutableArray *stories = [NSMutableArray new];
[self GET:resourceURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
for (NSDictionary *story in responseObject[@"stories"]) {
[stories addObject:[[DNStory alloc] initWithDictionary:story]];
}
success([NSArray arrayWithArray:stories]);
} failure:failure];
}
- (void)performActionOnObjectWithID:(NSString *)objectID
object:(DNObjectType)objectType
action:(DNActionType)actionType
comment:(NSString *)comment
success:(ActionSuccessBlock)success
failure:(RequestFailureBlock)failure
{
NSString *object, *action;
switch (objectType) {
case DNObjectTypeStory:
object = @"stories";
break;
case DNObjectTypeComment:
object = @"comments";
break;
}
switch (actionType) {
case DNActionTypeUpvote:
action = @"upvote";
break;
case DNActionTypeReply:
action = @"reply";
break;
}
NSString *resourceURL = [NSString stringWithFormat:@"%@/%@/%@/%@", DNAPIBaseURLString, object, objectID, action];
NSLog(@"%@", resourceURL);
NSString *accessToken = [self getAccessToken];
[self.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"];
if (actionType == DNActionTypeReply) {
[self POST:resourceURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFormData:[comment dataUsingEncoding:NSUTF8StringEncoding] name:@"comment[body]"];
} success:^(NSURLSessionDataTask *task, id responseObject) {
success();
} failure:failure];
} else {
[self POST:resourceURL parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
success();
} failure:failure];
}
}
- (void)performActionOnMOTD:(NSString *)actionType
success:(void (^) (NSURLSessionDataTask *task, id responseObject))success
failure:(RequestFailureBlock)failure
{
NSString *resourceURL = [NSString stringWithFormat:@"%@/motd/%@", DNAPIBaseURLString, actionType];
NSString *accessToken = [self getAccessToken];
[self.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"];
[self POST:resourceURL parameters:nil success:success failure:failure];
}
- (void)collapseNestedComments:(NSDictionary*)dict {
for (id comment in dict) {
DNComment *x = [[DNComment alloc] initWithDictionary:comment];
[self.allCommentsForStory addObject:x];
if (comment[@"comments"] != Nil) {
[self collapseNestedComments:comment[@"comments"]];
}
}
return;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment