开发者

Getting Properties Set in Constructor

I am new to Objective-C. I am trying to get a property set in the constructor, but am getting an EXC_BAD_ACCESS error. Here is my constructor:

- (id) init {
    self = [super init];
if (self != nil) {
    appFolderPath = [[NSBundle mainBundle] resourcePath];
    fileManager = [NSFileManager defaultManager]; 
    mediaArray = [file开发者_运维问答Manager directoryContentsAtPath: [appFolderPath stringByAppendingString:@"/Media/Silly"]];    
    mediaIndex = 0;
}
return self;

}

Here are my properties:

@property (retain) NSFileManager* fileManager;
@property (retain) NSString* appFolderPath;
@property int mediaIndex;
@property (retain) NSArray* mediaArray;

Any ideas?


You have the retain keyword on your properties, which is good. But, it doesn't matter because you are not actually using them. You are accessing the ivars directly, bypassing the getter method that the Objective-c compiler generated for you.

To contrast between and ivar and a property, see this code:

MyInterface.h

@interface MyInterface : NSObject {
@private
    NSFileManager * fileManager; // This is an instance variable, or 'ivar'
}

@property (retain) NSFileManager * fileManager; // This is the declaration of a property

MyInterface.m

@implementation MyInterface {

@synthesize fileManager;

// Calling this causes the Objective-C compiler to generate the following
// methods for you: 
//  -(NSFileManager *) getFileManager ...
// and - (void) setFileManager: (NSFileManager *) val ...
}

To use the ivar from your class you would simply reference its name, in your example you did that with the line:

fileManager = [NSFileManager defaultManager];

Since the auto-released instance returned by the method you called isn't being retained, you get an EXEC_BAD_ACCESS exception later on in your program. To use the property you need to preface it with the owning object reference:

self.fileManager = [NSFileManager defaultManager];

This ensures that your ivar is set and retained.


EDIT

Now, to really appreciate the difference between an instance variable and a property, you could have declared your interface as:

@interface MyInterface : NSObject {
@private
    NSFileManager * _fileManager; 
}

@property (retain) NSFileManager * fileManager;

And in your .m you would have:

@synthesize fileManager = _fileManager.

Now when you tried to do fileManager = [NSFileManager defaultManager]; your code would not compile, because there is no ivar called fileManager in your class. It's a useful coding style to avoid common mistakes.


@Perception already explained the ins and outs of why this doesn't work. Here's how to rectify this. Since you're not using the synthesized setters which would send the retain message, you're forced to handle it yourself when directly accessing the instance variables. Also note that objects of type NSString should be copied instead of retained, so the declaration of appFolderPath should actually be @property (nonatomic, copy) NSString *appFolderPath;. Taking all of this into account, your -init should look something like this.

- (id)init
{
    self = [super init];

    if (self != nil)
    {
        appFolderPath = [[NSBundle mainBundle] resourcePath] copy];
        fileManager = [NSFileManager defaultManager] retain]; 
        mediaArray = [fileManager directoryContentsAtPath:[appFolderPath stringByAppendingString:@"/Media/Silly"]] retain];    
        mediaIndex = 0;
    }

    return self;
}


You aren't assigning to properties, you're assigning to instance variables. If you want to use the properties, you need to prefix the names with self., as in self.fileManager.

The effect is that the objects aren't retained, and probably get deallocated later on. When you then later try to access the properties (or their instance variables) the objects are already gone. Sometimes there's a different object in their place, sometimes it's garbage. And that's when you get the crash.


I don't want to step on any toes - @Perception has provided a thorough explanation and then @Mark gave the actual solution.

Here's the quick version:

This

@property (nonatomic, copy) NSString* appFolderPath;

and matching

@synthesize appFolderPath = _appFolderPath;

mean that the compiler generates the following methods

- (void)setAppFolderPath(NSString *)appFolderPath;
- (NSString *)appFolderPath;

These methods will take care of the memory management of retaining/copying/referencing an object on assignment depending on which option you choose retain/copy/assign. This memory management will only come into effect if you use

[self setAppFolderPath:@"My Folder Path"];

or

self.appFolderPath = @"My Folder Path"; // which compiles to the previous call

Now this is all well and good for general use in your class but sometimes it is recommended to not use getters and setters in case they cause side effects. Two places where you normally want to avoid getters and setters are in init methods and dealloc.

Therefore as you was in the init method you should access the ivar directly without using the getters/setters. This means that you will need to perform the memory management yourself. e.g.

- (id)init
{
    self = [super init];
    if (self) {
      _appFolderPath = [[[NSBundle mainBundle] resourcePath] copy];
    }
    return self;
}

In the dealloc method you would access the ivar directly again like this

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

As @Mark pointed out you generally copy strings so that they can't be changed underneath you.

Additional notes on examples.

When I called @synthesize appFolderPath = _appFolderPath; It creates the getters and setters using the name appFolderPath but the ivar these methods effect is actually called _appFolderPath. This is good practice as you will always be sure when you are accessing the ivar directly or through a getter/setter because just referencing appFolderPath in your code will not compile.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜