@property and its attributes
Recently working on iOS project, and have met some problems while using the
attributes of @property
. I have searched some documents and here is my idea
about this.
What is @property
So, first thing is to talk about the @property
. This will offer a way to
encapsulate the class. With the @synthesize keyword, the compiler will
generate a getter and setter function for the variable. For example, there is
a class called FooTester, which contains a variable named foo, so here is
piece of the code:
// Definiation
@interface FooTester : NSObject
@property int foo;
@end
// Implementation
@implementation PropertyTester
@synthesize foo;
@end
In this case, the compiler will generate setter and getter for variable foo
,
like this:
- (int) foo {
return foo;
}
- (void) setFoo:(int) i {
foo = i;
}
It is really a great function, which brings lots of convenience to development. Also, it can accept various attributes as its parameter, which is describing below.
Attributes of @property
The full syntax for @property is
@property (<#attribute_list>) <#variable_type> <#variable_name>
The attributes is used to define some behavior of the variable.
readonly and readwrite
As the meaning of the word, those two attributes are used to control read-
write access of the property, while readwrite
is the default value.
atomic and nonatomic
atomic
and nonatomic
attributes are used to indicate whether the setter
and getter function returns the whole value of variable. With atomic
, the
synthesized getter and setter function will be guaranteed. For example, there
two thread A and B, thread A is getting value in middle while thread B is
setting value to the object. If the variable is declared with atomic
, the
value returned to A should be a complete one, which means there is impossible
to return a object combines the old and new value. Most likely, the getter
will generate a auto-release copy and then return it.
There is no this guarantee for nonatomic
property.
One more thing need to be aware is the atomic
do not guarantee thread
safety. It just guarantee the variable has atomic access. For example, if
thread A, B and C are setting the variable to different value at almost same
time, then the value of variable is unpredictable.
The default attribute is atomic
, in case nonatomic
is specified in the
@property
declaration. And generally speaking, in nonatomic
case, the
getter and setter function will a little more faster.
assign, copy and retain
Those three attributes are related to memory management, which are used for
control behavior of setters. The assign
attribute is a simple assignment,
without any other operations. The copy
attribute means the setter will
assign the copy of the value to the object variable. The retain
attribute
means it will assign the pointer to the object variable.
Hmm… It is really confusion, so let’s write some code to make it more clearly.
First, we need a class with those property type, here are the declaration and implementation:
#import <Foundation/Foundation.h>
@interface PropertyTester : NSObject
@property (assign) NSMutableString *str_assign;
@property (copy) NSMutableString *str_copy;
@property (retain) NSMutableString *str_retain;
@end
#import "property.h"
@implementation PropertyTester
@synthesize str_assign;
@synthesize str_copy;
@synthesize str_retain;
@end
Then we have to prepare some code to test the class. I have written two simple code to test this class, one is focusing on the memory and the other is about reference count.
Here comes the program for memory:
#import <Foundation/Foundation.h>
#import "property.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
PropertyTester *property_tester = [[PropertyTester alloc] init];
NSMutableString *tmp_str = [NSMutableString stringWithFormat:@"first"];
property_tester.str_assign = tmp_str;
property_tester.str_copy = tmp_str;
property_tester.str_retain = tmp_str;
NSLog (@"%p, %@", tmp_str, tmp_str);
NSLog (@"%@", property_tester);
[tmp_str appendString:@"second"];
NSLog (@"%p, %@", tmp_str, tmp_str);
NSLog (@"%@", property_tester);
[pool drain];
return 0;
}
The logic for this program is simple. I created a NSMutableString
object
named tmp_str
and assign it to three different type of property variables,
then update the object. The information for class object will be printed out
for examination. I have overloaded the description
of class to show some
internal information, so the output of the program is:
2013-02-08 16:39:14.557 property_update[66361:707] 0x10fa15030, first
2013-02-08 16:39:14.580 property_update[66361:707] PropertyTester: 0x10fa11b70
assign: 0x10fa15030, first
copy: 0x10fa14e80, first
retain: 0x10fa15030, first
2013-02-08 16:39:14.580 property_update[66361:707] 0x10fa15030, firstsecond
2013-02-08 16:39:14.581 property_update[66361:707] PropertyTester: 0x10fa11b70
assign: 0x10fa15030, firstsecond
copy: 0x10fa14e80, first
retain: 0x10fa15030, firstsecond
According to the screen log, the object tmp_str
has initialized value
first
, while the memory address is 0x10fa15030
. After the assignment
operations, three variables all contains the same value of tmp_str
, but the
memory address of str_copy
is different with original one. Then the original
string tmp_str
updates its value to firstsecond
, while the value for
variable str_assign
and str_retain
has also updated, but not the
str_copy
.
As mentioned above, the assign
will do simple assignment, while retain
will assign the pointer, which is shown in the screen log. The two variable
has the same value and memory address with original string, and will be
updated if original string updated. However, copy
means assing a copied
value to the property, so it contains different memory address, and will no
update if original object updated.
Now let’s use another program to show difference between assign
and
retain
. The prime difference between assign
and retain
is reference
count, I will use retainCount
in the program to get the reference count.
Here is the program:
#import <Foundation/Foundation.h>
#import "property.h"
void print_ref_cnt(NSObject *obj1, NSObject *obj2, void (^block)(void)) {
NSLog(@"====================");
NSLog(@"Reference count of %@", obj1);
NSLog(@"Before: obj1: %ld, obj2: %ld", (unsigned long)obj1.retainCount, (unsigned long)obj2.retainCount);
block();
NSLog(@"After : obj1: %ld, obj2: %ld", (unsigned long)obj1.retainCount, (unsigned long)obj2.retainCount);
NSLog(@"@@@@@@@@@@@@@@@@@@@@");
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
PropertyTester *property_tester = [[PropertyTester alloc] init];
NSMutableString *tmp_str_assign = [NSMutableString stringWithFormat:@"assign"];
NSMutableString *tmp_str_copy = [NSMutableString stringWithFormat:@"copy"];
NSMutableString *tmp_str_retain = [NSMutableString stringWithFormat:@"retain"];
NSLog(@"\n\n\nAssign");
print_ref_cnt(tmp_str_assign, property_tester.str_assign, ^(void) { property_tester.str_assign = tmp_str_assign; });
//print_ref_cnt(tmp_str, ^(void) { property_tester.str_assign.release; }); // Over release, seg fault
NSLog(@"\n\n\nCopy");
print_ref_cnt(tmp_str_copy, property_tester.str_copy, ^(void) { property_tester.str_copy = tmp_str_copy; });
NSLog(@"\n\n\nRetain");
print_ref_cnt(tmp_str_retain, property_tester.str_retain, ^(void) { property_tester.str_retain = tmp_str_retain; });
NSLog (@"%@", property_tester);
[pool drain];
return 0;
}
The logic for this program is also simple, it creates three objects which will
be used for assign
, copy
and retain
separatedly, then the program will
print out the reference count before and after invoking the setter function.
Here is the result of the program:
2013-02-09 10:18:57.058 property_ref[67671:707]
Assign
2013-02-09 10:18:57.060 property_ref[67671:707] ====================
2013-02-09 10:18:57.060 property_ref[67671:707] Reference count of assign
2013-02-09 10:18:57.061 property_ref[67671:707] Before: obj1: 1, obj2: 0
2013-02-09 10:18:57.062 property_ref[67671:707] After : obj1: 1, obj2: 0
2013-02-09 10:18:57.062 property_ref[67671:707] @@@@@@@@@@@@@@@@@@@@
2013-02-09 10:18:57.063 property_ref[67671:707]
Copy
2013-02-09 10:18:57.064 property_ref[67671:707] ====================
2013-02-09 10:18:57.064 property_ref[67671:707] Reference count of copy
2013-02-09 10:18:57.064 property_ref[67671:707] Before: obj1: 1, obj2: 0
2013-02-09 10:18:57.065 property_ref[67671:707] After : obj1: 1, obj2: 0
2013-02-09 10:18:57.065 property_ref[67671:707] @@@@@@@@@@@@@@@@@@@@
2013-02-09 10:18:57.066 property_ref[67671:707]
Retain
2013-02-09 10:18:57.066 property_ref[67671:707] ====================
2013-02-09 10:18:57.067 property_ref[67671:707] Reference count of retain
2013-02-09 10:18:57.067 property_ref[67671:707] Before: obj1: 1, obj2: 0
2013-02-09 10:18:57.067 property_ref[67671:707] After : obj1: 2, obj2: 0
2013-02-09 10:18:57.068 property_ref[67671:707] @@@@@@@@@@@@@@@@@@@@
2013-02-09 10:18:57.068 property_ref[67671:707] PropertyTester: 0x10b714760
assign: 0x10b715020, assign
copy: 0x7fe234101390, copy
retain: 0x10b715150, retain
For the assign
, the reference count for class object doesn’t change, while
for retain
, the reference count increased 1 after invoking setter function,
since the @property
with retain
will invoke retain
before assignment.
weak and strong
Another two attributes are weak
and string
, which is introduced from ARC
feature. The attribute strong
is the same with retain
. While the attribute
weak
is almost same with assign
. The minor difference between weak
and
assign
is that the weak
will set object to nil after releasing, which is
missed in assign
attribute.