There's one thing I don't understand regarding ARC: how should we now treat local variables that were created using [... copy]? If I make a property with (copy) flag, ARC will handle this automatically, but as far as I know there's no __copy flag for variables.
I've tested this with such code:
#interface Foo : NSString
#end
#implementation Foo
- (void) dealloc {
NSLog(#"%p deallocated", self);
}
- (NSUInteger) length {
return 1;
}
- (unichar) characterAtIndex: (NSUInteger) i {
return 'x';
}
#end
- (void) foo {
Foo *f = [[Foo alloc] init];
NSLog(#"%p", f);
Foo *f2 = [f copy];
NSLog(#"%p", f2);
}
What I get is:
0x102406530
0x102015f10
0x102406530 deallocated
I never get "0x102015f10 deallocated", which would suggest the copied variable doesn't get released. It doesn't even get autoreleased, because when I made another method [Foo foo] that returned an autoreleased object, I did get a "deallocated" message a moment later.
So is there any way I can cause it to be released without converting it to a property?
Ok, my bad - ARC does actually handle copied objects properly. I got wrong results because of using NSString for the test, because I wanted to use a class that already implemented copying instead of implementing it explicitly; when I repeated the test on a class inheriting from NSObject and implementing copyWithZone: by returning [[Foo alloc] init], I got two "deallocated" messages. Thanks to #Paul.s for pointing that out.
Related
The following program relies on the copy
attribute to copy a NSMutableDictionary.
The copy is apparently ok, but, if I try
to add a new element to the copy, the program crashes.
Is it some kind of bug?
PS. If it matters it's NON ARC
#import <Foundation/Foundation.h>
#interface Dog: NSObject
#property (copy) NSMutableDictionary *dict;
#end
#implementation Dog
#synthesize dict;
- (id) init
{
if ( (self = [super init]) ) {
dict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) print {
for (id key in dict) {
printf("%s --> %s\n", [key UTF8String], [dict[key] UTF8String] );
}
}
#end
//------------------------------------------------------
int main() {
Dog *dog1 = [[Dog alloc] init];
Dog *dog2 = [[Dog alloc] init];
dog1.dict[#"color"] = #"black";
dog2.dict = dog1.dict;
[dog2 print];
// the print shows that dog2.dict is indeed a copy of dog1.dict
// lldb shows that it is a shallow copy, which I guess is ok
// since values are immutable.
// ... so far so good.
dog1.dict[#"tail"] = #"long"; // This goes smoothly
//
// But...
dog2.dict[#"tail"] = #"long";
// here program crashes with the following message
//
// -[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x100108ee0
//*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x100108ee0'
return(0);
}
EDIT:
If I replace the line
dog2.dict = dog1.dict;
with
[dog2.dict addEntriesFromDictionary: dog1.dict];
then it works. There is no crash.
So, OK, this is the correct way of doing it.
But my point is: don't I deserve at least a warning
from the compiler?
You've broken some rules here, which is why it's going badly for you. As a rule, you should not return or accept NSMutableDictionary as a property. There are exceptions (and you have to code around that), but generally you should not. To code this correctly, you need to separate your interface from your implementation.
The correct interface is:
#interface Dog: NSObject
#property (copy) NSDictionary *dict;
#end
This object promises to accept and return NSDictionary, and it promises that the accepted and returned NSDictionaries will be independent copies, so the caller doesn't need to worry about mutability issues. We then need to implement those promises:
#implementation Dog {
NSMutableDictionary *mutableDict;
}
- (id) init
{
if ( (self = [super init]) ) {
mutableDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) print {
for (id key in mutableDict) {
printf("%s --> %s\n", [key UTF8String], [mutableDict[key] UTF8String] );
}
}
- (NSDictionary *)dict {
return [mutableDict copy]; // Add an -autorelease if this is MRC
}
- (void)setDict: (NSDictionary *)newValue {
// I'm going to pretend this is ARC; for MRC, code this in your style.
mutableDict = [newValue mutableCopy];
}
#end
On the other hand, if you really do want to share state with an NSMutableDictionary, you should not copy it, you should retain it to make clear the semantics.
#interface Dog: NSObject
#property (retain) NSMutableDictionary *dict;
#end
And this makes it (hopefully) clear to the caller that this is shared state and should be treated as such, and the caller should make copies if they so desire. But you generally should avoid this. And if you do go this way, I would call the property mutableDict or something like that to make it clear that this is unusual. For an example, see -[NSAttributedString mutableString].
There's not really a "mutable, but independent copy" semantic in ObjC property annotations, and I wouldn't create one unless you have a very strong need (generally performance related). I would generally just return an immutable copy and let the caller make their own mutable copy.
One more side note: Sometimes Cocoa implements "returns an immutable copy" as "just return the mutable one, cast to an immutable type." This improves performance by avoiding the copy, at the cost of sometimes the data changing behind your back. As an example, look at the docs for -[NSView subviews]. In my opinion you should avoid this pattern unless it is critical for performance (and even then, I'd make a special method like -subviewsBackingStore or something silly like that to make it clear "this is weird."
copy attribute creates an NSDictionary instance, not NSMutableDictionary, that's why it's crashing.
First, change the property to retain from copy:
#property (retain) NSMutableDictionary* duct;
Then you can do the following:
Dog *dog2 = [[Dog alloc] init];
dog2.dict = [dog1.dict mutableCopy];
I'm getting Property 'aVariable' not found on object of type id when trying to read or write aVariable to the array. Shouldn't it be known what class the object is that I added? Also noticed that it works to read the value with NSLog(#" %#",[[anArray objectAtIndex:0] aVariable]);
I'm a beginner at Objective C so it might be some simple thing I'm not getting.
AnObject
#interface AnObject : NSObject
#property (nonatomic,readwrite) int aVariable;
#end
AnotherObject
#interface AnotherObject : NSObject
#end
test.h
#import "test.h"
#implementation AnObject
#synthesize aVariable;
- (id)init
{
self = [super init];
if (self) {
aVariable=0;
}
return self;
}
#end
test.m
#implementation AnotherObject
- (id)init
{
self = [super init];
if (self) { }
return self;
}
- (NSMutableArray*) addToArray
{
NSMutableArray* anArray = [[NSMutableArray alloc] initWithCapacity:0];
AnObject* tempObject = [[AnObject alloc] init];
tempObject.aVariable=10;
[anArray addObject:tempObject];
// Property 'aVariable' not found on object of type 'id'
[anArray objectAtIndex:0].aVariable=[anArray objectAtIndex:0].aVariable + 1;
// Property 'aVariable' not found on object of type 'id'
NSLog(#" %i",[anArray objectAtIndex:0].aVariable);
// This works
NSLog(#" %i",[[anArray objectAtIndex:0] aVariable]);
return anArray;
}
#end
This code:
[anArray objectAtIndex:0].aVariable
Can be broken down into 2 sections:
[anArray objectAtIndex:0]
This returns an id- because you can put any type of object into an array. The compiler doesn't know what type is going to be returned by this method.
.aVariable
This is asking for the property aVariable on the object returned from the array - as stated above, the compiler has no idea what this object is - it certainly won't assume that it is an AnObject, just because that is what you added a line or two earlier. It has to evaluate each statement on its own. The compiler therefore gives you the error.
It is a little more forgiving when using accessor methods:
[[anArray objectAtIndex:0] aVariable];
This will give you a warning (that the object may not respond to the selector) but it will still let you run the code, and luckily enough your object does respond to that selector, so you don't get a crash. However this is not a safe thing to rely on. Compiler warnings are your friends.
If you want to use the dot notation, you need to tell the compiler what type of object is being returned from the array. This is called casting. You can either do this in two steps:
AnObject *returnedObject = [anArray objectAtIndex:0];
int value = returnedObject.aVariable;
Or with a mess of brackets:
int value = ((AnObject*)[anArray objectAtIndex:0]).aVariable;
The extra brackets are required to allow you to use dot notation when casting. If you want to use the accessor methods, you need fewer round brackets but more square brackets:
int value = [(AnObject*)[anArray objectAtIndex:0] aVariable];
-[NSArray objectAtIndex:] returns an id pointer. Since id does not contain information about your protocol the compiler cannot know the object has this property you declared; that is why it complains.
You can solve this by either cast the return value of objectAtIndex: or by using the getter/setter notation, i.e. [anArray objectAtIndex:0] setAVariable:...]. Also make sure you import your protocol definition, otherwise the compiler might also not know about the declared method and issue a warning.
if header file declares
#interface SomeClass: NSObject {
Data* d;
}
#property (nonatomic, retain) Data* d;
Why is the following line in the implementation file giving me a warning (and init method does not get called?)
[[[self d] alloc] init];
The warning i get is
Instance method '-alloc' not found (return type defaults to 'id')
Meanwhile, Data has
- (id) init method, that is not being called.
Please help me understand why.
alloc should be invoked on a class, not on an instance.
interface SomeClass : NSObject
{
Data *d;
}
Declare an init method on SomeClass and make it look like:
- (id) init
{
self = [super init];
if (self)
{
d = [[Data alloc] init];
}
return self;
}
- (void) dealloc
{
[d release];
[super dealloc];
}
Now you do:
SomeClass *c = [[SomeClass init] alloc];
And you can use the class. Note that you should probably read a little more on classes and objects and about memory management too (when you should release c, etc.).
If, by any chance, you have the possibility to use ARC (automatic reference counting), you won't need to take care of releasing stuff. But that doesn't come with Xcode 4.1, only with 4.2 which is not publicly accessible, apparently.
The problem isn't -(id)init, it's -(id)alloc. alloc is a class method of NSObject, which means you send it to the class itself and not to an instance of that class, i.e.:
[Data alloc]; // Correct
[someDataInstance alloc]; // Method not found
When you call [self d], you're given an instance of a Data, which you're then sending a -(id)alloc message to. Since NSObject doesn't have a -(id)alloc (only a +(id)alloc), you get the warning.
You should be doing
self.d = [[Data alloc] init];
As Matt says, alloc is a class method, and must be called on the class itself.
This is just a test to help me learn Objective-C, it uses NSMutableArray to add tire objects to an instance variable in a car object:
// INTERFACE
#interface CarBody : NSObject {
NSMutableArray *tires;
}
// Should this be (id *) it works but I was convinced it would be pointer?
- (void) addTire:(id)newTire;
#end
#interface TireSnow : NSObject {
}
#end
// IMPLEMENTATION
#implementation CarBody
- (void) addTire:(id)newTire {
[tires addObject:newTire];
// ** Release here or in main()?
}
- (id) init {
[super init];
tires = [[NSMutableArray alloc] init];
NSLog(#"_init: %#", NSStringFromClass([self class]));
return self;
}
- (void) dealloc {
NSLog(#"_deal: %#", NSStringFromClass([self class]));
[tires release];
[super dealloc];
}
#end
I do have a few questions ...
In the addTire method, is the (id) right, I thought it was going to be (id *)
Releasing the item I am adding to the array, should I do it inside the setter or in main() after I call it?
Am I allocating / releasing the NSMutableArray (tires) in the right place, it feels right?
Is there a way to do this with NSArray (as I only want 4 tires), I did try this but got mixed up trying to alloc the array and define its size.
thanks in advance for any help ...
gary
EDIT:
I am reading the memory management rules, but they do take some time to absorb and do require a certain level of understanding that is difficult to gain when starting out. What I am wondering about in this situation is where would I release the newSnowTire that I alloc in main. When I add it to the array in the setter does that create a new object in the array (thats my understanding) so my thinking was that I would need to release the instance I got from alloc?
// MAIN
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CarBody *newCarBody_001;
TireSnow *newSnowTire_001;
newCarBody_001 = [[CarBody alloc] init];
newSnowTire_001 = [[TireSnow alloc] init];
[newCarBody_001 addTire:newSnowTire_001];
// Clean up
[newCarBody_001 release];
[newSnowTire_001 release];
[pool drain];
return 0;
}
EDIT_002:
Just added the code to generate all 4 tires with the tire release moved into the loop after the setter is called.
// CREATE TIRES
for(int loopCounter=0; loopCounter<4; loopCounter++) {
newSnowTire_001 = [[TireSnow alloc] init];
[newCarBody_001 addTire:newSnowTire_001];
[newSnowTire_001 release];
}
I just checked this and it is correct ...
NewSnowTire_001 (alloc) RetainCount = 1
NewSnowTire_001 (addTire) RetainCount = 2
NewSnowTire_001 (release) RetainCount = 1
NewSnowTire_001 Finally Released by dealloc method.
(id) or (TireSnow*) is similar, I had problems with understanding this in the beginning too. So basically an object is of a pointer type (kind of), but the id is already a pointer, so you don't need a * after it.
In main. Releasing should happen in the same place as the alloc/retain/copy.
Seems okay to me.
You can use [[NSMutableArray alloc] initWithCapacity:4]. This is only a hint to the array, it will automatically expand if you insert more items. Check [tires length] in the addTire method.
Your -init should look more like this:
-(id)init
{
if (self = [super init]) {
// init here
}
return self;
}
This allows self to be nil if something breaks in the init-chain.
You should use id (not id*). Objective-C do not have a concept of a root object as you have in for example Java, where java.lang.Object is the root class for any and all classes. Cocoa adds two root classes (classes without a super class) named NSObject, and less common NSProxy. id is a pointer to any object regardless of super class.
It is unfortunate that id, and also Class, are defined as a pointers, which means they are the only places where you should not add the '*' character when defining references. An unfortunate legacy from the old days.
Release in main, you should always release objects int he same scope that you create or retain them. The addTire: method is exceptionally god example of this, never release objects that has been handed to you as an argument. Only release objects handed to you as a result (And even then only from the alloc, new and copy method).
The allocation and release of the instance variable tires is a schoolbook example of where it should be done. I would expand the init to check for the super class result, as this though (Never trust super to always work, or even return the same instance):
- (id) init {
self = [super init];
if (self) {
tires = [[NSMutableArray alloc] init];
NSLog(#"_init: %#", NSStringFromClass([self class]));
}
return self;
}
You can use NSArray if you have access to all four tires from the start. Best way would probably be to require the tires in the init method. If that is not a possibility then you have nothing to gain from using an NSArray over a NSMutableArray.
The type id is defined like this (in objc.h):
typedef struct objc_object {
Class isa;
} *id;
So id is already a pointer to an object. An id* would be a pointer to a pointer.
As for where you should release the tire — there's nothing in the code you posted that shows a need to release it at all. That object never claims ownership of the tire, so it has no need to release it. If something claimed ownership of the tire somewhere else in your code, then that object has a responsibility to release its claim when it's finished.
This is explained in the Objective-C memory management rules. It's pretty short and a must-read.
Can anyone tell me if the NSString instance variable "planetName" needs to be allocated / released by me (as in the example below) or is that done when the class instance is created / allocated?
My understanding is that int and float don't need to be, but not sure about NSString & NSArray ...
#interface PlanetClass : NSObject {
NSString *planetName;
}
- (NSString *)planetName;
- (void)setPlanetName:(NSString *)value;
#end
Like this ...
- (id) init {
[super init];
planetName = [[NSString alloc] init];
return self;
}
- (void) dealloc {
[planetName release];
[super dealloc];
}
** ---------------------------------- **
EDIT: EDIT: Here is another version
** ---------------------------------- **
int main(int argc, const char *argv[]) {
// ** Allocated here
PlanetClass *newPlanet_01 = [[PlanetClass alloc] init];
NSString *newPlanetName = [NSString alloc] init];
// ** Set the instance variable pointer here
newPlanetName = #"Jupiter";
[newPlanet_01 setPlanetName:newPlanetName];
// ** Released here
[newPlanet_01 release];
return 0;
}
the init & dealloc methods would then be like this ...
- (id) init {
[super init];
return self;
}
- (void) dealloc {
// Always release the current copy of planetName
// pointed to by the class instance.
[planetName release]
[super dealloc];
}
The setPlanetName method would look like this ...
- (void)setPlanetName:(NSString *)newPlanetName {
if (planetName != newPlanetName) {
[planetName release];
planetName = [newPlanetName copy];
}
}
PS: I am not using properties or synthesize, I have not gotten that far yet.
cheers -gary-
Your code is valid, but there's probably no reason to initialize planetName to an empty string. One of the nice features of Objective-C is that you can send messages to a nil object with no consequence. If your class is initialized and you never call -setPlanetName:, planetName will be nil (instance variables are always initialized to nil), so when your -dealloc method calls [planetName release], nothing will happen.
In general, the best practice is to use -copy when setting an NSString instance variable, and -retain when setting most other objects as instance variables. As such, your -setPlanetName: method would look something like this:
- (void)setPlanetName:(NSString *)newPlanetName {
NSString *tempPlanetName = [newPlanetName copy];
[planetName release];
planetName = tempPlanetName;
}
You still have an issue with your new code.
In your main function, you release newPlanetName but this is a little wrong. Your PlanetClass retained it with its setPlanetName: method, but your PlanetClass never releases it again unless the name of the planet changes. It should not be up to the caller of setPlanetName: to keep hold of the string, it is your classes responsibility to deal with it appropriately.
Your old dealloc method is correct. It should release the planet's name because your PlanetClass no longer needs it. Your main method should not release the planet's name because the string returned by stringWithString: does not belong to you, and you give it to PlanetClass to take care of.
So, keep your old dealloc method, and remove the [newPlanetName release] from the main function and you should be alright from there.
As a shortcut, you can even call [newPlanet_01 setPlanetName:#"Jupiter"] and do away with the newPlanetName variable altogether in your main function.
planetName is a pointer which, like an int or float, does not need to be allocated or initialized.
Just like you can assign values to an int or float, you can point planetName at different instances of a string, or it can point at nothing.
When you init your class, planetName will be nil (not pointing at anything). If you point planetName to an instance of a string, you have to retain that string, and release in dealloc.
In other words, this:
planetName = [[NSString alloc] init];
is unnecessary and meaningless.
In your setPlanetName method you would need to release the existing string that planetName is pointing to, assign planetName to the new string, and then retain the new string.
Your dealloc method is correct.
Your code looks good. NSObject subclasses (NSString included) need to have their memory mananged by the object that owns them. In this case, that owner is PlanetClass.