Re: Strange NSZombie occurring
Re: Strange NSZombie occurring
- Subject: Re: Strange NSZombie occurring
- From: Fritz Anderson <email@hidden>
- Date: Sun, 07 Aug 2011 10:10:48 -0500
Is this really your code? There are some oddities that make me wonder.
Also, your subject line talks about NSZombie (an instance of a class that gets substituted into an object when it is released, if you have NSZombieEnabled set), but your complaint is that a property is nil when you don't expect it. That has nothing to do with zombies.
Let me annotate your example, as I think the problem might be clearer if the code were cleaner:
On 7 Aug 2011, at 8:10 AM, Scott Steinman wrote:
> -(void)setUp;
> -(void)start;
> -(void)changeWords:(NSTimer*)theTimer;
> -(NSArray *)wordsInPhrase:(NSString *)thePhrase;
> @property (nonatomic, assign) int numWords;
> @property (nonatomic, assign) NSUInt wordChangeInterval;
Here's my first reason to believe you haven't provided your real code. There is no such thing as an NSUInt. I understand your desire to provide a compact example, but we can't help you if it hides your problem. And, getting the example to compile and run may point you to your bug.
> @property (nonatomic, copy) NSString *phrase;
> @property (nonatomic, copy) NSArray *words;
Properties: Tell us whether you've backed them explicitly with instance variables, or allowed @synthesize to generate instance variables; whether the backing ivars have the same name as the properties; and whether you're providing getters or setters for any of them.
Given that we're not seeing your actual code, I don't know whether you're assigning nil to ->words or .words somewhere. Try overriding
-(void) setWords: (NSArray *) newWords
{
if (newWords != words) {
[words release];
words = [newWords copy];
}
}
and setting a breakpoint at the beginning. Extra points for conditioning the breakpoint on newWords == nil. When the breakpoint hits, examine the backtrace. It won't catch your overuse of direct access to backing ivars (a major theme of this message), but it will be something.
> @property (nonatomic, copy) NSTimer *wordChangeTimer;
NSTimer doesn't implement <NSCopying>, as copying an NSTimer doesn't make sense. If you had actually used the property, the runtime would have caught this for you.
> - (id)init
> {
> self = [super init];
> if (self) {
> phrase = [[NSString stringWithString:@"This is the phrase to display"] retain];
phrase = @"This is the phrase to display"; // stringWithString: is almost never what you intend.
[@"This is the phrase to display" copy] is the prudent thing to do, but not idiomatic or necessary.
> wordChangeInterval = 0.2;
This was declared as "NSUInt", I assume is some sort of integer. The ivar will be set to zero.
> }
> return self;
> }
>
> -(void)setUp
> {
> words = [[self wordsFromPhrase:phrase]] retain];
Your brackets are unbalanced.
self.words = [self wordsFromPhrase: self.phrase];
1. You've got a property. Use it.
2. You're simulating* the retain attribute, when your property declaration said you want copy. This suggests to me that you don't mean it.
* ("Simulating" in that you're leaking the previous occupant of ->words. The synthesized accessor would have taken care of that.)
> [self start];
Do you ever want to change .words without doing [self start]? If not, make the call to -start inside setWords:. Then self.words = [self wordsFromPhrase: self.phrase] can completely replace -setUp. But see:
> }
>
> -(NSArray *)wordsInPhrase:(NSString *)thePhrase
> {
Perchance, do you call this method with any phrase other than self.phrase? If not, then you can implement -(NSArray *) words to return [self wordsFromPhrase: self.phrase], the property can become readonly, and your worry about self.words being nil goes away.
> NSArray *wordArray;
>
> [wordArray arrayByAddingObjectsFromArray:[phrase componentsSeparatedByString:@" "]];
This sentence no verb. wordArray has no initial value, so sending arrayByAddingObjectsFromArray: to it should crash most of the time (another reason I don't believe you're showing your code). arrayByAddingObjectsFromArray: returns an NSArray*, but you're throwing away the returned value, and the method itself has no side effects.
And thePhrase isn't used anywhere in the method.
> numWords = [wordArray count];
self.numWords = wordArray.count; // Use the property.
> return wordArray;
> }
I _think_ you mean:
- (NSArray *) wordsInPhrase: (NSString *) thePhrase
{
NSArray * wordArray = [thePhrase componentsSeparatedByString: @" "];
self.numWords = wordArray.count;
return wordArray;
}
But I'm _guessing_ you mean:
- (NSArray *) wordsInPhrase
{
NSArray * wordArray = [self.phrase componentsSeparatedByString: @" "];
self.numWords = wordArray.count;
return wordArray;
}
and that you _really_ mean:
@property(nonatomic, readonly) NSUInteger numWords;
@property(nonatomic, readonly) NSArray *words;
@property(nonatomic, retain) NSArray * backingWordsArray;
...
@synthesize backingWordsArray;
- (NSArray *) words
{
if (! self.backingWordsArray)
self.backingWordsArray = [self.phrase componentsSeparatedByString: @" "];
return self.backingWordsArray;
}
- (NSUInteger) numWords { return self.words.count; }
- (void) setPhrase: (NSString *) newPhrase
{
if (newPhrase != phrase) {
[phrase release];
phrase = [newPhrase copy];
self.backingWordsArray = nil;
[self start];
}
}
> - (void) start
> {
> currentWordIndex = 0;
This won't compile from the code you're showing. Is it an ivar?
> wordChangeTimer = [[NSTimer scheduledTimerWithTimeInterval:wordChangeInterval
[My customary objection to accessing an ivar directly.]
> target:self
> selector:@selector(changeWords:)
> userInfo:nil
> repeats:YES] retain];
self.wordChangeTimer = [NSTimer ... repeats: YES];
If you let the property manage your memory, you won't have to do it yourself. And if you have a setter method, you can invalidate the old timer.
> }
>
> - (void)changeWords:(NSTimer*)theTimer
> {
> currentWordIndex += 1;
> if (currentWordIndex > numWords)
> currentWordIndex = 0;
> messageLayer.string = [self.words objectAtIndex:currentWordIndex];
> }
For once you're actually using the property. If this is the only time, one has to wonder whether there is a -words method that doesn't do what you think it does.
> Now, the strangeness: words exists and is OK in setUpDisplay and startDisplay in that it contains the right words from the phrase. But in changeWords:, somehow words is nil. I'm at a loss to figure out how words could be released between start and changeWords:. I'd appreciate some help.
What are setUpDisplay and startDisplay? Are they the same as setUp and start?
A property going nil is not the same as releasing the object it once pointed to. The two leading candidates for the bug are:
* There has been an assignment. At the very minimum, override setWords:, be religious about using .words as a property, and audit where the setter is called.
* There is a -words getter, in the class or its parent, that does not reflect the backing ivar. Switching to near-exclusive access through the property (except in an init..., dealloc,** or the accessors themselves) may cure it; or using my suggestion of dropping most of the ivars and computing the properties on-demand; may cure the problem.
— F
** (Opinions differ on whether inits or dealloc should use accessor methods or the ivars that implement them. I feel strongly about the latter. See the archives for the religious wars.)
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden