UIButton and setting title several times

Discussion in 'iOS Programming' started by jared_kipe, May 14, 2009.

  1. jared_kipe macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #1
    I'm getting a crash when changing the title for a button twice.

    Code:
    // these are @property (retain, nonatomic) UILabel *label;
    // and @property (retain, nonatomic) UIButton *button;
    // and @property (retain, nonatomic) MyObject *currentObject;
    @synthesize label, button, currentObject;
    
    // every time a certain button (not my *button) is pressed it calls this
    - (IBAction) makeNewThing {
         MyObject *newObject = [[MyObject alloc] initSpecial];
         self.currentObject = newObject;
         [newObject release];
    
         label.text = currentObject.labelString;
    // problem code
         [button setTitle: currentObject.buttonString forState: UIControlStateNormal];
    
    
    Basically my object makes some strings, randomly they are nil or a string.
    Before adding the [button setTitle..]; code there were no problems, no memory leaks or anything. Every time I ran the method the label text would change to the new string in the object.

    After adding the text, one of 3 things would happen depending on the random order of nil and strings.

    run program -> buttonString=nil -> buttonString=nil ... you could do this forever without crashing.

    run program -> buttonString = @"something" -> buttonString = nil ... as long as it stays nil now its fine. The text goes to "something" then blank.

    run program -> buttonString = @"something" -> buttonString = @"somethingelse" *CRASH*

    I don't get it, there is nothing in UIButton's documentation that indicates it the title can only be changed once (or changed to nil after that but not back to a real string).
    I could probably make a new button object, and set it to my button object. But I'd rather not have to if there is something I'm missing about the setTitle: instance method.
     
  2. jnic macrumors 6502a

    Joined:
    Oct 24, 2008
    Location:
    Cambridge
    #2
    This is a memory management issue. It looks as though you're trying to assign a value to a released object. Messages in the console should indicate which object needs to be retained.
     
  3. jared_kipe thread starter macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #3
    Console didn't tell me anything, but I commented out [newObject release]; and it works fine. Except for leaking MyObjects and their strings. (without this release, the created object has 2 retain counts, thus never gets dealloced)

    Why does label.text = aString; not crash but [button setTitle: aString...]; crash? Better question, if label.text is retaining that string, why doesn't it leak memory, I'm not releasing it an extra time.
     
  4. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #4
    I don't think the problem is in that code. Can you post your "MyObject" class? Maybe your "initSpecial" method is not setup right.
     
  5. jared_kipe thread starter macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #5
    I think it is probably because [UILabel setText: aString]; must make a copy of the string, and [UIButton setTitle: aString ...]; just points to the string. So the current string is deallocated while it is still being displayed.

    However, you would expect moving it up the list such that..

    Code:
     
         MyObject *newObject = [[MyObject alloc] initSpecial];
         [button setTitle: newObject.buttonString forState: UIControlStateNormal];
         self.currentObject = newObject;
         [newObject release];
    
         label.text = currentObject.labelString;
    // problem code
    
    
    You would expect this to work since it is reassigning the button's title string to the freshly created object's string.

    This still crashes, and I tried it before I even posted the first time.
     
  6. jared_kipe thread starter macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #6
    I was simplifying enormously, my class and viewcontroller actually do something, this isn't a learning exercise (well maybe a little it would seem).

    Suffice to say the MyObject class looks like this.
    Code:
     
    @interface MyObject : NSObject {
         NSString *labelString;
         NSString *buttonString;
    }
    @property (retain, nonatomic) NSString *labelString;
    @property (retain, nonatomic) NSString *buttonString;
    - (id) initSpecial;
    @end
    
    @implementation MyObject
    @synthesize labelString, buttonString;
    - (id) initSpecial {
         self = [super init]; //I do more checking to make sure there isn't an error
         double a;
         a = random() % 270;
         a = a/10 +1;  // These make a random float between 1 and 27 with 1 decimal place.
    
         self.labelString = @"??"; //or sometimes @"" depending on some more random code
         self.buttonString = [[NSString alloc] initWithFormat:@"%0.1f", a];
         [buttonString release];  //remember my @property retains the string
         return self;
    }
    // there is also a dealloc overide that releases the strings again.
    
    I have setup break points around the assignment of [button setTitle: ...]; and I can see that the data it SHOULD be setting to it is valid.
     
  7. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #7
    That code looks fine. Your buttonString assignment could be dangerous in certain situations (such as if you had a copy instead of retain property, you'd instead be releasing the copied object which would cause it to deallocate). Instead use this format:
    Code:
    NSString *tmp = [[NSString alloc] initWithFormat:@"%0.1f", a];
    self.buttonString = tmp;
    [tmp release];
    
    // or (cleaner)
    self.buttonString = [NSString stringWithFormat:@"%0.1f", a];
     
  8. jared_kipe thread starter macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #8
    Ohh I like the second option quite a lot.

    EDIT: Oh and I forgot, there are some self.labelStrings that are created in practically the same way. label.text= aString; never causes a crash when [button setText: aString....] crashes even with identical strings.
     
  9. jared_kipe thread starter macrumors 68030

    jared_kipe

    Joined:
    Dec 8, 2003
    Location:
    Seattle
    #9
    SOLVED: apparently in my object's dealloc method instead of RELEASING objects I was sending dealloc to them. ha ha. Xcode will catch if you have [super release]; in your dealloc method and give you a warning saying you should have [super dealloc]; in your method, but won't say **** about having [someString dealloc]; in there. :p

    On a side note, I wonder why this would cause a crash when they were being displayed but NOT when they were not, the strings would still be set.. and they were not leaking. Very odd.
     
  10. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #10
    Here's my guess: When you click the button for the second time, the original MyObject method gets released and a dealloc is called on it. You then call dealloc on the strings, which results in any internal string objects being released, while the original string object itself is still valid. Then, in UIButton, when it's getting a new title, it releases the old title, which is your string with invalid internals, and so the string itself is releasing its own invalid objects, thus the crash.

    Does that make sense? :)
     

Share This Page