Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C property attributes best practices

After multiple searches and reads about property attributes, I still can't understand them completely and create a reflex of using them correctly.

I have multiple questions:

1) What does a default attribute mean?

As I understood, not specifying an attribute in a "group", the default one is used, so this:

@property NSString *string;

is atomic, right?

By this logic, this article says that strong and assign are defaults, so if I have:

@property (nonatomic) NSString *string;

is the string property strong or assign?

How are the available attributes "grouped"? Or as Xcode words this, what attributes are mutually exclusive?

2) Are there any generic rules that one should follow?

For example, I saw one comment that said that you should use copy for classes with mutable variants like NSString, NSArray.

And another one that said that you should use assign for C objects.

So, is it a good idea to always use:

@property (copy, nonatomic) NSString *string;
@property (assign, nonatomic) CGFloat float;

?

What other standard practices exist for property attributes?

3) What problems could arise if I use "wrong" attributes? What if I just use nonatomic for all the properties in a project?

like image 563
Iulian Onofrei Avatar asked Feb 04 '16 10:02

Iulian Onofrei


2 Answers

1a) The default attributes for a property are atomic, and strong (for an object pointer) or assign (for a primitive type), and readwrite. This assumes an all ARC project.

So @property NSString *string; is the same as @property (atomic, strong, readwrite) NSString *string;. @property int value; is the same as @property (atomic, assign, readwrite) int value;.

1b) Attributes are grouped as follows:

  • atomic/nonatomic
  • strong/weak/assign/copy
  • readwrite/readonly

Pick one and only one from each of those three groups.

Actually, the latest Objective-C adds support for nullable/nonnull with the default being nullable.

2) General rules are as you say.

  • Object pointers should usually be strong.
  • Primitive types should be assign.
  • weak should be used in child/parent references to avoid reference cycles. Typically the parent has a strong reference to its children and the children have a weak reference to their parent. Delegates are typically weak for the same reason.
  • copy is typically used for NSString, NSArray, NSDictionary, etc. to avoid issues when they are assigned the mutable variant. This avoids the problem of the value being changed unexpectedly.
  • There's a big "gotcha" using copy with NSMutableString, NSMutableArray, etc. because when you assign the mutable value to the property, the copy attribute results in the copy method being called which gives back a non-mutable copy of the original value. The solution is to override the setter method to call mutableCopy.

3) Using the wrong attribute could have serious problems depending on the needs of the property and the attribute being used.

  • Using assign instead of strong for an object pointer is probably the worst mistake. It can lead to app crashes due to trying to access deallocated objects.

  • Using nonatomic instead of atomic on a property that will be accessed concurrently on multiple threads may lead to really hard to find bugs and/or crashes.

  • Using strong instead of copy for NSString or NSArray (and other collections) can possibly lead to subtle and hard to find bugs if mutable variants were assigned to the property and other code later modifies those values.

like image 190
rmaddy Avatar answered Oct 14 '22 08:10

rmaddy


@rmaddy's answer is a good one.

I would add the following.

If you are creating (or have inherited) classes that interoperate with Swift, it is very useful to include nullable or nonnull property attributes. If you add it in any part of a header file, you will need to specify it for all parts of the header file (compiler warnings will help you). It's even quite useful for Objective-C callers to know from the method signature what may and may not be a nil value.

Another property of note is class. You can add a property to the class.

Adding these two items together, and if you are implementing a singleton,

+ (MyClass *)sharedInstance;

it's quite useful to define it as a property:

@property (class, nonatomic, nonnull, readonly) MyClass *sharedInstance;

(In which case you are required to add a backing variable for it as described in this article)

This will let you access the shared instance via dot notation.

[MyClass.sharedInstance showMeTheMoney:YES];

And in Swift, the rather annoying

MyClass.sharedInstance()?.showMeTheMoney(true)

turns into

MyClass.sharedInstance.showMeTheMoney(true)

‡ maybe it's just 3 characters to you, but it keeps my head from exploding mid-day.


Edit: I would add, try out

+ (instancetype)shared;

This 1) shortens the naming to concur with modern Swift convention, and 2) removes the hardcoded type value of a (MyClass *).

like image 41
bshirley Avatar answered Oct 14 '22 09:10

bshirley