蓝天,小湖,湖水中一方小筑

@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.