tags:

views:

42

answers:

4

So I'm trying to write an Objective-C macro that takes an object and a value, releases the original contents, and makes the assignment. Ideally, it would look like this:

myObj = RELEASE_AND_ASSIGN([SomeObject object])

Some things to note, if we leave out the assignment and put the object in the macro, the macro is easily written like so:

#define RELEASE_AND_ASSIGN_TO(obj, expr) [obj release]; obj = expr;

The reason why I don't want it that way is I feel that reading the code is tougher. I want my eyes to see the assignment on the left. Having something akin to a function call in the place of an assignment I think is awful for clean code and readability.

I tried one hybrid possibility:

#define RELEASE_AND_ASSIGN_TO(obj, expr) [obj release], obj = expr;

The idea is that the release and assignment occurs, and the comma operator returns obj. Much to my confusion, it seems that Objective-C wants to use the [obj release]'s return value. I don't know why, I thought the right side was taken?

I'm a little lost. Right now, not having the assignment made explicit when calling the macro I don't think is a style I want to advocate. Any advice?

+1  A: 

Be careful, this macro:

 #define RELEASE_AND_ASSIGN_TO(obj, expr) [obj release]; obj = expr;

will fail if obj has retain count 1 and expr uses it or is the same as obj. Assignment to self is something you have to watch for. Also, what retains expr?

What you are trying to do is already encapsulated in the synthesized setter for retained properties -- meaning, if you have

@property (retain, ...) TYPE* obj;

and

@synthesize obj;

then,

self.obj = expr;

will, make sure the old value is released and the new one is retained. It handles assign to self, nil, and if self.obj is already nil. Note that:

self.obj = expr;

is not the same as

obj = expr;

the former calls the set message (and release/retains properly), the latter is just a direct assignment to the instance variable.

To start to do a macro the way you want, use the comma operator. It runs each expression left to right and yields the last one. Something like:

 #define RELEASE_AND_ASSIGN_TO(obj, expr) ([obj autorelease], (obj=(expr)), [obj retain], obj)
Lou Franco
Good to note on the assignment equality. One problem with using properties is that they are public, so you can't use this for private fields. In fact, it's the reason I'm making it. I've always advocated for private properties...come on, Apple!
ZaBlanc
The autorelease will work (because it returns a value), but it doesn't feel right. It's misleading to the user. I still don't see why release just wouldn't work.
ZaBlanc
release calls dealloc immediately if it takes the retain count to 0. The object is no longer legal to use in any way. autorelease releases when the pool is drained, which is after this line of code is run (unless your expr somehow accesses the pool and drains it)
Lou Franco
if you want it to be private, just don't declare an @property and write setobj and getobj yourself in the .m (and don't put them in the .h). They can still be called, but you get warnings (and you don't know about them).
Lou Franco
You can declare private properties in a private category.
Kendall Helmstetter Gelner
+1  A: 

I don't know what the heck you are trying to do, but the comma operator takes the right expression, yes, but then you must also put ( and ) around it, for else the statement

myObj = [obj release], obj = expr;

will just be

myObj = [obj release]; obj = expr;

but

myObj = ([obj release], obj = expr);

will be

[obj release];
myObj = obj = expr;

So try it like this:

#define RELEASE_AND_ASSIGN_TO(obj, expr) ([(obj) release], (obj) = (expr))
mvds
This wins because "you must also put ( and )" was something I didn't realize. I'd looked up docs, too. Oops!
ZaBlanc
+1  A: 

Not a direct answer, but you can have "private" properties/methods in a "class continuation":

@interface MyFoo : NSObject {
    id publicReadonlyProperty;
    id privateProperty;
}

@property(nonatomic,readonly,retain) id publicReadonlyProperty;

-(void)somePublicMethod;

// Method names starting with "_" are "reserved for use by Apple".
//-(void)_dontDoThis;

@end

And in the source file:

// This is a class continuation:
@interface MyFoo()

@property(nonatomic,retain) id privateProperty;
// You can make properties read-write for private use too:
@property(nonatomic,retain) id publicReadonlyProperty;

-(void)somePrivateMethod;

@end

// The compiler checks for things in both the original definition and the continuation:
@implementation MyFoo

@synthesize privateProperty;

-(void)somePublicMethod{}

-(void)somePrivateMethod{}

@end

Of course, nothing in Obj-C is really private (you can use object_getInstanceVariable() or class_getInstanceVariable()), but it's generally understood that if you don't have the definition for the method, it's "private", which is good enough. Right?

tc.
I was just about to post the same thing... nice writeup.
Kendall Helmstetter Gelner
Oh, excellent. I use continuations, never considered properties a possibility.
ZaBlanc
Still cumberson. I hate Objective-C. :-)
ZaBlanc
Sure, but having random macros that do curious things is going to be a pain to debug. You could use Obj-C++ and write the equivalent of shared_ptr, but I don't know *anyone* advocating Obj-C++ despite its niceties like RAII. What would you use in plain C?
tc.
A: 

Thanks for all your help everyone! Ultimately, I was omitting the parens. Relied on some bad documentation for the comma operator. Here are the final macros:

#define RELEASE_AND_NIL(obj) ([(obj) release], nil)
#define RELEASE_AND_ASSIGN_TO(obj, expr) ([(expr) retain], [(obj) release], (obj) = (expr))

And they can be called as:

ptr = RELEASE_AND_NIL(ptr);
ptr = RELEASE_AND_ASSIGN_TO(ptr, newPtr);

The value in doing it this way is that when I write the code, seeing "ptr =" on the left allows me to read the code a lot easier than if it looked like:

RELEASE_AND_NIL(ptr);
RELEASE_AND_ASSIGN_TO(ptr, newPtr);

Thanks again!

ZaBlanc