I am struggling to set up a simple stub of a network POST request. I have modeled as much as I can from the OHHTTPStubs docs and other resources online, but I think I must be missing something. I would like to see the stub called based on the logging by the onStubActivation method. My test looks like:
#import "Cedar.h"
#import "OHHTTPStubs.h"
#import "Client.h"
SPEC_BEGIN(Spec)
describe(#"Client", ^{
__block Client *subject;
__block __weak id<OHHTTPStubsDescriptor> stub;
beforeEach(^{
subject = [[Client alloc] init];
stub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return YES;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
return [OHHTTPStubsResponse
responseWithJSONObject:#{}
statusCode:200
headers:#{ #"Access-Token": #"new-token"}];
}];
stub.name = #"success-stub";
[OHHTTPStubs onStubActivation:
^(NSURLRequest *request, id<OHHTTPStubsDescriptor> stub) {
NSLog(#"%# stubbed by %#.", request.URL, stub.name);
}];
});
describe(#"-signInWithUsername:Password:SuccessBlock:FailureBlock:", ^{
subjectAction(^{
[subject signInWithUsername:#"email#domain.com"
Password:#"password"
SuccessBlock:^void(){NSLog(#"GREAT-SUCCESS");}
FailureBlock:^void(){NSLog(#"GREAT-FAILURE");}];
});
context(#"when the user/password is valid", ^{
it(#"should update the auth token", ^{
subject.token should equal(#"new-token");
});
});
});
});
SPEC_END
Client looks like:
#import "Client.h"
#import "AFNetworking.h"
#interface Client ()
#property (nonatomic) NSString *token;
#property (nonatomic) AFHTTPRequestOperationManager *manager;
#end
#implementation Client
- (instancetype)init
{
self = [super init];
self.manager = [[AFHTTPRequestOperationManager alloc] init]];
return self;
}
- (void)signInWithUsername:(NSString *)username
Password:(NSString *)password
SuccessBlock:(void (^)())successBlock
FailureBlock:(void (^)())failureBlock;
{
[self.manager POST:#"http://localhost:3000/auth/sign_in"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
successBlock();
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
failureBlock();
}];
}
#end
Related
I wrote a category for FBSDKProfile provided by the Facebook SDK V4 for iOS. This category enables me to fetch the user profile image and access it using the [FBSDKProfile currentProfile] singleton instance.
This is my category header file:
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <objc/runtime.h>
static char const * const kProfileImageKey = "profile_image";
#interface FBSDKProfile (ProfileImage)
+(void)fetchProfileImageWithBlock:(void (^)(BOOL succeeded))handler;
#property (nonatomic, strong) UIImage *profileImage;
#end
And here's the implementation file:
#import "FBSDKProfile+ProfileImage.h"
#implementation FBSDKProfile (ProfileImage)
+(void)fetchProfileImageWithBlock:(void (^)(BOOL succeeded))handler {
FBSDKProfile *currentProfile = [FBSDKProfile currentProfile];
NSString *userId = currentProfile.userID;
if (![userId isEqualToString:#""] && userId != Nil)
{
[self downloadFacebookProfileImageWithId:userId completionBlock:^(BOOL succeeded, UIImage *profileImage) {
currentProfile.profileImage = profileImage;
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKProfileDidFetchProfileImageNotification object:nil];
if (handler) { handler(succeeded); }
}];
} else
{
/* no user id */
if (handler) { handler(NO); }
}
}
+(void)downloadFacebookProfileImageWithId:(NSString *)profileId completionBlock:(void (^)(BOOL succeeded, UIImage *profileImage))completionBlock
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", profileId]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error)
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES, image);
} else{
completionBlock(NO, nil);
}
}];
}
#pragma mark - custom getter/setter methods
-(void)setProfileImage:(UIImage *)profileImage {
objc_setAssociatedObject(self, kProfileImageKey, profileImage, OBJC_ASSOCIATION_ASSIGN);
}
-(UIImage *)profileImage {
return objc_getAssociatedObject(self, kProfileImageKey);
}
#end
The problem
This solution works just the way it should most of the time, but it does, however, frequently fail. From what I can tell, I think it has to do with the storage of the image.
Upon the exception, if I do po [FBSDKProfile currentProfile].profileImage, it returns:
error: property 'profileImage' not found on object of type 'FBSDKProfile *'
error: 1 errors parsing expression
If I hover the pointer over a [FBSDKProfile currentProfile] instance, it doesn't display the profileImage property in the list of properties.
This is where it failed:
May be this could help you.
-(void)getFacebookProfileInfos:(NSString*)token{
FBSDKGraphRequest *requestMe = [[FBSDKGraphRequest alloc]initWithGraphPath:#"me" parameters:#{#"fields":#"id, name, picture.type(large),email"}];
FBSDKGraphRequestConnection *connection = [[FBSDKGraphRequestConnection alloc] init];
[connection addRequest:requestMe completionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error)
{
if(result)
{
APP_DELEGATE.socialEmail=result[#"email"];
APP_DELEGATE.socialName= result[#"name"];
APP_DELEGATE.socialImage= result[#"picture"][#"data"][#"url"];
APP_DELEGATE.socialAcessToken=token;
HomeVC *obj = SB_IDENTIFIER(#"home");
SB_PUSH(obj);
}
else
{
NSLog(#"%#", [error localizedDescription]);
}
}];
[connection start];
}
I am using the below code below to perform my webservice calls with the service.I used AFNetworking version below 2.0 where AFHTTPClient .Now i migrated to latest version of AFNetworking .I donot find the AFHTTPClient class in the latest version . What should i replace with the curent code so that it works again .Any help please
#interface APIClient : AFHTTPClient
+ (APIClient*)client;
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock;
#end
// Singleton method
+ (APIClient*)client {
static APIClient *client = nil;
static dispatch_once_t onceInst;
dispatch_once(&onceInst, ^{
client = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObjects:
#"application/json",
#"text/json",
#"text/javascript",
#"text/plain",
#"text/html",
#"application/x-www-form-urlencoded", nil]];
});
return client;
}
#pragma mark - Init
// Intialize the API class with the destination host name
- (APIClient*)init {
self = [super init]; // call super init
if (self != nil) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
#pragma mark - Core API Methods
// This function sends an API call to the server
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock {
[MBMNetworkActivity pushNetworkActivity];
NSMutableURLRequest *apiRequest = [self requestWithMethod:#"POST" path:method parameters:params];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// success! :)
[MBMNetworkActivity popNetworkActivity];
successBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure! :(
[MBMNetworkActivity popNetworkActivity];
failureBlock(error);
}];
[operation start];
}
You can use NSURLSession for quite a bunch of the AFHTTPClient Stuff.
But to achieve all functionality just write a class like you now did but based on NSObject.
NSURLSession has a really nice API and great functionality combined with it.
AFHTTPRequestOperationManager is the replacement class to subclass instead of AFHTTPClient. It's not the same but it's probably what your looking for.
I would suggest you read Mattt Thompson's blog NSHipster. He is the author of AFNetworking and covered the changes a while back http://nshipster.com/afnetworking-2/. There is also an AFNetworking 2.0 migration guide https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide that will be usefull to you.
Finally i was able to do with following changes replacing AFHttpClient with AFHTTPRequestOperationManager
typedef void (^APIClientSuccessCallback) (id response);
typedef void (^APIClientFailureCallback) (id error);
#interface APIClient : AFHTTPRequestOperationManager
+ (APIClient*)client;
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock;
#end
#import "APIClient.h"
#implementation APIClient
+ (APIClient*)client {
static APIClient *client = nil;
static dispatch_once_t onceInst;
dispatch_once(&onceInst, ^{
client = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
client.responseSerializer = [AFJSONResponseSerializer serializer];
[client.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
});
return client;
}
#pragma mark - Core API Methods
// This function sends an API call to the server
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock {
[self POST:method parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"response --- %#",responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error ----- %#",error);
}];
}
#end
I was trying to implement an AFNetworking client by subclassing AFHTTPClient and setting base path
#define BaseURLString #"http://company.com/api/"
#implementation WineAPIClient
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
});
return __sharedInstance;
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if(self){
[self setParameterEncoding:AFJSONParameterEncoding];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
return self;
}
#end
This is how i am making request to the server:
[[APIClient sharedInstance] getPath:#"wines"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching wines!");
NSLog(#"%#",error);
}];
Now i have a class called LoginViewController which gets token number ones i loged in.
Now my question in how to set the token as an Authorization header in my AFHttpClinet class.
and make rest other request using the Authorization header.
Can any one help me out with this?
You can use methods of AFNetworking
setAuthorizationHeaderWithUsername:password:
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
[__sharedInstance setAuthorizationHeaderWithUsername:#"username" password:#"password"];
});
return __sharedInstance;
}
OR
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
});
return __sharedInstance;
}
- (void)updateAuthorizationHeaderUsername:(NSString *)username Password:(NSString *)password
{
[self setAuthorizationHeaderWithUsername:username password:password];
}
- (void)updateAuthorizationHeader:(NSString *)token
{
[self setDefaultHeader:#"token" value:token];
}
Try to do like this
[[APIClient sharedInstance] getPath:#"wines"
parameters:#{#"Authorization":TOKEN}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching wines!");
NSLog(#"%#",error);
}];
Where TOKEN is you token
I'm new to iOS and working on a basic app, it's currently working with SSKeychain and AFNetworking to interact with an API. When you log in with the app I retrieve and set the auth_token in my CredentialStore class, I need to send the auth token to the API as a http header to get access. How can I retrieve the token that I'm storing in the CredentialStore class in my HomeViewController.
Here is my CredentialStore:
#import "GFCredentialStore.h"
#import "SSKeychain.h"
#define SERVICE_NAME #"Groupify"
#define AUTH_TOKEN_KEY #"auth_token"
#implementation GFCredentialStore : NSObject
- (BOOL)isLoggedIn {
return [self authToken] != nil;
}
- (void)clearSavedCredentials {
[self setAuthToken:nil];
}
- (NSString *)authToken {
return [self secureValueForKey:AUTH_TOKEN_KEY];
}
- (void)setAuthToken:(NSString *)authToken {
[self setSecureValue:authToken forKey:AUTH_TOKEN_KEY];
[[NSNotificationCenter defaultCenter] postNotificationName:#"token-changed" object:self];
}
- (void)setSecureValue:(NSString *)value forKey:(NSString *)key {
if (value) {
[SSKeychain setPassword:value
forService:SERVICE_NAME
account:key];
} else {
[SSKeychain deletePasswordForService:SERVICE_NAME account:key];
}
}
- (NSString *)secureValueForKey:(NSString *)key {
return [SSKeychain passwordForService:SERVICE_NAME account:key];
}
#end
Here I am trying to retrieve the authToken value and set it to a string so that I can send it as a http header:
#import "GFHomeViewController.h"
#import <AFNetworking.h>
#import "GFCredentialStore.h"
#define kBaseURL "http://localhost:3000/"
#define kHomeURL "newsfeed.json"
#interface GFHomeViewController ()
#end
#implementation GFHomeViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
__weak typeof(self)weakSelf = self;
NSString *urlString = [NSString stringWithFormat:#"%s%s", kBaseURL, kHomeURL];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue: forHTTPHeaderField:#"auth_token"];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Assuming SSKeychain is saving and returning values to/from the keychain, I'd think you would simply be able to do something like this
GFCredentialStore *credentialStore = [[CGCredentialStore alloc] init];
if ([credentialStore isLoggedIn]) {
NSString *authToken = [credentialStore authToken];
[manager.requestSerializer setValue:authToken forHTTPHeaderField:#"auth_token"];
} else {
// prompt/display controller for login
}
If I'm missing something in your problem/issue you're having, please clarify your question, and I'll have another go at it.
I am having problems in saving a response coming from a POST request.
Based on AFnetworking documentation and NSScreencast tutorial I created my own subclass of AFHTTPRequestOperationManager, but I am not sure why the response is not saved.
How do I know, the response is not saved?
Because there is an error:(null) message in console and the my method does not perform segue.
I know that I am getting the values, because of the breakpoint that I put NSURLSessionDataTask
But I do not know why the values are not saved and I am getting an error message. I appreciate any help.
The APIClient/Manager
AuthAPIManager.h
#import "AFHTTPSessionManager.h"
#interface AuthAPIManager : AFHTTPSessionManager
+(AuthAPIManager *)sharedManager;
-(NSURLSessionDataTask *)initializeLogin:(NSString *)username completion:(void(^)(NSDictionary *results, NSError *error))completion;
//for login
#property (nonatomic,readonly,retain)NSString *StoreIdentifierForVendor;
#property(nonatomic,copy)NSString *devicetype;
#end
AuthAPIManager.m
#import "AuthAPIManager.h"
#import "LoginInfo.h"
#import "CredentialStore.h"
static AuthAPIManager *sharedManager =nil;
static dispatch_once_t onceToken;
#implementation AuthAPIManager
+(AuthAPIManager *)sharedManager
{
dispatch_once(&onceToken, ^{
sharedManager = [[AuthAPIManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
sharedManager.responseSerializer=[AFJSONResponseSerializer serializer];
sharedManager.requestSerializer = [AFJSONRequestSerializer serializer];
});
return sharedManager;
}
-(id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
}
return self;
}
-(NSURLSessionDataTask *)initializeLogin:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion
{
_devicetype = #"ios";
_StoreIdentifierForVendor = [[[UIDevice currentDevice]identifierForVendor]UUIDString];
id loginParameters =#{#"AccountId":username,
#"DeviceType":_devicetype
};
NSURLSessionDataTask *task =[self POST:#"/Accn" parameters:loginParameters
success:^(NSURLSessionDataTask *task, id responseObject)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 200) {
LoginInfo *loginInfo =[[LoginInfo alloc]initWithDictionary:responseObject];
CredentialStore *credStore =[CredentialStore sharedStore];
credStore.loginInfo =loginInfo;
completion(responseObject,nil);
loginInfo = responseObject;
} else {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, nil);
});
NSLog(#"Received: %#", responseObject);
NSLog(#"Received HTTP %d", httpResponse.statusCode);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}];
return task;
}
#end
And this is how I am calling my method in my view controller
- (IBAction)login:(id)sender
{
[_usernameTextField resignFirstResponder];
[SVProgressHUD show];
NSURLSessionDataTask *task = [[AuthAPIManager sharedManager] initializeLogin:self.usernameTextField.text completion:^(NSDictionary *results, NSError *error)
{
if (results) {
LoginInfo *loginInfo = [[LoginInfo alloc]initWithDictionary:results];
CredentialStore *credStore =[ CredentialStore sharedStore];
credStore.loginInfo =loginInfo;
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
}
else
{
NSLog(#"there is an error:%#",error);
}
}];
[SVProgressHUD dismiss];
}
Your success block is being called, but your status code is not 200.
You are calling your completion block like this:
completion(nil, nil);
So this code:
if (results) {
[snip]
}
else
{
NSLog(#"there is an error:%#",error);
}
is passed a nil results object.
Set a breakpoint on if (httpResponse.statusCode == 200) and inspect httpResponse in your debugger to see why it's not what you expect. (You may get a different success code, such as 204.)
Call your completion block with results instead of nil.