4

i have searched a lot of examples and tutorials on how to run AFNetworking 2.0 synchronously and found only solutions for AFNetworking 1.0. What i have found: Can AFNetworking return data synchronously (inside a block)?

My example:

- (User *)getUserWithUsername: (NSString *) username andPassword: (NSString *) password {

    NSDictionary *params = @{@"email": username, @"password": password};


    [[DCAPIClient sharedClient] POST:@"login" parameters:params success:^(NSURLSessionDataTask * __unused task, id JSONResult) {
        NSLog(@"JSON %@", JSONResult);
        BOOL errorCode = [JSONResult objectForKey:@"error"];
        if (!errorCode) {
            self.username = [JSONResult objectForKey:@"name"];            
            // Fill the attributes
            // self.email = .. a
         } else {
            // error with login show alert
         }

     } failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
         NSLog(@"error %@", error);
     }];

     // this does not work
     //[[[DCAPIClient sharedClient] operationQueue] waitUntilAllOperationsAreFinished];


     if (self.username == nil) {
         return nil;
     }

     return self;

}

But this does not work, because if (self.username == nil) is called first.

How can i get AFNetworking 2.0 lib run synchronously that i can return response?

DCAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient {
    static DCAPIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [[DCAPIClient alloc] initWithBaseURL:[NSURL URLWithString:DCAPIBaseURLString]];
        _sharedClient.responseSerializer = [AFJSONResponseSerializer serializer];
    });

    return _sharedClient;
}
Community
  • 1
  • 1
q0re
  • 1,401
  • 19
  • 32

3 Answers3

5

Haters gonna hate, but sometimes you just want your HTTP calls to be synchronous.

For that, you could use the AFNetworking-Synchronous library. It adds a convenient category to AFHTTPRequestOperationManager with sync methods such as syncGET and syncPOST which internally use AFNetworking's waitUntilFinished to achieve synchronous calls.

yonilevy
  • 5,320
  • 3
  • 31
  • 27
  • It's not a good practice to make HTTP request synchronous. Note also, that any "synchronous" program which uses blocking calls, can be implemented asynchronously using only asynchronous calls. Once you get familiar with asynchronous programming idioms, it's oftentimes no more "difficult" than the synchronous style ;) It's up to you if you want to learn this technique and unleash its power :) – CouchDeveloper Mar 02 '16 at 09:56
  • 1
    @CouchDeveloper Please refer to the very first sentence of my answer, key word being "sometimes". – yonilevy Mar 04 '16 at 22:21
0

I am not sure why you are doing that way, but one solution would be using callback blocks. Something like this:

- (void)getUserWithUsername: (NSString *) username andPassword: (NSString *) password success:(void (^)(User *user))success failure:(void (^)(NSError *error))failure
{
    NSDictionary *params = @{@"email": username, @"password": password};

    __weak __typeof(self)weakSelf = self;    

    [[DCAPIClient sharedClient] POST:@"login" parameters:params success:^(NSURLSessionDataTask * __unused task, id JSONResult) {

        BOOL errorCode = [JSONResult objectForKey:@"error"];
        if (!errorCode) {
            weakSelf.username = [JSONResult objectForKey:@"name"];

            if (weakSelf.username == nil) {
                failure(nil);
            }else{
                success(weakSelf)
            }
        } else {
            failure(nil);
        }

    } failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
         failure(error);
    }];
}
sash
  • 8,423
  • 5
  • 63
  • 74
0

You should not make an inherently asynchronous method synchronous, but instead make your call-site asynchronous as well.

That is, your method becomes asynchronous and provides a completion handler:

- (void) userWithUsername:(NSString *)username 
                 password:(NSString *)password 
               completion:(completion_handler_t)completion;

where completion_handler_t is a type definition and may be declared in the header file like this:

typedef void (^completion_handler_t)(User*, NSError*);

Note that using a typedef is optional and may make your code more comprehensible.

Then you can use it as follows:

[self userWithUsername:username 
              password:password 
            completion:^(User* user, NSError*error){
    // Check error; do something with user
    ...
}];

You can implement it as shown below:

- (void) userWithUsername:(NSString *)username 
                 password:(NSString *)password 
               completion:(completion_handler_t)completion
{
    NSDictionary *params = @{@"email": username, @"password": password};
    [[DCAPIClient sharedClient] POST:@"login" parameters:params   
        success:^(NSURLSessionDataTask * __unused task, id JSONResult) {
            NSLog(@"JSON %@", JSONResult);
            BOOL errorCode = [JSONResult objectForKey:@"error"];
            if (!errorCode) {
                self.username = [JSONResult objectForKey:@"name"];            
                // Fill the attributes
                // self.email = .. a
                if (completion) {
                    completion(theUser, nil);  // completion(self, nil)?? 
                }
            } else {
                if (completion) {
                    completion(nil, error);
                }
            }

        } failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
             if (completion) {
                 completion(nil, error);
             }
        }];
}
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • I get compiler error : completion:(completion_handler_t)completion expected a type – Bernard Mar 02 '16 at 08:01
  • @Bernard You probably didn't define the `completion_handler_t` type. I edited my answer which includes the type definition. Note that a `typedef` is some sort of _alias_ and is often a more readable version of the actual type. The typedef goes into the header file. Purposefully it's just above the class definition which declares the public method which uses the type. – CouchDeveloper Mar 02 '16 at 09:48