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.
Related
I have multiple GET API request methods that call a completion block when it finishes. Here is an example of one.
- (void)getUserInfo
onSuccess:(void (^)(id))successBlock
onFailure:(void (^)(NSError *))failureBlock {
NSString *urlStr = [NSString stringWithFormat:#"%#/user/", baseUrl];
[manager GET:urlStr parameters:nil progress:nil
success:^(NSURLSessionTask *task, id responseObject) {
successBlock(responseObject);
}
failure:^(NSURLSessionTask *operation, NSError *error) {
failureBlock(error);
}];
}
However, I noticed that I am repeating the manager GET request code in other methods. I want to create another method that handles all of the requests and remove the repetitive code. The URL seems to be the only thing that changes. However, there is one flaw. I need to call the successBlock to let the method know the request has finished.
Maybe I need to take another path altogether and do something different.
You can pass completion blocks around and then call them from your final method which handles all the get requests. I usually make completion blocks that are going to be reused typedefs for brevity. Here's an example of what I mean (I added a second example method that also passes through to the center getRequestWithURLString:onSuccess:onFailure: method):
LLFakeManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^_Nullable SuccessCompletionBlock)(id responseObject);
typedef void (^_Nullable FailureCompletionBlock)(NSError *error);
#interface LLFakeManager : NSObject
- (void)getUserInfoOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock;
- (void)getBooksCheckedOutOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock;
#end
NS_ASSUME_NONNULL_END
LLFakeManager.m
#import "LLFakeManager.h"
#interface LLFakeManager()
- (void)getRequestWithURLString:(NSString *)urlString
onSuccess:(SuccessCompletionBlock)successBlock
onFailure:(FailureCompletionBlock)failureBlock;
#end
#implementation LLFakeManager
- (void)getUserInfoOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock {
NSString *urlStr = #"FakeUserUrlPath";
[self getRequestWithURLString:urlStr onSuccess:successBlock onFailure:failureBlock];
}
- (void)getBooksCheckedOutOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock {
NSString *urlString = #"FakeBooksUrlPath";
[self getRequestWithURLString:urlString onSuccess:successBlock onFailure:failureBlock];
}
// central method that will handle all the get requests
- (void)getRequestWithURLString:(NSString *)urlString
onSuccess:(SuccessCompletionBlock)successBlock
onFailure:(FailureCompletionBlock)failureBlock {
// some fake implementation here to do your request, then use the completion block passed in from whatever other method
if (successBlock) {
successBlock(#"responseObjectPassedBackHere");
}
}
#end
And an example of calling it:
LLFakeManager *manager = [[LLFakeManager alloc] init];
[manager getUserInfoOnSuccess:^(id _Nonnull responseObject) {
NSLog(#"Here's my response object = %#", responseObject);
} onFailure:^(NSError * _Nonnull error) {
// no implementation but same idea
}];
Would produce this log:
Here's my response object = responseObjectPassedBackHere
This site: http://goshdarnblocksyntax.com is a handy list of block syntax which may also be helpful for you.
The blocks -- if they have the same signature -- they can be passed along a chain of methods. Your GET blocks carry an unneccesary first parameter. The NSURLSessionTask *, if it is to be returned at all, should be returned synchronously. Moving that out of the block signature will allow you to standardize the blocks.
It's easier to say in code...
// changed this method name so it would compile
- (void)getUserInfoOnSuccess:(void (^)(id))successBlock
onFailure:(void (^)(NSError *))failureBlock {
NSString *urlStr = [NSString stringWithFormat:#"%#/user/", baseUrl];
// two things: get the task as a return value (if you need it)
// pass the blocks directly, without nesting them in new blocks
NSURLSessionTask *task = [manager GET: urlStr
parameters: nil
progress: nil
success: successBlock
failure: failureBlock];
// do something with the task
}
To make this work, alter the GET method return type and block signatures...
- (NSURLSessionTask *)GET:(NSString *)url parameters:(id)params progress:(id)progress success:(void (^)(id))successBlock failure:(void (^)(NSError *))failureBlock {
// return the session task created here
return task
}
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.
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);
I have a unit test in which I need to wait for an async task to finish. I am trying to use NSConditionLock as it seems to be a pretty clean solution but I cannot get it to work.
Some test code:
- (void)testSuccess
{
loginLock = [[NSConditionLock alloc] init];
Login login = [[Login alloc] init];
login.delegate = self;
// The login method will make an async call.
// I have setup myself as the delegate.
// I would like to wait to the delegate method to get called
// before my test finishes
[login login];
// try to lock to wait for delegate to get called
[loginLock lockWhenCondition:1];
// At this point I can do some verification
NSLog(#"Done running login test");
}
// delegate method that gets called after login success
- (void) loginSuccess {
NSLog(#"login success");
// Cool the delegate was called this should let the test continue
[loginLock unlockWithCondition:1];
}
I was trying to follow the solution here:
How to unit test asynchronous APIs?
My delegate never gets called if I lock. If I take out the lock code and put in a simple timer it works fine.
Am I locking the entire thread and not letting the login code run and actually make the async call?
I also tried this to put the login call on a different thread so it does not get locked.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[login login];
});
What am I doing wrong?
EDIT adding login code. Trimmed do the code for readability sake. Basically just use AFNetworking to execute a POST. When done will call delegate methods.
Login make a http request:
NSString *url = [NSString stringWithFormat:#"%#/%#", [_baseURL absoluteString], #"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (_delegate) {
[_delegate loginSuccess];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (_delegate) {
[_delegate loginFailure];
}
}];
The answer can be found in https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPRequestOperation.m.
Since you are not setting the completionQueue property of the implicitly created AFHTTPRequestOperation, it is scheduling the callbacks on the main queue, which you are blocking.
Unfortunately, many answers (not all) in the given SO thread ("How to unit test asynchronous APIs?") are bogus and contain subtle issues. Most authors don't care about thread-safity, the need for memory-barriers when accessing shared variables, and how run loops do work actually. In effect, this leads to unreliable and ineffective code.
In your example, the culprit is likely, that your delegate methods are dispatched on the main thread. Since you are waiting on the condition lock on the main thread as well, this leads to a dead lock. One thing, the most accepted answer that suggests this solution does not mention at all.
A possible solution:
First, change your login method so that it has a proper completion handler parameter, which a call-site can set in order to figure that the login process is complete:
typedef void (^void)(completion_t)(id result, NSError* error);
- (void) loginWithCompletion:(completion_t)completion;
After your Edit:
You could implement your login method as follows:
- (void) loginWithCompletion:(completion_t)completion
{
NSString *url = [NSString stringWithFormat:#"%#/%#", [_baseURL absoluteString], #"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completion) {
completion(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) {
completion(nil, error);
}
}];
Possible usage:
[self loginWithCompletion:^(id result, NSError* error){
if (error) {
[_delegate loginFailure:error];
}
else {
// Login succeeded with "result"
[_delegate loginSuccess];
}
}];
Now, you have an actual method which you can test. Not actually sure WHAT you are trying to test, but for example:
-(void) testLoginController {
// setup Network MOCK and/or loginController so that it fails:
...
[loginController loginWithCompletion:^(id result, NSError*error){
XCTAssertNotNil(error, #"");
XCTAssert(...);
<signal completion>
}];
<wait on the run loop until completion>
// Test possible side effects:
XCTAssert(loginController.isLoggedIn == NO, #""):
}
For any other further steps, this may help:
If you don't mind to utilize a third party framework, you can then implement the <signal completion> and <wait on the run loop until completion> tasks and other things as described here in this answer: Unit testing Parse framework iOS
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