Encapsulating Data for Good Design and Maintainability in Swift
Consider the concept of encapsulation, an important concept of object‐oriented programming. The idea behind encapsulation is that objects should be opaque: You should not be able to tell what’s inside an app’s data, what functionality it contains, or what operations it carries out. Doing this makes your objects flexible and reusable.
This concept has proven its value over the years. The original architecture of Objective‐C consisted of frameworks of objects (although the word “framework” was not used at the beginning). The objects were opaque so that software engineers couldn’t look inside them (or, at least, they weren’t supposed to). An object named (say) myObject, was assumed to have some data — perhaps called data — and this data itself was off‐limits to developers.
Instead of accessing the data directly, developers learned to send a message to myObject to obtain its data. By convention, the message would be named objectData and would cause myObject to return the data of myObject.
If you look closer at this design, myObject is the class; objectData is the name of the function or method that serves as a getter. Another method called (by default) setObjectData is the setter. The setter takes a single parameter (typically called (newObjectData).
You would only access objectData using either objectData to get it or setObjectData to set or update it. One interesting and useful consequence of this design is that objectData (the data stored by myObject) need not exist at all.
The accessors (objectData and setObjectData) can work with dynamic data that may never actually be stored. With encapsulation like this, you would never need to know whether or not the data is stored or calculated on the fly. All you need to know is that myObject gets data for you with objectData and takes data from you with setObjectData.
As long as both accessors work, whether it’s calculated on the fly, stored in a database, or even stored on the moon doesn’t matter: myObject encapsulates its data.
Other languages would allow you to access the data directly using some syntax such as myObject.objectData, but because the actual reference to the data in this case would be myObject.objectData() — a method or function call (note the parentheses at the end) — encapsulation is complete.
The hypothetical syntax shown in the previous paragraphs is a generic version of modern syntax or “dot syntax” used in Objective‐C from the late 1990s onward. Using the original style of Objective‐C (sometimes called the message format) the way to send the objectData message to myObject would be with code like this: [myObject objectData].
Creating and preserving encapsulation became an important part of Objective‐C development. It is true that encapsulation preserves many of the powerful features of Objective‐C and makes reusing code much easier, but in some cases, encapsulation can be a bit more complicated to write and sometimes takes more resources to execute.
Thus, although the structure isn’t particularly complicated, storing, retrieving, and setting a variable’s value requires three elements.
The components of this structure are:
an actual variable or code to compute it (this is all invisible to you)
a getter method to get the value of the variable (this is visible to you)
a setter method to set the value of the variable (this is visible to you)
In Objective‐C 2 (released in 2007), named properties were introduced. They provide a way of simplifying this structure, but they are totally compatible with it. A named property has a format such as this:
@property (strong, nonatomic) id detailItem; @property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
The property begins with the compiler directive @property followed by memory and usage attributes in parentheses, such as strong, weak, readonly, and nonatomic — the specific meanings don’t matter at this point.
Following the parenthesized attributes are the type of the property and then its name. For object types, a pointer is used. The class type id is not a pointer. Again, the details don’t matter at this point.
What does matter is that with a property declaration, the compiler is able to automatically declare and create a backing variable, which is the variable itself where the data is stored. It also is able to create a getter and a setter using the conventions described previously (the getter is the name of the variable and the setter is setMyVariable).
Several conventions come into play here. Variable names start with a lowercase letter. If the variable name consists of multiple words, words after the first begin with capital letters (this is called camelCase). Methods and functions start with lowercase letters; classes start with uppercase letters. The conventions meet up in the name of a setter.
Consider a variable called myVariable whose setter is called setMyVariable. This conforms to the convention that methods start with lowercase letters, and it also conforms to the camelCase convention. However, because the meeting of these two conventions might suggest the name setmyVariable, camelCase overrides other conventions.
Thus, named properties reduce the amount of typing required to use properties by having the compiler do the work of creating the getter and setter accessors and of creating the backing variable.
In some cases, developers have needed more control over matters. The two most common cases of this were the following:
When a developer needs a backing variable to have a certain name and needs to be able to access it directly.
When the setter or the getter needs to do more than just set or get the value of a variable.
Here’s where complexity starts to come in.
The automated property synthesizer synthesizes a backing variable with the property name preceded by an underscore. Thus, the default backing variable for myProperty is _myProperty. (You can set this as an option in the property declaration.)
You can also write your own accessors. It is common to have them perform additional tasks above and beyond their simple accessor roles. One of the most common of these tasks is establishing a database connection so that a getter can obtain a previously stored value for its property.
There are many other variations on this structure (including properties that have no backing variables but rely simply on a getter to compute or retrieve a value on as‐needed basis). And, just to complete this part of the picture, you can mix and match properties with traditional variables (naked variables are not part of a declared property).
A lot of the arguments against the use of named properties (and they have been bitter) center around the fact that accessing a named properties value in the backing variable requires more machine resources than just accessing a memory location. With today’s computers — even in mobile devices — this may seem like an extreme reaction, but it has been common in some circles.
This, then, is the world of properties and variables that Swift addresses. Whether all of its complexities and combinations are necessary is worth discussing: All have found uses in apps and even in the Apple frameworks that make up Cocoa and Cocoa Touch.
There is another layer of complexity with regard to the declarations of properties and variables. Most declarations in classes of Objective‐C code are placed in a header file in the @interface section. (By convention, this file is named with a .h after the class name.)
Fairly recently (in the late 2000s) it became commonplace to add a second @interface section in the format of a class extension in the main body of the class file (the file with the .m extension). Declarations in the header file can be grouped into sections with various access — public, private, and protected. public and private interfaces are just that. protected interface elements are visible to the class itself and to any of its subclasses.
Finally, inside the main file (the .m extension file), you can access the backing variable by using its name, as in _myBackingVariable. This bypasses the use of the getter and saves a few machine cycles. It also can side‐step any additional processing that the getter does; this convention can be very useful.
This is the background of properties and variables in Objective‐C and now in Swift.