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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to