Property as a conveyor of State
Consider the following code:
...
@implementation Test0
@synthesize iVar;
@end
@synthesize iVar;
@end
...
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
Test0 *test0 = [[Test0 new] autorelease];
test0.iVar = 5;
NSLog(@"%d", test0.iVar);
[pool drain];
return 0;
}
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
Test0 *test0 = [[Test0 new] autorelease];
test0.iVar = 5;
NSLog(@"%d", test0.iVar);
[pool drain];
return 0;
}
After you declare a property in Objective-C, you have to tell the compiler to instantiate it by using @synthesize directive. This actually tells the compiler to generate a getter and a setter messages. By default, these messages are named var and setVar, where varis a name of the variable. Also, by default, the variable represented by the property has the same name as a property. In our example, the compiler will generate the following:
-(void)setIVar:(int)i
{
iVar = i;
}
-(int)iVar
{
return self->iVar;
}
{
iVar = i;
}
-(int)iVar
{
return self->iVar;
}
As you can see from the Test0 example, properties can be accessed by using the DOTted notation. They can also be accessed via accessor methods, for example [self setIVar:5] or int i = [self iVar].
We can add simple permissioning by specifying readonly attribute instead of readwrite.
We can add simple permissioning by specifying readonly attribute instead of readwrite.
@property (readonly,assign) int iVar;
This would prevent the property from being assigned to. You can still change the value of the corresponding member variable, though, by referencing it directly. For example, I can add a message foo:
-(void)foo
{
self->iVar = 5; //legal because we are referencing a member variable
iVar = r; // illegal because we are referencing a readonly property
}
{
self->iVar = 5; //legal because we are referencing a member variable
iVar = r; // illegal because we are referencing a readonly property
}
We can even do this:
...
@implementation Test1
@synthesize iVar=iVar1;
@end
@synthesize iVar=iVar1;
@end
...
Test1 *test1 = [[Test1 new] autorelease];
test1.iVar = 6;
NSLog(@"%d", test1->iVar1);
test1.iVar = 6;
NSLog(@"%d", test1->iVar1);
Here, we are actually telling the property to latch to different a different instance variable.
I showed all these examples is to show you how to use the properties to describe the state of an object.
Property as a conveyor of the Behavior
As I mentioned previously, compiler generates accessors for each property. Read-write properties get both a getter and a setter, while read-only properties get only a getter. Getters and setters are messages, and if overwritten, can be used to do virtually anything.
@interface Test2 : NSObject {
@private int iVar1;
}
@property (readwrite,assign) int iVar;
-(void)foo;
@end
@implementation Test2
@synthesize iVar=iVar1;
-(void)foo
{
NSLog(@”Foo called”);
}
-(void)setIVar:(int)inIVar
{
iVar1 = inIVar;
[self foo];
}
@end
@private int iVar1;
}
@property (readwrite,assign) int iVar;
-(void)foo;
@end
@implementation Test2
@synthesize iVar=iVar1;
-(void)foo
{
NSLog(@”Foo called”);
}
-(void)setIVar:(int)inIVar
{
iVar1 = inIVar;
[self foo];
}
@end
When we defined a setter, we told the compiler that it does not need to generate another one. Notice how we called [self foo]. We just altered the behavior of the object.
Another way to achieve similar results is by using @dynamic directive. @dynamic tells the compiler not to generate the accessor messages and that we will do it ourselves either by directly defining them or through introspection.
Another way to achieve similar results is by using @dynamic directive. @dynamic tells the compiler not to generate the accessor messages and that we will do it ourselves either by directly defining them or through introspection.
@interface Test3 : NSObject {
}
@property (readwrite,assign) int iVar;
@end
@implementation Test3
@dynamic iVar;
-(int)iVar
{
NSLog(@”iVar called”);
return 1;
}
-(void)setIVar:(int)inIVar
{
NSLog(@”setIVar called”);
}
@end
}
@property (readwrite,assign) int iVar;
@end
@implementation Test3
@dynamic iVar;
-(int)iVar
{
NSLog(@”iVar called”);
return 1;
}
-(void)setIVar:(int)inIVar
{
NSLog(@”setIVar called”);
}
@end
Here, we actually went a step further. As you can see, we don’t even have to alter the state. We can simply alter the behavior of the object!
In reality, accessors don’t even need to be named in default fashion. We can provide our own names by using getter and setter attributes.
In reality, accessors don’t even need to be named in default fashion. We can provide our own names by using getter and setter attributes.
@interface Test4 : NSObject {
@private int iVar;
}
@property (getter=foo,setter=foo1:) int iVar;
@end
@implementation Test4
@synthesize iVar;
-(int)foo
{
return 4;
}
-(void)foo1:(int)inFoo
{
self->iVar = inFoo;
}
@end
@private int iVar;
}
@property (getter=foo,setter=foo1:) int iVar;
@end
@implementation Test4
@synthesize iVar;
-(int)foo
{
return 4;
}
-(void)foo1:(int)inFoo
{
self->iVar = inFoo;
}
@end
More Attributes
We already saw a few attributes, such as readwrite and readonly, getter and setter. Here are some other attributes:
assign, retain, copy
These attributes are pretty self explanatory. They have to do with the way instance variables are assigned. They are also mutually exclusive. assign enforces a brute-force assignment of a value, retain retains a pointer, and copy assigns a copy of the original.
Imagine this:
Imagine this:
The setter method generated will look like this:
-(void)setSVar:(NSString*)inSVar
{
if (self->sVar != inSVar)
{
[self->sVar release];
self->sVar = [inSVar retain];
}
}
{
if (self->sVar != inSVar)
{
[self->sVar release];
self->sVar = [inSVar retain];
}
}
What if we decided to assign-by-copy:
...
-(void)setSVar:(NSString*)inSVar
{
if (self->sVar != inSVar)
{
[self->sVar release];
self->sVar = [inSVar copy];
}
}
{
if (self->sVar != inSVar)
{
[self->sVar release];
self->sVar = [inSVar copy];
}
}
When would we want to do copy assignment? When there is a possibility that inSVar is mutable. Since NSMutableString derives from NSString, we may have a problem if the behavior of the object depends on immutability of sVar. You can find out more about this from "Advanced Strings in Cocoa" article.
nonatomic
By default, property assignments are atomic, i.e. they are mutexed to guarantee thread-safety. Atomicity is a nice feature, but hardly ever needed if application is not multithreaded or you guarantee thread-safety on your own. Therefore, it’s a good practice to use nonatomic attribute, while providing own thread-safety.
A Trick
There is an interesting side-effect to assigning nil to a pointer property. What happens when I execute the following code?
...
-(void)foo
{
self.iVar = @”123”;
self.iVar = nil;
[iVar release];
}
{
self.iVar = @”123”;
self.iVar = nil;
[iVar release];
}
The side-effect of this code is that iVar is not only properly released, but the iVar now points to a nil value. Therefore, [iVar release] WILL NOT crash the code.
Conclusion
The purpose of this article was to give a general introduction to properties. There is more to learn about properties that I was able to discuss. For more details, please refer to “The Objective-C 2.0 Programming Language” guide.
No comments:
Post a Comment