I have updated the AFNetworking Pod to get rid of the UIWebView
as at
https://github.com/ElfSundae/AFNetworking/issues/1
suggested.
But now I get some depreciated warnings:
'GET:parameters:progress:success:failure:' is deprecated.
At the code:
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) { ....
I have double checked the code with the migration-guide - but I can't see whats wrong..
Thank you
Martin
The commit ded6a76 added GET:parameters:headers:... method to support setting headers per HTTP request, and deprecated GET:parameters:... method.
My fork is based on the latest commit on the master branch of AFNetworking, includes this commit.
The old GET method without headers parameter is just deprecated, you can use it safely, or migrate your code to use the new GET method passing headers:nil. Or you may create a subclass of AFHTTPSessionManager to disable the warnings:
#interface MyHTTPSessionManager : AFHTTPSessionManager
// These three methods below have been marked as deprecated in AFNetworking,
// we override them here and remove DEPRECATED_ATTRIBUTE to silence the
// deprecated-warning.
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
#end
#implementation MyHTTPSessionManager
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
return [self POST:URLString parameters:parameters constructingBodyWithBlock:block progress:nil success:success failure:failure];
}
#end
code from ESAPIClient
Related
Here's my code
AFHTTPSessionManager *manger = [[AFHTTPSessionManager alloc] init];
[manger GET:requestUrl parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
I got two error from Xcode.
Unknown type name 'id'
Incompatible block pointer types sending 'void (^)(NSURLSessionDataTask * _Nonnull __strong, int)' to parameter of type 'void (^ _Nullable)(NSURLSessionDataTask * _Nonnull __strong, id _Nullable __strong)'.
Why is this happen?I had imported AFNetworking file in my header file.My Xcode version is 8.2,and AFNetworking version is 3.1.When I put this code snippet in my other project,there was no error.That was really confused me.Anyone can help?
I used Carthage to import AFNetwoking,still the same error.:(
Final solution:I recreate the project.And migrate the old code to new project.It works.But still know the reason why the old version code not work.
your method should like this
-(void)callWebserviceWithParams:(NSMutableDictionary *)_params
action:(NSString *)_action
success:(void (^)(id))_success
failure:(void (^)(NSError *))_failure
{
if ([[AFNetworkReachabilityManager sharedManager] isReachable]) {
//AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [BASE_URL stringByAppendingString:_action];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:url]];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript",#"text/html", nil];
[manager POST:url parameters:_params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"response = %#", responseObject);
if( _success )
{
_success( responseObject ) ;
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"error = %#", error);
if( _failure )
{
_failure( error) ;
}
}];
}
}
I have this class that performs a REST call .
- (NSString*)cerca:(NSString*)email{
if ([[Utility sharedUtility] connected]) {
HttpClient *self = [HttpClient sharedHTTPClient];
[self.requestSerializer setValue:[self getUserAgent] forHTTPHeaderField:#"User-Agent"];
[self.requestSerializer setValue:NST_CODE forHTTPHeaderField:#"Nst-Code"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"nst_id"] forHTTPHeaderField:#"Nst-Id"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"api_key"] forHTTPHeaderField:#"Api-Key"];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"email"] = email;
__block NSString *result;
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
result = responseObject;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
return result;
}
The call returns a JSON . My problem is that when I do return result ; Nothing is passed to the calling method .
Can you tell me why ??
The calling method is
HttpClient *client = [HttpClient alloc];
NSString *result = [client cerca:email];
That method you are calling is an asynchronous call, meaning the result comes after you have returned. You need to change your method to accept a block argument and return the result in the callback.
- (void)cerca:(NSString*)email callback:(void (^)(id result)) callback {
if ([[Utility sharedUtility] connected]) {
...
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
if (callback) {
callback(responseObject)
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
}
To call the method you would do:
[client cerca:email completion:^(NSString *response) {
// Do what you want with the response.
}];
Its because the self POST call is asynchronous and you are returning the result before it has had time to be assigned. You need to rebuild your - (NSString*)cerca:(NSString*)email method so it somehow can handle the asynchronousness of this. Easiest way is usually to change the method so it takes a completion block as in parameter. Maybe something like:
- (void)cerca:(NSString *)email completion:(void (^)(NSString *res))completion {
[self POST:#"get_info" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(responseObject);
}
}failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion){
completion(nil);
}
}];
}
I changed my method
- (NSString*)cerca:(NSString*)email completion:(void (^)(NSString *res))completion {
But I do not understand what to call it . Indications ?
[client cerca:email completion:nil];
I'm posting a notification in the request failure block:
[manager POST:path
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (operation.response.statusCode == 200) {
//message delegate
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:HOST_UNREACHABLE object:operation];
}];
In the method that receives the notification, the completionBlock attribute is nil.
How do I access it without subclassing & overriding?
First to answer the question of how to send a block to an NSNotification:
The way you're attempting to do it is dangerous, because we don't know how AFHTTPSessionManager handles the blocks you pass it, and, unless its in the public interface, what it does may not remain fixed over time.
So, make a local variable to represent the block you want to pass, say, the completionBlock...
// this is a local variable declaration of the block
void (^completionBlock)(AFHTTPRequestOperation*,id) = ^(AFHTTPRequestOperation *operation, id response) {
if (operation.response.statusCode == 200) {
//message delegate
}
};
[manager POST:path
parameters:nil
success:completionBlock
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:HOST_UNREACHABLE object:completionBlock];
}];
The observer can get the block and invoke it this way...
- (void)didReceiveNotification:(NSNotification *)notification {
void (^completionBlock)(AFHTTPRequestOperation*,id) = notification.object;
// use it
[manager POST:someOtherPath
parameters:nil
success:completionBlock
// etc.
];
}
But I think the approach is strange. It spreads out responsibility for making the request to the object that gets notified, leaving it needing to know the path to retry the parameters (which you don't have in this case, but you might one day).
Consider instead subclassing the manager and adding behavior that does the retry. Now your manager subclass can be in charge of all requests, including retries, and your other classes are just customers who handle the outcome. Something like...
#interface MyAFHTTPRequestManager : AFHTTPSessionManager
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
retryURL:(NSString *)retryURLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask *task, NSError *error))failure;
#end
Have your subclass implementation call super with the first URLString, and upon failure, call super with the retryURLString. e.g.
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
retryURL:(NSString *)retryURLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask *task, NSError *error))failure {
[super POST:URLString parameters:parameters success:success
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[super POST:retryURLString parameters:parameters success:success failure:failure];
}];
}
I have several methods that return signals that created by
+ (RACSignal *)createSignal:
And in the signals I send different requests using the methods from AFHTTPRequestOperationManager in AFNetworking:
- GET:parameters:success:failure
- POST:parameters:success:failure
- PUT:parameters:success:failure
- DELETE:parameters:success:failure
Supposed that I have a property to store the instance of AFHTTPRequestOperationManager:
#property(strong, nonatomic) AFHTTPRequestOperationManager *manager;
This is the body of the GET request method, others are almost identical with it except the method calls from AFHTTPRequestOperationManager:
- (RACSignal *)GET:(NSString *)url parameters:(NSDictionary *)parameters {
#weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self)
// [self.manager POST:...
// [self.manager PUT:...
// [self.manager DELETE:...
[self.manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, NSDictionary *response) {
[subscriber sendNext:response];
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
And I want to extract the [self.manager GET...] logic out so that can I eliminate the duplicate code and only to pass different selectors(or something like it) to send different requests.
How do I achieve it ?
This is untested, but should get you most of the way there:
- (RACSignal *)GET:(NSString *)url parameters:(NSDictionary *)parameters {
SEL sel = #selector(GET:parameters:success:failure:);
return [self signalForRequest:sel url:url parameters:parameters];
}
- (RACSignal *)POST:(NSString *)url parameters:(NSDictionary *)parameters {
SEL sel = #selector(POST:parameters:success:failure:);
return [self signalForRequest:sel url:url parameters:parameters];
}
- (RACSignal *)PUT:(NSString *)url parameters:(NSDictionary *)parameters {
SEL sel = #selector(PUT:parameters:success:failure:);
return [self signalForRequest:sel url:url parameters:parameters];
}
- (RACSignal *)DELETE:(NSString *)url parameters:(NSDictionary *)parameters {
SEL sel = #selector(DELETE:parameters:success:failure:);
return [self signalForRequest:sel url:url parameters:parameters];
}
- (RACSignal *)signalForRequest:(SEL)requestSEL url:(NSURL *)url parameters:(NSDictionary *)parameters {
#weakify(self);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self);
void (^success)(AFHTTPRequestOperation *, NSDictionary *) = ^(AFHTTPRequestOperation *operation, NSDictionary *response) {
[subscriber sendNext:response];
[subscriber sendCompleted];
};
void (^failure)(AFHTTPRequestOperation *, NSDictionary *) = ^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
};
NSMethodSignature *methodSignature = [self.manager methodSignatureForSelector:requestSEL];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:requestSEL];
[invocation setTarget:self.manager];
[invocation rac_setArgument:url atIndex:2];
[invocation rac_setArgument:parameters atIndex:3];
[invocation rac_setArgument:success atIndex:4];
[invocation rac_setArgument:failure atIndex:5];
[invocation invoke];
}];
}
Have you seen Reactive AFNetworking.
It should solve your problem
Edit:
The code below doesn't work at all.
I borrowed some code from Reactive AFNetworking and the code as follows is my solution:
- (void)GET:(NSString *)URL parameters:(NSDictionary *)parameters {
return [self signal_requestURL:URL method:#"PUT" parameters:parameters];
}
- (RACSignal *)signal_requestURL:(NSString *)URL method:(NSString *)method parameters:(NSDictionary *)parameters {
AFHTTPRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
NSURLRequest *request = [serializer requestWithMethod:method URLString:URL parameters:parameters error:NULL];
RACReplaySubject *subject = [RACReplaySubject replaySubjectWithCapacity:1];
[self.manager HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, NSDictionary *responseObject) {
[subject sendNext:responseObject];
[subject sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subject sendError:error];
}];
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subject subscribe:subscriber];
return nil;
}];
}
I'm testing a method using OCMock. The method is as follows:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
block:(void (^)(id responseObject, NSError *error))block
{
return [self GET:URLString parameters:parameters success:^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
} failure:^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
}];
}
This test fails with "expected method was not invoked":
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
void (^successBlock)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
};
void (^failureBlock)(NSURLSessionDataTask *, NSError *) = ^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:successBlock failure:failureBlock];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
But this passes:
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
Why does the first test fail and how can I make it pass?
I've put an example project on GitHub demonstrating the issue: https://github.com/paulyoung/OCMockExample
As others have said, blocks are compared by comparing their addresses. Two different blocks - even though they do the exact same thing - are not equal.
You can still make your test more specific by actually invoking the success and failure block, and check if they behave as expected.
As far as I understand you want to test that
the method calls another method with two blocks, that when invoked, invoke the original block with
some response object as the first and nil as the second argument in case of success
nil as the first, an NSError* as the second argument in case of failure
Here is the test for the success case:
id responseMock = #"responseObject";
__block BOOL blockCalled = NO;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {
XCTAssertNil(error, #"");
XCTAssertEqualObjects(responseObject, responseMock, #"");
blockCalled = YES;
};
[[[sessionManagerPartialMock expect] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSURLSessionDataTask *, id);
[invocation getArgument:&successBlock atIndex:4];
successBlock((id)#"task", responseMock);
}] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
XCTAssert(blockCalled, #"");
I checked your repo and I think now I can give you good answer.
What you are trying to do in your test is blocks comparing and currently this is not possible in Obj-c world. Even if you block from test and block created inside singleton looks identical they are not the same blocks.
You can fix this by using you fail and success block as private properties and use them in your OCMock expect. Like :
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:sessionManager.successBlock failure:sessionManager.failureBlock];