Thursday 1 March 2012

Properties in Objective-c


Properties are a feature in Objective-C that allows us to automatically generate accessors. In this blog I will illustrate what different attributes in @property (params) will interpolate . In property we have four kinds of attributes.

1. Writability
2. Setter Semantics
3. Atomicity
4. Accessor Method Names

You can use all them or none of them. e.g.

@property([Atomicity],[Writability],[Setter Semantics],[Accessor Method Names]) type var_name;

Where [ ] denotes that they are optional.

Note:
1. You can only use one attribute from each category except Accessor Method Names. You can use both in this case.
2. If use readonly(writability) with setter(accessor method name), it will generate warning.




Writability
In this category we have two attributes
a. readwrite
b. readonly


readwrite
Indicates that the property should be treated as read/write. This attribute is the default.
e.g.

@interface Person : NSObject {
int age;
float salary;
}
@property int age; // by default attributes are readwrite, assign and atomic
@property (readwrite) float salary; //This is also same as above
@end

@implementation Person
@synthesize age;
@synthesize salary;

@end

int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
Person *objPerson = [[Person alloc] init];
objPerson.age = 25; //setting the value
NSLog(@"Person age is %d years",objPerson.age); //getting the value
[objPerson setAge:12]; //This is also valid as copiler will create setter and getter for this property
NSLog(@"Person age is %d years",[objPerson age]); // accessing the getter method
objPerson.salary = 50000.0f; //setting the value
NSLog(@"Person salary is %f years",objPerson.salary); //getting the value
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
[pool release];
return 0;
}

The compiler will create a setter and getter for the properties age and salary. By default the getter method is named same as propertyName and setter is setPropertyName:, in above example you the compiler will create accessors like this

/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ Compilers generated accessors ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
- (void)setAge:(int)pAge{
age = pAge;
}
- (int)age{
return age;
}

- (void)setSalary:(float)pSalary{
salary = pSalary;
}
- (float)salary{
return salary;
}
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ Compilers generated accessors ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */


readonly
This indicates that the property is read-only.

@interface Person : NSObject {
int age;
float salary;
}
@property(readonly) int age;
@end

@implementation Person
@synthesize age;
@end

int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
Person *objPerson = [[Person alloc] init];
objPerson.age = 25; //setting the value will generate error
NSLog(@"Person age is %d years",objPerson.age); //accessing the getter method
NSLog(@"Person age is %d years",[objPerson age]); // accessing the getter method
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
[pool release];
return 0;
}



Setter Semantics
These attributes specify the semantics of a set accessor.
1.copy
2.assign
3.retain
4.strong
5.weak



copy
Specifies that a copy of the object should be used for assignment.
The previous value is sent a release message. The copy is made by invoking the copy method. This attribute is valid only for object types, which must implement the NSCopying protocol.

e.g
I'm taking NSMutableData ivar as this class implements NSCopying protocol.

@interface Person : NSObject {
NSMutableData *objCopy;
}
@property(copy) NSMutableData *objCopy;
@end

@implementation Person
@synthesize objCopy;
@end
int main(int argc, char *argv[])
{
@autoreleasepool {
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
Person *objPerson = [[Person alloc] init];
NSMutableData *tObj = [[NSMutableData alloc] init];
objPerson.objCopy = tObj;
NSLog(@"Instance variable %p != temporary object %p",objPerson.objCopy,tObj);
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
}
return 0;
}
Log: Instance variable 0x6830bd0 != temporary object 0x682ced0

/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ The compiler generated setter may look like this ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */

- (void)setObjCopy:(NSObject*)tObj{
if (objCopy != tObj) {
[objCopy release];
objCopy = [tObj copy];
}
}

Note: I haven't included getter definition here, I will give the definition of setter when Atomicity category comes.


assign
Specifies that the setter uses simple assignment. This attribute is the default.

@property(assign) type ivar_name;
@property type ivar_name;

Both are same for example you can refer to first example.


retain
Specifies that retain should be invoked on the object upon assignment.
The previous value is sent a release message and new value is retained.

@property(retain) int abc; //This will generate error
@property(retain) NSObject *obj; //Ok
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ The compiler generated setter will look like this ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
- (void)setObj:(NSObject*)tObj{
if (obj != tObj) {
[obj release];
obj = [tObj retain];
}
}


strong
Specifies that there is a strong (owning) relationship to the destination object. This was introduced with the ARC and is similar to retain in non-ARC environment.



weak
Specifies that there is a weak (non-owning) relationship to the destination object.
If the destination object is deallocated, the property value is automatically set to nil.

e.g.
I have created a strong and a week reference and after the block is over the strong reference is retained and the week one is released and assigned nil.

@interface Person : NSObject {
NSObject *objStrong;
__weak NSObject *objWeek;
}
@property(strong) NSObject *objStrong;
@property(weak) NSObject *objWeak;
@end

@implementation Person
@synthesize objStrong;
@synthesize objWeak;
@end

int main(int argc, char *argv[])
{
@autoreleasepool {
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
Person *objPerson = [[Person alloc] init];
{
objPerson.objStrong = [[NSObject alloc] init];
objPerson.objWeak = [[NSObject alloc] init];
}
NSLog(@"Strong is %@",objPerson.objStrong);
NSLog(@"Weak is %@",objPerson.objWeak);
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
}
return 0;
}

After the block is over the week object is deallocated and objWeek reference is assigned nil;


Here it the log
Strong is <NSObject: 0x68189f0>
Weak is (null)



Atomicity
You can use this attribute to specify that accessor methods are not atomic. There is no keyword to denote atomic.
nonatomic
Specifies that accessors are nonatomic. By default, accessors are atomic.
Properties are atomic by default so that synthesized accessors provide robust access to properties in a multithreaded environment, that is, the value returned from the getter or set via the setter is always fully retrieved or set regardless of what other threads are executing concurrently.
If you specify strong, copy, or retain and do not specify nonatomic, then in a reference-counted environment, a synthesized get accessor for an object property uses a lock and retains and autoreleases the returned value. The implementation of accessors will be similar to the following:
@property(retain) NSObject *obj;
- (void)setObj:(NSObject*)tObj{
[_internal lock]; // lock using an object-level lock
if (obj != tObj) {
[obj release];
obj = [tObj retain];
}
[_internal unlock];
}
- (NSObject*)obj{
[_internal lock]; // lock using an object-level lock
id result = [[ obj retain] autorelease];
[_internal unlock];
return result;
}

And if you write nonatomic then accessors will be similar to the following:
@property(retain) NSObject *obj;
- (void)setObj:(NSObject*)tOb{
if (obj != tObj) {
[obj release];
obj = [tObj retain];
}
}
- (NSObject*)obj{
return obj;
}



Accessor Method Names
The default names for the getter and setter methods associated with a property are propertyName and setPropertyName: respectively for example, given a property foo, the accessors would be foo and setFoo:. The following attributes allow you to specify custom names instead. They are both optional and can appear with any other attribute (except for readonly in the case of setter=).


getter=getterName
Specifies the name of the get accessor for the property. The getter must return a type matching the property's type and take no parameters.


setter=setterName
Specifies the name of the set accessor for the property. The setter method must take a single parameter of a type matching the property's type and must return void.
If you specify that a property is readonly and also specify a setter with setter=, you get a compiler warning.


@interface Person : NSObject {
NSObject *obj;
}
@property(getter = myGetter, setter = setMySetter:) NSObject *obj; //Ok

@end

@implementation Person
@synthesize obj;
@end
int main(int argc, char *argv[])
{
@autoreleasepool {
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
Person *objPerson = [[Person alloc] init];
NSObject *tObj = [[NSObject alloc] init];
objPerson.mySetter = tObj;
NSLog(@"Object reference %p",objPerson.myGetter);
/* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
}
return 0;
}


Property Implementation Directives
1. @synthesize
2. @dynamic


@synthesize
You use the @synthesize directive to tell the compiler that it should synthesize the setter and/or getter methods for a property if you do not supply them within the @implementation block. The @synthesize directive also synthesizes an appropriate instance variable if it is not otherwise declared.
You can use the form property=ivar to indicate that a particular instance variable should be used for the property, for example:

@interface Person : NSObject {
NSObject *obj;
}
@property(nonatomic, retain) NSObject *obj; //Ok

@end

@implementation Person
@synthesize obj = myObj;

- (void)aMehtod{
obj = nil;
myObj = nil; //both will work
}
@end


@dynamic
You use the @dynamic keyword to tell the compiler that you are going to provide fulfill the API contract implied by a property either by providing method implementations directly or at runtime using other mechanisms such as dynamic loading of code or dynamic method resolution. It suppresses the warnings that the compiler would otherwise generate if it can't find suitable implementations. You should use it only if you know that the methods will be available at runtime.

e.g.


@interface Person : NSObject {
NSObject *obj;
}
@property(nonatomic, retain) NSObject *obj; //Ok

@end

@implementation Person
@dynamic obj;

- (void)setObj:(NSObject*)tObj{
if (obj != tObj) {
[obj release];
obj = [tObj retain];
}
}
- (NSObject*)obj{
return obj;
}
@end

Here you have provided the implementation at compile time
If you have ever used core data then the managed class will look like

@interface MyClass : NSManagedObject
@property(nonatomic, retain) NSString *value;
@end

@implementation MyClass
@dynamic value;
@end

And  in this case the definitions are provided at runtime.

2 comments: