Saturday, September 26, 2009

Read for You, Write for Me, Right?

Properties are a way to expose data to other classes. Because they make memory management more convenient on the iPhone and in non-GCC Cocoa apps when you declare them retain, many of us also use properties within our class. When we use synthesized instance variables, we don't have a choice, because the underlying instance variables created by the runtime aren't available even to our own class, and we have to use the accessors and mutator or dot notation to use them.

What happens, however, when you want a property to be read-only to other objects, but you also want to be able to use the synthesized mutator within your own class to assign new values? In other words, what do you do when you want a property to be read-only to the rest of the world, but read/write within your own class?

Extensions to the rescue. Before we had extensions, it was common to declare a category at the top of your implementation file with any private methods. This would prevent the compiler from complaining when you called a private method from within your class. The compiler would see the private method declaration in the implementation file when your class was compiled, but wouldn't see those methods when it compiled other classes. Under this old way of doing it, we would simply include the mutator method in the category, and only put the accessor in the header file, and that would effectively make the instance value read-only to the rest of the world by not exposing the mutator.

In Objective-C 2.0, this practice has been formalized into extensions, which are basically just anonymous, nameless categories. One thing you can do with an extensions is to redefine your declared properties. So, you can declare a property in your header file as readonly, and then in your implementation file, create a class extension with the same properties re-declared as readwrite. Ét voilà! Your class will be able to use both the accessor and mutator for those properties while external classes will only be able to use the accessor. Cake and nom nom. Life is good.

Note: you cannot change all aspects of a property. You can't, for example, declare it copy in your header file and retain in your extension.
Let's look at a simple example. Let's say we were writing a subclass of NSOperation that had two properties. We want these values to be set by in the init call, but because our operation will be running concurrently on another thread, we don't want the values changed after that by external objects, but we may need to change them ourselves. To do that, we might declare our class like this:
#import <Foundation/Foundation.h>
#import <QTKit/QTKit.h>


@interface MyUpdateOperation : NSOperation {

}

@property (readonly, copy) QTMovie *movie;
@property (readonly, copy) NSView *view;
- (id)initWithMovie:(QTMovie *)inMovie andView:(NSView *)inView;
@end


And then, in the implementation file, we would use an extension to redefine those two properties:

#import "MyUpdateOperation.h"


@interface MyUpdateOperation ()
@property (readwrite, copy) QTMovie *movie;
@property (readwrite, copy) NSView *view;
@end


@implementation MyUpdateOperation
- (id)initWithMovie:(QTMovie *)inMovie andView:(NSView *)inView
{
// Logic goes here
}

-(void)main
{
// Logic goes here
}

@end


It's a simple trick, but one with the potential to make your life more pleasant.



7 comments:

Michael said...

Jeff,

Whilst I understand the solution, what I don't know is where and why I would I use this sort of approach?

Could you enlighten me a bit.

Thanks, Michael.

Jeff LaMarche said...

It's a good fit pretty much anywhere that you would declare a readonly property - basically any place where want other objects to be able to see a value, but not change it. Let's say we had an object with a float property that store our progress doing a long task on a scale of 1-100. We'd want other objects to be able to SEE that value, but we wouldn't want other objects updating it.

You can, of course, use direct instance variable access in that case, but if you want to use properties in your class to set values in that scenario this works.

Michael said...

thanks jeff...think i've got it now. appreciated. Michael

Carlo said...

Jeff,

at the beginning of your post you state:

When we use synthesized instance variables, we don't have a choice, because the underlying instance variables created by the runtime aren't available even to our own class

I'm a little bit confused: why we cannot use ivar again? for example in the "init" we are strongly suggested to not use setter/getters. And I have never been warned or blocked by the compiler.

Thanks

Carlo

Jeff LaMarche said...

Carlo:

If you used synthesized instance variables (available only on the iPhone device or in 64-bit Cocoa apps), you don't declare your iVars - they are created at runtime and, so the compiler doesn't see the underlying variable, and you have to use the the accessor or dot notation.

Although that may have changed. When I tested this on the iPhone a few weeks back, I was unable to directly access the synthesized instance variables, but I recently (by accident) referred to a synthesized instance variable in a 64-bit Cocoa app, and the compiler didn't complain, so this may be functionality that's in flux or inconsistent between the iPhone (device only) and the Mac (64-bit only).

Carlo said...

Hi Jeff,

thanks for your clarification: so I don't declare my ivar. I didn't catch this point. Thanks a lot.

Carlo

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels