Should I dealloc a nonnull property; and if so, how?

Viewed 466

I'm exposing a few properties from an Objective-C project to Swift (based on this repo), but have no experience in Objective-C, so I'm rather out of my depth here, so please bear with me.

I'm wondering how to correctly dealloc a nonnull property (or whether it's necessary at all!). I've provisionally dealloc'ed the nonnull property surface by setting it to null (in the same manner as is done for the nullable partOfSpeech). However, this prompts the following warning:

Null passed to a callee that requires a non-null argument

... so I wonder whether it's redundant. Is there anything I should do instead to handle my nonnull property, during the Node class's dealloc block?

Given the interface, node.h:

@interface Node : NSObject {
    NSString *surface;
    NSString *partOfSpeech;
}

@property (nonatomic, retain, nonnull) NSString *surface;
@property (nonatomic, retain, nullable) NSString *partOfSpeech;

- (nullable NSString *)partOfSpeech;

@end

... And the implementation, node.m:

@implementation Node

@synthesize surface;
@synthesize partOfSpeech;

// surface is assumed to be set post-initialisation.

- (void)setPartOfSpeech:(NSString *)value {
    if (partOfSpeech) [partOfSpeech release];
    partOfSpeech = value ? [value retain] : nil;
}

- (NSString *)partOfSpeech {
    if (!features || [features count] < 1) return nil;
    return [features objectAtIndex:0];
}

- (void)dealloc {
    // WARNING: "Null passed to a callee that requires a non-null argument"
    self.surface = nil;
    self.partOfSpeech = nil;
    [super dealloc];
}

@end

... And given that a Node's lifecycle is like this:

    Node *newNode = [Node new];
    newNode.surface = [[[NSString alloc] initWithBytes:node->surface length:node->length encoding:NSUTF8StringEncoding] autorelease];
   // ... Do stuff with newNode (eg. add to array of Node)...
    [newNode release];
2 Answers

First: The compiler can automatically synthesize instance variables and setters/getters for your properties. So your interface should be just

// Node.h
@interface Node : NSObject

@property (nonatomic, retain, nonnull) NSString *surface;
@property (nonatomic, retain, nullable) NSString *partOfSpeech;

@end

and no @synthesize statements are needed in the implementation file. The compiler will automatically create instance variables _surface and _partOfSpeech, and also create accessor methods

- (NSString *) surface;
- (void)setSurface:(NSString *)value;
- (NSString *)partOfSpeech;
- (void)setPartOfSpeech:(NSString *)value;

which do "the right thing", with or without ARC. You can override those methods if you want to implement some custom logic, but you don't have to implement a standard setter like your setPartOfSpeech.

If you use ARC (automatic reference counting) then that is all, nothing more is needed. And I would really recommend to do so. The compiler inserts the required retain/release calls at compile time, and is quite clever in avoiding unnecessary calls. See for example

about some comparisons. With MRC (manual reference counting), your code might even be slower, or have memory leaks.

But to answer your question: With MRC you have to release the instance variables in dealloc

- (void)dealloc {
    [_surface release];
    [_partOfSpeech release];
    [super dealloc];
}

as explained in Memory Management Policy in the "Advanced Memory Management Programming Guide".

You should not use the accessor methods in dealloc as in your

self.surface = nil;
self.partOfSpeech = nil;

see Don’t Use Accessor Methods in Initializer Methods and dealloc.

If you are using manual memory management you can just release the object stored in the properties backing variable. As you've named the backing variable the same as the property use the -> to clearly reference the backing variable:

[self->surface release];

Or if you want to do this with assignment just assign the empty string literal:

self.surface = @"";

The string literal is created at compile time, lives throughout the program execution, and takes up very little space. The assignment will caused the release (and deallocation if the reference count reaches zero) of the previous value in the property, just like assigning nil (or any other value).

HTH

Related