Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton pattern with parameter

in my iPhone application, I'm using a subclass of AFHTTPClient to access a rest web service. I want all my requests to be handled by one instance of my API client so I use a singleton pattern.

This works fine when the service is running only on once URL. I can use a constant value to set the URL.

Now, in the final version of the application, each app will actually talk to another service that will be installed in the corporate network.

So I will be getting the service URL from a remote configuration. Is the singleton pattern still a good choice here? How am I supposed to parameterise it if the URL could actually even change during the runtime of the app ?

cheers

#import "FooAPIClient.h"
#import "AFJSONRequestOperation.h"

static NSString * const kFooAPIBaseURLString = @"http://192.168.0.1";

@implementation FooAPIClient

+ (instancetype)sharedClient {
    static FooAPIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
    });

    return _sharedClient;
}

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (!self) {
        return nil;
    }

    [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [self setDefaultHeader:@"Accept" value:@"application/json"];

    return self;
}

@end
like image 664
Jan Avatar asked Sep 03 '13 15:09

Jan


2 Answers

This could be the solution. Instead of subclassing the AFHTTPClient, just set it as property and re-instantiate it if the URL changes:

#import "FooAPIClient.h"
#import "AFJSONRequestOperation.h"
#import "AFHTTPClient.h"

static NSString * const kFooAPIBaseURLString = @"http://192.168.0.1";

@interface FooAPIClient ()
@property AFHTTPClient * httpClient;
@end

@implementation FooAPIClient

+ (instancetype)sharedClient {
    static FooAPIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
    });

    return _sharedClient;
}

- (id)initWithBaseURL:(NSURL *)url {

    self = [super init];
    if (!self) {
        self.httpClient = [self setupClientForURL:url];
    }
    return self;
}

-(AFHTTPClient*) setupClientForURL:(NSURL*) url {
    AFHTTPClient * httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
    [httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [httpClient setDefaultHeader:@"Accept" value:@"application/json"];
    return httpClient;
}

#pragma mark - RemoteConfigurationDelegate

-(void) apiURLChanged:(NSURL*) newURL {
    self.httpClient = [self setupClientForURL:newURL];
}


#pragma mark - Public

-(void) consumeAPI:(CompletionBlock) completion {
    [self.httpClient getPath:@"foo" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        if(completion) {
            completion(responseObject, nil);
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        if(completion) {
            completion(nil, error);
        }
    }];
}



@end
like image 150
Jan Avatar answered Oct 04 '22 17:10

Jan


The singleton pattern doesn't have to be a straitjacket.

This lesson I learned from Cocoa Touch. There are several classes in the framework that use shared instances, but allow you the flexibility of creating your own instances if you need them. NSNumberFormatter, NSDateFormatter, NSBundle, NSFileManager and many many others are examples of classes where you can create your own instances if you need them.

In your case, I would have two class methods that return instances:

+ (instancetype)sharedClient {
    static FooAPIClient *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
    });

    return instance;
}

static FooAPIClient *FooSharedCorporateInstance;

+ (instancetype)sharedCorporateClient {
   @syncronized (FooSharedCorporateInstance) {
       return FooSharedCorporateInstance;
   }
}

+ (void)setSharedCorporateClientWithURL:(NSURL *)URL {
   @syncronized (FooSharedCorporateInstance) {
       FooSharedCorporateInstance = [[self alloc] initWithBaseURL:URL];
   }
}

As a side benefit, this forces separate class and instance responsibilities that tend to blur in singleton classes.

like image 34
Jeffery Thomas Avatar answered Oct 04 '22 16:10

Jeffery Thomas