Problem with Cocoa Bindings saving-loading. Help!

Discussion in 'Mac Programming' started by Soulstorm, Oct 25, 2006.

  1. Soulstorm macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #1
    I have developed an application that will hold the lessons of each day of the week. On the right tableview, the user creates a day, then he/she creates the lessons for this day and sets the assignment for this day. I have done the application in Cocoa bindings for the first time, and I have some problems with saving-loading.

    This is what I have done so far

    When I create some information on the 2 tableviews, all is fine. When I save them and try to load them, nothing is being changed, and when I try to create some more days and lessons, there is an "invisible" day allocated, that can be pressed but cannot be edited. Nothing more is loaded properly. Any ideas of what may be wrong?

    PS. The application is in testing stage: There are some really obvious mistakes. However, think that the solution to my problem is really simple, only I cannot see it! Any help would be appreciated.
     

    Attached Files:

  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    There are a few problems here. Simple ones: your setter for the days array is wrong! I changed it to:

    Code:
    - (void) setDays: (NSArray *)newDays
    {
    	[days release];
    	days = [newDays retain];
    }
    
    I then changed the end of the load code to:

    Code:
    array = [NSUnarchiver unarchiveObjectWithFile:[sheet filename]];
    [self setDays:array];
    
    Note that you could (and should) do the above on a single line.

    So now if we save a file with 3 days and quit the app and reload the file we get 3 days reloaded. Unfortunately they don't have names so the table view does not appear to update but a few clicks on the rows shows they are there.

    So why are there not any names? Because they did not get archived. Why? Because NSObjects do not archive anything when they are archived so any instance variables will be lost, in particular your properties dictionary. How do we fix this? Write a custom archiver method for your NSObject subclass. I'd read this as a starting point.
     
  3. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #3
    OK so I see you do have encodeWithCoder: I need to look at this a bit more. Is there a reason for having custom day and lesson classes? You could have just used dictionaries...
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    Another simple fix. Your setter for the properties array in the day class was wrong as well. It should read:

    Code:
    - (void) setProperties: (NSDictionary *)newProperties
    {
    	[properties release];
    	properties = [newProperties retain];
    }
    
    Check ALL your accessors. Your way of doing it does not work at all.
     
  5. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #5
    Isn't this code dangerous? What happens when newProperties is the same object as properties? It'll get released on the first line and you'll have a crash when you try to assign the now invalid object on the second line. I think it should be:
    Code:
    - (void)setProperties:(NSDictionary*)newProperties {
        if (properties != newProperties) {
            [properties release];
            properties = [newProperties retain];
        }
    }
     
  6. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #6
    or
    Code:
    - (void) setFoo:(Bar *)newFoo
    {
        [foo autorelease];
        foo = [newFoo retain];
    }
    
    or, of course, once we get 10.5...
    (in the .h file)
    Code:
    @property(ivar) foo;
    
    and nothing at all in the .m :) Information from publicly available GCC sources, just in case someone thinks I'm breaking my NDA.
     
  7. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
  8. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #8
    By fixing the setter methods, the program now works just fine. Thanks RobbieDuncan and HiRez!

    But I would like to have something explained: Why the previews setter methods didn't work? What's wrong to have the contents of an an NSArray replaced with the -setArray method?
     
  9. wjsdelicious macrumors newbie

    Joined:
    Oct 26, 2006
    #9
    Yipes!

    The biggest problem you have is that you aren't notifying the bindings layer when you change a variable. You've got to do this, in one of two ways:

    1) You can call

    [self setValue:value forKey:mad:"variable"];

    instead of

    [variable autorelease];
    variable = [value retain];

    And -setValue:... will automatically post the proper notifications, which will tell the UI layer to update itself, if anyone is watching "variable".

    2) You can post notifications yourself, in your set methods, as such:

    - (void)setVariable:(id)newValue;
    {
    [self willChangeValueForKey:mad:"variable"];
    [variable autorelease];
    variable = [newValue retain];
    [self didChangeValueForKey:mad:"variable"];
    }

    This is how you need to write accessor methods, so you need to go back and rewrite yours to be this way, and then make sure you either call them directly or call -setValue:forKey: to change ANY variable which is being bound to.

    --

    Other, lesser issues include (a) you should use CoreData, not archiving (which kind of sucks), and (b) your -init methods are very ugly, (c) you should be using a subclass of NSDocument, not NSObject, for your main controller, and (d) don't use [NSDictionary initWithObjects:forKeys:] if you have a static list, use [NSDictionary initWithObjectsAndKeys:], which lets you write it all in a much more compact and much less error-prone way, (e) instead of having a generic dictionary of "properties", if the "properties" are always the same you should use a real class with those properties as ivears (or, if you are using CoreData, a real entity).

    --

    Sorry about the double-post, my spacebar went nuts.
     
  10. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #10
    I'm now at work and don't have the code in front of me so I will assume that you are actually talking about NSMutableArray not NSArray (as you can't change an NSArray). I'm not sure why this doesn't work. I simply changed the accessors to be the same as the ones I normally write, are in all the examples and work!
     
  11. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #11
    @wjsdelicious:

    Why do my -init methods suck? I would also use CoreData, but since CoreData uses CoreBindings, I tried doing it in CoreBinding first to understand the under-the-hood code that is generated.

    I will also use NSDocument for this kind of work. I just made a test project.

    Can you explain this to me more, please? I'm not sure why this is better.

    Last question: Lets just sayt that for the table views I want to add 2 more buttons: Move up and Move Down. These will take the selected item and move it one place up or down accordingly. How can I do this? Could this be done by just writing methods in MyController that alter the contents of the arrays in each class (the "days" and "lessons" arrays, I mean)?

    This is my project so far:
     

    Attached Files:

  12. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #12
    Whilst move up/down buttons are easier to program I'd go for drag-and-drop for re-ordering. This is a significant pain with table views normally, but it's what the user will expect...

    Edit to add:

    I think you use initWithObjectsAndKeys like this:

    Code:
    NSDictionary *myDict = [NSDictionary initWithObjectsAndKeys:[NSArray arrayWithObjects:@"key1",@"object1",@"key2",@"object2",nil]];
    
    Obviously you can use any objects you like instead of strings.
     
  13. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #13
    Yes, but how can I do that? I am not asking for the exact solution, just a reference or some classes I could use.
     
  14. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #14
  15. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #15
    But where do I write the proper methods for it? I mean... how do I access the nsArrayControllers in my project? I haven't written any code for the ArrayControllers to manage the tableviews. Where do I write the code that can access the arrayControllers set in Interface Builder? Is there any tutorial that I can read for this stuff?
     
  16. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #16
    The NSArrayControllers were in your nib. Simple add IBOutlets to your controller and connect them to the array controllers. Then add some IBActions and connect the buttons to them.
     
  17. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #17
    Thanks robbieduncan!

    I have one last problem. I managed to create the code for the 2 buttons that handle the lessons, but for the days, I have one problem: Although the code is nearly the same, the dayArrayController doesn't seem to recognize how many objects it contains! When I press the "move up" or "move down" buttons, a message appears in the run log, saying that there is an index error because the indexes of the arrayController are from 0 to -1! Can anyone point me to the source of the problem?
     

    Attached Files:

  18. Soulstorm thread starter macrumors 68000

    Soulstorm

    Joined:
    Feb 1, 2005
    #18
    Problem solved. It was relly stupid, actually. Thanks a lot everyone who helped me on this. I really don't know how at first I had the patience to get involved with these kinds of projects without knowing coredata and cocoa bindings.
     
  19. wjsdelicious macrumors newbie

    Joined:
    Oct 26, 2006
    #19
    More guides.

    I wrote a long description of writing init methods at http://wilshipley.com/blog/2005/07/code-insults-mark-i.html .

    With NSDictionarh -initWithObjectsAndKeys: you can take eliminate two lines and two variables. Less code is better code!

    Yes, you just modify the array, making sure you call -willChangeValueForKey:... first and -didChangeValueForKey:... after.

    -OR- you could ask your object for a "magic" array that'll post these for you, by saying:

    NSMutableArray *mutableMagicDaysArray = [self mutableArrayValueForKey:"days"];

    And then just modifying 'mutableMagicDaysArray'. Your choice.

    -W
     

Share This Page