What clang taught us about Objective-C properties

TL;DR – Objective-C properties are far more complex than they appear. For best results: 

  • Use nonatomic everywhere
  • Make sure you understand the retain-autorelease pattern for getters
  • Never, ever use the NS_NONATOMIC_IOSONLY macro
  • Always specify setter semantics
  • Re-declare readonly properties in class extensions for private setters
  • Try out -Weverything, it can be fun

We recently finished up a major update to Crashlytics for Mac, and we’re all really proud of how it turned out.  As one of the developers, I always want the code to look as good as the UI.  Good style, especially in headers, is something I care about really deeply.  So, naturally, when it comes to Objective-C, I use properties.

I’ve been a big fan of ObjC properties since they were introduced in Objective-C 2.0.  Prior to that release, getters and setters in Obj-C involved a bunch of boilerplate, but also had some subtlety that tended to easily trip me up.  More concise headers and the ability to @synthesize implementations seemed like a mini-revolution at the time.  Now it feels weird not to use them, right?

Well, they might seem pretty straightforward, but don’t be fooled: Objective-C properties are complex in both syntax and semantics.  In fact, it was my discovery of clang’s “-Weverything” flag at WWDC that made me realize just how complex they can be.

Don’t get me wrong: properties are good, and you should use them. The question is: do you really understand what’s going on?

Declaration Style

Over time, I’ve adopted a very consistent property declaration style.  Nearly all of my properties look like this:

1 @property (nonatomic, copy, readonly) NSString* name;

There can be as many as five arguments to an @property statement, and they all have profound effects on semantics.  Let’s have a look at them.

Writability

The keywords readonly and readwrite are mutually exclusive, and allow you to control the availability of a setter.  readwrite is actually the default, so I’ve always found this keyword to be a little redundant.  I’ve also never run across a use case for it, but I suppose you may just not be into the whole brevity thing.  There is no way to make a write-only property.

readonly, on the other hand, I use all the time.  It’s great for declaring only a getter.

Accessor Method Names

There are ways to control the actual names of the methods that are used for your getter and setter.  These are specified with getter=myGetterMethod, and setter=mySetterMethod.  I’ve not run into a use case for the setter argument, but as the documention says, getter is useful for adding the “is” in front of boolean properties.

Setter Semantics

Here is where things start getting interesting.  Properties allow you to specify five different kinds of semantics for your setter’s behavior.

I use copy for types that contain a single value and might mutate.  Consider the following code:

1 NSMutableString* string;
2 string = [NSMutableString stringWithString:@"Dude"];
3 [obj setName:string];
4 [string setString:@"El Duderino"];

Unless you don’t mind your name changing without another call to -setName:, retain is probably the wrong choice!

assign is perfect for value-types, and can be useful for non-owning relationships, like delegates.  In these cases, you might prefer weak over assign, because it automagically nils out the pointer should the target object get deallocated.

I personally don’t love the weak reference behavior, because messages to nil fail silently.  I could write an entire book on my feelings about Objective-C’s nil-messaging behavior, but let’s sum it up by saying that the liberal use of assertions can help mitigate its side-effects.  Given the choice, I’d prefer to crash so at least I know that I’ve made an error somewhere.

ARC also introduced an alternative to retain, which is strong.  A little googling shows that there’s quite a bit of confusion about retain vs strong.  This isn’t surprising, since they have identical semantics, and produce identical code.  Still, strong better communicates the relationship type, so I would recommend using this going forward.

Atomicity

Obj-C accessors sometimes have quite a bit of work to do, and this work is not inherently thread-safe.  @synthesize can generate setters and getters atomically on your ivars.  This is the aspect of properties that is the most subtle and confusing of them all.  I would never recommend using atomic properties and am often frustrated that it is the default. Why?

First of all, the very implication of atomicity sometimes tempts me into thinking I could get away without other kinds of synchronization.  How often do you find yourself needing to just read/write an ivar across threads?  Sure, it can happen.  But, it’s much more likely that the interaction is more complex.  If you start off without a lock, others reading your code may not even realize you are depending on an implicit lock.  Knowing that all properties are thread-unsafe forces you to consider synchronization more carefully.

Another huge downside is that atomic properties are not recommended on ARM for performance reasons.  That the default behavior is inappropriate for Apple’s most dominant platform should be a red flag right away.

Finally, I often find myself writing custom getters and setters.  This can be useful for caching an expensive ivar, or just exposing something more complex as a property.  Many times I’ve started with an synthesized version, but eventually overridden it.  This can lead to a mildly annoying header change, since my custom implementations are typically not atomic.  While the compiler seems fine with it, it is a logical error to claim that the default atomic declaration is accurate.

Unfortunately, there’s one additional aspect of atomicity that might be surprising.  Let’s call out a section in the Objective-C property documentation:

1 [_internal lock]; // lock using an object-level lock
2 id result = [[value retain] autorelease];
3 [_internal unlock];
4 return result;

If you specify nonatomic, a synthesized accessor for an object property simply returns the value directly.

Yes, this means that nonatomic has a potentially profound effect on the semantics of the synthesized getter that is unrelated to atomicity.  There are a host of bugs related to accessors that can be exposed by returning properties without using the retain-autorelease idiom.  Take a look at the following:

1 NSString *name = [obj name];
2 [obj setName:nil]; // this could cause name to be deallocated
3
4 // and if it was not retain-autoreleased, this will cause problems
5 NSLog(@"My name is %@, so that's what you call me!", [self name]);

I wouldn’t be surprised at all if this hazard of getters isn’t widely known.  There is even a macro in NSObjCRuntime.h called NS_NONATOMIC_IOSONLY that lets you specify the default on Mac OS X but not on iOS.  My mind recoils in terror at the suble iOS-only bugs this macro could introduce.

It’s a tough call.  Atomic properties have desirable getter behavior.  It is just unfortunate that you cannot control getter semantics independently of atomicity, particularly because of the iOS conundrum.

Objective-C documentation, meet Clang

Keeping the documentation in-sync with the vast software systems that is Mac OS X and iOS is a herculean task.  I mean no disrespect in this section, but I did find some disconnects that make for an interesting read.  I also happen to really like compiler warnings (static analysis of all kinds, really), so I was indulging a little here.  I was able to use clang’s -Weverything flag to find a number of places where the compiler and the documentation disagree, sometimes in amusing ways.

1 clang-warnings.m:19:1: warning: property is assumed atomic by default [-Wimplicit-atomic-properties]
2 @synthesize name = _name;
3 ^
4 clang-warnings.m:7:28: note: property declared here
5 @property (copy) NSString* name;
6 ^
7 1 warning generated.

Assumed atomic?  According to the docs and numerous stack overflow posts, there isn’t even an atomic keyword.  At the time of writing:

“There is no keyword to denote atomic.”

Well, someone tell clang, because this code not only compiles, but also removes the warning:

1 @property (atomic, copy) NSString* name;

Continuing, consider the following code example.  This is a very typical use of properties for me: readonly in the header, with a private setter in a class extension.

 1 // clang -Weverything -o myclass.o -c clang-warnings.m
 2 #import <Foundation/Foundation.h>
 3
 4 @interface MyClass : NSObject
 5
 6 @property (nonatomic, copy, readonly) NSString* name;
 7
 8 @end
 9
10 @interface MyClass () {
11 NSString* _name;
12 }
13
14 @property (nonatomic, copy) NSString* name;
15
16 @end
17
18 @implementation MyClass
19
20 @synthesize name = _name;
21
22 @end

Clang doesn’t like this:

1 clang-warnings.m:7:1: warning: property attributes 'readonly' and 'copy' are mutually exclusive [-Wreadonly-setter-attrs]
2 @property (nonatomic, copy, readonly) NSString* name;
3 ^
4 1 warning generated.

There is no mention of readonly and setter semantics being mutually exclsuive.  In fact, this exact idiom appears in the docs.  But, you could convince yourself that perhaps this warning makes sense.  After all, readonly means there is no setter.  Typically, I like to include one to provide some additional information to myself and to clients of that property API.

So, what happens if we remove the copy?

1 clang-warnings.m:13:1: warning: property attribute in continuation class does not match the primary class
2 @property (nonatomic, copy) NSString* name;
3 ^
4 clang-warnings.m:5:43: note: property declared here
5 @property (nonatomic, readonly) NSString* name;
6 ^
7 1 warning generated.

This appears to be a bug in clang.  I think this is forgivable because -Weverything seems to contain “well-established” warnings that have an associated -W flag, and those that have no such flag, like this one.  In this particular case, this warning is definitely bogus.  The only way I found to silence it was to remove the setter semantics.  This is not acceptable as the setter semantics in combination with @synthesize are critical for correct behavior.

A source of bugs that I love is sending stack-based blocks a retain message.  I thought I’d found a great loop-hole in clang’s error-checking with the following:

1 @property (nonatomic, retain) void (^myBlock)(void);

It turns out this is in fact a warning, but only if you have ARC enabled.

1 clang-warnings.m:7:1: warning: retain'ed block property does not copy the block - use copy attribute instead [-Wobjc-noncopy-retain-block-property]
2 @property (nonatomic, retain) void (^myBlock)(void);
3 ^
4 1 warning generated.

While this isn’t in conflict with the documentation, this is a favorite of mine and I just felt that it would really tie the room together.

Summary

While I’m not a fan of the dot-syntax at all (see https://gist.github.com/3150651 for just one of the reasons why), properties are great.  Not only can they help to reduce boilerplate, but synthesis can be a significant win for correctness as well.  It isn’t great that the compiler and documentation don’t alway agree, but you should definitely be using properties in your own code.  Just be sure to understand how they work, because things aren’t always as simple as they appear.

Relevant Radars:

12103527 – Ability to control getter semantics independent of atomicity
12103400 – With -Weverything, clang warns about property redeclaration
12103434 – Clang warnings about mixing writability and setter semantics in properties
12107139 – Objective-C docs claim there is no ‘atomic’ keyword
11921441 – Dot-syntax property does not produce expected error
12103569 – retain block properties should be a warning for non-ARC too

Join the team!

Interested in diving-deep into these and other esoteric challenges?  We’re hiring!  Give us a shout at jobs@crashlytics.com. You can stay up to date with all our progress on TwitterFacebook, and Google+.