Cocoa bindings - getting "deallocated while key value observers were still registered" warnings - macos

I have been using Cocoa bindings in my NSTableCellView inside my NSOutlineView, but supplying my own data into the outlineView objectValueForTableColumn:byItem: method (i.e. not using an NSArrayController for the content). It works fine, till I delete a row from the table, where I get the managed object and delete it from the context, and in the NSManagedObjectContextWillSaveNotification observer, I update my data model and remove the row from the outlineView. But when I do this, I get this warning:
An instance 0x10d922890 of class Log_Log_ was deallocated while key
value observers were still registered with it. Observation info was
leaked, and may even become mistakenly attached to some other object.
Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger.
Here's the current observation info ....
Setting the breakpoint doesn't really help identify the problem. I know something is observing the properties on Log (the tableCellView that is doing the binding) but how do I clean it up when the row is removed from the outline view? I override Log's didTurnIntoFault and I do get a non-nil value in self.observationInfo, but I don't think that can help identify what is the observing object that needs to be removed as an observer at this point.
I'm not sure why this is happening, and what is the best way to debug this to find the offending object that is the 'bindings' observer.

NSTableCellViews are saved for reuse, so your zombie-esque cellView is holding onto the objectValue. When you delete the row, you might want to nil out the objectValue in the cellviews.
Might make sense to use the NSTableViewDelegate didRemoveRowView:forRow for this.

Related

NSTableView & CoreData: Delete Object at clicked row

I am pretty new to Core Data and am currently working on a small (OSX) app that uses an NSTableView to organise objects. I would now like to delete a row/object with the click of a button on that targeted row.
I access the managed object within the table controller by calling [NSApp managedObjectContext] (still trying to figure out that dependency injection thing) but I can't easily delete an objectAtIndex: like I used to with the array (which has now been replaced by the core data stack, right?).
How do I identify the object to be deleted? And consequently, how can I cleanly remove it from the stack?
This is probably a really basic question but I couldn't find any resources on it. Bindings obviously don't work because the row does not get selected before the click occurs.
Any help is much appreciated!
Bindings would work, in that you could have the button's IBAction query the objectValue for the parent NSTableCellView. Once you have that objectValue, you could call the bound arrayController to delete the object, and then the cell/row would disappear.
So, if you have a tableCellView that has a delete button with an IBAction, within that IBAction, you could get the sender's superview, ensure it's an NSTableCellView, get the objectValue, and call [myArrayController removeObject:...]
As it says in the NSTableCellView class reference:
The objectValue is automatically set by the table when using bindings or is the object returned by the NSTableViewDataSource protocol method tableView:objectValueForTableColumn:row:.
This is actually a typical pattern with views in cocoa. objectValue or often representedObject are properties on the views that refer to the data model objects they represent, so if you have a view pointer from sender on the IBAction, you can get the related data model object. And if you're using bindings and a controller, you can then just have the controller remove that object.
With bindings, you will often create buttons that need IBActions attached, rather than some direct binding. But those IBActions can most definitely interact with the controller and not the view.
And with core data, array controllers are really slick vs. assuming you have to do it all programmatically.

NSArrayController returns null

I have an NSArrayController bound to CoreData in my application. It is also bound to a TableView that displays the data. Two buttons are bound to the ArrayController that add and remove lines. All of this is working as expected. I can add, edit, save, and remove CoreData Entries.
There is a section of my app that is to accept drag and drop operations from files (working). It takes the data from the files, looks for various information, and is to insert this information into the Core Data database via the NSArray Controller.
I have added the class handling the parsing/adding of the file to the database as an object in IB. I created an IBOutlet for the array controller in the class, and bound the controller to the class' referencing outlet.
If I add a button to the interface to directly call the method that adds a custom record to the database, everything works. If the method is called via the drag and drop operation, nothing works, even logging a simple [arrayController className] returns null (though returns NSArrayController as expected when the method is called from the button click).
The only difference I can see is that when accessed through the button click, the method is called directly, while the other way passes through my drag and drop class before loading the parsing class, but I'm completely stuck on how to remedy this situation. I'll be happy to provide code, just not sure which code you'll need.
Any help is appreciated. Thanks!
==================
UPDATE
turns out I was connecting the IBOutlet to a class (a subclass of a view) object in IB instead of to the view itself handling the drops. Connecting these up made things work. Well, not work, I have other issues to iron out now, but the Array controller is now instantiated.
Moved from comment to answer: The array controller you are trying to add stuff is not instantiated. I assume you are not referring to your original NSArrayControllerinstance but maybe a new created one? Probably a problem of communication between your class instances.
Debugging this should be straightforward ... using the debugger. Set a few breakpoints (one at each action the button(s) call, and one at each point where your class instances are meant to talk to each other (your importer and your main controller)). Run, test, step through the code when the debugger breaks at each breakpoint.
My guess: An outlet is not hooked up (is nil) in IB or is not yet reconnected at runtime (see -awakeFromNib and make sure you're not trying to touch an outlet or action that hasn't been fully reconnected from the nib at runtime by the time you're trying to use it).
Something’s not hooked up right, BUT you don’t want to do it this way anyways. There’s no advantage to inserting via an NSArrayController. Just create new objects with NSEntityDescriptions:
+ (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
And you’re done. If your NSArrayController is hooked up correctly it’ll auto-fetch the new objects at the end of the event so the user will see them “immediately.”

Array Controller not updating Table View until "Add" button is pressed

I'm new to Cocoa and Objective-C, so I'm following the Lynda courses and learning a lot. Thing is, I've encountered a problem that I cannot figure out, even though I seem to be doing it exactly the same way...
Basically, I'm trying to get a Table View hooked up through bindings to an Array Controller, just to list out the contents of a simple NSMutableArray in my code. I'd gotten it all hooked up properly, but no matter what I did it wasn't displaying anything when I ran the program.
Here's where it gets weird: on a lark, I added a "+" button and hooked it into the "add" function of the Array Controller, and when I ran the app and clicked that button, it not only added a new line, but it displayed the whole array as well! Apparently everything had been hooked up properly the whole time, it just wasn't displaying the information. Further experimentation revealed that I could make any changes I wanted to the array, whether in the original code or during the runtime of the app, but they would only be updated in the Table View when I clicked that "+" button.
I feel like this is probably a simple solution, just some "Continuous" box that needs to be checked or something, but I cannot for the life of me find it... Can anyone point out what I need to do to get my TableView to show its contents automatically?
(Also, I don't know if this is related or not, but none of the "Model Key Path" fields in the inspector are offering suggestions as I type, which they do in the Lynda course. The app works fine if I manually type everything in, but it says "no completions found" the whole time.)
Thank you in advance for helping out a n00b!
none of the "Model Key Path" fields in the inspector are offering suggestions as I type
As I understand it this is probably because the NSMutableArray that holds your data array i.e. dogPound or similar, isn't also declared as a property, only an instance variable.
Declare the property #property NSMutableArray * dogPound; and change the instance variable declaration to _dogPound and I think interface builder should offer you the auto-completes.
I'm new to Cocoa and Objective-C
Me too.
I'd gotten it all hooked up properly,
In about 30 minutes, I can get everything setup with a custom class like Dog, and another class called AppController that consists of one instance variable: NSMutableArray* dogPound. The init() method for the AppController class creates the array and adds some Dog instances to the array. I also bound an NSArrayController to the dogPound array, and I bound the NSTableView columns to the NSArrayController. After I Build&Run the NSTableView displays the information for each Dog instance in the dogPound array.
I also tried a simpler version where there is no Dog class and the array in the AppController class just consists of some NSString objects. Once again, I was able to successfully bind an NSArrayController to the array and bind the table's columns to the NSArrayController, so that an NSTableView displayed all the NSString's in the array.
You need to post your exact code, and you need to write down every step you did in IB, which of course is a huge pain in the ass, but it's the only way anyone will be able to help you.
Here's where it gets weird: on a lark, I added a "+" button and hooked
it into the "add" function of the Array Controller, and when I ran the
app and clicked that button, it not only added a new line, but it
displayed the whole array as well!
Of course. The add: method in the NSArrayController adds a new item to the array and then signals the NSTableView that it should reload the data, i.e display everything that's currently in the array.
I feel like this is probably a simple solution, just some "Continuous"
box that needs to be checked or something,
Nope, nothing like that.
none of the "Model Key Path" fields in the inspector are offering
suggestions as I type
Lack of autocompletion choices is a big hint that you are doing something wrong--even though I find I can't always figure it out, so I just keep typing. Did you remember to start your bindings in the Attributes Inspector(Object Controller section) for the NSArrayController? In IB, did you create an instance of your AppController class, or whatever you called the class that contains the NSMutableArray, by dragging an Object onto MainWindow.xib?

How can I directly respond to NSTableView edits while still using NSArrayController?

In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.

NSLevelIndicator Not Updating

I've got an application with an NSLevelIndicator Object on it that's refusing to update.
I have a timer that's shoved off during init and updates the value of the NSLevelIndicator using its setIntValue method. Whilst the code executes without any exceptions, the NSLevelIndicator never visually updates. I have some other labels on the window that are updating through this timer, so I know that it is executing.
I've tried using all of the setTypeValue methods (String, straight value and double with appropriate variables being assigned in each). I even tried linking the "setStringValue" action through interface builder from the NSLevelIndicator to a label representation on the window to no avail. It still sits at its initial value (0).
I noticed that setIntValue (and all the other setTypeValue methods) are undocumented in Apple's documentation for NSLevelIndicator - so I'm wondering if I'm approaching this wrong.
Does anyone have any clue what the proper way to set an NSLevelIndicator's value from code is?
setIntValue should work, so it sounds like your IBOutlet for the NSLevelIndicator isn't set properly - most likely its value is nil.
This is probably due to your outlet not being connected in IB, as Johan Kool suggested.
One thing worth mentioning, however, is that IBOutlets don't yet have a valid value at the time your initializer is called - they're hooked up after the initializer returns and shouldn't be referenced until your instance receives the awakeFromNib message.
You mentioned your timer is set up from your initializer - if you happen to be passing your instance's NSLevelIndicator pointer to the timer as its userInfo parameter, the userInfo will have the wrong value (nil) since it isn't yet initialized when the timer is created.
Regardless of whether you use userInfo this way, anything that depends on IBOutlet values should be set up from within awakeFromNib rather than init.

Resources