16

I am using AFNetworking with the singleton model suggested in their example.

+ (SGStockRoomHTTPClient *)sharedClient
{
    static SGStockRoomHTTPClient *_sharedClient = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        NSString *baseUrlString = [[NSUserDefaults standardUserDefaults] stringForKey:@"server_root_url_preference"];
        _sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:baseUrlString]];
    });

    return _sharedClient;
}

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (!self) {
        return nil;
    }
    [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [self setDefaultHeader:@"Accept" value:@"text/html"];
return self;
}

Initialization is done with a baseURL taken from the user defaults.

My problem is that the baseURL property is read-only. If the user goes to settings and changes the baseURL user default, how can I change it in my client?

Another similar case I have with a need to change the baseURL is an API which requires multiple calls and logic to determine to the right baseURL. And the base url can still change while the app is running (e.g. user changes networking environment requiring a change from local connection to 3G connection via external proxy server.).

I see why the baseURL property is read-only: there are things like networkReachabilityStatus that run in the background and are tied to that setting. This said, it seems fairly easy to have a setBaseURL method that stops monitoring, changes the value, then starts monitoring again...

I guess my design is not right, should I give-up the singleton in this case and re-create the client each time the baseURL should change?

caiman
  • 405
  • 4
  • 10
  • 1
    To clarify, the question is quite specific to AFNetworking: how should one use AFHTTPClient with APIs that don't have a fixed base url but something the user (or program logic) should specify? The example with user defaults is a good one, the above code works but the user has to quit and re-launch the app to activate the changes. – caiman Jul 25 '12 at 11:49
  • Good question! I have similar problem. I should switch server to a secondary server when the primary server is down. And the switch back when the primary when this is up and running again. – Johan Karlsson Oct 29 '12 at 13:18

2 Answers2

23

The class AFHTTPClient is designed to work with a single base URL. This is why it is readonly.

Here are some solutions if you have more than one base URL:

  • in your AFHTTPClient subclass override the property qualifier. Place this inside the @interface: @property (readwrite, nonatomic, retain) NSURL *baseURL;

  • Don't use AFHTTPClient at all

  • Create multiple instances of AFHTTPClient

  • When you create HTTP operations you can override the baseURL. Just set the full URL in the path instead of a relative path.

Felix
  • 35,354
  • 13
  • 96
  • 143
  • 4
    Thanks for your suggestions! Just overriding and making the property readwrite is dangerous because the class keeps track of the reachability of the service referenced by baseURL. But it could be overwritten with careful unsubscription of the monitoring, then re-activation. I ended up moving away from the singleton model, and holding the AFHTTPClient subclass object in my main controller. There I can overwrite it with a new fresh object when I need to change the baseURL. I agree with you on options 2, 3 and 4 ;-) – caiman Jul 25 '12 at 15:56
  • 5
    I've found @phix23 option 3 works best for me. Singletons tend to be more pain than their worth a lot of the time. Just creating a new instance of the AFHTTPClient each time you need one has made the most sense for my apps where I've used AFNetworking. It's fairly pain free that way. – Matt Long Jul 26 '12 at 17:49
  • 1
    I hate nuggets of wisdom like @MattLong's, because they always end up forcing me to refactor my code for the better! (But seriously, thank you. Using the non-singleton approach with a changing base URL is absolutely the way to go.) – Ben Kreeger Apr 01 '13 at 22:55
  • Are there any disadvantages of using multiple instances of the AFHTTPClient: I guess if you only read data from your backend it is safe and if you use the multiple instances to write to different backends is safe, too. Am I right? – NewYearsEve Dec 17 '13 at 13:20
  • Are there any examples on how to do the first option in Swift? – Hackmodford Aug 31 '15 at 19:53
0

Hope this helps you.

@interface EFApiClient : AFHTTPSessionManager
@property (nonatomic,assign)BOOL isTestEnvironment ;
+ (instancetype)sharedMClient;
@end


@implementation EFApiClient
+ (instancetype)sharedMClient
{
    if ([EFApiClient sharedClient].isTestEnvironment) {
        return [EFApiClient sharedTestClient] ;
    }
    else{
        return [EFApiClient sharedClient];
    }
}   

+ (instancetype)sharedClient
{
    static EFApiClient *_sharedMClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedMClient = [[EFApiClient alloc] initWithBaseURL:[NSURL URLWithString:@"https://xxx.xxx.com"]];
        [EFApiClient clientConfigWithManager:_sharedMClient];
    });
    return _sharedMClient;
}
+ (instancetype)sharedTestClient
{
    static EFApiClient *_sharedMClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedMClient = [[EFApiClient alloc] initWithBaseURL:[NSURL URLWithString:@"https://test.xxx.xxx.com"]];
        [EFApiClient clientConfigWithManager:_sharedMClient];
    });
    return _sharedMClient;
}

+ (void)clientConfigWithManager:(EFApiClient *)client
{
    AFSecurityPolicy* policy = [[AFSecurityPolicy alloc] init];
    [policy setAllowInvalidCertificates:YES];
    [policy setValidatesDomainName:NO];
    [client setSecurityPolicy:policy];
    client.requestSerializer = [AFHTTPRequestSerializer serializer];
    client.responseSerializer = [AFHTTPResponseSerializer serializer];
    //client.requestSerializer.HTTPMethodsEncodingParametersInURI = [NSSet setWithArray:@[@"POST", @"GET", @"HEAD"]];
    client.responseSerializer = [AFJSONResponseSerializer serializer];
    client.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/x-javascript",@"application/json", @"text/json", @"text/html", nil];
}
@end
Pang
  • 9,564
  • 146
  • 81
  • 122
stack Stevn
  • 114
  • 6
  • A direct solution is welcome, but please ensure you add context around it so your fellow users will understand how it solves the problem – Pratibha Jan 31 '18 at 02:58