Understanding Objective-C Property Attributes and Memory Management

In Objective-C, the @property directive simplifies the declaration of accessor methods and instance variables. At compile time, the compiler automatically synthesizes an instance variable (prefixed with an underscore), a getter, and a setter for each property. This process is known as "auto-synthesis".

The synthesized methods access the instance variable via a memory offset. For example, declaring:

@property (nonatomic, strong) UIButton *myButton;
@property (nonatomic, strong) NSMutableArray *myArray;

results in the following runtime metadata (verified with runtime functions):

  • Instance variables: _myButton, _myArray
  • Methods: myButton, setMyButton:, myArray, setMyArray:

@synthesize vs @dynamic

Two directives control the synthesis behavior:

  • @synthesize (default in modern Xcode): Instructs the compiler to auto-generate getter/setter methods. If you provide a custom implemantation for one of these methods, your version overrides the auto-generated one. Example: ``` @synthesize name = _name;
    • (void)setName:(NSString *)value { if (_name != value) { _name = value; } }
    • (NSString *)name { return _name; }
  • @dynamic: Tells the compiler that the getter and setter will be provided at runtime (e.g., via dynamic method resolution). You must supply both methods yourself, or the application will crash when they are called. Example: ``` @dynamic heButton;
    • (void)setHeButton:(UIButton *)button { if (_heButton != button) { _heButton = button; } }
    • (UIButton *)heButton { return _heButton; }
    
    

Property Attributes Explained

Attribute Description
readwrite Default; allows both reading and writing.
readonly Only a getter is synthesized; no setter.
assign Direct assignment, no reference count change. Used for primitive types (int, float, etc.) and non-Objective-C objects.
retain In MRC, releases old value and retains new one (increases ref count). In ARC, use strong instead.
copy Sends a copy message to the incoming value, producing an immutable copy. Commonly used for NSString, NSArray, NSDictionary.
nonatomic No thread-safety lock on getter/setter; faster but unsafe for concurrent access.
atomic Getter/setter are locked to ensure atomic read/write. Not full thread safety—only protects the accessor, not other operations (e.g., concurrent release).
strong (ARC) Equivalent to retain; increases the reference count and keeps the object alive.
weak (ARC) A non-owning reference; does not increase ref count. When the referenced object is deallocated, the weak variable is automatically set to nil. Essential for avoiding retain cycles (e.g., delegates).

Atomic vs Nonatomic Example

// Nonatomic (no lock)
- (void)setName:(NSString *)name {
    if (_name != name) {
        _name = name;
    }
}
- (NSString *)name {
    return _name;
}

// Atomic (with @synchronized)
- (void)setMyButton:(UIButton *)button {
    @synchronized (self) {
        if (_myButton != button) {
            _myButton = button;
        }
    }
}
- (UIButton *)myButton {
    @synchronized (self) {
        return _myButton;
    }
}

Weak vs Assign vs Strong

  • assign: For non-object types. If used on an object, the pointer becomes a dangling pointer when the object is deallocated.
  • weak: For objects. Automatically nilifies the pointer upon the object’s deallocation, preventing dangling pointers.
  • strong: Takes ownership of the object, increasing its retain count.

Example demonstrating reference count changes:

UIButton *btn = [[UIButton alloc] init];
NSLog(@"Ref count: %ld", CFGetRetainCount((__bridge CFTypeRef)btn));

self.strongRef = btn;       // Ref count becomes 2
self.strongRef2 = btn;      // Ref count becomes 3
self.assignRef = btn;       // Ref count stays 3 (no retain)
self.weakRef = btn;         // Ref count stays 3 (no retain)
[self.view addSubview:btn]; // Ref count becomes 4

self.strongRef = nil;
self.strongRef2 = nil;
self.assignRef = nil;
self.weakRef = nil;
NSLog(@"Ref count after nilling: %ld", CFGetRetainCount((__bridge CFTypeRef)btn)); // 2

Weak implementation overview: The runtime maintains a hash table (weak table) mapping object addresses to arrays of weak pointer addresses. On deallocation, all weak pointers are set to nil and the entry is removed.

Copy Attribute Deep Dive

Using copy ensures that the property holds an immutable version of the assigned object. This is critical for NSString, NSArray, NSDictionary when the assigned value might be mutable.

Example with NSString

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyString;

NSMutableString *mutableString = [NSMutableString stringWithString:@"original"];
self.strongString = mutableString;
self.copyString   = mutableString;

NSLog(@"Before: strong=%@, copy=%@", self.strongString, self.copyString);
[mutableString appendString:@" - modified"];
NSLog(@"After:  strong=%@, copy=%@", self.strongString, self.copyString);
// Output: strong="original - modified", copy="original"

Copy vs MutableCopy for Collections

For immutable containers (NSArray, NSDictionary):

  • copy returns the same object (shallow copy, ref count increases).
  • mutableCopy creates a new mutable container containing the same elements (shallow copy of elements).

For mutable containers (NSMutableArray, NSMutableDictionary):

  • copy creates a new immutable container with the same elements (shallow).
  • mutableCopy creates a new mutable container with the same elements (shallow).

Important: When declaring a mutable property (e.g., NSMutableArray), use strong – not copy – otherwise the property will hold an immutible copy and you won’t be able to mutate it.

@property (nonatomic, strong) NSMutableArray *mutableArray; // correct
@property (nonatomic, copy) NSMutableArray *wrongArray; // becomes immutable NSArray

Example with mutable collection:

NSMutableArray *original = [NSMutableArray arrayWithObjects:@"A", nil];
self.mutableArray = original;        // strong reference, same mutable object
self.wrongArray = original;          // copy → immutable NSArray

[self.mutableArray addObject:@"B"];  // OK
// [self.wrongArray addObject:@"B"]; // crash: unrecognized selector

By following these guidelines, you can avoid common pitfalls related to object ownership, thread safety, and unintended mutation.

Tags: Objective-C iOS ARC property attributes copy

Posted on Thu, 10 Sep 2026 16:14:05 +0000 by ezekiel