iOS singleton shared instance and loading remote json data - ios

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.

Related

NSCache is returning null after restarting project

I'm working with NSCache in Objective-C and Cocoa for iOS. Every time I restart the project, the getCacheRecommend call returns null and I expect it to return a value.
#import <Foundation/Foundation.h>
#class ASJsonDiscoverModel;
#interface ASUserCache : NSObject
+ (ASUserCache *)sharedInstance;
- (void)clear;
- (void)setCacheRecommend:(ASJsonDiscoverModel *)discover;
- (ASJsonDiscoverModel *)getCacheRecommend;
ASJsonDiscoverModel is my custom object class.
#import "ASUserCache.h"
#interface ASUserCache ()
#property (nonatomic,strong) NSCache *cache;
#end
#implementation ASUserCache
+ (ASUserCache *)sharedInstance
{
__strong static ASUserCache *cache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [[ASUserCache alloc] init];
});
return cache;
}
- (instancetype)init
{
if (self = [super init]) {
_cache = [[NSCache alloc] init];
}
return self;
}
- (void)setCacheRecommend:(ASJsonDiscoverModel *)discover
{
NSString *key = #"channelRecommend";
[_cache removeObjectForKey:key];
[_cache setObject:discover forKey:key];
}
- (ASJsonDiscoverModel *)getCacheRecommend
{
NSString *key = #"channelRecommend";
return [_cache objectForKey:key];
}
- (void)clear
{
if (_cache) {
[_cache removeAllObjects];
}
}
- (NSString *)keyforUserID:(NSString *)userID
{
return [NSString stringWithFormat:#"**%#",userID];
}

#sychronized alternative in class method

I want to have a method that will either create a new object or return an existing one based on an identifier string.
This is what I have:
#implementation MyObject {
}
#synthesize data = _data;
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
// these methods are the only ones to be used for managing the MyObject life cycle
+ (NSMutableDictionary *)objectsDict
{
static NSMutableDictionary *map = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = [NSMutableDictionary new];
});
return map;
}
+ (MyObject *)getRefrenceForId:(NSString *)identifier
{
return [[MyObject objectsDict] objectForKey:identifier];
}
+ (MyObject *)newRefrenceWithId:(NSString *)identifier
{
MyObject *obj;
#synchronized (self) {
obj = [[MyObject objectsDict] objectForKey:identifier];
if (obj == nil) {
obj = [[MyObject alloc] init];
obj.identifier = identifier;
[[MyObject objectsDict] setObject:obj forKey:identifier];
NSLog(#"new instance of MyObject created with id:%#",identifier);
}
}
return obj;
}
+ (MyObject *)newRefrenceWithId:(NSString *)identifier andClassType:(Class)classType
{
MyObject *obj;
#synchronized (self) {
obj = [[MyObject objectsDict] objectForKey:identifier];
if (obj == nil) {
obj = [[MyObject alloc] initWithClassType:classType andId:identifier];
[[MyObject objectsDict] setObject:obj forKey:identifier];
NSLog(#"new instance of MyObject created with id:%# of ClassType :%#",identifier,NSStringFromClass(classType));
}
}
return obj;
}
+ (void)deleteInstance:(NSString *)identifier
{
#synchronized (self) {
[[MyObject objectsDict] removeObjectForKey:identifier];
}
}
+ (void)clearAllMyObjectsFromMap
{
#synchronized (self) {
[[MyObject objectsDict] removeAllObjects];
}
}
Is there a better way to do this? I hear that #synchronized is very CPU expensive but GCD concurrent queues can't be used in class methods...
UPDATE: Where should the global sync queue be .. in init? That's an instance method so I doesn't work there...
You can use GCD:
Create a global "sync queue" using dispatch_once in a free function defined in module scope:
static dispatch_queue_t get_sync_queue() {
static dispatch_once_t onceToken;
static dispatch_queue_t sync_queue;
dispatch_once(&onceToken, ^{
sync_queue = dispatch_queue_create("my.sync_queue", DISPATCH_QUEUE_CONCURRENT);
});
return sync_queue;
}
Use this queue with dispatch_sync and the block modifying your object:
+ (MyObject *)newRefrenceWithId:(NSString *)identifier
{
__block MyObject *obj;
dispatch_barrier_sync(get_sync_queue(), {
obj = [[MyObject objectsDict] objectForKey:identifier];
if (obj == nil) {
obj = [[MyObject alloc] init];
obj.identifier = identifier;
[[MyObject objectsDict] setObject:obj forKey:identifier];
NSLog(#"new instance of MyObject created with id:%#",identifier);
}
});
return obj;
}
Method newRefrenceWithId: now is fully thread-safe.
Edit:
Alternatively, you can also use a concurrent sync_queue and use dispatch_barrier_sync when reading and writing in the block (as in your case)
Use dispatch_barrier_async when writing the object(s).
In case you just need to read and return the shared object's state you should use dispatch_sync - which allows for concurrent reads.

ios Singleton Pattern and completion handlers not being called

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;
*/
}

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;

setting / retrieving data from singleton

Apple rejected our app siting that page loads times between tabs was too long. Before I was simply calling a webview to display content managed through a CMS. Now we have implemented JSON and I am tring to preload the 5 tabs' data using the singleton design pattern. I can't seem to set the singleton value as I see in examples. On to the code:
header.h
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController {
NSString *someProperty;
...
}
#property (nonatomic, retain) NSString *someProperty;
+ (id)sharedManager;
#property (strong, nonatomic) NSString* tab3data;
#end
Implementation.m
//Create a seperate thread to download JSON thread
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
//Set JSON URL
#define GWDiOSURL [NSURL URLWithString:#"http://m.web.org/cms_mapper.php"]
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize someProperty;
- (id)init {
if (self = [super init]) {
someProperty = #"Default Property Value";
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"First", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"first"];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
FirstViewController *sharedManager = [FirstViewController sharedManager];
NSLog(#"Toll%#",sharedManager);
// Do any additional setup after loading the view, typically from a nib.
//Get JSON and load into 'data'
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:GWDiOSURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
}
//Begin JSON Data Parsing and loading
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
//Parse JSON
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// Load JSON into a dictionary
NSDictionary *tabData = [json objectForKey:#"mapper"];
// Get Tab3 data from dictionary
NSDictionary *tab3 = [tabData objectForKey:#"#tab3_content"];
// Load Tab3 data into a string from dictionary
NSString *html = [NSString stringWithFormat:#"%#",tab3];
// Verify content via counsel
//NSLog(#"Second Data:%#",html);
// Load content into webView
[webView loadHTMLString:html baseURL:nil];
[FirstViewController sharedManager].someProperty = #"asdf";
}
+ (id)sharedManager {
static FirstViewController *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
I need to set the value of html to the singleton. The follow line
[FirstViewController sharedManager].someProperty = #"asdf";
produces this error
Propery 'someProperty' not found on object of type 'id'.
I have been trying to get this whole process to work for days.. I appreciate the insight.
Well, your class method, sharedManager, returns an id. Try returning FirstViewController* in sharedManager.
+ (FirstViewController *)sharedManager;

Resources