Singleton not updating variable immediately - objective-c

I have a singleton here is the header file:
#import <Foundation/Foundation.h>
#interface Shared : NSObject
{
NSString *messages;
}
#property (nonatomic, retain) NSString *messages;
+ (Shared*)sharedInstance;
#end
Here is the implementation:
#import "Shared.h"
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
+ (Shared*)sharedInstance
{
if ( !sharedInstance)
{
sharedInstance = [[Shared alloc] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if ( self )
{
messages = [[NSString alloc] init];
}
return self;
}
#end
The problem is when the I use
[Shared sharedInstance].messages = someVariable;
I can use
NSLog([Shared sharedInstance].messages);
and it shows the right output, but when i check from another class, NSLog doesn't show any output. I have the NSLog in the viewDidLoad method of another class, so when I click a button to go to the next view, it should output the value of the string, but it only works the second time. If the variable is set to dog, first it outputs nothing, then when I close the view and try again, it outputs dog. however, if I then change the variable to cat, it will output dog, and on the next attempt, output cat. I want it to update immediately, rather than remain one behind all the time.
EDIT: Here's the code from the other classes
This particular section is from a view controller class in the method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Omitted, just preparing the DB, and emptying the array.
if ([db open])
{
FMResultSet *s = [db executeQueryWithFormat:#"SELECT ShabadID FROM Shabad WHERE Gurmukhi LIKE %#", currentLine];
while ([s next])
{
lineID = [s intForColumn:#"ShabadID"];
}
s = [db executeQueryWithFormat:#"SELECT Gurmukhi, ShabadID FROM Shabad WHERE ShabadID LIKE %i", lineID];
while ([s next])
{
//NSLog([s stringForColumn:#"Gurmukhi"]);
[paragraphArray addObject:[s stringForColumn:#"Gurmukhi"]];
}
Text = #"";
for (int i = 0; i<[paragraphArray count]; i++)
{
Text = [Text stringByAppendingFormat:#"%#\n", [paragraphArray objectAtIndex:i]];
}
[Shared sharedInstance].messages = Text;
}
Then in the another class, where I want the text to appear, in the viewDidLoad method,
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog([Shared sharedInstance].messages);
UITextView *myUITextView = [[UITextView alloc] initWithFrame:CGRectMake(0,30,310,450)];
myUITextView.text = [Shared sharedInstance].messages;
myUITextView.textAlignment = NSTextAlignmentCenter;
myUITextView.textColor = [UIColor blackColor];
myUITextView.font = [UIFont fontWithName:#"GurbaniLipiLight" size:24];
[myUITextView setBackgroundColor:[UIColor clearColor]];
myUITextView.editable = NO;
myUITextView.scrollEnabled = YES;
[ScrollerView addSubview:myUITextView];
}
Sure the NSLog doesn't show up right, but neither does the text in the textview, it does the same thing the NSLog does.

There is an assumption here about what order things happen in that's not quite right. Assuming there's a segue involved in this, didSelectRowAtIndexPath: is called after the new view controller is prepared but before it's displayed. Moving code to viewWillAppear: or viewDidAppear: delays execution until after the calling controller has set new data.
The other approach for communication between controllers that use a segue, is to use prepareForSegue: in the first controller to set data that the second controller needs. That way it should be available when the view is loaded.

Related

nsmutabledictionary returning null when accessed outside of class

I have searched around a bit and not found an answer to my question. I am a beginner to objective-c and I am currently experimenting around with dictionaries. I have a class called "SETestBank" that creates 3 dictionaries of images and then adds them to a large dictionary. I am doing it this way because down the line I may add more smaller dictionaries. I've created a specific "accessBank" method to pull objects out via other classes.
SETestBank.m
#import "SETestBank.h"
#implementation SETestBank
#synthesize mathBankA, mathBankB, mathBankC, mathTest;
- (id)init
{
[self createBankA];
[self createBankB];
[self createBankC];
[self createTest];
return 0;
}
- (NSMutableDictionary *)createBankA
{
mathBankA = [[NSMutableDictionary alloc] init];
for (int i=0; i < 11; i++) {
NSString *aKey = [NSString stringWithFormat:#"%da", i];
NSString* imageName = [NSString stringWithFormat:#"%da.png",i];
[mathBankA setObject:imageName forKey:aKey];
NSLog(#"%#", aKey);
}
return mathBankA;
}
//same occurs to generate bankB and bankC
- (NSMutableDictionary *)createTest
{
mathTest = [[NSMutableDictionary alloc] init];
[mathTest addEntriesFromDictionary:mathBankA];
[mathTest addEntriesFromDictionary:mathBankB];
[mathTest addEntriesFromDictionary:mathBankC];
return mathTest;
}
- (NSString *)accessBank:(NSString *)accessor
{
NSString *img = [mathTest objectForKey:accessor];
return img;
}
#end
now this code seems to work fine. The dictionaries are created and console logs display the correct keys and object filenames (the images are all in the project and properly named)
However, when I access it in my view controller and try to apply an image to a UIImageView I get nothing.
SEViewController:
- (void)viewDidLoad
{
SETestBank *mathTest = [[SETestBank alloc] init];
UIImage *img = [UIImage imageNamed:[mathTest accessBank:#"1a"]];
NSString *test = [mathTest accessBank:#"1a"];
NSLog(#"%#", test);
[imageView setImage:img];
[self.view addSubview:imageView];
[super viewDidLoad];
}
the log here simply returns null along with a "CUICatalog: Invalid asset name supplied: (null), or invalid scale factor: 1.000000" error which I gather is from trying to assign null to an image. I'm stumped on this one. If I hardcode the image to display "1a.png" rather than trying to access it by key it works fine but that is not what I'm trying to accomplish. imageView is connected to a UIImageView in storyboard and the view controller is set to use SEViewController as it's class.
Thanks for any help!
In the init method, you return a 0, which means nothing (nil)
- (id)init
{
[self createBankA];
[self createBankB];
[self createBankC];
[self createTest];
return 0;
}
Change this to
- (id)init
{
if (self = [super init]) {
[self createBankA];
[self createBankB];
[self createBankC];
[self createTest];
}
return self;
}
may solve your problem.
Your init in SETestBank is returning 0, so your mathTest object is nil when you do
SETestBank *mathTest = [[SETestBank alloc] init];

Expanding my singleton class

I have use a singleton class that contained just one passed value. I then tried to add another one.
#import <Foundation/Foundation.h>
#interface GlobalValueContainer : NSObject {
NSString *passedText;
NSString *myPassedPictureName;
}
#property (nonatomic, strong) NSString* passedText;
#property (nonatomic, strong) NSString* myPassedPictureName;
+ (GlobalValueContainer *) sharedStore;
#end
#import "GlobalValueContainer.h"
#implementation GlobalValueContainer;
#synthesize passedText;
#synthesize myPassedPictureName;
static GlobalValueContainer *sharedStore = nil;
+ (GlobalValueContainer *) sharedStore {
#synchronized(self){
if (sharedStore == nil){
sharedStore = [[self alloc] init];
}
}
return sharedStore;
}
#end
From the first view I then try to set the myPassedPictureName
-(IBAction)setPicture:(id)sender{
myPicture = #"Hus";
GlobalValueContainer* localContainer = [GlobalValueContainer sharedStore];
localContainer.myPassedPictureName = myPicture;
}
and on the second view I want to set an imageview with that name (+png that is)
- (void)viewDidLoad
{
[super viewDidLoad];
//Store* myStore = [Store sharedStore];
GlobalValueContainer* localContainer = [GlobalValueContainer sharedStore];
myPassedPictureName = localContainer.myPassedPictureName;
myPicture.image = [UIImage imageNamed:myPassedPictureName];
whatFile.text = myPassedPictureName;
//object.imageView.image = [UIImage imageNamed:#"downloadedimage.png"];
}
the picture doesnt show. I have also tried to add a UIlabel and set the string that should have been passed. But it also turns out blank.
When I pass text using the "passedText" it works fine. When I added the second NSString, nothing happens?.
First things first. Can anyone see what´s wrong (still learning obj c here :) and, is it the correct way I try to manipulate an UIImageView. I want to use the myPassedPictureName to set a picture on a number of UIViews depending on the button being pressed.
Looking forward to your input.
I'm quite sure using singleton for passing value like this is not a good idea.Singleton is not designed for passing value,but for doing something.So you can not use Property like
sharedManager.passValue
Here has some good discussion about singleton.
When should you use the singleton pattern instead of a static class?
and
What should my Objective-C singleton look like?
So I suggest write it like this:
#import <Foundation/Foundation.h>
#interface GlobalValueContainer : NSObject {
}
+ (id) sharedManager;
-(NSString*)myText;
-(NSString*)myPictureName;
#end
#import "GlobalValueContainer.h"
#implementation GlobalValueContainer;
static GlobalValueContainer *sharedManager = nil;
+ (id) sharedManager {
#synchronized(self){
if (sharedManager == nil){
sharedManager = [[self alloc] init];
}
}
return sharedManager;
}
-(NSString*)myText
{
return #"your text";
}
-(NSString*)myPictureName
{
return #"yourPictureName.png";
}
#end
If variable "myPassedPictureName" is URL to file then you cannot use this method for it:
[UIImage imageNamed:myPassedPictureName];
you should use
NSData *data = [[NSData alloc] initWithContentsOfURL:myPassedPictureName];
UIImage *image = [[UIImage alloc] initWithData:data];

Possible memory leak in UIViewController with UITableView

I have a UIViewController thats presented modally. When I watch the memory allocations Instrument, the memory usage increases when the view is presented, but when it's exited the memory isn't released.
If I keep opening and closing the view, the memory just keeps getting higher.
Instruments doesn't report a memory leak!
What could be causing this? The View Controller code is below (I've skipped the didSelectRow code).
Dealloc is always called.
EDIT - I am using ARC
.h
#import <UIKit/UIKit.h>
#class OutlineTextUILabel;
#interface StoreViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
int starCount;
NSMutableArray *_singleUseArray;
NSMutableArray *_fullUseArray;
}
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (weak, nonatomic) IBOutlet OutlineTextUILabel *starCountLbl;
- (IBAction)exitBtnPressed:(id)sender;
.m
#import "StoreViewController.h"
#import "NSUserDefaults+MPSecureUserDefaults.h"
#import "PowerUpCell.h"
#import "OutlineTextUILabel.h"
#import "PowerUpSingleton.h"
#import "PowerUp.h"
#define kPrefsNumberOfStars #"numberOfStars"
#interface StoreViewController ()
#end
#implementation StoreViewController
#synthesize tableView = _tableView;
#synthesize starCountLbl;
#pragma mark View Methods
- (void)viewDidLoad
{
[super viewDidLoad];
// Display star count
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
BOOL valid = NO;
starCount = [prefs secureIntegerForKey:kPrefsNumberOfStars valid:&valid];
if (!valid) {
NSLog(#"Stars Tampered With!");
self.starCountLbl.text = #"Err";
} else {
self.starCountLbl.text = [NSString stringWithFormat:#"%d",starCount];
}
// Tableview setup
CGRect frame2 = CGRectMake(0, 0, 320, 40);
UIView *footer = [[UIView alloc] initWithFrame:frame2];
footer.backgroundColor = [UIColor clearColor];
self.tableView.tableFooterView = footer;
self.tableView.opaque = NO;
self.tableView.backgroundView = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
if (![[PowerUpSingleton sharedList] refreshArray]) {
NSLog(#"Error, %s",__FUNCTION__);
} else {
[self performSelectorOnMainThread:#selector(workOutSingleUseToDisplay) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(workOutFullUseToDisplay) withObject:nil waitUntilDone:YES];
[self.tableView reloadData];
}
}
- (void)workOutSingleUseToDisplay
{
_singleUseArray = [[NSMutableArray alloc] init];
for (PowerUp *pu in [[PowerUpSingleton sharedList] sharedArray]) {
if (!pu.fullUnlock) {
[_singleUseArray addObject:pu];
}
}
}
- (void)workOutFullUseToDisplay
{
_fullUseArray = [[NSMutableArray alloc] init];
for (PowerUp *pu in [[PowerUpSingleton sharedList] sharedArray]) {
if (pu.prefFullName != nil) {
[_fullUseArray addObject:pu];
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
}
- (void)viewDidUnload {
[self setTableView:nil];
[self setStarCountLbl:nil];
[super viewDidUnload];
}
#pragma mark TableView Setup Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return #"Single Use";
} else if (section == 1) {
return #"Use forever";
}
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return [_singleUseArray count];
} else if (section == 1) {
return [_fullUseArray count];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier;
if (indexPath.section == 0) {
cellIdentifier = #"powerUpCellSingleUse";
} else if (indexPath.section == 1) {
cellIdentifier = #"powerUpCell";
}
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (indexPath.section == 0) {
PowerUp *tmpPU = [_singleUseArray objectAtIndex:indexPath.row];
cell.descriptionLbl.text = tmpPU.displayName;
int cost = tmpPU.costSingle;
cell.costLbl.text = [NSString stringWithFormat:#"%d",cost];
if (cost > starCount) {
cell.costLbl.textColor = [UIColor redColor];
} else {
cell.costLbl.textColor = [UIColor blueColor];
}
int howMany = tmpPU.numberOwned;
cell.howManyLbl.text = [NSString stringWithFormat:#"%d",howMany];
} else if (indexPath.section == 1) {
PowerUp *tmpPU = [_fullUseArray objectAtIndex:indexPath.row];
cell.descriptionLbl.text = tmpPU.displayName;
int cost = tmpPU.costFull;
cell.costLbl.text = [NSString stringWithFormat:#"%d",cost];
if (cost > starCount) {
cell.costLbl.textColor = [UIColor redColor];
} else {
cell.costLbl.textColor = [UIColor blueColor];
}
if (tmpPU.fullUnlock) {
cell.costLbl.textColor = [UIColor greenColor];
cell.costLbl.text = #"---";
}
}
return cell;
}
#pragma mark -
- (IBAction)exitBtnPressed:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc
{
NSLog(#"%s",__FUNCTION__);
self.tableView = nil;
self.starCountLbl = nil;
}
#end
EDIT -------------
Something seems not to be right. I've added an NSLog to the cell alloc, and it's never called, even though the cells are created!
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(#"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
EDIT July 1st ------
I've added a Navigation controller and now use push instead of modal and this problem is still here.
I've taken heap shots with Instruments by moving back and forward between views a few times and it seems maybe the cells are still hanging around, as this screenshot shows gesture recogniser still around from a previous load of the view.
It is because you used your IBOutlets as weak, instead of using strong.
I actually believe this is a flaw in the XCode environment, as it should warn you of this kind of behavior.
As a best practice, I would suggest letting XCode generate the IBOutlets by dragging the views to the code in the Interface Builder, to avoid such annoying pitfalls.
Looks like you've already found some ways around this, but just in case this helps:
1) Make sure you haven't got Zombies turned on while you're debugging, as this causes objects to hang around after you think they should be dealloc-ed (Edit scheme -> Run -> Diagnostics).
2) You're using ARC and so I assume storyboards or at least prototype UITableView cells in your storyboard/NIB? If so, then the reason your NSLog() below never gets called is because the dequeueReusableCellWithIdentifier call knows to create cells from these prototype cells via the defined cellIdentifier. Pretty handy.
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(#"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
You have to rely on the UITableView to manage this cache of UITableViewCells, and release them appropriately. So it's possible they are just hanging around because your UITableView isn't being released (though I think you're saying it is).
I'm not sure if I got the anwer, but there's something strange in your code :
your are using weak properties :
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (weak, nonatomic) IBOutlet OutlineTextUILabel *starCountLbl;
But according to the doc (search "weak"), weak propoerty is quite similar to assign.
In you dealloc, you have
self.tableView = nil;
self.starCountLbl = nil;
I'm pretty sure that the generated setter of these properties doesn't release them at all !
But if you declare your properties like :
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#property (nonatomic, retain) IBOutlet OutlineTextUILabel *starCountLbl;
the generated setter would be like
(void)setTableView(UITableView *)newTableView {
[tableView release];
if(newTableView != nil)
tableView = [newTableView retain];
}
And your properties would be released.
[EDIT]
In your viewWillAppear method, have you printed out to see how often you move through your else clause. To me it seems that you call your workOutSingleUseToDisplay and workOutFullUseToDisplay methods. Each time you call those, you are allocating _singleUseArray and _fullUseArray. Just because you move in and out of a view, does not mean it calls the dealloc, or that it will auto-release your current arrays. What I think you are seeing is that when you move out of your view, it does not release those two arrays, but does try to reallocate them.
[ORIGINAL]
Well, in your viewDidLoad, you perform an alloc. In your dealloc, I don't see a [footer release]. This may be your leak!!! Nor do I see releasing of your _singleUseArray or _fullUseArray arrays
At least, use the Leaks instrument to monitor memory leaks. The Allocations instrument won't actually show the memory leaks. If you run Analyze, you will see the lines that are potentially causing the leaks.
This is your code:
PowerUpCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSLog(#"new cell");
cell = [[PowerUpCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
You see, cell is not going to be nil... This is stated in the API doc for dequeueReusableCellWithIdentifier::
Return Value
A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Anyway, if there are leaks, maybe they are very much caused by:
_singleUseArray = [[NSMutableArray alloc] init];
and
_fullUseArray = [[NSMutableArray alloc] init];
When you declared
NSMutableArray *_singleUseArray;
NSMutableArray *_fullUseArray;
I think, by default both were assigned with a __strong qualifier. I'm not really sure but this could be the real cause of the problem. How about declaring this instead?
NSMutableArray * __weak _singleUseArray;
NSMutableArray * __weak _fullUseArray;
Also, before declaring
_singleUseArray = [[NSMutableArray alloc] init];
and
_fullUseArray = [[NSMutableArray alloc] init];
how about assigning it first to nil to remove the previous reference?
_singleUseArray = nil;
_singleUseArray = [[NSMutableArray alloc] init];
and
_fulUseArray = nil;
_fullUseArray = [[NSMutableArray alloc] init];

Populating an IKImageBrowserView

I am currently working on building up a view which shows icons, and text labels to the right of the icons. After some searching, I've decided that an IKImageBrowserView is most suitable. As such, I've gone ahead and created my IKImageBrowserView and set it's data source as follows:
// Setup image browser view
IKImageBrowserView *browser = [IKImageBrowserView new];
// Build our datasource delegate
MyDataStore *ds = [[MyDataStore alloc] init];
[browser setDelegate:ds];
[browser setDataSource:ds];
NSImage *image = // . . . File path
NSString *imageID = // . . . Filename
IKBBrowserItem *item = [[IKBBrowserItem alloc] initWithImage:image imageID:imageID];
[[ds images] addObject:item];
I've also created my data source, implementing the two required methods in the protocol:
import "MyDataStore.h"
#implementation MyDataStore
- (id) init {
if ([super init] != nil) {
images = [NSMutableArray new];
}
return self;
}
- (NSUInteger) numberOfItemsInImageBrowser:(IKImageBrowserView *) aBrowser {
return [images count];
}
- (id) imageBrowser:(IKImageBrowserView *) aBrowser itemAtIndex:(NSUInteger)index {
return [images objectAtIndex:index];
}
- (NSMutableArray *) images {
return images;
}
#end
However, when I try and add an item (as shown in the first block of code), nothing shows up in my IKImageBrowserView. I suspect I'm doing something glaringly incorrect with this view. Anyone know what that may be?

NSTableView don't display data

I have data in NSMutableArray and I want to display it in NSTableView, but only the number of cols has changed.
This use of NSTableView is based on tutorial here.
FinalImageBrowser is IBOutlet to NSTableView.
#implementation AppController
NSMutableArray *listData;
- (void)awakeFromNib {
[FinalImageBrowser setDataSource:self];
}
- (IBAction)StartReconstruction:(id)sender
{
NSMutableArray *ArrayOfFinals = [[NSMutableArray alloc] init]; //Array of list with final images
NSString *FinalPicture;
NSString *PicNum;
int FromLine = [TextFieldFrom intValue]; //read number of start line
int ToLine = [TextFieldTo intValue]; //read number of finish line
int RecLine;
for (RecLine = FromLine; RecLine < ToLine; RecLine++) //reconstruct from line to line
{
Start(RecLine); //start reconstruction
//Create path of final image
FinalPicture = #"FIN/final";
PicNum = [NSString stringWithFormat: #"%d", RecLine];
FinalPicture = [FinalPicture stringByAppendingString:PicNum];
FinalPicture = [FinalPicture stringByAppendingString:#".bmp"];
[ArrayOfFinals addObject:FinalPicture]; // add path to array
}
listData = [[NSMutableArray alloc] init];
[listData autorelease];
[listData addObjectsFromArray:ArrayOfFinals];
[FinalImageBrowser reloadData];
NSBeep(); //make some noise
NSImage *fin = [[NSImage alloc] initWithContentsOfFile:FinalPicture];
[FinalImage setImage:fin];
}
- (int)numberOfRowsInTableView:(NSTableView *)tv {
return [listData count];
}
- (id)tableView:(NSTableView *)tv objectValueFromTableColumn:(NSTableColumn *)tableColumn row:(int)row {
return (NSString *)[listData objectAtIndex:row];
}
#end
When the StartReconstruction end the number of cols have changed right, but they're empty. When I debug app, items in listData is rigth.
I'm guessing you forgot to connect the FinalImageBrowser outlet to the table view. That would mean your setDataSource: message is to nil, which would leave the table view without a data source.
You don't need to send that message anyway—you can set the data source in the nib. Remove your awakeFromNib implementation and connect the table view's dataSource outlet to your data source object in IB, as well as the FinalImageBrowser outlet to the table view (also in IB).