Subclassing a singleton not working as expected - objective-c

I'm writing a class that communicates with an API. I started by creating a SessionManager:
#interface SessionManager : AFHTTPSessionManager
+ (id)sharedManager;
#end
static NSString *const kBaseURL = #"https://myapi.com";
#implementation SessionManager
- (id)init {
self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]];
if(!self) return nil;
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.requestSerializer = [AFJSONRequestSerializer serializer];
return self;
}
+ (id)sharedManager {
static SessionManager *_sessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sessionManager = [[self alloc] init];
});
return _sessionManager;
}
#end
I wrote several classes that extend this SessionManager. One for every entity in the API: RestaurantManager, StreetManager and AreaManager. What happens, though, is that when I use one of them and then another, it will still use the first one.
NSArray *restaurants = [[RestaurantManager sharedManager] getRestaurants];
// restaurants contains all the restaurants
NSArray *streets = [[StreetsManager sharedManager] getStreets];
// streets still contains all the restaurants
Any ideas as to how to fix this?

You should override + (id)sharedManager methods in subclasses. Otherwise, they go to the same sharedManager method and interact with the same static SessionManager variable.

Related

Objective-c singleton creation

Code sample 1:
+ (MyClass *)sharedInstance{
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
code sample 2
+ (MyClass *)sharedInstance{
static MyClass *sharedInstance = nil;
if (!sharedInstance) {
sharedInstance = [[MyClass alloc] init];
}
return sharedInstance;
}
Are there any differences in the result of the above code samples ?
The first one is better as it prevents multiple threads from creating multiple instances of the Singleton class given the necessary circumstances are met.
E.g: Take the 2nd example.
+ (MyClass *)sharedInstance{
static MyClass *sharedInstance = nil;
if (!sharedInstance) {
sharedInstance = [[MyClass alloc] init];
}
return sharedInstance;
}
Suppose Theread1 executes the following LOC and then gives the handle to Thread2
if (!sharedInstance)
Now Thread2 executes the following LOC and then hands over the handle to Thread1
sharedInstance = [[MyClass alloc] init];
Now, since the if condition was met first by Thread1, Thread1 will continue and execute the following LOC as well
sharedInstance = [[MyClass alloc] init];
Now, you have 2 instances of MyClass created.
Therefore, the 1st approach is best. It will make sure the block within
dispatch_once(&onceToken, ^{
});
gets executed only Once!
However, if you ONLY access the Singleton via the Main Thread (UI thread), then the second scenario will also work.
Using dispatch_once() is faster and it performs something only once so if you access it twice from different threads there won't be any problems.

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
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
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.

Force deletion of singleton in test code that was created with dispatch_once

I'm writing some unit test code for a model class and want to simulate the behavior of the class during app exit and relaunch. I could achieve this by deleting and re-allocing the object, however its a singleton and thus the following code doesn't have the desired effect:
+ (id) sharedInstance
{
static MyModel *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^ {
singleton = [[MyModel alloc] initSharedInstance];
});
return singleton;
}
// Test code:
MyModel* gModel = [MyModel sharedInstance];
... tests
gModel = nil;
gModel = [MyModel sharedInstance];
... more tests
Is there a neat solution so I can delete/recreate the object?
static MyModel *singleton = nil;
static dispatch_once_t onceToken;
+ (instancetype) sharedInstance
{
dispatch_once(&onceToken, ^ {
if (singleton==nil){
singleton = [[MyModel alloc] initSharedInstance];
}
});
return singleton;
}
+(void)setSharedInstance:(MyModel *)instance {
onceToken = 0;
singleton = instance;
}
Nil it:
[MyModel setSharedInstance:nil];
Note that you can also set it to an arbitrary class to mock it.
[MyModel setSharedInstance:someMock];
sure something like this would be fine for unit testing, you can turn it off for prod:
static MyModel *singleton = nil;
+ (id) sharedInstance
{
if(!singleton)
{
singleton = [self new];
}
return singleton;
}
+ (void)resetSingleton
{
[singlelton release];
singleton = nil;
}

changing AFNetworking baseURL

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?
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.
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

How to load an Objective C Singleton using NSCoding?

I am writing a basic game, I am using a GameStateManager which is a singleton and handles all state management, such as saves, loads, delete methods.
I have a separate singelton which handles all the Game stuff. The game object is inside the game state manager so I can save it out using NSCoding and Archiving.
When I save the state there appears to be no issues and the Game object (singleton) is saved properly.
However, when I try to load the state (by relaunching the app), the game object is always null.
Strangley, if I remove the singleton properties and make it a standard class, this issue goes away and I can load the class and all its properties without any major issues.
In summary, I do this:
GameStateManager = Singleton, handles all game state management (load, save) and has a game object (game)
Game = Singleton which handles things within the game and has NSCoding protocol employed.
Saving the game state with the game object is fine, the object is clearly there.
Loading the game state seems to make the game object null. It should be there, but for some reason it never loads it.
If I remove all the properties that make the game class a singelton and make it a normal class, the issue seems to go away.
I think it has something to do with the fact that Game is never init'd, but this does not make sense because I can get Game to load when it has no singleton properties.
Code now follows.
// GameStateManager.m
-(void)saveGameState
{
CCLOG(#"METHOD: saveGameState()");
self.lastSaveDate = [NSDate date];
NSMutableData *data;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
NSKeyedArchiver *archiver;
BOOL result;
data = [NSMutableData data];
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[archiver encodeObject:self.game forKey:#"game"];
[archiver finishEncoding];
result = [data writeToFile:archivePath atomically:YES];
[archiver release];
}
-(void)loadGameState
{
CCLOG(#"METHOD: loadGameState()");
NSData *data;
NSKeyedUnarchiver *unarchiver;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
data = [NSData dataWithContentsOfFile:archivePath];
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// Customize unarchiver here
self.game = [unarchiver decodeObjectForKey:#"game"];
self.lastSaveDate = [unarchiver decodeObjectForKey:#"lastSaveDate"];
[unarchiver finishDecoding];
[unarchiver release];
CCLOG(#"Last Save Date = %#", self.lastSaveDate);
NSLog(#"game = %#", self.game);
}
// END OF GAMESTATEMANAGER
// -------------------------
// Game.h
#interface Game : NSObject
<NSCoding>
{
NSMutableArray *listOfCities;
NSMutableArray *listOfColors;
NSMutableArray *listOfPlayers;
}
#property (nonatomic, retain) NSMutableArray *listOfCities;
#property (nonatomic, retain) NSMutableArray *listOfColors;
#property (nonatomic, retain) NSMutableArray *listOfPlayers;
+(Game *) sharedGame;
//
// Game.m
// This is a cut-down version of the game object
// Game is a singelton
// The listOfCities, etc are arrays
//
#implementation Game
SYNTHESIZE_SINGLETON_FOR_CLASS(Game)
#synthesize listOfCities, listOfPlayers;
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.listOfCities = [aDecoder decodeObjectForKey:#"listOfCities"];
self.listOfPlayers = [aDecoder decodeObjectForKey:#"listOfPlayers"];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
// Archive objects
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
[aCoder encodeObject:self.listOfCities forKey:#"listOfCities"];
[aCoder encodeObject:self.listOfPlayers forKey:#"listOfPlayers"];
}
#end
My question is, how do I successfully save and load an Objective C singelton using NSCoding, and the Archiver?
Edit;
I have tried:
// In the GameStateManager
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.lastSaveDate = [[decoder decodeObjectForKey:#"lastSaveDate"] retain];
self.game = [[decoder decodeObjectForKey:#"game"] retain];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[encoder encodeObject:self.game forKey:#"game"];
}
and
// In Game.m
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
Since you're dealing with a singleton, you only ever want a single instance of the class to exist at any time. So you will want to archive and unarchive that single instance only.
Consider this code (assumes ARC is enabled):
#interface Singleton : NSObject <NSCoding>
+ (id)sharedInstance;
#property (strong, nonatomic) NSString *someString;
#end
#implementation Singleton
+ (id)sharedInstance {
static Singleton instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Singleton alloc] init];
});
return instance;
}
#pragma mark - NSCoding implementation for singleton
- (id)initWithCoder:(NSCoder *)aDecoder {
// Unarchive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[instance setSomeString:[aDecoder decodeObjectForKey:#"someStringKey"]];
return instance;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Archive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[aCoder encodeObject:[instance someString] forKey:#"someStringKey"]];
}
#end
Using this template, you can archive/unarchive the singleton, like this:
// Archiving calls encodeWithCoder: on the singleton instance.
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[Singleton sharedInstance]] forKey:#"key"];
// Unarchiving calls initWithCoder: on the singleton instance.
[[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"key"]];
try this in your initWithCoder method :
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
Easiest way:
1. Load singleton:
+ (AppState *)sharedInstance
{
static AppState *state = nil;
if ( !state )
{
// load NSData representation of your singleton here.
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"appStateData"];
if ( data )
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[AppState alloc] init];
}
}
return state;
}
2. Save singleton on a disk
- (BOOL)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
// now save NSData to disc or NSUserDefaults.
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"appStateData"];
}
3. Implement NSCoding methonds:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
Done!
I'm using a hybrid approach encompassing answers by Eric Baker and skywinder.
+ (GameStateManager *)sharedInstance {
static GameStateManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
if (data) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
instance = [[GameStateManager alloc] init];
}
});
return instance;
}