I am trying to implement AFNetworking code to communicate with a web API. I am getting the following errors in the code:
No visible #interface for APIClass declares the selector
registerHTTPOperationClass
No visible #interface for APIClass declares the selector
setDefaultHeader:Value
No visible #interface for APIClass declares the selector
multiPartFormRequestWithMethod:path:parameters:constructingBodyWithblock
Obviously something to do with the new AFNetworking 2.0 migration...however I have been looking at all the migration posts and documentation and connot find the replacements for these without throwing an error:
// add the location details of the web service we wrote
#define kAPIHost #"http://myurl"
#define kAPIPath #"mywebapi/"
#implementation APIClass
// this is the implementation of the singleton method
+(APIClass*)sharedInstance{
static APIClass *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kAPIHost]];
});
return sharedInstance;
}
-(APIClass*)init{
// call super init
self = [super init];
if (self != nil){
user = nil;
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
// Accept HTTP header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
// call to the server
-(void)commandWithParams:(NSMutableDictionary*)params onCompletion:
(JSONResponseBlock)completionBlock
{
// prepare e POST request by creating an NSMutableURLRequest instance using the
// parameters we want to send via POST
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path:kAPIPath
parameters: params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
// attach file if needed
}];
// create an operation to handle the network communication in the background
// and intialize it with the POST request we just prepared
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:
apiRequest];
// now set the 2 blocks needed for success and failure
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
responseObject)
{
// success! - if the call is successful then we just pass in the JSON response
NSLog(#"responseObject: %#", responseObject);
completionBlock(responseObject);
}
// if there is a failure in the network call then we call the failure block
// and contrcut a new dictinary to hold the message of the network error
failure:^(AFHTTPRequestOperation *operation, NSError * error) {
//failure!
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription]forKey:#"error"]);
}];
// at this point we can call the start method so that AFNetworking can do its
// magic in the background
[operation start];
}
#end
You're getting these errors because the methods you're calling aren't methods of whatever class you're subclassing. I'll assume you're subclassing AFHTTPSessionManager, which is recommended for iOS 7 in AFNetworking 2.0. Based on that...
For your first two errors, I believe the updated lines below are the AFNetworking 2.0 way of doing it with AFHTTPSessionManager:
-(APIClass*)init{
// call super init
self = [super init];
if (self != nil){
user = nil;
self.requestSerializer = [AFJSONRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
}
}
For your third error, the method multiPartFormRequestWithMethod:path:parameters:constructingBodyWithblock should be replaced with:
[self POST:kAPIPath parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// attach file if needed
} success:^(NSURLSessionDataTask *task, id responseObject) {
// handle success
} failure:^(NSURLSessionDataTask *task, NSError *error) {
// handle failure
}];
Related
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
On previous versions of AFNetworking I could make use of AFHTTPRequestOperation to create multiple requests, create dependencies between them and enqueue them pretty easily. Example (inside of an AFHTTPClient subclass):
NSURLRequest *categoriesRequest = [self requestWithMethod:#"GET" path:#"categories" parameters:nil];
AFHTTPRequestOperation *categoriesOperation = [self HTTPRequestOperationWithRequest:categoriesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonCategories = responseObject;
for (NSDictionary *jsonCategory in jsonCategories) {
SPOCategory *category = [[SPOCategory alloc] initWithDictionary:jsonCategory];
[self.categories addObject:category];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// …
}];
NSURLRequest *incidencesRequest = [self requestWithMethod:#"GET" path:#"incidences" parameters:nil];
AFHTTPRequestOperation *incidencesOperation = [self HTTPRequestOperationWithRequest:incidencesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonIncidences = responseObject;
for (NSDictionary *jsonIncidence in jsonIncidences) {
SPOIncidence *incidence = [[SPOIncidence alloc] initWithDictionary:jsonIncidence];
[self.incidences addObject:incidence];
}
completionBlock(self.incidences, self.categories, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// …
}];
[incidencesOperation addDependency:categoriesOperation];
[self enqueueBatchOfHTTPRequestOperations:#[categoriesOperation, incidencesOperation] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
// Processing…
} completionBlock:^(NSArray *operations) {
// Completed
}];
I know I can continue to make use of AFHTTPRequestOperation but, I'd like to know if there is a similar way to achieve the same thing inside a subclass of AFHTTPSessionManager, using NSURLSession as the backing library instead of NSURLConnection.
Thank you!
AFHTTPSessionManager's connection factory methods create connections which will be represented by a NSURLSessionDataTask object.
Unlike AFHTTPRequestOperation these are not NSOperation subclasses, and thus declaring dependencies is not possible.
One could imagine to wrap a factory method like
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
into a helper method/function which returns a NSOperation object. That might (will) become cumbersome and looks quite weird, though.
If you are courageous enough to consider another third party library, you can solve your problem as explained below:
The idea is to represent the eventual result of the asynchronous operation by a "Promise". Think of a Promise as a placeholder of the result, which will eventually be set by the operation. So, basically you wrap a factory method into one which then effectively yields a method having this signature:
-(Promise*) fetchCategories;
or
-(Promise*) fetchCategoriesWithParameters:(NSDictionary*)parameters;
Notice that above methods are asynchronous - yet they have no completion handler. The Promise will instead provide this facility.
Initially, when fetchCategories returns, the promise object does not "contain" the result.
You obtain (at some tme later) the eventual result respectively and error by "registering" a completion handler block respectively an error handler block with a then property like so (pseudo code):
[self.fetchCategoriesWithParameters].then(
<success handler block>,
<failure handler block> );
A more complete code snippet:
Promise* categoriesPromise = [self fetchCategories];
categoriesPromise.then(^id(id result){
self.categories = result;
... // (e.g, dispatch on main thread and reload table view)
return nil;
}, ^id(NSError* error){
NSLog(#"Error: %#", error);
return nil;
});
Note: The parameter result of the success handler block is the eventual result of the operation, aka the responseObject.
Now, in order to "chain" multiple asynchronous operations (including the handlers), you can do this:
self.categoriesPromise = [self fetchCategories];
Promise* finalResult = self.categoriesPromise.then(^id(id result){
NSArray *jsonCategories = result;
for (NSDictionary *jsonCategory in jsonCategories) {
SPOCategory *category = [[SPOCategory alloc] initWithDictionary:jsonCategory];
[self.categories addObject:category];
}
return [self fetchIncidencesWithParams:result);
}, nil)
.then(^id(id result){
NSArray *jsonIncidences = result;
for (NSDictionary *jsonIncidence in jsonIncidences) {
SPOIncidence *incidence =
[[SPOIncidence alloc] initWithDictionary:jsonIncidence];
[self.incidences addObject:incidence];
}
return #[self.incidences, self.categories];
}, nil)
.then(^id(id result){
NSArray* incidences = result[0];
NSArray* categories = result[1];
...
return nil;
}, nil /* error handler block */);
You create and "resolve" (that is, setting the result) a Promise as shown below:
- (Promise*) fetchCategories {
Promise* promise = [[Promise alloc] init];
NSURLRequest *categoriesRequest = [self requestWithMethod:#"GET" path:#"categories" parameters:nil];
AFHTTPRequestOperation *categoriesOperation = [self HTTPRequestOperationWithRequest:categoriesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
[promise fulfillWithResult:responseObject];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[promise rejectWithReason:error];
}];
return promise;
}
Disclaimer:
There are a few third party Objective-C libraries which implement a Promise in this or a similar way. I'm the author of RXPromise which implements a promise according the Promises/A+ specification.
So after much research I figured out how to send a NSDictionary from my iOS 6 client to my rails server. I used AFNetworking. Below is the client side code that sends the JSON. However, Im not sure if this is an asynchronous call since I dont see where an operation queue is being used. If it isn't then how do I make it into an asynchronous call?
-(void)sendEntryToServerAsJSON:(EntryParent*)_entryToBeSaved
{
NSDictionary* dictToBeSerialized = [_entryToBeSaved convertEntryParentObjToDict];
[[appAPIClient sharedClient]postPath:#"entries.json"
parameters:dictToBeSerialized
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully sent JSON %#", [responseObject description]);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Could not send JSON %#", [error description]);
}];
}
Here is my implementation of the AFHTTPClient
+ (appAPIClient *)sharedClient
{
NSLog(#"Inside appAPIClient sharedclient ");
static appAPIClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc]
initWithBaseURL:[NSURL URLWithString:URL_STR]];
});
return _sharedClient;
}
//==============================================================================
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (!self)
{
return nil;
}
NSLog(#"init with base url - appAPIClient");
[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;
}
I would appreciate any feedback to improve this code and make it asynchronous.
to a)
the requests are added to the main threads runloop so they are async without a thread or queue. runloops are kinda like C's select method
see:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/Reference/Reference.html
to b) please make into a separate question :)
I switched from AFnetworking to RestKit. In AFnetworking had an API class. The API.h class contained the following.
#import <UIKit/UIKit.h>
typedef void (^JSONResponseBlock)(NSDictionary* json);
#interface API : NSObject
//the authorized user
#property (strong, nonatomic) NSDictionary* user;
+(API*)sharedInstance;
//check whether there's an authorized user
//send an API command to the server
-(void)loginCommand:(NSMutableDictionary*)params onCompletion:(JSONResponseBlock)completionBlock;
And my API.m class looks like this.
+(API *)sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^ {
sharedInstance = [[self alloc]initWithBaseURL:[NSURL URLWithString:kAPIHost]];
});
return sharedInstance;
}
#pragma mark - init
//intialize the API class with the destination host name
-(API *)init
{
//call super init
self = [super init];
if (self != nil){
//initialize the object
user = 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;
}
-(void)loginCommand:(NSMutableDictionary *)params onCompletion:(JSONResponseBlock)completionBlock{
NSLog(#"%#%#",kAPIHost,kAPILogin);
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST" path:kAPILogin parameters:params constructingBodyWithBlock:^(id <AFMultipartFormData>formData){
//TODO: attach file if needed
}];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
//success!
NSLog(#"SUCCESSSS!");
completionBlock(responseObject);
}failure:^(AFHTTPRequestOperation *operation, NSError *error){
//Failure
NSLog(#"FAILUREE!");
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
Like you can see I only instantiate it once and put all my methods in over here. In my view controller I only need to call this method with a parameter-dictionary. Then I could read the whole JSON file.
Now with restKit I do this all on viewController level. I want to split it up like I did by AFNetworking. This is what I do in RestKit, At the moment is this all on viewController level.
//let AFNetworking manage the activity indicator
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
// Initialize HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://virtuele-receptie.preview.sanmax.be"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
//we want to work with JSON-Data
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
//Do mapping
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dataMapping
pathPattern:nil
keyPath:#"data"
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
NSDictionary *dict = [[NSDictionary alloc]initWithObjectsAndKeys:_txtLogin.text,#"email",_txtPass.text,#"pwd", nil];
[objectManager getObject:nil path:#"/nl/webservice/company-user/login/apikey/key12345678" parameters:dict
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//Success
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
//Failure
}];
So far with RestKit I haven't seen a huge need for an API class like you might create with other HTTP frameworks. RestKit has its own HTTP client (actually, just AFNetworking's client), so there's no need to have a class for your HTTP client, and I've found that each time I use the RKObjectManager I generally want access to the method parameters and block callbacks within each view controller. In other words, I don't want to run RestKit networking code in an API class, because I would essentially have to wrap the entire call in a method that can be accessed in the view controller (success block, failure block, etc).
In essence, RestKit's design lightens the networking code so much that in my experience with it - 3 or 4 apps now - I've yet to see enough reason write an API class like you describe.
I am currently developing an iOS app that allows a user to post a classified listing for other users to see. On all devices, that app hangs in a "O% Uploaded" SVProgressHUD for a while before displaying "Our server is temporarily unavailable. Please try again later.", which is what I have coded in the case of a Status Code 500 error from the server side. However, on the iOS simulator, everything works smoothly.
All other functionality involving network requests is working perfectly except for this, so it must have something to do with the actual upload process. I've posted the corresponding code below; if you require anything additional to help figure this out, please let me know.
Networking Code
- (IBAction)publishPressed:(UIBarButtonItem *)sender {
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeBlack];
// Set the params for the API call in an NSMutableDictionary called mutableParams.
// Create the form request with the post parameters and image data to pass to the API.
NSMutableURLRequest *request = [[UAPIClient sharedClient] multipartFormRequestWithMethod:#"POST"
path:#"mobPost.php"
parameters:mutableParams
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
if (self.imageHasBeenSet) {
[self.listingImageView.image fixOrientation];
NSData *imageData = UIImagePNGRepresentation(self.listingImageView.image);
[formData appendPartWithFileData:imageData name:#"userfile"
fileName:#"postImage.png" mimeType:#"image/png"];
}
}];
// Create the `AFJSONRequestOperation` from the form request, with appropriate success and failure blocks.
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseJSON) {
if ([[responseJSON objectForKey:#"status"] intValue] == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:#"Success!"];
});
// Pass a message back to the delegate so that the modal view controller
// can be dismissed successfully.
[self.delegate uCreateListingTableViewController:self didCreateListing:YES];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Post failed. Please try again."];
});
NSLog(#"Status %#", [responseJSON objectForKey:#"status"]);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Our server is temporarily unavailable. Please try again later."];
});
}];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:[[NSString stringWithFormat:#"%lli", (totalBytesWritten / totalBytesExpectedToWrite)] stringByAppendingString:#"% Uploaded"]];
});
}];
[[UAPIClient sharedClient] enqueueHTTPRequestOperation:operation];
}
UAPIClient.m
#import "UAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const kUAPIBaseURLString = #"hiding string for privacy";
#implementation UAPIClient
+ (UAPIClient *)sharedClient {
static UAPIClient *_sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[UAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kUAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[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;
}
#end