I have void method declared in a file called LHJSonData.h:
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password;
and in my LHJsonData.m file I have this line:
#implementation LHJSonData
which gives me this warning:
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:12:17: Method definition for 'UserLogin:andPassWordExists:' not found
and I have this method in LHJsonData.m
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[[NSOperationQueue mainQueue] addOperation:operation];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
[operation start];
}
I get no errors or warnings with that code.
When I call this method in another file, lets call it Login.m:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
//BOOL *isAuthenticated = [userName User:self.idTextField.text andPassWordExists:self.passwordTextField.text];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text:^(id responseObject, NSError *error) {
if (responseObject) {
[self CustomAlert:#"You have login"];
/*[self.idTextField removeFromSuperview];
[self.passwordTextField removeFromSuperview];
[self.loginButton removeFromSuperview];
self.idTextField = nil;
self.passwordTextField = nil;
self.loginButton = nil;
[self CreateMenu];*/
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
}];
and I get this error:
ARC Semantic Issue
LHLoginController.m:240:15: No visible #interface for 'LHJSonData' declares the selector 'UserLogin:andPassWordExists::'
I went into Build Settings and add this to Login.m:
-fno-objc-arc
That got rid of the error, but now I get a warning and my app crashes, the warning is:
Semantic Issue
LHLoginController.m:240:15: Instance method '-UserLogin:andPassWordExists::' not found (return type defaults to 'id')
How can I fix this?
Declare your method in your .h file just like you do in your .m file.
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler;
The .m method and .h declaration do not match.
You should be seeing the warning/error in Xcode on this line:
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text:^(id responseObject, NSError *error) {
You are missing a name for your third parameter, going straight from passwordTextField.text to :. The compiler is reading that as an unnamed parameter and translating it to the selector UserLogin:andPassWordExists::. Notice that it has two colons at the end rather than one. Since you don't ever declare the selector, the error/warning is raised.
The line should look like:
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
As others mentioned your method signature is different in your header than your implementation. They need to be the same. It is likely you got into this situation because you autocompleted a method that didn't have a completion handler and tried to fix it.
Also, make sure to turn ARC back on for that file so you don't run into memory leaks. As you can see it wasn't really an ARC problem since both settings produced a similar warning/error. The reason ARC refused to compile is that when it encounters an undeclared selector (in this case UserLogin:andPassWordExists::), it doesn't know whether or not the returned value is an object or not and it can't make a memory management decision. Before ARC a developer could look up the undeclared method, see the return type and apply the correct action. ARC's just stricter.
Related
I have a question on which is best way or the correct way to send AFNetworking results to controller. Is it via delegate or notification?
I created a class to handle make API calls that has the code below. So if imported this class to another controller and call this method to make API call. Should I do delegate or notification?
I have read www.raywenderlich.com/59255/afnetworking-2-0-tutorial and it is using delegates. I also been watched CodeSchool tutorial, which they used notification from Model to Controller.
I added the code below in a hope to better show my question.
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
// notification way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil
userInfo:responseObject ];
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil ];
}];
// delegate way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
if ([delegate respondsToSelector:#selector(getUserFeedsDidFinish:resultDict:)])
{
[delegate performSelector:#selector(getUserFeedsDidFinish:resultDict:) withObject:self withObject:resultDict];
}
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
if ([delegate respondsToSelector:#selector(getUserFeeds:didFailWithResultDict:)]) {
[delegate performSelector:#selector(getUserFeeds:didFailWithResultDict:)
withObject:self
withObject:[NSDictionary dictionaryWithObject:error.userInfo forKey:KEY_ERRORS]];
}
}];
I will recommend use blocks, how? I will write a service for you, this one is wrote in a class called Connection:
+(void)requestLocation:(NSString*)googleReference completionBlock:(void (^)(NSString * coordinates, NSError * error)) handler{
NSString * urlString = #"https://maps.googleapis.com/maps/";
NSMutableDictionary * parametersDictionary = [NSMutableDictionary dictionary];
[parametersDictionary setObject:googleReference forKey:#"reference"];
[parametersDictionary setObject:#"true" forKey:#"sensor"];
[parametersDictionary setObject:#"key(it is not)" forKey:#"key"];
AFHTTPClient *HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:urlString]];
NSURLRequest *URLRequest = [HTTPClient requestWithMethod:#"GET" path:#"api/place/details/json" parameters:parametersDictionary];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:URLRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError * error = nil;
NSDictionary * response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
NSDictionary * dicGeo = [((NSDictionary*)[response objectForKey:#"result"]) objectForKey:#"geometry"];
NSDictionary * coords = [dicGeo objectForKey:#"location"];
NSNumber * lat = [coords objectForKey:#"lat"];
NSNumber * lng = [coords objectForKey:#"lng"];
NSString * coordinates = [NSString stringWithFormat:#"%#,%#", lat.description, lng.description];
handler(coordinates, error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[requestOperation start];
}
Then to call this service:
[Connection requestLocation:#"google reference (it is not)" completionBlock:^(NSString *coordinates, NSError *error) {
//Your code with results.
}
I've only scratched the surface with AFNetworking. From what I've seen, most of it seems to use a third approach, blocks.
Blocks are somewhat new, and different than both delegates and notifications.
Blocks are an extension to C function pointers that let you pass code into a method when you call it.
A common design pattern using blocks is to create a method that takes a completion block. A completion block is a piece of code that gets invoked when an async request is completed.
Take the AFNewtworking method HTTPRequestOperationWithRequest as an example. That method takes a success block, that gets called if the request succeeds, and a failure block, that gets called if the request fails.
Block is the easiest way to use IMO. You don't need to implement extra delegate methods or you don't need any conformations.
Basically define your wrapper like this.
typedef void(^SampleRequestCompletion)(NSError *error, id data);
- (void)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
completion:(SampleRequestCompletion)completion
{
[self GET:URLString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Do what you want
if (completion) {
completion(nil, data);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Failure case
if (completion) {
completion(error,nil);
}
}];
}
And call this method from any objects like this,
[self GET:path parameters:dictionary completion:^(NSError *error, id data) {
}];
So you can manage what to do whenever the call ends with success or failure.
As the tutorial recommended, we can extract the web service related code into a module which acts more like a model level thing. Considering the communication between the network module and views, view invoke/start the request on a singleton web service client, once response back the usual workflow would be send the result to view controller and show the data in the views. We don't need to return anything back to network module.
So this workflow is more like a notification than delegation. And set the V as the M's delegate, it's weird.
Notification : Hey, man, I have done my job, it's your turn.
Delegation: Hey, man, I have done lots, now I need you cover/back up/provide me some tasks, then I will continue/complete the work.
In some situations, it's difficult to choose which one better. For AFNetworking, I thought the Notification approach better.
I am making a simple GET request using AFNetworking 2, but I am getting a NSURLErrorDomain error.
I created a manager class which subclasses AFHTTPRequestOperationManager and creates a singleton instance so that I can use a shared manager.
+ (id)manager {
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
- (id)init {
NSURL *baseURL = [ZSSAuthentication baseURL];
self = [super initWithBaseURL:baseURL];
if (self) {
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:[ZSSAuthentication username] password:[ZSSAuthentication password]];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
}
return self;
}
- (void)getData:(NSString *)pubID parameters:(NSDictionary *)parameters completion:(void (^)(NSDictionary *results))completion failure:(void (^)(NSError *error))failure {
NSString *url = [NSString stringWithFormat:#"data/all/%#", pubID];
[self GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Check to see if there are errors
ZSSError *error = [self errorForAPICall:responseObject status:[operation.response statusCode]];
if (error) {
[self logMessage:error.localizedDescription];
failure(error);
return;
}
NSDictionary *data = [responseObject objectForKey:#"data"];
completion(data);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Then, in my viewController's viewDidLoad method I make a call to that method:
[[ZSSManager manager] getData:self.pubID parameters:nil completion:^(NSDictionary *results) {
self.items = results;
[self dataWillReload];
NSLog(#"%#", results);
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error: %# %li", error, (long)error.code);
}];
Then I get this error:
Error Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)" UserInfo=0x7ff952306610 {NSErrorFailingURLKey=http://test.mysite.com/v1/data/all/5}
The strange thing is, on a previous viewController, I make a different call to the manager, and it completes and returns data correctly. But, when I make this second call, I get the error. AND, if I move that getData call out of the viewDidLoad method, and invoke it with a button press, it DOES WORK. What the heck?
What could be causing this?
I am using AFNetworking (2.3.1) to parse JSON data and display it in labels.
To do this, I am using setCompletionBlockWithSuccess which is declared in AFHTTPRequestOperation.h.
Three functions like this are being called on viewDidLoad, one looks like:
-(void)parse {
NSURL *url = [[NSURL alloc] initWithString:kURL];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Parse Successful");
//Code for JSON Parameters and to display data
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", [error localizedDescription]);
//Code for Failure Handling
}];
[operation start];
}
While this works like a charm, because it is being contained in a block request, this process continues throughout the application state. So when this data does not need to be displayed, the requests are still loading, and I am receiving memory warnings because of these blocks.
My question is, how can I stop, cancel, or pause these processes once I leave the View Controller that they are created on in order to save memory and data, or handle them appropriately?
Forgive me if this is an obvious answer, and I am just handling or creating blocks in a totally wrong way. I am new to both AFNetworking and Blocks, operations, async requests, and the like.
Thanks.
Assuming you have an AFHTTPRequestOperation object called operation:
[operation cancel];
This probably belongs in ViewWillDisappear.
And then inside your failure block (which will then be called) you can check to see if it failed because of an error or if you canceled it:
if ([operation isCancelled])
{
//I canceled it.
}
Update - a more concrete example of how to save a reference to the operation and cancel it when the view dissapears.
#interface myViewController ()
#property (strong, nonatomic) AFHTTPRequestOperation *parseOperation;
#end
#implementation myViewController
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
//no need to check to see if the operation is nil (because it never happened or it's complete) because
//messages sent to nil are ok.
[self.parseOperation cancel];
}
-(void)parse {
NSURL *url = [[NSURL alloc] initWithString:kURL];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
//save the parse operation so we can cancel it later on if we need to
self.parseOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
self.parseOperation.responseSerializer = [AFJSONResponseSerializer serializer];
__weak myViewController *weakSelf = self;
[self.parseOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//nil out the operation we saved earlier because now that it's finished we don't need to cancel it anymore
weakSelf.parseOperation = nil;
NSLog(#"Parse Successful");
//Code for JSON Parameters and to display data
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (!operation.isCancelled) {
NSLog(#"%#", [error localizedDescription]);
//Code for Failure Handling
}
}];
[self.parseOperation start];
}
As it turns out, the lack of stopping was stemming from a timer (that called the block request, or the function parse) that was not invalidated once the view disappears. Invalidating the timer on -(void)viewDidDisappear: fixed the problem.
I'm migrating my project to AFNetworking 2.0. When using AFNetworking 1.0, I wrote code to log each request/response in the console. Here's the code:
-(AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *, id))success
failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
AFHTTPRequestOperation *operation =
[super HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject){
[self logOperation:operation];
success(operation, responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){
failure(operation, error);
}];
return operation;
}
-(void)logOperation:(AFHTTPRequestOperation *)operation {
NSLog(#"Request URL-> %#\n\nRequest Body-> %#\n\nResponse [%d]\n%#\n%#\n\n\n",
operation.request.URL.absoluteString,
[[NSString alloc] initWithData:operation.request.HTTPBody encoding:NSUTF8StringEncoding],
operation.response.statusCode, operation.response.allHeaderFields, operation.responseString);
}
I'm trying to do the same thing using AFNetworking 2.0, which to my understanding, means using a NSURLSessionDataTask object instead of AFHTTPRequestOperation. Here's my shot at it.
-(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler {
NSURLSessionDataTask *task = [super dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error){
[self logTask:task];
completionHandler(response, responseObject, error);
}];
return task;
}
-(void)logTask:(NSURLSessionDataTask *)task {
NSString *requestString = task.originalRequest.URL.absoluteString;
NSString *responseString = task.response.URL.absoluteString;
NSLog(#"\n\nRequest - %#\n\nResponse - %#\n\n", requestString, responseString);
}
The dataTaskWithRequest:completionHandler method is successfully intercepting each call, so I think that's the right method to override, but when I try to log the task in the completionHandler, task is nil. Thus getting nulls printed in the console. However a proper task object is still returned from that method. What's happening here? How can I properly log the request/response for each call?
you can use the library AFNetworking/AFNetworkActivityLogger
https://github.com/AFNetworking/AFNetworkActivityLogger
from the doc:
AFNetworkActivityLogger is an extension for AFNetworking 2.0 that logs
network requests as they are sent and received.
usage:
[[AFNetworkActivityLogger sharedLogger] startLogging];
output:
GET http://example.com/foo/bar.json
200 http://example.com/foo/bar.json
using devel logging level you should have responseHeaderFields and responseString too
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.