In all of my iOS application I use this approach to respect MVC, I want to be sure that my implementation is correct and respects the best practices and the MVC design pattern :
Singleton of AFNetworking acting as API for network calls:
MyAPI.h :
#import "AFHTTPSessionManager.h"
#import "AFNetworking.h"
#interface MyAPI : AFHTTPSessionManager
+(MyAPI *)sharedInstance;
#end
MyAPI.m :
#pragma mark - Singleton
+(MyAPI*)sharedInstance
{
static MyAPI *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[MyAPI alloc] initWithBaseURL:[NSURL URLWithString:kROOT_URL]];
});
return sharedInstance;
}
Model User that uses the singleton to fecth the data of user (is that good as implementation ?):
User.h
#interface User : NSObject
#property (strong,nonatomic) NSString *userId;
#property (strong,nonatomic) NSString *email;
#property (strong,nonatomic) NSString *password;
-(id) initWithDictionary: (NSDictionary *) dictionay;
+(BOOL) isConnected;
+(void) disconnect;
+(NSString *) idOfConnectedUser;
+(User *) connectedUser;
+(void) loginWith : (NSString *) email andPassword :(NSString *) password complete:(void(^)(id result, NSError *error))block;
+(void) searchUsersFrom : (NSString *) countryCode withName :(NSString *) name andLevel:(NSString *) levelCode complete: (void(^)(id result, NSError *error)) block;
+(void) signup:(void(^)(id result, NSError *error)) block;
+(void) getUserFriends:(void(^)(id result, NSError *error)) block;
#end
User.m
[......]
+(void) loginWith : (NSString *) email andPassword :(NSString *) password complete: (void(^)(id result, NSError *error)) block
{
__block NSString * result ;
NSDictionary *params = #{#"email": email, #"password": password};
[[MyAPI sharedInstance] POST:#"auth/" parameters:params success:^(NSURLSessionDataTask *task, id responseObject)
{
if([responseObject objectForKey:#"id"])
{
[[NSUserDefaults standardUserDefaults] setObject:(NSDictionary*) responseObject forKey:USER_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
result = [responseObject objectForKey:#"id"];
}
else
{
result = nil ;
}
if (block) block(result, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error)
{
if (block) block(nil, error);
}];
}
[.....]
LoginController.m :
-(void)loginButtonAction:(UIButton *)sender
{
[......]
[ User loginWith:text andPassword:text complete:^(id result, NSError *error)
{
if (result)
{
[APPDELEGATE start];
}
else
{
// ERROR
}
}];
}
So does my implementation respects the MCV and follows the best practices and how can I improve it if not ?
Singletons: You might want to avoid using singletons, it'll help you to improve your API design and make code more testable. Also, in case of User, imagine you will want to support changing user (logout/guest user/etc). With current approach, you will be limited to sending a NSNotification because everyone who uses connectedUser can not know that underlying reference has changed.
ActiveRecord:
What you did with your model User that is capable of performing networking is somewhat similar to active record approach which might not scale so well when you model becomes more complicated and the number of actions it can perform increases. Consider separating those into pure model and services that actually perform networking (or whatever else you will need in the future).
Model Serialisation:
Consider encapsulating model & network response serialisation logic into a separate class (e.g. LoginResponse that among other things points to a User) frameworks like Mantle make it much easier.
MVC: from my experience in iOS MVC might not be the most optimal approach for anything but simple apps. With MVC tendency is to put put all the logic into your ViewController making it very big and hard to maintain. Consider other patterns such as MVVM
All in all I understand that it is hard to learn all the new technologies at once, but you can definitely start by making sure each class performs one thing and one thing only: Model does not do networking or persisting to the disk, API client doesn't deserialise each response or saves data to NSUserDefaults, view controller doesn't do anything except for listening to user events (button taps etc). This alone would make your code much easier to reason about and to follow if a new developer would be introduced to your codebase.
Hope it helps!
I dont have anything to say about your MVC(Model–view–controller) correct?
I just want to add something that may be useful approach avoiding unwanted crashes..
First is under
[[MyAPI sharedInstance] POST:#"auth/" parameters:params success:^(NSURLSessionDataTask *task, id responseObject)
{
if([responseObject objectForKey:#"id"])
{
[[NSUserDefaults standardUserDefaults] setObject:(NSDictionary*) responseObject forKey:USER_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
result = [responseObject objectForKey:#"id"];
}
else
{
result = nil ;
}
}];
it is always possible for so many reason to mention that the reponseObject could be nil and therefore object dont have the key #"id" and will lead to error(crash in worst case). i have this code i dont know if this can be considered as best practice but here it is:
if ([responseObject isKindOfClass:[NSArray class]])
{
NSLog(#"Log: Response is of class NSArray");
}
else if ([responseObject isKindOfClass:[NSDictionary class]])
{
NSLog(#"Log: Response is of class NSDictionary");
}
else
{
NSLog(#"Log: Kind of class is not supported");
}
This example restricts other kind of class especially [NSNull class]
Second in line:
NSDictionary *params = #{#"email": email, #"password": password};
by checking email and password first before assigning to NSDictionary, setting nil to NSDictionary will cause crash.
Third is line:
if (block) block(result, nil);
block returns void from your implementation. Is this working? Sorry for asking i haven't tried if-statement with a block like this..
complete: (void(^)(id result, NSError *error)) block
void in this is the returning value of your block, or i'm wrong.. hmm..
if (block) only checks if the block block exist, so intead of checking it (which we are sure that is existing)..
maybe you want to check the result instead...
if (result != nil) block(result, nil); is the right statement
the reason behind is:
if (result != nil) // will return NONE nil value only
{
block(result, nil);
}
// else will not set things to the block
//or maybe just
block(result, nil); // which will allow the 'block(nil, nil);' and under your implementation
[ User loginWith:text andPassword:text complete:^(id result, NSError *error)
{
if (result)
{
[APPDELEGATE start];
}
else if (result == nil & error == nil)
{
// NO objectForKey #"id"
}
else
{
// ERROR
}
}];
while under failure:^(NSURLSessionDataTask *task, NSError *error) simply block(nil, error);
Related
Trying to improve my coding style and was wondering what the proper way of going about things is. Should they be placed in a NSObjects separate from the view controller and are there any open source clean code examples for me to reference online available?
As a general rule, it's always a good idea to separate code that accesses a back end server from your UI components. The one best reason being that it's often a requirement for several UI components to access the same server calls.
As far as examples, there are probably thousands. But perhaps a better idea might be to read up on things such as design patterns and app architectural patterns.
objc.io has some good articles on these subjects. Here's another on Medium. There are lots of other, just google search.
It's always a good idea to create a model if your app is going to be relying on an construct. I.e if you're building an app which contains a tableview full or photos, it's a good idea to create a model for photos.
Here are a few examples:
Clean Table View Code - Objc.io
In this tutorial, get an understanding of the best way to populate a tableview, and how the relationship works between a model, a cell, and a tableview.
A Weather App Case Study
This is an end-to-end walk through of building a model for your tableview, then populating the tableview.
MVC in Objective-C (II): Model
An overall understanding of how models fit into the MVC pattern.
If you want to construct an object form the JSON response you want to something like this:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:&error];
// Sanity check
if ([json isKindOfClass:[NSDictionary class]]){
NSArray *jsonArray = json[#"items"];
// Sanity check
if ([jsonArray isKindOfClass:[NSArray class]]){
// Here you go through all your items one by one
for (NSDictionary *dictionary in jsonArray) {
Model *staff = [[Staff alloc] init];
model.id = [dictionary objectForKey:#"id"];
model.name = [dictionary objectForKey:#"name"];
model.attribute = [dictionary objectForKey:#"attribute"];
// Do this for all your attributes
[arrayContainingObjects addObject:model];
}
}
}
Use common NSObject Class for Calling WS with AFNetworking 2.0
First make NSObject Class with any name here i am creating NSObject Class With name Webservice.h and Webservice.m
Webservice.h
#interface Webservice : NSObject
+ (void)callWSWithUrl:(NSString *)stUrl parmeters:(NSDictionary *)parameters success:(void (^)(NSDictionary *response))success failure:(void (^)(NSError *error))failure;
#end
Webservice.m your nsobject.m file is look like this.(add two functions in .m file)
#import "Webservice.h"
#define kDefaultErrorCode 12345
#implementation Webservice
+ (void)callWSWithUrl:(NSString *)stUrl parmeters:(NSDictionary *)parameters success:(void (^)(NSDictionary *response))success failure:(void (^)(NSError *error))failure {
[self requestWithUrl:stUrl parmeters:parameters success:^(NSDictionary *response) {
//replace your key according your responce with "success"
if([[response objectForKey:#"success"] boolValue]) {
if(success) {
success(response);
}
}
else {
//replace your key according your responce with "message"
NSError *error = [NSError errorWithDomain:#"Error" code:kDefaultErrorCode userInfo:#{NSLocalizedDescriptionKey:[response objectForKey:#"message"]}];
if(failure) {
failure(error);
}
}
} failure:^(NSError *error) {
if(failure) {
failure(error);
}
}];
}
- (void)requestWithUrl:(NSString *)stUrl parmeters:(NSDictionary *)parameters success:(void (^)(NSDictionary *response))success failure:(void (^)(NSError *))failure {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager setResponseSerializer:[AFHTTPResponseSerializer serializer]];
//remove comment if you want to use header
//[manager.requestSerializer setValue:#"" forHTTPHeaderField:#"Authorization"];
[manager GET:stUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if([responseObject isKindOfClass:[NSDictionary class]]) {
if(success) {
success(responseObject);
}
}
else {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:nil];
if(success) {
success(response);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure) {
failure(error);
}
}];}
#end
make sure you have to replace your dictionary key with success and
message for handling of responce callback function
Use like this call this common method from any viewcontroller.m and any methods from any viewControllers. for temporary i am using viewDidLoad for calling This WS.
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dictParam = #{#"parameter1":#"value1",#"parameter1":#"value2"};
[WebClient requestWithUrlWithResultVerificationWithLoder:#"add your webservice URL here" parmeters:dictParam success:^(NSDictionary *response) {
//Success
NSLog(#"responce:%#",response);
//code here...
} failure:^(NSError *error) {
//Error
NSLog(#"error:%#",error.localizedDescription);
}];
}
add your Parameter, values and webservice URL in upper method. you can easyly use this NSObjcet Class. for more details please visit AFNetworking or here.
I've subclassed AFHTTPSessionManager according to the recommended best practice for iOS 8 (in place of AFHTTPOperationManager, which I was using before).
I can grab the NSHTTPURLResponse from the task (except that has no body, only headers), and the callback returns the serialized responseObject which is fine.
Sometimes I need to log the response as a string or display it in a text field - there doesn't appear to be a way to do this natively using SessionManager? OperationManager allowed you to reference the raw response as an NSString:
operation.responseString;
I suppose I could stringify the serialized requestObject, but that seems like a lot of unnecessary overhead, and won't help if the response object is invalid JSON.
Here's my subclassed singleton:
#implementation MyAFHTTPSessionManager
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
And then to make a simple GET (which I've added to a block method), I can do:
[[MyAFHTTPSessionManager sharedManager] GET:_url parameters:queryParams success:^(NSURLSessionDataTask *task, id responseObject) {
completion(YES, task, responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
completion(NO, task, nil, error);
}];
You can accomplish this by creating a custom response serializer that records the data and serializes the response using the standard response serializer, combining both the raw data and parsed object into a custom, compound response object.
#interface ResponseWithRawData : NSObject
#property (nonatomic, retain) NSData *data;
#property (nonatomic, retain) id object;
#end
#interface ResponseSerializerWithRawData : NSObject <AFURLResponseSerialization>
- (instancetype)initWithForwardingSerializer:(id<AFURLResponseSerialization>)forwardingSerializer;
#end
...
#implementation ResponseWithRawData
#end
#interface ResponseSerializerWithRawData ()
#property (nonatomic, retain) forwardingSerializer;
#end
#implementation ResponseSerializerWithRawData
- (instancetype)initWithForwardingSerializer:(id<AFURLResponseSerialization>)forwardingSerializer {
self = [super init];
if (self) {
self.forwardingSerializer = forwardingSerializer;
}
return self;
}
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
id object = [self.forwardingSerializer responseObjectForResponse:response data:data error:error];
// TODO: could just log the data here and then return object; so that none of the request handlers have to change
if (*error) {
// TODO: Create a new NSError object and add the data to the "userInfo"
// TODO: OR ignore the error and return the response object with the raw data only
return nil;
} else {
ResponseWithRawData *response = [[ResponseWithRawData alloc] init];
response.data = data;
response.object = object;
return response;
}
}
#end
Then set this serializer on your session manager:
#implementation MyAFHTTPSessionManager
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
instance.responseSerializer = [[ResponseSerializerWithRawData alloc] initWithForwardingSerializer:instance.responseSerializer];
});
return instance;
}
Now in your completion handler you will get an instance of ResponseWithRawData:
[[MyAFHTTPSessionManager sharedManager] GET:_url parameters:queryParams success:^(NSURLSessionDataTask *task, id responseObject) {
ResponseWithRawData *responseWithRawData = responseObject;
NSLog(#"raw data: %#", responseWithRawData.data);
// If UTF8 NSLog(#"raw data: %#", [[NSString alloc] initWithData:responseWithRawData.data encoding:NSUTF8StringEncoding]);
// TODO: do something with parsed object
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
I just whipped this up without compiling/testing, so I will leave it to you to debug and fill in the gaps.
You can access the “data” object directly from AFNetworking by using the “AFNetworkingOperationFailingURLResponseDataErrorKey” key so there is no need for subclassing the AFJSONResponseSerializer. You can the serialize the data into a readable dictionary. Here is some sample code to get JSON Data :
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Here is code to Get Status code in Failur block
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( #"success: %d", r.statusCode );
here is my issue:
I have a AFHTTPSessionManager file, which is also a singleton, and manage all my API requests to my server. Once I got the answer from the server with a responseObject, I pass it back to the UIViewController who asked for it using a delegate.
My problem is : since my manager is a singleton, if another API request is made in the meantime by another UIViewController, the delegate is set to this controller and when my first request responseObject is received I can't pass it back to the first UIViewController anymore.
I hope it's easy to understand.
What would be the right way to solve this problem ?
Here is what a method looks like in my AFHTTPSessionManager class :
- (void)getStaffForCompany:(int)companyID
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"currentUser"])
{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"apiKey"] = agendizeApiKey;
parameters[#"token"] = [[AGZUserManager sharedAGZUser] currentApplicationUser].token;
[self GET:[NSString stringWithFormat:#"scheduling/companies/%d/staff", companyID] parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if ([self.delegate respondsToSelector:#selector(AGZClient:successedReceiveStaffList:)]) {
[self.delegate AGZClient:self successedReceiveStaffList:responseObject];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([self.delegate respondsToSelector:#selector(AGZClient:failedReceiveStaffList:)]) {
[self.delegate AGZClient:self failedReceiveStaffList:error];
}
}];
}
}
Thanks!
You could define your own completion block and pass your responseObject back to the controller, here is an example CustomCompletion.
Add this to your AFHTTPSessionManager.h just above the #implementation line.
typedef void(^CustomCompletion)(id responseObject);
Update your method to include the CustomCompletion object.
- (void)getStaffForCompany:(int)companyID withCompletion:(CustomCompletion)completion {
// On success pass the responseObject back like so.
completion(responseObject);
}
Then where all the magic happens, back in your controller call this method on your singleton and handle the completion.
[SingletonManager getStaffForCompany:1 withCompletion:^(id responseObject) {
if (responseObject) {
// do something with this object
}
}];
I haven't tested this code, but I do something very similar in Swift and it works a treat.
I am doing a simple GET request with AFNetworking
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Once I have made the request I want to be able to access the responseObject from any other method in the class.
I want to be able to save the responseObject so I can do something like display the output in a tableview.
It's common to creat object models that will be represented by JSON. When you get the response you would then parse the data into the models. The approach we use is to return the response to the requester through a completion block. You don't have to parse the JSON into strongly typed objects, but it really is helpful long term. It's probably a good idea to farm out the network request operations into a separate class (called a service) as well. This way you can instantiate a new service and get notified through a completion block that it is finished. For example your service's request signature could look like this:
typedef void(^HelloWorldCompletionHandler)(NSString *helloWorld, NSError *error);
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler;
// implementation
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
id JSONResponse = [operation responseObject];
if (operation.error) {
completionHandler(nil, error);
} else {
// parse the response to something
id parserResult = [self parseJSONResponse:JSONResponse];
completionHandler(parserResult, nil);
}
}];
This way you'll know when the network request is complete, and you can set the data you want on a property within your class. Then you could call tableView.reloadData in order to use the data in your table.
All that code would go into a service type class. I like to organize my services by responsibility. I don't know how many different data calls you make, but we have several for our project. If for instance you were making a weather app you could potentially organize by Current Conditions, Daily Forecasts, and Hourly Forecasts. I would make a service for each one of these requests. Say I created a CurrentConditionsService. The header would look something like this:
typedef void(^CurrentConditionsCompletionHandler)(CurrentConditions *currentConditions, NSError *error);
#interface CurrentConditionsService : NSObject
// locationKey is some unique identifier for a city
+ (instancetype)serviceWithLocationKey:(NSString *)locationKey;
- (void)retrieveCurrentConditionsWithCompletionHandler:(CurrentConditionsCompletionHandler)completionHandler;
#end
Then in my implementation file I would make the request and invoke the given completion handler like I demonstrated above. This pattern can be followed by many different services to the point where all your services could inherit from a base class that handles the request/response portions. Then your subclasses could override specific methods and handle/parse the data appropriately based on type.
If you go the route of parsing the JSON responses into model objects, all your parsers will need to conform to a protocol. This way in your super class it doesn't matter what the concrete implementation of your parser is. You supply the super class with a concrete implementation and all it knows how to do is invoke the parser and return the response.
An example JSON parser protocol would look like this:
#protocol AWDataParser <NSObject>
#required
- (id)parseFromDictionary:(NSDictionary *)dictionary;
- (NSArray *)parseFromArray:(NSArray *)array;
#end
And invoking it in your services super class:
- (id)parseJSONResponse:(id)JSONResponse error:(NSError **)error {
NSAssert(self.expectedJSONResponseClass != nil, #"parseResponse: expectedJSONResponseClass cannot be nil");
NSAssert(self.parser != nil, #"parseResponse: parser cannot be nil");
id parserResult = nil;
if (![JSONResponse isKindOfClass:self.expectedJSONResponseClass]) {
//handle invalid JSON reponse object
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:#{#"Invalid JSON type": [NSString stringWithFormat:#"expected: %#, is: %#",self.expectedJSONResponseClass, [JSONResponse class]]}];
}
} else {
if (self.expectedJSONResponseClass == [NSArray class]) {
parserResult = [self.parser parseFromArray:JSONResponse];
}else {
parserResult = [self.parser parseFromDictionary:JSONResponse];
}
if (!parserResult) {
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:nil];
}
}
}
return parserResult;
}
Use this approach:
NSURL *COMBINAT = [[NSURL alloc] initWithString:#"http://someapi.com/hello.json"];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
COMBINAT];
[self performSelectorOnMainThread:#selector(savedata:) withObject:data waitUntilDone:YES];
});
then simply call:
- (void)savedata:(NSData *)responseData {
NSError* error;
NSLog(#"Answer from server %#", responseData);
// ... your code to use responseData
}
Just create a property:
#property(nonatomic, strong) id savedResponseObject;
and set it in the success handler of the request:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
self.savedResponseObject = responseObject;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#", error);
}];
Then you will be able to access it from other places in your class by referencing:
self.savedResponseObject
i am new to iOS programming, still learning.
EDIT: !!!!!! Everything in my code works. My question is about the delegation pattern i use,
if i am generating problems in the background that i have no idea of, or if there is a better way to handle my situation in AFNetworking...
I have created an API for my app by subclassing AFHTTPSessionManager.
My API creates a singleton and returns it and supplies public functions for various requests. And those functions create parameter lists, and make GET requests on the server like this:
- (void)getCharacterListForKeyID:(NSString *)keyID vCode:(NSString *)vCode sender:(id)delegate
{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"keyID"] = keyID;
parameters[#"vCode"] = vCode;
[self GET:#"account/Characters.xml.aspx" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
self.xmlWholeData = [NSMutableDictionary dictionary];
self.errorDictionary = [NSMutableDictionary dictionary];
NSXMLParser *XMLParser = (NSXMLParser *)responseObject;
[XMLParser setShouldProcessNamespaces:YES];
XMLParser.delegate = self;
[XMLParser parse];
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didHTTPRequestWithResult:)]) {
[delegate EVEAPIHTTPClient:self didHTTPRequestWithResult:self.xmlWholeData];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didFailWithError:)]) {
[delegate EVEAPIHTTPClient:self didFailWithError:error];
}
}];
}
I was using a normal protocol/delegate method earlier. But once i make calls this API more than once like this: (IT WAS LIKE THIS:)
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
client.delegate = self;
[client getCharacterListForKeyID:self.keyID vCode:self.vCode];
Previous call's delegate was being overwritten by next. So i changed to above style. Passing sender as an argument in the function:
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
[client getCharacterListForKeyID:self.keyID vCode:self.vCode sender:self];
And i pass this sender to GET request's success and failure blocks.
What i wonder is : "Is this a good programming practice ?". Passing objects to blocks like this should be avoided if possible ? Is there any other more elegant way in AFHTTPSessionManager to handle this type of work (making same GET request over and over with different parameters and returning results to the respective request owners) more elegantly ?
Delegation pattern falters when it comes to simplicity and asynchronous request processing. You should be using blocks, here's an example
Your server class:
static NSString *const kNews = #"user_news/"; // somewhere above the #implementation
- (NSURLSessionDataTask *)newsWithPage:(NSNumber *)page
lastNewsID:(NSNumber *)lastNewsID
completion:(void (^)(NSString *errMsg, NSArray *news, NSNumber *nextPage))completionBlock {
return [self GET:kNews
parameters:#{#"page" : page,
#"news_id" : lastNewsID
}
success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *news = nil;
NSNumber *nextPage = nil;
NSString *errors = [self errors:responseObject[#"errors"]]; // process errors
if ([responseObject[#"status"] boolValue]) {
news = responseObject[#"news"];
nextPage = responseObject[#"next_page"];
[self assignToken];
}
completionBlock(errors, news, nextPage);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSString *errors = [self errors:error];
completionBlock(errors, nil, nil);
}];
}
The caller
- (void)dealloc {
[_task cancel]; // you don't want this task to execute if user suddenly removes your controller from the navigation controller's stack
}
- (void)requestNews {
typeof(self) __weak wself = self; // to avoid the retain cycle
self.task = [[GSGServer sharedInstance] newsWithPage:self.page
lastNewsID:self.lastNewsID
completion:^(NSString *errMsg, NSArray *news, NSNumber *nextPage) {
if (errMsg) {
[GSGAppDelegate alertQuick:errMsg]; // shortcut for posting UIAlertView, uses errMsg for message and "Error" as a title
return;
}
[wself.news addObjectsFromArray:news];
wself.lastNewsID = [wself.news firstObject][#"id"];
wself.page = nextPage;
[wself.tableView reloadData];
}];
}