Now that we've explored what data types are available, we can talk about actually using them in a productive manner. We learned how to declare properties in Hello, Objective-C, but this chapter dives deeper into the nuances behind public properties and instance variables. First, we'll take a quick look at the basic syntax of properties and instance variables, and then we'll discuss how to use behavior attributes to modify accessor methods.
Declaring Properties
Properties can be declared in an interface using the @property
directive. As a quick review, let's take a look at the Person.h
file we created in the Hello, Objective-C chapter:
#import <Foundation/Foundation.h> @interface Person : NSObject @property (copy) NSString *name; @end
This declares a property called name
of type NSString
. The (copy)
attribute tells the runtime what to do when someone tries to set the value of name
. In this case, it creates an independent copy of the value instead of pointing to the existing object. We'll talk more about this in the next chapter, Memory Management.
Implementing Properties
In Hello, Objective-C, we used the @synthesize
directive to automatically create getter and setter methods. Remember that the getter method is simply the name of the property, and the default setter method is setName
:
#import "Person.h" @implementation Person @synthesize name = _name; @end
But, it's also possible to manually create the accessor methods. Doing this manually helps to understand what @property
and @synthesize
are doing behind the scenes.
Included code sample: ManualProperty
First, add a new property to the Person
interface:
@property (copy) NSString *name; @property unsigned int age;
Note that we're storing age
as a primitive data type (not a pointer to an object), so it doesn't need an asterisk before the property name. Back in Person.m
, define the accessor methods explicitly:
- (unsigned int)age { return _age; } - (void)setAge:(unsigned int)age { _age = age; }
This is exactly what @synthesize
would have done for us, but now we have the chance to validate values before they are assigned. We are, however, missing one thing: the _age
instance variable. @synthesize
automatically created a _name
ivar, allowing us to forgo this for the name
property.
Instance Variables
Instance variables, also known as ivars, are variables intended to be used inside of the class. They can be declared inside of curly braces after either the @interface
or @implementation
directives. For example, in Person.h
, change the interface declaration to the following:
@interface Person { unsigned int _age; }
This defines an instance variable called _age
, so this class should now compile successfully. By default, instance variables declared in an interface are protected. The equivalent C# class definition would be something like:
class Person { protected uint _age; }
Objective-C scope modifiers are the same as in C#: private variables are only accessible to the containing class, protected variables are accessible to all subclasses, and public variables are available to other objects. You can define the scope of instance variables with the @private
, @protected
, and @public
directives inside of @interface
, as demonstrated in the following code:
@interface Person : NSObject { @private NSString *_ssn; @protected unsigned int _age; @public NSString *job; }
Public ivars are actually a bit outside Objective-C norms. A class with public variables acts more like a C struct than a class; instead of the usual messaging syntax, you need to use the ->
pointer operator. For example:
Person *frank = [[Person alloc] init]; frank->job = @"Astronaut"; NSLog(@"%@", frank->job); // NOT: [frank job];
However, in most cases, you'll want to hide implementation details by using an @property
declaration instead of public instance variables. Furthermore, because instance variables are technically implementation details, many programmers like to keep all instance variables private. With this in mind, ivars declared in @implementation
are private by default. So, if you were to move the _age
declaration to Person.m
instead of the header:
@implementation Person { unsigned int _age; }
_age
would be scoped as a private variable. Keep this in mind when working with instance variables in subclasses, as the different defaults for interface versus implementation declaration can be confusing for newcomers to Objective-C.
Customizing Accessors
But enough about instance variables; let's get back to properties. Accessor methods can be customized using several property declaration attributes (e.g., (copy)
). Some of the most important attributes are:
-
getter=getterName
- Customize the name of the getter accessor method. Remember that the default is simply the name of the property. -
setter=setterName
- Customize the name of the setter accessor method. Remember that the default isset
followed by the name of the property (e.g.,setName
). -
readonly
- Make the property read-only, meaning only a getter will be synthesized. By default, properties are read-write. This cannot be used with thesetter
attribute. -
nonatomic
- Indicate that the accessor methods do not need to be thread safe. Properties are atomic by default, which means that Objective-C will use a lock/retain (described in the next chapter) to return the complete value from a getter/setter. Note, however, that this does not guarantee data integrity across threads-merely that getters and setters will be atomic. If you're not in a threaded environment, non-atomic properties are much faster.
A common use case for customizing getter names is for Boolean naming conventions. Many programmers like to prepend is
to Boolean variable names. This is easy to implement via the getter
attribute:
@property (getter=isEmployed) BOOL employed;
Internally, the class can use the employed
variable, but other objects can use the isEmployed
and setEmployed
accessors to interact with the object:
Person *frank = [[Person alloc] init]; [frank setName:@"Frank"]; [frank setEmployed:YES]; if ([frank isEmployed]) { NSLog(@"Frank is employed"); } else { NSLog(@"Frank is unemployed"); }
Many of the other property attributes relate to memory management, which will be discussed in the upcoming section. It's also possible to apply multiple attributes to a single property by separating them with commas:
@property (getter=isEmployed, readonly) BOOL employed;
Dot Syntax
In addition to getter/setter methods, it's also possible to use dot notation to access declared properties. For C# developers, this should be much more familiar than Objective-C's square-bracket messaging syntax:
Person *frank = [[Person alloc] init]; frank.name = @"Frank"; // Same as [frank setName:@"Frank"]; NSLog(@"%@", frank.name); // Same as [frank name];
Note this is just a convenience-it translates directly to the getter/setter methods described previously. Dot notation cannot be used for instance methods.
Summary
Properties are an integral aspect of any object-oriented programming language. They are the data that methods operate on. The @property
directive is a convenient way to configure a property's behavior, but it doesn't do anything that can't be done by manually creating getter and setter methods.
In the next chapter, we'll take a detailed look at how properties are stored in memory, as well as a few new property attributes for controlling this behavior. After that, we'll dive into methods, which rounds out the core object-oriented tools of Objective-C.
This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Comments