I'm writing a ios app which has to send and receive data from the API at various screens in the app. Currently each view controller is calling this code
// AFAppDotNetAPIClient is a subclass of AFHTTPClient, which defines the base URL and default HTTP headers for NSURLRequests it creates
[[AFAppDotNetAPIClient sharedClient] getPath:#"stream/0/posts/stream/global" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSLog(#"App.net Global Stream: %#", JSON);
} failure:nil];
I want to keep things DRY and so I created a requested builder and response handler to create request and parse responses. I also want to move all the API calls to one class but since it uses blocks I don't know how do this.
Can someone explain how this is done so I call one method with a enum and some params for request and I probably just get a NSDictionary back without having API calls and blocks in all view controllers. Thanks
This is a concern for the Model part of your MVC architecture. The example project has a good implementation of this:
Post.h
+ (void)globalTimelinePostsWithBlock:(void (^)(NSArray *posts, NSError *error))block;
Define class methods on the model that take care of making requests (translating any method parameters into request params) and serializing objects from the response.
Define your new interface to include your enum, parameters and a block. Define the block to have a BOOL (status) and an NSDictionary (result). This is basically just simplifying and abstracting the interface away from the client, path and operation. Then in the success and failure blocks you can call the outer block with appropriate parameters.
The block is required to maintain the asynchronous nature of the interface. You could use a target and selector but that will be more code and less flexible. You can't have the method just return the resulting dictionary.
There are obviously many other options for what parameters to pass to the method and in the block.
Related
It seems to me that the proper place to do this is in AFURLSessionManager, in setting the taskWillPerformHTTPRedirection block, but I am unsure of the best way to handle it.
Currently, in my AFHTTPSessionManager subclass, I am setting the redirect block globally for all requests, and I know I can prevent redirects by returning nil here:
- (void)setupRedirectBlock {
[self setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest *(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request) {
return nil;
}];
}
...but I need to only do this on specific tasks, and there doesn't appear to be a way to get this information from the task itself.
I guess I am looking for some sort of user info dictionary or something I can use to set a flag telling this method to either return the request or return nil. Currently, it looks like I would have to do a string comparison on the response/request URL in the client where it is far away from where the task and path is actually created.
So this begs the question, am I fighting convention, or is there really no better way to intercept an AFNetworking 2.0 redirect on a task-by-task basis?
setTaskWillPerformHTTPRedirectionBlock is the best way to intercept redirects. The session manager is responsible for determining when or when not to prevent redirects based on the request associated with the task. In most cases, the path of the request should be a sufficient determination, but the user could additionally tag information in a custom request header field.
I have the same question, but unfortunately I don't yet have a good enough answer. One workaround could be using taskDescription https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionTask_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionTask/taskDescription
Just put there some constant like DO_NOT_FOLLOW_REDIRECT and check for it in your setTaskWillPerformHTTPRedirectionBlock block.
My example on iOS 6:
10 Multi-Part requests need to be sent (in order) to the server.
(so the request forms a queue)
progress should be shown.
if one request fails all following should fail
a request queue should be cancellable
Can AFNetworking help me with this? Or should I try to build something with NSOperations and run the loops myself?
If I need to pass context data between theses requests for example a transaction id produced by the first request. Are there any considerations about thread visibility I need to consider?
AFNetworking can do this. I recommend that you use AFHTTPRequestOperationManager (which itself uses NSOperation), rather than AFHTTPSessionManager. There are ways to do it with AFHTTPSessionManager, but none as elegant as with operations.
Under the hood, here's what you'd do without the manager:
You will use a request serializer to make your NSMutableURLRequest (for example, [AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:]; there's a similar JSON request serializer too).
Once you have a URL Request, make the operation with [AFHTTPRequestOperation -initWithRequest:]. You should also set its completion blocks.
Finally, add your operation to [AFHTTPRequestOperationManager manager].operationQueue and start it.
Now that you understand how this is basically all working together, here's a simpler approach:
Subclass AFHTTPRequestOperationManager, optionally setting the requestSerializer if you don't like the default
Override (or copy with new implementation) -POST:parameters:constructingBodyWithBlock:success:failure:] - what you want to do is NOT start your operation right away.
Set the NSOperation dependency chains
start the first one
Currently I am working on building an iOS app that asynchronously connects to a web server. I make about 5 different calls to the web service, depending on which view controller I am in. On a lot of the views, I end up making the same calls, so I have a lot of identical/similar code. Do you guys have any suggestions on how I can limit the amount of code reuse I am making. I was thinking about creating a special class that has the method/code for all these asynchronous calls, then on each View controller, I can just instantiate the special class and invoke whatever methods I want. The problem with this is that since I am making asynchronous calls, how will I know which view controller to send the data back to (via a callback). I know there should be a simple, elegant way to achieve this but I am just new to this way of thinking so help would be greatly appreciated.
Usually what I do is a class that is responsible for the requests (or part of them depending on the amount of requests you have and how they are logically related).
Imagine that you have a NetworkManager. In this manager you can have a singleton if you need to persist state or just use class methods. All methods should have at least a completion block that will be called when the async method that you use for your server has the response.
The block can be defined with a typedef:
typedef void (^NetworkCompletionBlock)(NSError *error, NSArray* data);
Then you call methods like this:
+(void) requestInfoFromServerOnCompletion:(NetworkCompletionBlock)onComplete;
Then, on your method, you call your async functions and when they are complete you call the onComplete block with the parameters he requests, like this:
if(onComplete)
onComplete(yourError, yourDataArray);
To call this class method just do:
[NetworkManagerrequestInfoFromServerOnCompletion:^(NSError *error, NSArray* data)){
if(error)
NSLog(#"Ops, an error ocurred");
else
NSLog(#"Received data: %#", data);
}];
You can also have two blocks, one for error and one for completion.
Depending on how you are doing your requests you might need to save the block in the NetworkManager. Imagine that you use a NSURLConnection with delegates. You had to save your block in a networkManager singleton and then call it when the request is complete.
To do this you need to declare a property on your singleton:
#property (nonatomic, copy) NetworkCompletionBlock onRequestCompletedBlock;
And then on your method you do:
self.onRequestCompletedBlock = onComplete;
When the request is completed you can call with:
self.onRequestCompletedBlock(requestError, requestDataArray);
Note that if you do this you need to be careful with retain cycles.
I would like to use RestKit 0.20 on my app but I am a bit confused on how the pieces fit together. Each one of my view controllers needs to get data from the server each with their own route on the server returning a different object. I would like to keep all of the server requests away from the view controllers so I can manage them centrally (but open to another setup if it makes sense). Right now I have the following setup but I am pretty sure there is a better way to go about it using RKObjectManager and RKRouter :
1) Each view controller triggers a method in a Gateway object that is dedicated to it.
2) The method will create a request and a response map. Then a request and response description and finally a request operation that uses a manually created NSURL.
3) In the success block I pass the response to the view controller with an NSnotification.
Is there a better setup? Can I just use one RKObjectManager for all the request? How does that work? Do I put it in a separate method in my gateway? Is there a better way to get back to my view controller then NSNotification?
Sorry if some of these are very basic.
I would suggest the following structure (using Instagram as an example):
1) first of all split all your requests based on the "Resource" you use e.g. Users/Comments/Likes, etc
2) For each "Resource" create a separate class, subclass of RKObjectManager, for example UsersManager, CommentsManager, LikesManager (all inherited from RKObjectManager)
3) Define extra methods on every manager, that you will use from View Controller.
For example, for loading "likes" resource for user, you'd define this method in LikesManager (or UserManager) -- this is very opinionated decision, and it's really up to you.
- (void)loadLikesForUser:(User *)user
success:(void (^)(NSArray *likes))successCallback
failure:(void (^)(NSError *error))failureCallback;
4) Implement this method and call appropriate methods using self, because you've created a subclass of RKObjectManager class and have access to all basic methods.
[self getObjectsAtPath:#"/resources/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// pass mappingResult.array to the successCallback
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// pass error object to the failureCallback
}];
5) In your view controller, you'd call this like
[[LikesManager sharedManager] loadLikesForUser:user success:^(NSArray *likes) {
self.likes = likes;
// work with likes
} failure:^(NSError *error) {
// handle error
}];
Notice that you don't do any stuff with NSNotification, as it'll be a bad design decision in this kind of situations.
Generally speaking you can put all of your requests in a subclass of RKObjectManager, if you have very few of them, but if you have more than 5-6 requests, you'll find it tedious and hard to manage to keep all of them in one file. So that's why I suggest to split them based on a Resource.
UPDATE
Based on questions asked in comments, providing answers here
a) Where to set Base URL?
Base URL is a property on instance of RKObjectManager, so you definitely want to set it before making any requests to API. To me ideal place would be at the initialization of RKObjectManager instance.
b) Where to define Object Mapping?
Again it's up to you. Answer to this question is very opinionated. I'd consider 2 options:
create a separate class to hold all your objects related to mappings, like MappingProvider. Then whenever you create RKResponseDescriptor or RKRequestDescriptor, you'd just access properties of MappingProvider to get your mappings.
Define mappings in manager's class, because you'll assign them to the RKResponseDescriptor instances that will be used in this manager.
UPDATE: Check out this blog post on RestKit setup: http://restkit-tutorials.com/code-organization-in-restkit-based-app/
While using a 3rd party API, I have the requirement to cancel all traffic when a custom response header is set to a certain value. I am trying to find a nice place to do this check only once in my code (and not in every success/failure block, where it works fine). From what I understand, this could be done by overriding -(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation in my custom AFHTTPClient subclass, but when I implement it like that:
-(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation
{
NSLog(#"[REQUEST URL]\n%#\n", [operation.request.URL description]);
NSLog(#"[RESPONSE HEADERS]\n%#\n", [[operation.response allHeaderFields] descriptionInStringsFileFormat]);
[super enqueueHTTPRequestOperation:operation];
}
the response headers are nil. Can anybody help me with that?
At the moment when operations are being created and enqueued in AFHTTPClient, they will not have the response from the server--that will be assigned when the request operation is actually executed.
Although the requirement to cancel all traffic seems unorthodox (at least if outside of the conventions of HTTP), this is easy to accomplish:
In your AFHTTPClient subclass, add a BOOL property that stores if requests should be prevented, and then used in enqueueHTTPRequestOperation. Then, override HTTPRequestOperationWithRequest:success:failure: to execute the specified success block along with some logic to set the aforementioned property if the salient response is present.