ios Singleton Pattern and completion handlers not being called - ios

So I have a WebImage class which holds images and image url's. I have an ImageLoader class which is designed to only have one instance. It holds a mutable array of WebImages and basically just loads all the images. But for some reason the recursive method which grabs items from the queue and loads their images stops after loading one image, sometimes before. I suspect it has to do with the singleton pattern and that the object is somehow releasing something prematurely. Some code:
My ImageLoader Class:
#property (strong, nonatomic) ConnectionManager *conman;
#property (strong, nonatomic) NSMutableArray *queue;
#end
...
+(ImageLoader*)defaultLoader{
static dispatch_once_t pred;
static ImageLoader *sharedInstance = nil;
dispatch_once(&pred, ^{
sharedInstance = [[ImageLoader alloc] init];
});
return sharedInstance;
}
-(id)init{
self = [super init];
self.queue = [[NSMutableArray alloc] init];
self.conman = [[ConnectionManager alloc] init]; //a manager for loading images and whatnot.
return self;
}
-(void)addToQueue:(WebImage*)webimage{
[self.queue addObject:webimage];
if (!fetching){ //if this method is already running then just let it keep going
[self loadImages];
}
}
-(void)loadImages{
fetching = YES;
if (self.queue.count == 0){ //base case
fetching = NO;
return;
}
WebImage *webimage = [self.queue firstObject];
[self.queue removeObject:webimage]; //move closer to the base case
[self.conman getImageWithURL:webimage.image_URL inBackgroundWithBlock:^(NSString *error, id image) {
if (image){
webimage.image = image;
}else{
NSLog(#"error getting image %#, error: %#", webimage.image_URL, error);
[self.queue addObject:webimage]; //add it to the end of the queue to try again later
}
[self loadImages];
}];
}
and in other classes I just call:
[[ImageLoader defaultLoader] addToQueue:webImage];
EDIT:
something is definitely being released. I don't know what the problem is but I changed the defaultLoader method to just return a pointer to an instance that the appdelegate holds which fixes the problem. Although I would still like to understand why what I have above isn't working.
+(ImageLoader*)defaultLoader{
ImageLoader *loader = ((AppDelegate*)[UIApplication sharedApplication].delegate).imageLoader;
if (!loader) loader = [[ImageLoader alloc] init];
return loader;
/*
static dispatch_once_t prede;
static ImageLoader *sharedInstance = nil;
dispatch_once(&prede, ^{
sharedInstance = [[ImageLoader alloc] init];
});
return sharedInstance;
*/
}

Related

Singleton Object in iOS [duplicate]

This question already has answers here:
Singleton in iOS Objective C doesn't prevent more than one instance
(9 answers)
Closed 7 years ago.
As we know that a singleton object can be instantiated only once and we use singletons in objective C to have a global access to shared resources. We also know that singletons are instantiated using the following method.
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[[self class] alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
});
return sharedPhotoManager;
}
But I can also do this -
PhotoManager *sharedManager = [[PhotoManager alloc] init];
Now this way I can also create another instance of the same singleton class then how come the class is singleton if it is having two instances.
Please clarify.
You can forbid calls of init with such trick:
add - (instancetype)init NS_UNAVAILABLE; definition to your singleton interface.
Instead of [[PhotoManager alloc] init]; use [[[self class] alloc] init];
PhotoManager *sharedManager = [[PhotoManager alloc] init]; won't compile.
There is my example:
#interface SomeSingleton : NSObject
+ (instancetype)sharedInstance;
- (instancetype)init NS_UNAVAILABLE;
#end
#implementation SomeSingleton
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static SomeSingleton *instance;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
return self;
}
#end
As result SomeSingleton *s1 = [SomeSingleton sharedInstance]; works, but SomeSingleton *s2 = [[SomeSingleton alloc] init]; leads to the compile error.
You have singleton behaviour for object only when you use sharedManager: class method. All other ways you instantiate object are not guaranteed to produce singleton object.
Objective-C allows you to do lots of things that may not be intended. Like calling a private method if you know the method name.
If you are obsessed to make sure your class is only used as singleton, maybe something like this would be of use:
static PhotoManager *sharedPhotoManager = nil;
+ (instancetype)sharedManager
{
if (!sharedPhotoManager) {
sharedPhotoManager = [[PhotoManager alloc] init];
}
return sharedPhotoManager;
}
- (instancetype)init {
if (sharedPhotoManager) {
// init method is allowed to return different object
return sharedPhotoManager;
}
self = [super init];
if (self) {
}
return self;
}
When you write
PhotoManager *sharedManager = [[PhotoManager alloc] init];
you are not getting your sharedInstance, you are creating a new one. The way you should use it is
[PhotoManager sharedInstance];
instead of
PhotoManager *sharedManager = [[PhotoManager alloc] init];
When you create a singleton class you has never use alloc init, you always should use sharedInstance method to keep the same instance in your app
By the way... as T_77 suggest you, you should use dispatch_once instead of your implementation to create an instance.
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

Trying to temporarily store objects in an NSMutableArray in a Singleton class for later use in iOS

I have a Singleton class that has two methods:
- (void)saveString:(NSString *)stringObject {
[[[Singleton sharedInstance] stringArray] addObject:stringObject];
}
- (NSArray *)getArrayContents {
return [[Singelton sharedInstance] stringArray];
}
Here is the implementation code of my Singleton class:
static Singleton *sharedSingleton = nil;
+ (Singleton *) sharedInstance {
if (sharedSingleton == nil) {
sharedSingleton = [[super alloc] init];
}
return sharedSingleton;
}
I have two View Controllers (vcA, and vcB) in my application. What I am trying to do is temporarily store the data from vcA, so that the data inside stringArray will be accessible later to vcB.
Here is the code that vcA uses to store the data:
[[Singleton sharedInstance] saveString:stringName];
Later in the lifecycle of the application, vcB calls the Singleton class to retrieve the values from the NSMutableArray:
NSArray *newArray = [[Singleton sharedInstance] getArrayContents];
for (NSString *test in newArray) {
NSLog(#"Here are the contents of the array %#", test);
}
Unfortunately, when I make the call in vcB to print the contents of the Array, there is no output because the array is empty, despite the fact that values are added to the array. What is it I'm doing wrong?
Try this,
to create Singleton
+(Singleton *)sharedSingleton {
static dispatch_once_t once;
static Singleton *sharedSingleton;
dispatch_once(&once, ^{
sharedSingleton = [[self alloc] init];
});
return sharedSingleton;
}
and the init method of singleton class
- (id)init
{
self = [super init];
if (self) {
//#property stringArray
self.stringArray = [[NSMutableArray alloc] init];
}
return self;
}
Other methods of Singleton
- (void)saveString:(NSString *)stringObject {
[self.stringArray addObject:stringObject];
}
- (NSArray *)getArrayContents {
return self.stringArray;
}
I had this problem. My code in the singleton looked like this:
+ (ReportDataList*)sharedDataArray
{
static dispatch_once_t pred;
static ReportDataList *shared = nil;
dispatch_once(&pred, ^{
shared = [[ReportDataList alloc] init];
self.rDetailsArray = [[NSMutableArray alloc] init];
});
return shared;
}
I had incorrectly initialised the array, so it was emptying it when I created a reference to the singleton later in my code. I removed the array initialisation, which is done in the -(id)init method and it worked fine. So, my code then looked like this:
+ (ReportDataList*)sharedDataArray
{
static dispatch_once_t pred;
static ReportDataList *shared = nil;
dispatch_once(&pred, ^{
shared = [[ReportDataList alloc] init];
});
return shared;
}
- (id)init
{
self = [super init];
if (self) {
self.rDetailsArray = [[NSMutableArray alloc] init];
[self initWithDummyValues];
}else{
NSLog(#"problem initialising array list");
}
return self;
}
First off, these two methods should probably use self, not sharedInstance:
- (void)saveString:(NSString *)stringObject {
[[self stringArray] addObject:stringObject];
}
- (NSArray *)getArrayContents {
return [self stringArray];
}
Second, there’s no point in having a getArrayContents method when you already have stringArray, and get as a prefix is usually reserved for methods that take a parameter to be copied into, anyhow.
Third, I don’t see you initializing stringArray anywhere, so unless there’s code missing, it’s nil and it’s staying nil. Maybe try:
+ (Singleton *) sharedInstance {
if (!sharedSingleton) {
sharedSingleton = [[self alloc] init];
sharedSingleton.stringArray = [NSMutableArray new];
}
return sharedSingleton;
}
Assuming stringArray is declared something like:
#property (readwrite, strong) NSMutableArray *stringArray;

Singleton NSMutableDictionary property won't allow setObject:forKey

I have a complete noob question for you. I'm obviously rusty with obj-c. I have a simple shopping cart class implemented as a singleton and just want it to store a single NSMutableDictionary. I want to be able to add objects to this dictionary from anywhere in the app. But for some (I'm sure simple) reason it's just returning null. No error messages.
ShoppingCart.h:
#import <Foundation/Foundation.h>
#interface ShoppingCart : NSObject
// This is the only thing I'm storing here.
#property (nonatomic, strong) NSMutableDictionary *items;
+ (ShoppingCart *)sharedInstance;
#end
ShoppingCart.m:
// Typical singelton.
#import "ShoppingCart.h"
#implementation ShoppingCart
static ShoppingCart *sharedInstance = nil;
+ (ShoppingCart *)sharedInstance
{
#synchronized(self)
{
if (sharedInstance == nil)
sharedInstance = [[self alloc] init];
}
return(sharedInstance);
}
#end
And in my VC I'm trying to set it with:
- (IBAction)addToCartButton:(id)sender
{
NSDictionary *thisItem = [[NSDictionary alloc] initWithObjects:#[#"test", #"100101", #"This is a test products description"] forKeys:#[#"name", #"sku", #"desc"]];
// This is what's failing.
[[ShoppingCart sharedInstance].items setObject:thisItem forKey:#"test"];
// But this works.
[ShoppingCart sharedInstance].items = (NSMutableDictionary *)thisItem;
// This logs null. Specifically "(null) has been added to the cart"
DDLogCInfo(#"%# has been added to the cart", [[ShoppingCart sharedInstance] items]);
}
Thanks
You are never creating a NSMutableDictionary object named items.
You could create it in the init of ShoppingCart.
-(id)init
{
if(self = [super init]) {
_items = [NSMutableDictionary dictionary];
}
return self;
}
or in sharedInstance
+ (ShoppingCart *)sharedInstance
{
#synchronized(self)
{
if (sharedInstance == nil)
sharedInstance = [[self alloc] init];
sharedInstance.items = [NSMutableDictionary dictionary];
}
return(sharedInstance);
}
I might also add it's better (arguably) to set up your shared instance like so:
static ShoppingCart *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
instance.items = [NSMutableDictionary dictionary];
});
return instance;

iOS singleton shared instance and loading remote json data

This is my situation:
// data.m
#property (nonatomic, strong) NSMutableArray *jsonData;
+ (Data *)sharedData
{
static Data *sharedData;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
_sharedData = [[Data alloc] init];
});
return _sharedData;
}
- (id)init
{
self = [super init];
if (self)
{
[self clear];
[self load]; // here i start loading my remote JSON data and fill self.jsonData
}
return self;
}
and I have
// main.m
[Data sharedData].jsonData; // this return nil
the jsonData return nil , i think because the request to the server is not done yet..
How can I do for wait till the remote request is done?
Thanks.
EDIT:
here's my load method
[[ApiClient sharedClient] loadDataWithSuccess:^(NSMutableArray *data)
{
self.jsonData = data;
}
fail:^(NSString *errorMessage)
{
NSLog(#"%#", errorMessage);
}];
loadDataWithSuccess returns void.

Objective C - sample Singleton implementation

*I definitely need a break... cause was simple - array was not allocated... Thanks for help. Because of that embarrassing mistake, I flagged my post in order to delete it. I do not find it useful for Users ;) *
I have just tried to create a singleton class in iOS, but I probably I am making a mistake. Code (no ARC is a requirement):
#import "PeopleDatabase.h"
#import "Person.h"
#import <Foundation/Foundation.h>
#interface PeopleDatabase : NSObject{objetive
NSMutableArray* _arrayOfPeople;
}
+(PeopleDatabase *) getInstance;
#property (nonatomic, retain) NSMutableArray* arrayOfPeople;
#end
--
#implementation PeopleDatabase
#synthesize arrayOfPeople = _arrayOfPeople;
static PeopleDatabase* instance = nil;
-(id)init{
if(self = [super init]) {
Person* person = [[[Person alloc] initWithName:#"John" sname:#"Derovsky" descr:#"Some kind of description" iconName:#"johnphoto.png" title:Prof] retain];
[_arrayOfPeople addObject:person];
NSLog(#"array count = %d", [_arrayOfPeople count]); // <== array count = 0
[person release];
}
return self;
}
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return(instance);
}
-(void)dealloc {
[instance release];
[super dealloc];
}
#end
When invoking getInstance like here:
PeopleDatabase *database = [PeopleDatabase getInstance];
NSLog(#"Adress 2: %p", database);
Address 2 value the same value as in getInstance.
The standard way of creating a singleton is like...
Singleton.h
#interface MySingleton : NSObject
+ (MySingleton*)sharedInstance;
#end
Singleton.m
#import "MySingleton.h"
#implementation MySingleton
#pragma mark - singleton method
+ (MySingleton*)sharedInstance
{
static dispatch_once_t predicate = 0;
__strong static id sharedObject = nil;
//static id sharedObject = nil; //if you're not using ARC
dispatch_once(&predicate, ^{
sharedObject = [[self alloc] init];
//sharedObject = [[[self alloc] init] retain]; // if you're not using ARC
});
return sharedObject;
}
#end
Check this apple doc on how to create singleton instance:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
You appear to be missing your braces for that if statement. As written, the only thing you do different when instance == nil is emit a log message.
After web reading and personal practicing, my current singleton implementation is:
#interface MySingleton
#property myProperty;
+(instancetype) sharedInstance;
#end
#implementation MySingleton
+ (instancetype) sharedInstance
{
static dispatch_once_t pred= 0;
__strong static MySingleton *singletonObj = nil;
dispatch_once (&pred, ^{
singletonObj = [[super allocWithZone:NULL]init];
singletonObj.myProperty = initialize ;
});
return singletonObj;
}
+(id) allocWithZone:(NSZone *)zone
{
return [self sharedInstance];
}
-(id)copyWithZone:(NSZone *)zone
{
return self;
}
this is a thread safe implementation and avoids the risk to create new objects by calling "alloc init" on your class. Attributes initialization has to occur inside the block, not inside "init" override for similar reasons.
This is an error that can be avoided by some disziplined convention which is to always use curly brackets followed by if and else.
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return(instance);
}
If instance is nil then the very next statement and only that is executed. And that is the nslog and not the allocation. Then instance is allocated anyway, regardless wether it was used before or not. This will provide you with a new singleton on each call. BTW that causes a leak.
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil) {
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
}
return(instance);
}
But this error came in while debugging. It may confuse you but does not solve your original problem. Please add an alloc and init and retain for _arrayOfPeople as well.
-(id)init{
if(self = [super init]) {
Person* person = [[[Person alloc] initWithName:#"John" sname:#"Derovsky" descr:#"Some kind of description" iconName:#"johnphoto.png" title:Prof] retain];
_arrayOfPeople = [[[NSMutableArray alloc] init] retain]; //dont forget the release
[_arrayOfPeople addObject:person];
NSLog(#"array count = %d", [_arrayOfPeople count]); // <== array count = 1 !!!
[person release];
}
return self;
}
In your code _arrayOfPeople is nil and addObject is sent to nil which does not cause an abort but does not do anything either. Then count is sent to nil wich returns 0/nil.
in this function +(PeopleDatabase *)getInstance i think you need to place curly Braces correctly : like this
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
{
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return instance ;
}
}

Resources