Properties and Encapsulation
|
This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A @property declares a contract for accessing a value: a getter, usually a setter, a memory-management
policy and an atomicity guarantee. The compiler generates the accessors and the backing instance variable for
you. Properties replaced hand-written accessor pairs in Objective-C 2.0 and are now the default way to express
any piece of object state.
Declaring a Property
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, weak) id<PersonDelegate> delegate;
@property (nonatomic, readonly) NSString *fullDescription;
@end
Each line generates, by default:
-
a getter named after the property (
name,age), -
a setter named
set+ capitalised name (setName:,setAge:), unlessreadonly, -
a backing ivar named with a leading underscore (
_name,_age).
The Attribute Table
Attributes come in four independent groups. Pick one from each group that applies; the order inside the parentheses does not matter.
Memory Management
| Attribute | Meaning |
|---|---|
|
The property owns the object: setting it retains the new value and releases the old. The default for object types under ARC. |
|
A non-owning reference that is automatically set to |
|
The setter stores a |
|
Plain assignment with no memory management. Correct for scalars ( |
|
A non-owning object reference that is not zeroed on deallocation — it becomes a dangling pointer. Use only for the rare class that does not support weak references (a few Core Foundation-backed classes), or for deliberate performance work. |
|
A synonym for |
The copy-for-NSString rule is worth spelling out, because the bug it prevents is subtle:
@property (nonatomic, copy) NSString *nameCopied;
@property (nonatomic, strong) NSString *nameStrong;
NSMutableString *m = [NSMutableString stringWithString:@"Ada"];
person.nameCopied = m;
person.nameStrong = m;
[m appendString:@" Lovelace"];
person.nameCopied; // @"Ada" -- an immutable snapshot was taken
person.nameStrong; // @"Ada Lovelace" -- the caller mutated your object's state!
copy on an already-immutable value is essentially free: NSString’s `copy returns self with a retain.
Atomicity
| Attribute | Meaning |
|---|---|
|
The default. The generated accessors use a lock so that a get or a set is never interleaved with another get or set — you can never read a half-written pointer. |
|
No locking. Faster, and what virtually all application code uses. |
atomic is not thread safety. It guarantees only that an individual accessor call is indivisible. It says
nothing about the consistency of two related properties, and this classic sequence is still a race under
atomic:
if (self.items.count > 0) { // atomic read
id first = self.items[0]; // another thread may have emptied it in between
}
Real thread safety requires a lock, a serial queue or an immutable design — see
Concurrency. Because atomic costs performance
without buying safety, nonatomic is the conventional default; write it explicitly on every property.
Access
| Attribute | Meaning |
|---|---|
|
Getter and setter are generated. The default. |
|
Only a getter is generated. Callers cannot assign. |
|
Rename the getter. Conventional for Booleans: |
|
Rename the setter. Rare. |
|
Declares a class property — accessors on the class object rather than instances. No ivar is synthesised; you must implement the accessors yourself. |
|
Nullability, covered in Lightweight Generics and Nullability. |
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
// generates -isEnabled and -setEnabled:
@property (class, nonatomic, readonly) Person *defaultPerson;
// [Person defaultPerson] -- implement +defaultPerson yourself
The getter=is… form is not decoration: Cocoa’s key-value coding and bindings look for exactly this naming,
and if (view.isHidden) reads far better than if (view.hidden).
Autosynthesis, @synthesize and @dynamic
Modern Clang synthesises accessors and the backing ivar automatically — @synthesize is needed only in the
cases below.
@implementation Person
// Nothing needed: _name, -name and -setName: all exist.
@end
@synthesize becomes necessary when:
@implementation Person
// 1. You implemented BOTH accessors yourself for a readwrite property --
// autosynthesis then does not run, so ask for the ivar explicitly.
// The same applies to a `readonly` property once you write its getter:
// that is the only accessor it has, so autosynthesis stops there too.
@synthesize name = _name;
// 2. You want a differently-named backing ivar.
@synthesize age = age_;
// 3. The property comes from a protocol (autosynthesis does not cover those).
@synthesize delegate = _delegate;
@end
@dynamic is the opposite declaration: do not generate anything, the accessors will exist at run time. It
silences the missing-implementation warning and is what NSManagedObject subclasses in Core Data use, along
with any class that supplies accessors through +resolveInstanceMethod::
@implementation Record
@dynamic identifier; // Core Data (or your own runtime code) provides the accessors
@end
Backing Ivars and When to Bypass the Accessor
The synthesised ivar is _propertyName. Use it directly in exactly two places, and use the property
everywhere else:
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = [name copy]; // direct: a subclass override of -setName: must not run
} // on a half-initialised object
return self;
}
- (void)dealloc {
[_connection invalidate]; // direct: same reasoning, in reverse
}
- (void)rename:(NSString *)newName {
self.name = newName; // everywhere else: go through the accessor, so that
} // KVO notifications, copy semantics and overrides all apply
Note the [name copy] in the initializer: assigning to the ivar directly bypasses the setter, so the copy
attribute’s behaviour must be reproduced by hand.
Custom Accessors
// A lazily-computed readonly property. Writing the getter is writing *every*
// accessor a readonly property has, so autosynthesis does not run and the
// backing ivar must be requested explicitly -- without this line the method
// below fails with "use of undeclared identifier '_fullDescription'".
@synthesize fullDescription = _fullDescription;
- (NSString *)fullDescription {
if (!_fullDescription) {
_fullDescription = [NSString stringWithFormat:@"%@ (%ld)", self.name, (long)self.age];
}
return _fullDescription;
}
// A setter with a side effect. Note it must honour the declared `copy` policy.
- (void)setName:(NSString *)name {
if (_name != name) {
_name = [name copy];
[self invalidateCache];
}
}
A custom accessor takes over the contract completely — including the memory-management policy the attribute
promised. Getting that wrong (storing without copy on a copy property) is a silent bug.
Dot Syntax versus Accessor Messages
These are identical — dot syntax is pure sugar, resolved to the same message send:
person.name = @"Ada"; // [person setName:@"Ada"]
NSString *n = person.name; // [person name]
The conventions that keep dot syntax readable:
-
Use it for properties — state access that is cheap and side-effect-free.
-
Use bracket syntax for methods, especially anything that performs work:
[array count]on a plain method,[connection start],[view layoutIfNeeded]. -
Chaining through dots (
a.b.c.d) is legal but hides four message sends, each of which may returnnil; break it up when it stops being obvious.
Dot syntax on a struct-typed property has a genuine trap:
// view.frame.origin.x = 10; // compile error: cannot assign to a returned struct
CGRect f = view.frame; // read it out
f.origin.x = 10; // mutate the copy
view.frame = f; // write it back
KVC-Compliant Accessor Naming
Key-value coding finds accessors by name. Following the conventional naming is therefore what makes a
property work with KVC, KVO, bindings, NSPredicate, NSSortDescriptor and Core Data — and the compiler
cannot warn you when you break it:
| Purpose | Required name for key name |
|---|---|
Getter |
|
Setter |
|
Direct ivar |
|
Validation |
|
To-many count |
|
To-many access |
|
To-many mutation |
|
Declaring a @property gives you the first three for free, which is why properties and KVC fit together so
naturally. See
Key-Value Coding and Observing.
Private Properties and the readonly/readwrite Idiom
A class extension — @interface ClassName () with empty parentheses, placed in the .m — declares
properties and methods that are invisible to clients:
// Person.m
@interface Person ()
@property (nonatomic, strong) NSMutableArray *mutableHistory; // entirely private
@property (nonatomic, assign) BOOL isLoading;
@end
The most useful application of this is redeclaring a public readonly property as readwrite privately, so
the value can be maintained internally while remaining immutable to everyone else:
// Download.h -- clients can read progress but never set it.
@interface Download : NSObject
@property (nonatomic, readonly) double progress;
@end
// Download.m -- internally it is settable.
@interface Download ()
@property (nonatomic, readwrite) double progress;
@end
@implementation Download
- (void)didReceiveBytes:(NSUInteger)n {
self.progress = (double)self.received / self.expected; // setter exists here
}
@end
A closely related pattern keeps a collection immutable in public and mutable in private:
// Public: an immutable snapshot.
@property (nonatomic, readonly, copy) NSArray<Item *> *items;
// Private: the real storage.
@interface Library ()
@property (nonatomic, strong) NSMutableArray<Item *> *mutableItems;
@end
@implementation Library
- (NSArray<Item *> *)items { return [self.mutableItems copy]; }
@end
Class extensions are covered alongside categories in Categories and Extensions.
See Also
-
Classes and Objects — ivars, visibility and initialisation.
-
Categories and Extensions — the class extension in full.
-
Automatic Reference Counting — what
strong,weakandcopyactually do. -
Key-Value Coding and Observing — why accessor naming matters.