Property Semantics in Depth
Go far beyond the introductory Properties module and master every attribute a @property declaration can carry — strong, weak, assign, copy, unsafe_unretained, nonatomic, atomic, readonly, readwrite, class, custom getter/setter names, dynamic, synthesized vs explicit ivars, and property overriding in subclasses — so that every accessor you write or read is a deliberate choice, not a default you never examined.
The earlier "Properties" module covered the basics: declare a @property, Objective-C generates a getter and setter for you, done. That is enough to write working code, but it hides an enormous amount of decision-making behind a single line of syntax. Every @property declaration is really a small contract with four separate questions baked into it — who owns this value, does reading and writing it need to be safe across threads, can outside code change it, and where does the value actually live — and the attribute list in parentheses is where you answer each of those questions explicitly. This lesson is the deep, comprehensive companion to that earlier module: rather than a quick tour, it walks through every attribute in isolation, shows what code the compiler actually generates for each one, and covers the more advanced corners — @dynamic, explicit ivars with @synthesize, and overriding a property's attributes in a subclass — that most introductions skip entirely.
What a @property declaration actually expands into
When you write @property (nonatomic, strong) NSString *name; in an @interface, you are not writing an instance variable. You are writing a declaration that tells the compiler three things: generate a getter method named -name, generate a setter method named -setName:, and — unless you say otherwise — synthesize a backing instance variable named _name to store the value between calls. The @property line itself produces no executable code by itself; the getter and setter bodies, and the ivar, are generated separately by the compiler based on the attributes you list in the parentheses.
Those attributes fall into four independent categories, and a full @property declaration picks exactly one from each: an ownership/memory-management attribute (strong, weak, assign, copy, or unsafe_unretained), a threading attribute (atomic or nonatomic), an access/writability attribute (readwrite or readonly), and, optionally, a storage-location attribute (instance, the default, or class). Understanding each category separately — rather than treating the whole attribute list as one opaque blob you copy from an example — is the difference between writing a property that behaves the way you intend and one that merely compiles.
• Ownership attributes (strong / weak / assign / copy / unsafe_unretained) answer: who is responsible for keeping this object alive? • Threading attributes (atomic / nonatomic) answer: is a single get or set operation safe if two threads do it at the same time? • Access attributes (readonly / readwrite) answer: can code outside this class assign a new value through the generated setter? • Storage attributes (instance property vs class property) answer: does each object get its own copy of this value, or is there exactly one shared across the whole class? • Every @property line implicitly or explicitly picks one option from each category — there is no such thing as a property attribute list that leaves a category unanswered, because the compiler always falls back to a default.
Ownership: strong, weak, assign, copy, and unsafe_unretained
strong is the default ownership for an object-type property under ARC, and it means the property retains its value: as long as this object holds onto that value through this property, the value's retain count reflects that ownership, and the value cannot be deallocated out from under you. Almost every property that holds a normal Cocoa object — a delegate's owning reference, a view controller's child view, a model object's array of items — should be strong unless there's a specific reason it shouldn't be.
weak is the deliberate opposite: the property holds a reference to the object but does not keep it alive, and critically, if the object it points to is deallocated elsewhere, a weak property is automatically set to nil rather than becoming a dangling pointer. This is exactly what you want for a delegate property (the delegate should not be kept alive just because something has a reference to it — that would often create a retain cycle) and for any back-reference where object A holds a reference to object B, and B already holds a strong reference to A.
assign is the ownership attribute you use for non-object, non-ARC-managed values — C primitives like NSInteger, CGFloat, BOOL, or a C struct like CGRect — where "ownership" and retain counts don't apply at all; the property simply stores the bytes directly. Using assign on an Objective-C object pointer under ARC is a mistake covered in this lesson's mistakes section, because it silently behaves like unsafe_unretained without saying so.
copy is used when you want the property to hold its own independent snapshot of a value at the moment it was assigned, immune to later mutation by whoever handed you the original object. The single most common case is NSString: if a property holding a "name" is declared strong instead of copy, and the caller passes in an NSMutableString and later mutates that same string object, your property's value changes too, silently, without you ever calling the setter again — because strong just points at the same object. copy avoids this entirely by making an immutable copy at assignment time.
unsafe_unretained is what weak used to be before ARC could add the "automatically becomes nil" safety net — it is a non-owning reference, exactly like weak, but with no zeroing behavior: if the object it points to is deallocated, the property still holds the old pointer value, which is now dangling, and using it is undefined behavior. It exists mainly for compatibility with older runtime targets and specific low-level cases; weak should be your default choice for a non-owning object reference, and unsafe_unretained should be a deliberate, rare exception.
Threading: nonatomic vs atomic
atomic is actually the default if you omit both nonatomic and atomic from a property's attribute list — a detail that surprises people used to seeing nonatomic everywhere in real code. An atomic property's generated getter and setter each take an internal lock around their own body, which guarantees that a single call to the getter, or a single call to the setter, completes as one indivisible operation with respect to other threads calling the same accessor.
What atomic does NOT guarantee is much more important in practice than what it does guarantee: it says nothing about a sequence of operations across multiple property accesses, and it says nothing about the safety of the object the property points to once you have it — only the pointer copy-in/copy-out itself is protected. Two threads can each safely call the getter and each get back a valid pointer without crashing, and that is the entire guarantee; if one thread then reads and writes that same object's own mutable state without synchronization, atomic on the property that vends the pointer does nothing to protect that. This distinction — and the actual thread-safety guarantees Objective-C code needs — is covered in full in the "Objective-C Concurrency Memory Model" module; this lesson deliberately stops at what the property attribute itself does.
nonatomic removes that per-accessor locking entirely, trading the small safety guarantee atomic does provide for meaningfully faster property access — the lock/unlock overhead on every single get and set is not free, and for a property accessed extremely often (inside a tight loop, or on a hot path like table view cell configuration), that overhead adds up. Because atomic's actual protection is so narrow, and because most real thread-safety needs require coordinating a sequence of operations rather than a single access anyway, the overwhelming majority of properties in production Objective-C code are declared nonatomic, with real cross-thread coordination handled deliberately elsewhere (locks, serial queues, or the primitives covered in the Concurrency Memory Model module) rather than leaned on implicitly through atomic.
Access: readonly, readwrite, and the class-extension pattern
readwrite is the default: the compiler generates both a getter and a setter, and any code that can see the property declaration can assign a new value through it. readonly generates only the getter — no setter is synthesized at all, and any attempt to write to the property from outside the class produces a compile error, not a runtime failure.
A readonly property does not mean the value can never change — it means outside code cannot change it through the generated setter. The class itself can still set the underlying ivar directly (self->_score = newScore;), or, more commonly and more idiomatically, the class can privately redeclare the same property as readwrite in a class extension inside its own .m file. The public header exposes @property (nonatomic, readonly) NSInteger score; so external callers can only read it, while the class extension in the .m redeclares @property (nonatomic, readwrite) NSInteger score; which generates the setter and makes it available only to code inside that implementation file. This "public readonly, private readwrite" pattern is extremely common for any property that represents state the object itself should control — a counter, a computed cache, a loading flag — and it is covered in full detail, including how class extensions work as a language feature in their own right, in the "Class Extensions" module.
Custom getter and setter names
By default, the getter for a property named foo is -foo and the setter is -setFoo:. You can override either name explicitly with the getter= and setter= attributes, and the single most common reason to do this is a BOOL property, where Objective-C convention strongly prefers a getter that reads like a question — isEnabled rather than enabled — while still keeping the setter as the expected -setEnabled:.
@property (nonatomic, assign, getter=isEnabled) BOOL enabled; keeps the ivar and the property name as enabled and the setter as -setEnabled:, but changes only the getter's name to -isEnabled, which reads naturally at call sites (if (view.enabled)) reads awkwardly compared to if ([view isEnabled]), and Cocoa's own BOOL properties like UIView's isHidden and isUserInteractionEnabled follow exactly this convention. setter= exists symmetrically for renaming the setter, though it is used far less often in practice than getter=.
class properties
Every @property discussed so far is an instance property — each object of the class gets its own independent storage for it. Marking a property with the class attribute — @property (class, nonatomic, strong) NSArray<NSString *> *supportedFormats; — instead declares a class-level property: exactly one value shared across the entire class, accessed through the class object itself (MediaParser.supportedFormats or [MediaParser supportedFormats]) rather than through any instance.
Class properties are less common than instance properties, but they show up for exactly the same reasons a plain class method returning a constant or shared value does — a shared default configuration, a class-wide singleton-style accessor, or a class-scoped setting. Unlike instance properties, a class property is never backed by an auto-synthesized ivar the way instance properties are, since there is no per-instance storage to synthesize into; you must supply the getter (and setter, if writable) implementation yourself, typically backed by a static variable inside the .m file.
@dynamic and telling the compiler accessors exist elsewhere
Normally, when you declare a property and don't do anything special, the compiler auto-synthesizes both the getter/setter methods and the backing ivar for you. @dynamic is the explicit opposite instruction: it tells the compiler "do not synthesize anything for this property — I promise the getter and setter methods will exist at runtime, supplied some other way." If you declare a property @dynamic and never actually provide -foo and -setFoo: methods some other way, calling them at runtime raises an "unrecognized selector" exception, because nothing generated them.
The canonical real-world use of @dynamic is Core Data: an NSManagedObject subclass declares properties for its attributes (@property (nonatomic, strong) NSString *title;) but marks them @dynamic, because Core Data generates the actual getter and setter implementations at runtime based on the entity's model description, resolving them dynamically rather than through compiler-synthesized methods. This mechanism, and how it fits into the rest of Core Data's object graph management, is covered in depth in the "Core Data Fundamentals" module; the point to take away here is narrower — @dynamic is the property attribute that opts a property fully out of compiler synthesis because something else, at runtime, is going to be responsible for its accessors.
Auto-synthesis vs explicit @synthesize with a differently-named ivar
Modern Objective-C auto-synthesizes both the ivar and the accessor implementations for any property you don't otherwise handle yourself — this is why most property declarations you see never mention @synthesize at all anymore; it used to be mandatory boilerplate in every @implementation and is now almost always unnecessary. The auto-synthesized ivar name is always the property name with a leading underscore: a property named count gets an ivar named _count.
You can still write @synthesize explicitly, and the main reason to do so is to bind a property to an ivar with a different name than the default: @synthesize count = _internalCount; tells the compiler to still generate the getter and setter for count, but to back them with an ivar literally named _internalCount instead of the default _count. This is occasionally useful when a legacy ivar name needs to be preserved, or when you want the ivar's name to intentionally differ from the property's public name for clarity inside a complex implementation. It is also possible to declare an ivar explicitly in the @implementation's own ivar block and use @synthesize to connect it to the property, which is functionally the same outcome reached from the ivar-first direction instead of the property-first direction.
Property overriding in a subclass
A subclass can redeclare a property it inherits in order to change its attributes, and the most common and useful version of this is exactly the same "loosen from readonly to readwrite" pattern seen earlier, but across a subclass boundary instead of a class extension. If a base class declares @property (nonatomic, readonly) NSString *identifier; in its public header, a subclass's own class extension (or public header, if the subclass intentionally wants to expose mutability the base class didn't) can redeclare @property (nonatomic, readwrite) NSString *identifier;, which is legal specifically because readwrite is a strict widening of readonly's contract — it never removes a capability the base class promised, it only adds one.
The reverse — a subclass narrowing readwrite down to readonly — is not something the language directly supports through a redeclared @property; you can only hide the setter's visibility by omitting it from what you expose in headers, not truly revoke it at the class level, since the base class's setter method still exists and is still callable if something holds a reference typed as the base class. This asymmetry is worth understanding precisely: property overriding is a mechanism for a subclass to grant itself more access to something it inherited, not to take capability away from callers who already have it through the base class's own declared interface.
The full attribute vocabulary in one property list
getter= and setter= are independent of ownership/threading/access — they only rename the generated method, never change what it does. class properties are never auto-synthesized with a backing ivar; you must implement their accessor(s) yourself. Combining strong/weak/assign/copy/unsafe_unretained on the same property is never valid — exactly one ownership attribute applies.
// Every category represented at once (not all combinations are meaningful together —
// this is a reference, not a realistic single declaration)
@property (nonatomic, strong, readwrite, getter=isValid, setter=setValidFlag:) BOOL valid;
// The four ownership attributes, each in isolation
@property (nonatomic, strong) NSArray<Item *> *items; // owns and retains
@property (nonatomic, weak) id<SomeDelegate> delegate; // does not own; zeroes on dealloc
@property (nonatomic, assign) NSInteger retryCount; // primitive, no ownership concept
@property (nonatomic, copy) NSString *title; // owns an independent snapshot
@property (nonatomic, unsafe_unretained) id legacyObserver; // does not own; can dangle
// class-level property, not per-instance
@property (class, nonatomic, readonly) NSArray<NSString *> *supportedFormats;
// telling the compiler accessors exist elsewhere (e.g. Core Data)
@property (nonatomic, strong) NSString *title; // @dynamic in @implementationCode Examples
DownloadManager owns its currentTask strongly, and the task's delegate points back at the manager. If delegate were also strong, the two objects would retain each other and neither could ever be deallocated — a retain cycle. Declaring delegate weak breaks the cycle: the task references the manager without keeping it alive, and if the manager is ever deallocated first, delegate automatically becomes nil rather than dangling.
@property (nonatomic, weak) id<DownloadTaskDelegate> delegate;
Common mistake: Declaring a delegate property strong "just to be safe." This is one of the most common sources of retain cycles in real Objective-C codebases.
Because title is declared copy, assigning mutableTitle to it immediately produces an independent, immutable copy of the string's current contents. The article's title is completely insulated from anything the caller does to mutableTitle afterward. Had title been declared strong instead, article.title and mutableTitle would be the same object, and the later appendString: call would silently change the article's title too — a subtle bug that is easy to miss because no code ever calls the setter again. Expected result: Draft
@property (nonatomic, copy) NSString *title;
Declaring value atomic guarantees that any single call to -value or -setValue: is internally locked and will not race with another single call to the same accessor. It does NOT guarantee that self.value = self.value + 1 is atomic as a whole — that line is actually a read, then a separate computation, then a write, and two threads running -incrementUnsafely concurrently can still interleave between the read and the write, losing an increment. Real cross-thread safety for a sequence like this requires the coordination primitives covered in the "Objective-C Concurrency Memory Model" module, not the atomic attribute alone.
self.value = self.value + 1;
Common mistake: Assuming atomic makes an entire multi-step operation thread-safe. It only protects a single getter or setter call in isolation.
Outside code sees only the public header's readonly declaration and cannot call -setPoints: at all — it's a compile error. Inside Score.m, the class extension privately widens the same property to readwrite, generating the setter for use only within this implementation file. This is the standard pattern for a property that represents state the object should fully own the mutation of, while still letting outside callers read the current value. See the "Class Extensions" module for how class extensions work as a language mechanism in general.
@property (nonatomic, readwrite) NSInteger points;
getter=isEnabled renames only the getter method to -isEnabled, matching Cocoa convention for BOOL properties (compare to UIView's isHidden). The setter is unaffected and remains -setEnabled:, and the underlying ivar is still named _enabled by default. Calling [toggle enabled] would be a compile error, since -enabled was never generated — only -isEnabled was.
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
Without @dynamic, the compiler would try to auto-synthesize a getter, setter, and ivar for title and dueDate — but Core Data's NSManagedObject infrastructure supplies its own accessor implementations dynamically, based on the entity description in the data model, and does not want or need compiler-synthesized ones underneath it. @dynamic tells the compiler "trust me, these methods will exist at runtime," suppressing synthesis entirely. If Core Data (or whatever runtime mechanism is responsible) failed to actually provide them, calling title at runtime would crash with an unrecognized-selector exception.
@dynamic title;
Common mistake: Marking a property @dynamic without actually arranging for its accessors to exist at runtime some other way — this compiles fine and crashes the first time the property is used.
Without this explicit @synthesize line, the auto-synthesized ivar for a property named storage would be _storage. This line overrides that default, binding the same generated getter (-storage) and setter (-setStorage:) to an ivar explicitly named _cacheStorage instead — useful when preserving a legacy ivar name, or when a name clash or convention requires the ivar to differ from the property's public name. Any code inside the implementation that accesses the ivar directly (rather than through self.storage) must use the actual ivar name, _cacheStorage, not the default _storage that would have been assumed otherwise.
@synthesize storage = _cacheStorage;
MutableModel redeclares the inherited identifier property with readwrite instead of readonly. This is legal specifically because it widens the contract — anyone holding a BaseModel-typed reference still only sees identifier as readonly, exactly as promised, while code that specifically knows it has a MutableModel can now also call -setIdentifier:. Attempting the reverse — a subclass trying to narrow an inherited readwrite property down to readonly — is not supported this way, since the base class's setter still exists and remains callable through a base-class-typed reference.
@property (nonatomic, readwrite) NSString *identifier;
@protocol DownloadTaskDelegate <NSObject>
- (void)downloadTaskDidFinish:(DownloadTask *)task;
@end
@interface DownloadTask : NSObject
@property (nonatomic, weak) id<DownloadTaskDelegate> delegate;
@end
@interface DownloadManager : NSObject <DownloadTaskDelegate>
@property (nonatomic, strong) DownloadTask *currentTask;
@end
@implementation DownloadManager
- (void)start {
self.currentTask = [DownloadTask new];
self.currentTask.delegate = self; // DownloadManager strongly owns the task,
// so the task must NOT strongly own it back
}
- (void)downloadTaskDidFinish:(DownloadTask *)task {
NSLog(@"Finished");
}
@end@interface Article : NSObject
@property (nonatomic, copy) NSString *title; // correct
@end
// Demonstrating the bug that "strong" instead of "copy" would allow:
NSMutableString *mutableTitle = [NSMutableString stringWithString:@"Draft"];
Article *article = [Article new];
article.title = mutableTitle; // copy makes an immutable snapshot right now
[mutableTitle appendString:@" - Final"];
NSLog(@"%@", article.title); // still "Draft" — unaffected by the later mutation@interface Counter : NSObject
@property (atomic, assign) NSInteger value; // per-accessor locking
@end
@implementation Counter
- (void)incrementUnsafely {
// Even with an atomic property, this compound read-modify-write
// is NOT protected as a single unit — two threads can interleave here.
self.value = self.value + 1;
}
@end// Score.h — public header
@interface Score : NSObject
@property (nonatomic, readonly) NSInteger points; // callers can read, not write
- (void)addPoints:(NSInteger)amount;
@end
// Score.m — private redeclaration
@interface Score ()
@property (nonatomic, readwrite) NSInteger points; // this file can now write it too
@end
@implementation Score
- (void)addPoints:(NSInteger)amount {
self.points = self.points + amount; // legal here — the class extension made it writable
}
@end@interface FeatureToggle : NSObject
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
@end
@implementation FeatureToggle
@end
FeatureToggle *toggle = [FeatureToggle new];
toggle.enabled = YES; // setter is still -setEnabled:
BOOL on = [toggle isEnabled]; // getter is -isEnabled, not -enabled// A Core Data-style managed object — see "Core Data Fundamentals" for the full picture
@interface Task : NSManagedObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSDate *dueDate;
@end
@implementation Task
@dynamic title; // do NOT auto-synthesize — Core Data resolves this at runtime
@dynamic dueDate; // same
@end@interface LegacyCache : NSObject
@property (nonatomic, strong) NSDictionary *storage;
@end
@implementation LegacyCache {
// Nothing unusual here; ivar block is optional and independent of @synthesize
}
@synthesize storage = _cacheStorage; // property "storage", ivar "_cacheStorage"
- (BOOL)isEmpty {
return _cacheStorage.count == 0; // must refer to the actual ivar name, not "_storage"
}
@end// BaseModel.h
@interface BaseModel : NSObject
@property (nonatomic, readonly) NSString *identifier;
@end
// MutableModel.h
@interface MutableModel : BaseModel
@property (nonatomic, readwrite) NSString *identifier; // legal: widening, not narrowing
@end
@implementation MutableModel
- (void)regenerateIdentifier {
self.identifier = [[NSUUID UUID] UUIDString]; // only compiles because of the override above
}
@endThe layers behind a single @property declaration
Every @property you write flows through these four layers, top to bottom: the declaration itself, the getter/setter methods the compiler generates from it, the backing ivar those methods read from and write to, and the ownership semantics (strong/weak/assign/copy/unsafe_unretained) that determine exactly what the setter does with the value it's handed.
@property
↓
Getter + Setter
↓
Ivar / Backing Storage
↓
Ownership SemanticsImportant Rules
strong is the default object ownership under ARC and is correct for the majority of properties that should keep their value alive; weak is for non-owning references (delegates, back-references) and auto-nils on deallocation; assign is for primitives and structs with no ownership concept; copy protects against a caller mutating a value out from under you after assignment; unsafe_unretained is a rare, non-zeroing legacy alternative to weak.
atomic (the default if omitted) only guarantees a single getter or setter call is internally locked — it never makes a multi-step read-modify-write sequence, or the mutable object a property points to, thread-safe as a whole.
nonatomic is the overwhelmingly common choice in real code because atomic's guarantee is narrow and its per-access locking overhead is real; genuine cross-thread coordination belongs to dedicated synchronization tools, not to the atomic attribute.
readonly generates only a getter; the standard pattern for internally-mutable, externally-readonly state is to redeclare the property readwrite in a private class extension inside the .m file.
@dynamic tells the compiler not to synthesize any accessor or ivar for a property because something else supplies the accessor methods at runtime — most commonly Core Data's NSManagedObject machinery.
Explicit @synthesize is now rarely required, since modern Objective-C auto-synthesizes both accessors and a _propertyName ivar automatically; the main remaining reason to write it is to bind a property to a differently-named ivar.
A subclass may widen an inherited readonly property to readwrite by redeclaring it, but cannot truly narrow an inherited readwrite property down to readonly, since the base class's setter remains callable through a base-class-typed reference.
Where this shows up
Every single @property declaration in a real Objective-C codebase is this lesson's content applied in miniature: a view controller's delegate is weak, its array of fetched model objects is strong, its user-facing title string is copy, its internal loading flag is readonly outside the class and readwrite inside it via a class extension, and any Core Data-backed model property is @dynamic. Getting these choices right the first time — rather than defaulting to strong for everything and discovering retain cycles or mutation bugs later — is one of the most consequential low-level habits a working Objective-C developer builds, and it is exactly what separates code that merely compiles from code that behaves correctly under real, concurrent, long-running use.
@property (nonatomic, assign) NSString *name; // object property declared assign
@property (nonatomic, copy) NSString *name; // or strong, if snapshot semantics aren't needed assign on an Objective-C object pointer under ARC compiles, but it silently behaves like unsafe_unretained: the property does not retain its value, and if the object it points to is deallocated elsewhere, this property is left holding a dangling pointer with no automatic nil-ing the way weak provides. assign should be reserved for primitives and structs; object properties need strong, weak, or copy.
@property (nonatomic, strong) id<SomeDelegate> delegate; // strong delegate
@property (nonatomic, weak) id<SomeDelegate> delegate;
A strong delegate property very often creates a retain cycle: the object holding the delegate is frequently also strongly owned, directly or indirectly, by whatever it is delegating to. weak is the conventional choice for delegate properties specifically because it lets the delegate reference exist without contributing to a cycle, and automatically becomes nil if the delegate is deallocated first.
// Declaring the property @dynamic but never providing accessors any other way @implementation Widget @dynamic tint; // nothing anywhere actually implements -tint / -setTint: @end
// Either let the compiler auto-synthesize (remove @dynamic entirely) @implementation Widget // no @dynamic — accessors and ivar are auto-synthesized normally @end
@dynamic is a promise to the compiler that accessor methods will exist at runtime through some other mechanism (a runtime-generated method, a framework like Core Data, manual -forwardingTargetForSelector: handling). If nothing actually fulfills that promise, calling the property at runtime raises an unrecognized-selector exception — the mistake usually isn't caught until the property is actually used, since it compiles cleanly.
[Multiple Choice] Which ownership attribute makes a property automatically become nil when the object it points to is deallocated? A. assign B. unsafe_unretained C. weak D. copy
C. weak — weak is a non-owning reference that zeroes automatically when its target is deallocated. unsafe_unretained is also non-owning but does not zero, leaving a dangling pointer instead; assign has no ownership concept for objects; copy creates an owned, independent snapshot.
[True / False] Declaring a property atomic guarantees that a compound operation like self.value = self.value + 1 is safe from race conditions across threads. A. True B. False
B. False — False. atomic only guarantees that a single getter or setter call is internally locked. A read, followed by a separate computation, followed by a write is three separate operations, and two threads can still interleave between them even though each individual accessor call is atomic.
[Multiple Choice] A public header declares a property readonly. What is the standard pattern for letting the class itself change that property's value internally? A. Change the public header to readwrite so the class can use the setter B. Redeclare the property readwrite in a private class extension inside the .m file C. Use assign instead of readonly D. Mark the property @dynamic
B. Redeclare the property readwrite in a private class extension inside the .m file — The class-extension pattern lets the public header keep the property readonly for outside callers while the .m file privately redeclares it readwrite, generating a setter usable only within the implementation.
[Multiple Choice] What does @dynamic tell the compiler about a property? A. Generate the getter and setter, but not the ivar B. Do not synthesize a getter, setter, or ivar — the accessor methods will be supplied some other way at runtime C. Make the property thread-safe automatically D. Treat the property as a class property instead of an instance property
B. Do not synthesize a getter, setter, or ivar — the accessor methods will be supplied some other way at runtime — @dynamic suppresses all compiler synthesis for that property — no getter, no setter, no ivar — on the promise that accessor methods will exist at runtime through some other mechanism, such as Core Data's NSManagedObject machinery.
[Multiple Choice] @property (nonatomic, assign, getter=isEnabled) BOOL enabled; changes which method name(s)? A. Both the getter and the setter are renamed B. Only the setter is renamed, to -setIsEnabled: C. Only the getter is renamed, to -isEnabled; the setter remains -setEnabled: D. Neither is renamed; getter= only affects documentation
C. Only the getter is renamed, to -isEnabled; the setter remains -setEnabled: — getter= renames only the getter method. The setter is unaffected by it and remains -setEnabled: unless a separate setter= attribute is also provided.
[True / False] A subclass can redeclare an inherited readwrite property as readonly to truly prevent it from being set. A. True B. False
B. False — False. A subclass can widen readonly to readwrite (this is legal), but cannot truly narrow readwrite down to readonly — the base class's setter method still exists and remains callable through a reference typed as the base class.
[Multiple Choice] What is the main reason to write an explicit @synthesize propertyName = _customIvarName; in modern Objective-C? A. It is required for every property or the code will not compile B. To bind the property's generated accessors to a backing ivar whose name differs from the auto-synthesized default C. To make the property atomic D. To convert an instance property into a class property
B. To bind the property's generated accessors to a backing ivar whose name differs from the auto-synthesized default — Modern Objective-C auto-synthesizes both accessors and a _propertyName ivar without needing @synthesize at all. The remaining reason to write it explicitly is to bind the property to a differently-named backing ivar, such as preserving a legacy ivar name.
Design a Document class with: a title property that should be immune to external mutation after assignment; a delegate property that must never cause a retain cycle; a readonly wordCount property whose value the class itself can update internally via a class extension; and a class-level property defaultFileExtension shared across all Document instances. Write the full @interface (public header) and the private class-extension section, choosing the correct attribute for each property and justifying each choice in a comment.
// Document.h @interface Document : NSObject @property (nonatomic, copy) NSString *title; // copy: immune to caller mutation @property (nonatomic, weak) id<DocumentDelegate> delegate; // weak: avoids retain cycle @property (nonatomic, readonly) NSInteger wordCount; // readonly: callers can't set it directly @property (class, nonatomic, readonly) NSString *defaultFileExtension; // shared across all instances @end // Document.m @interface Document () @property (nonatomic, readwrite) NSInteger wordCount; // widened to readwrite for internal use only @end @implementation Document + (NSString *)defaultFileExtension { return @"doc"; } - (void)recomputeWordCount { self.wordCount = [self.title componentsSeparatedByString:@" "].count; // legal: class extension } @end
Map each requirement directly to one attribute from this lesson: "immune to external mutation" is copy, "must never retain-cycle" is weak, "class controls it internally but callers only read it" is public readonly + private class-extension readwrite, and "shared across all instances" is the class attribute.