Cocoa bindings between NSTableView and NSMutableArray refuse to update - objective-c

Ok, I'm very new to Obj-C and Cocoa, but I'm sure my bindings here are correct. I've been googling, searching stack overflow and have checked my values again and again.
So, here are my bindings:
They connect to this class:
#interface TMMaddMangaWindowDelegate : NSWindowController {
...
}
...
#property (copy) NSMutableArray* mangaList;
...
#end
#implementation TMMaddMangaWindowDelegate
...
#synthesize mangaList;
// - (NSMutableArray*) mangaList {
// NSLog(#"mangaList was called!");
// return mangaList;
//}
//- (void) setMangaList:(NSMutableArray *) input{
// NSLog(#"setMangaList was called!");
// [mangaList autorelease];
// mangaList = [input retain];
//}
...
-(void) populateList:(NSArray*)list{
NSMutableArray* newArray = [[NSMutableArray alloc] initWithArray:list];
NSLog(#"Populating List.");
for(NSXMLNode* node in list){
[newArray addObject:node.description];
//[[self mutableArrayValueForKey:#"mangaList"] addObject:node.description];
//NSLog(#"%#", node.description);
}
[self setMangaList:newArray];
[[self chapterListDownloadIndicator] stopAnimation:self];
}
As you can see, I also tried the mutableArrayValueForKey approach, which yielded nothing. I know for a fact mangaList is gaining items.
I've been working on this for a while, and probably made a stupid mistake.
Thanks in advance.

It looks like you are changing mangaList behind the array controller's back. Whenever you are making a change to mangaList you should first call [self willChangeValueForKey:#"mangaList"]; and then [self didChangeValueForKey:#"mangaList"]; once you are done with the change. this will let the array controller know it needs to take a look at what changed.

It turns out that the problem was that the window did not have the class identity of Files Owner set to my window controller/delegate. The moment I set this the window sprang to life.
That problem was also preventing my NSProgressIndicator from working.

Related

NSImage memory leak

First of all: I already searched on google and SO for solutions - none worked.
I've got an application which loads the artwork of the current iTunes track and displays it; this is stored in a NSImage instance, among some other variables, in a class:
#interface infoBundle : NSObject
#property (strong) NSImage *track_artwork;
#property (weak) NSString *track_title;
#property (weak) NSString *track_album;
#property (weak) NSString *track_artist;
#end
Then, an instance of this class is created:
-(infoBundle*)returnInfoBundle {
infoBundle* tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
And later used:
-(void)iTunesDidChange {
infoBundle* tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
That's eating up ~2MB (Cover size, I'd guess) per call of iTunesDidChange.
I already tried:
[tmpBundle autorelease];
[tmpBundle release];
[tmpBundle dealloc];
tmpBundle = nil;
and, after that didn't help:
- Enabling ARC.
=> Why is this eating up memory, although the object (tmpbundle) should get removed?
=> How may I achieve leak-less NSImage usage?
Thanks for any tips/suggestions/solutions :)
Issue
You will have a memory leak if you create your object on your method and not release it inside that method or you have to reference it when you pass it as a parameter by reference : Passing arguments by value or by reference in objective C
Your problem is that you are creating an instance of infoBundle two times, and when you are initializing another instance of it, you are leaving the first one without reference, so it remains in memory, and without connection to remove it (memory leak).
Solution
To make your things easier you should create an instance of your object
#implementation
{
infoBundle* tmpBundle;
}
Use it where ever you need it
-(infoBundle*)returnInfoBundle
{
tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
-(void)iTunesDidChange
{
tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
And when you are finished with that object dealloc will automatically release it if you add it to dealloc method:
- (void) dealloc
{
[tmpBundle release];
tmpBundle = nil;
}
Hope it helps! :)
Just modifiy this line :-
infoBundle* tmpBundle = [[[infoBundle alloc]init]autorelease];
I can’t tell from your code what you are doing in [imageView setImage:tmpbundle.track_artwork]; but you may be having the same problem I had.
I was using
self.imageToDisplay = [UIImage imageNamed:pictFileName];
and kept getting leaks. I switched to
self.imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
and they went away.
According to the documentation for imageNamed,
This method looks in the system caches for an image object with the
specified name and returns that object if it exists… If you have an
image file that will only be displayed once and wish to ensure that it
does not get added to the system’s cache, you should instead create
your image using imageWithContentsOfFile:. This will keep your
single-use image out of the system image cache, potentially improving
the memory use characteristics of your app.
It sounds like you have either the same or a similar issue.

My NSString is determined to equal null, infuriating

This has been bugging me all night, It doesn't make any sense. This function returns whatever it's supposed to. EG, the issueName.
-(id)initWithIssue:(NSString *)string {
self = [super initWithNibName:nil bundle:nil];
if (self) {
NSString *thing = string;
issueName = [[NSString alloc]initWithString:thing];
NSLog(#"The issue name = %#", issueName);
}
return self;
}
However if I try to access 'issueName' in the viewDidLoad: nothing, it's equal to null no matter what I do. I've tried cleaning, setting a custom setter, switching between a property or a Ivar... ect. What's so infuriating is that this string just disappears at this point in the programe.
What the hell is going on, this is infuriating.
Edit
This the the entire code that is relevant. And how I started off.
Dot h file:
#interface BFPaidAreaViewController : UITabBarController <BFNewsTableViewControllerDelegate> {
NSString *issueName;
}
-(id)initWithIssue:(NSString *)string;
Dot m file:
-(id)initWithIssue:(NSString *)string {
self = [super init];
if (self) {
// PLPiper I had it that way before, because I was fiddling out of frustration
issueName = [[NSString alloc] initWithString:string];
NSLog(#"This is Called, the issue name is equal to = %#", issueName);
}
return self;
}
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(#"The issue = %#", issueName);
}
I'm calling the view controller like so:
BFPaidAreaViewController *pavc = [[BFPaidAreaViewController alloc]initWithIssue:#"test"];
This will log:
This is Called, the issue name is equal to = test
The issue = (null)
New Edit
Found the problem. It's a UITableViewController. Strange, when I change it's class to a UIViewController it works. Is this a bug or just normal behaviour? But more pressing, how to I get round this limitation?
(Just to explain what I've done UI wise, the UITabBarController is in a modal View. This works fine with a UIViewController.)
God Awful Fix
-(id)initWithIssue:(NSString *)string {
self = [super initWithNibName:nil bundle:nil];
if (self) {
NSString *thing = string;
issueName = [[NSString alloc]initWithString:thing];
NSLog(#"The issue name = %#", issueName);
}
[self viewDidLoad];
return self;
}
Makes me feel dirty. But it will have to do for now, I can continue. If anyone can think of a solution please tell. Sorry about my feistiness, it was incredibly frustrating listening to people say, 'what the hell is this?? what is issueName?? an ivar??' when it was really implicit in the question.
Okay, first of all, replace:
self = [super initWithNibName:nil bundle:nil]; // Unneeded nil arguments
with:
self = [super init]; // Equivalent method, less processing involved.
Secondly, replace:
NSString *thing = string;
issueName = [[NSString alloc]initWithString:thing];
with just:
_issueName = [[NSString alloc] initWithString:string];
If issueName is a property (and you haven't #sythesized it to anything else) its representation should be _issueName.
The above fixes are more or less just make the code more succinct. The issue is probably with the code in viewDidLoad: (See below).
Now you can initialise your Issue object, and use the following code to display the issue name:
// Init:
Issue *myIssue = [[Issue alloc] initWithIssue:#"Example Issue"];
// Log:
NSLog(#"%#", myIssue.issueName);
And the log should show:
Example Issue
can you try this:
make the issueName a property, like
#property (strong, nonatomic) NSString *issueName;
then use it like this,
-(id)initWithIssue:(NSString *)string {
self = [super initWithNibName:nil bundle:nil];
if (self) {
NSLog(#"The string = %#", string);
self.issueName = string;
NSLog(#"The issue name = %#", issueName);
}
return self;
}
if you are using the automated synthetized property (i.e not declaring the #synthentize manually for the issueName), then your iVar will be called _issueName instead of issueName
what do you get from the above code ?
I find this somewhat curious. You call [super initWithNibName:nil bundle:nil]. This leads me to believe that this might be a subclass of NSViewController. If you init an NSViewController like this, barring some other, pretty non-standard stuff, -viewDidLoad probably won't get called because there's no NIB to be loaded (because you passed nil to super). But clearly you're setting a breakpoint in -viewDidLoad so it's getting called (on something). This makes me think that you have this class specified in a XIB somewhere as a File's Owner or as a NIB-loaded custom object. If that's the case, it leads me to believe that the instance you're init-ing and the instance on which -viewDidLoad is being called aren't the same instance. You can confirm this for yourself by putting NSLog(#"self: %p", self); in each method and seeing whether they are the same or different.
If the instance that is getting a call to -viewDidLoad is NIB-loaded, then your init method won't be called. Instead it will use -initWithCoder
If you can elaborate on the situation here (i.e. how this is getting instantiated, are there any XIBs involved, etc), I will edit my answer to provide more help, but I don't think there's enough information here to be truly helpful.
I feel your frustration. Assuming standard behavior, any of the suggestions here should have worked. This only reinforces my suspicion that these are not the same instance (between -initWithIssue and -viewDidLoad.

How to keep data in a NSMutableArray

AAA.m:
- (void)keepCurrentArray:(id)object
{
_currentTest=[[NSMutableArray alloc]init];
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
Class BBB.m is passing objects to class AAA.
Right now if i'm passing X to the above method so the array will be: one,two,X . Then i'll send it Y and the array will be one,two,Y instead of what i want to accomplish: one,two,x,one,two,y.
Is that because I'm alloc and init _currentTest every time? How can I solve it?
Update:
I had a few suggestions on how to solve this and none of them worked for me. I've created a new project with just the code in the answers and i'm still getting the same result when I try to add the second object i get: one, two, test instead of one,two,test,one,two,test
Yes, it's because that you're alloc and init-ing every time you run that method. Instead, put _currentTest = [[NSMutableArray alloc] init]; in AAA.m's init method.
AAA.m
-(id)init
{
if ((self = [super init]))
_currentTest = [[NSMutableArray alloc] init];
return self;
}
- (void)keepCurrentArray:(id)object
{
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
_currentTest=[[NSMutableArray alloc]init]; in a method is never a good thing!!!
As per naming convention it seems to be a property to the AAA Class. So for property, the alloc+init should be either in init or awakeFromNib. So that if is initialized just once.
However in some situations init is called more than once then your previous values are lost and new set are added.
So what you can do is make another class and put this _currentTest Array there and make it static and use it here. I hope this will work fine. And make sure in the init method of that class it is initialized just once, as :
//**this is not compiled and checked may contains typo and errors**
#implementation Storage
static NSMutableArray *yourStaticArray;
-(id)init{
self = [super init];
if (self) {
if (!yourStaticArray) {
yourStaticArray=[NSMutableArray new];
}
}
return self;
}
-(void)addYourStaticArray:(NSString *)val{
[yourStaticArray addObject:val];
}
-(NSArray *)yourStaticArray {
return yourStaticArray ;
}
#end
Well you need to have a property for that _currentTest if you want to be able to keep it around between method call.
Put this in your .h file
#property (nonatomic, copy) NSMutableArray * currentTest;
And this in hour .m file
- (NSMutableArray *)currentTest
{
if (!_currentTest)
_currentTest = [[NSMutableArray alloc] initWithCapacity:11];
return _currentTest;
}
- (void)keepCurrentArray:(id)object
{
[self.currentTest addObject:#"one"];
[self.currentTest addObject:#"two"];
[self.currentTest addObject:object];
NSLog(#"My Array is:%#", self.currentTest);
}
I Just try the code you've put on drop box and it's working exactly as it is suppose to, the array keeps it's value and everything,
BUT
Exactly as it is suppose to is not what you are trying to achieve
Your problem is not in AAA.m, your problem is in BBB.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ViewController *kios = [ViewController new];
[kios keepCurrentArray:#"Test"];
[kios keepCurrentArray:#"Test2"];
}
I took the liberty of adding the #"test2" to the code you've send. If you run it you will see that your array still exist when the second call is made.
The REAL problem here is that you are creating a NEW ViewController each time. A brand new one, it is normal that it is empty (clean), it's a new one.
If I buy a note pad monday and fill it up, I don't expect when I'm buying an other one on friday to be already fill with the stuff I've wrote on monday in the previous one.
But this is exactly that behaviour that you are expecting from your ViewController.
You need to store your NSMutableArray in an other object that doesn't
get destroy and created over and over again.
This is happening because you are creating a new array every time that your method is called. Basically, you need to see if it has already been created, and only create it if needed. You can change your method to:
- (void)keepCurrentArray:(id)object
{
if (!_currentTest)
{
_currentTest=[[NSMutableArray alloc]init];
}
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
EDIT:
In addition to the above problem, you also have this code which needs to be corrected (comments removed):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ViewController *kios = [ViewController new];
[kios keepCurrentArray:#"Test"];
}
This code creates a new instance of ViewController every time that you click on a row in the table. Because you are creating a new instance instead of reusing the old one, you start with an empty array each time. In order to keep adding to the same array, you need to keep using the same view controller.
In order to do this, you need to add a declared property to your .h file, similar to your currentTest declared property:
#property (strong,nonatomic) ViewController *kios;
Then, change your action so that you only create a new view controller if needed (the first time) and then reuses it after that:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!_kios)
{
_kios = [ViewController new];
}
[_kios keepCurrentArray:#"Test"];
}

Objective-C setter is never called

I'm trying to make an NSMutableArray usable in multiple classes. I'm having an issue with defining and using a custom setter, for some reason, even though I call my setter, it is never executed (I have an NSLog set up in the method). Here is all of the relevant code:
AppDelegate.h
#interface TouchTrackerAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *completeLines;
}
#property (nonatomic, retain, setter = setCompleteLines:, getter = getCompleteLines) NSMutableArray *completeLines;
-(NSMutableArray*) getCompleteLines;
-(void) setCompleteLines:(NSMutableArray *) newLines;
AppDelegate.m
#implementation TouchTrackerAppDelegate
-(NSMutableArray*) getCompleteLines {
return self.completeLines;
}
-(void) setCompleteLines:(NSMutableArray *)newLines {
NSLog(#"gets here");
if (completeLines != newLines) {
[completeLines release];
completeLines = [newLines retain];
}
NSLog(#"completeLines global count: %i",[completeLines count]);
}
View.h
#import "TouchTrackerAppDelegate.h"
#interface TouchDrawView : UIView {
NSMutableDictionary *linesInProcess;
NSMutableArray *completeLines;
TouchTrackerAppDelegate *navigationDelegate;
}
#end
View.m*
#import "TouchTrackerAppDelegate.h"
- (id)initWithCoder:(NSCoder *)c
{
[super initWithCoder:c];
linesInProcess = [[NSMutableDictionary alloc] init];
completeLines = [[NSMutableArray alloc] init];
return self;
}
- (void)viewDidLoad {
navigationDelegate = (TouchTrackerAppDelegate *)[[UIApplication sharedApplication] delegate];
}
-(void)endTouches:(NSSet *)touches
{
if([EditModeSingleton isEditMode]){
for(UITouch *t in touches){
NSValue *key = [NSValue valueWithPointer:t];
Line *line = [linesInProcess objectForKey:key];
if(line){
[completeLines addObject:line];
[linesInProcess removeObjectForKey:key];
[navigationDelegate setCompleteLines:completeLines];
NSLog(#"completeLines count: %i", [completeLines count]);
}
}
[self setNeedsDisplay];
}
else {NSLog(#"in Play mode");}
}
The problem arises in my View.m when I call '[navigationDelegate setCompleteLines:completeLines];'. As far as I can tell, this never executes. I'm also not sure if my setter method is correct in the way I'm trying to pass the array from my view to the app delegate for use in other classes. If there is a better way of doing that, I'd appreciate some help.
Thank you!
If you're not entering that function, there's really only one solid possibility:
navigationDelegate is nil. Verify this by logging or asserting it just before sending the message to it in endTouches and then figure out why.
Cnage:
[linesInProcess removeObjectForKey:key];
[navigationDelegate setCompleteLines:completeLines];
To:
[linesInProcess removeObjectForKey:key];
NSAssert(navigationDelegate != nil, #"navigationDelegate is nil");
[navigationDelegate setCompleteLines:completeLines];
For future reference/help (and to answer your question in comments) -
Breakpoint basics in brief:
Set breakpoints at or before the line where you suspect your code breaks/fails/behaves-unexpectedly. Run your program in debug...
If the breakpoint gets hit: Examine both the call-stack and variable-values in the various Debugging panes in Xcode for clues.
Or if the breakpoint is never hit: Go back up a step in your function calls and set a breakpoint there.
If nothing else, breakpoints can narrow your issue down by process of elimination and help you ask better questions that get answered faster. =)
Although StackOverflow helped you track down this problem pretty fast, you can save yourself a lot of time and frustration in the future if you make use of breakpoints.
In this case, setting a breakpoint at or before the line: [navigationDelegate setCompleteLines:completeLines]; would have revealed navigationDelegate was nil. Then you repeat: set a breakpoint at or before navigationDelegate is assigned and re-run it. When this breakpoint didn't get hit, you would then realize your problem is something other than your setter! =)
You might still have had to ask "why isn't viewDidLoad being called?" but with part of the confusion already solved by you, your answer would have arrived much faster! Hope that helps you in the future~

NSMutableDictionary Singleton issue

I am coding Objective-C using the Cocos2D framework, and I have a singleton used for multiple purposes. One new purposes is to get and set character's "states" which are strings. I've recently made an NSDictionary for this purpose, but I have issues with the program freezing up when a method inside the singleton is called.
Here's the singleton code. I'm just leaving in the character state stuff:
.h
#interface ExGlobal : NSObject {
NSArray *charStates_keys;
NSArray *charStates_objects;
NSMutableDictionary *charStates;
}
#property(nonatomic, retain) NSMutableDictionary *charStates;
+(ExGlobal*)sharedSingleton;
- (NSString *)charState:(NSString *)charName;
- (void)set_charState:(NSString *)value forCharName:(NSString *)charName;
#end
.m
#import "ExGlobal.h"
#implementation ExGlobal
#synthesize charStates;
static ExGlobal* _sharedSingleton = nil;
+(ExGlobal*)sharedSingleton {
#synchronized([ExGlobal class]) {
if (!_sharedSingleton) {
[[self alloc] init];
}
return _sharedSingleton;
}
return nil;
}
+(id)alloc {
#synchronized([ExGlobal class]) {
NSAssert(_sharedSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedSingleton = [super alloc];
return _sharedSingleton;
}
return nil;
}
-(id)init {
self = [super init];
if (self != nil) {
// initialize stuff here
exitName = #"ruinsSkyMid";
sceneChangeKind = #"reborn";
charStates = [[NSMutableDictionary alloc] init];
charStates_keys = [NSArray arrayWithObjects:#"Feathers", #"Hummus", nil];
charStates_objects = [NSArray arrayWithObjects:#"at wall", #"with Feathers", nil];
charStates = [NSMutableDictionary dictionaryWithObjects:charStates_objects forKeys:charStates_keys];
}
return self;
}
- (NSString *)charState:(NSString *)charName{
NSString *value = [charStates objectForKey:charName];
return value;
}
- (void)set_charState:(NSString *)charState forCharName:(NSString *)charName{
[charStates setObject:charState forKey:charName];
}
- (void)dealloc {
//I know it doesn't get called, but just in case
[charStates release];
[super dealloc];
}
#end
It's unclear to me what exactly the issue is when it freezes. When this happens, all I get in the console is:
Program received signal: “EXC_BAD_ACCESS”.
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.5 (8L1)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
Previous frame inner to this frame (gdb could not unwind past this frame)
Previous frame inner to this frame (gdb could not unwind past this frame)
Which I'm sure doesn't help finding the issue. I found if I redefine charStates_keys, charStates_objects and charStates inside both the charState and set_charState methods, it seems to work without freezing, except set_charState does not change the state.
It isn't freezing, it is crashing. Hence the EXC_BAD_ACCESS. It looks like your Xcode installation is borked, too, as the two messages following should not happen.
Note that methods should not have _s in the name; not a cause of the problem, but a comment on following convention.
You aren't retaining charStates and that is likely the cause of the crash.
Not an answer as such but I didn't have enough space in the comments field above to post this, but it might be useful.
As bbum already said, your lack of retaining charStates is likely the problem.
If you are confused about when to retain and not retain objects there's a really good book called "Learn Objective-C on the Mac" and I know it's a Mac book but most of it applies to iPhone too. On page 171 of chapter 9 (Memory Management) it talks about the "Memory Management Rules" and how if you are confused about when to retain or not then you don't understand the simple rules of Objective C memory management.
Essentially if you create an object using new, alloc or copy, then the retain count is automatically set to 1 so the object is retained and does not require you to retain it and will require a subsequent release to deallocate.
If you create the object any other way then the object will be an autoreleased object.
Obviously these rules only apply within the standard iOS libraries and can't necessarily be applied to third party libraries.
I recommend anyone who doesn't fully understand memory management in Objective C read this book. I found highly enlightening even for my iPhone work.
Hope that helps/.