The fundamental role of model objects is to encapsulate data, and to provide access to that data. Model classes can add value in the form of custom behavior. As general design and implementation principles, you should ensure that you follow usual Cocoa standards such as naming conventions and so on as described in Naming Conventions and Defining a Class in The Objective-C Programming Language. For example, class names should start with a capital letter; instance variable names should start with a lowercase letter; instance variables should not be public, and so on. In implementing a model object, you should adhere to the model-view-controller (MVC) design pattern.
If you are creating a traditional Cocoa application, you typically subclass NSObject
. If you are creating an application that uses Core Data, then for classes that represent instances of entities, you subclass NSManagedObject
. Most of the principles described in this document apply to subclasses of both NSObject
and NSManagedObject
. Where there are differences, they are either called out inline, or a separate section is included that addresses Core Data–specific issues.
The following checklist enumerates the additional features a model object might have to fully integrate into the Cocoa environment. It provides links to sections that discuss the design considerations you should bear in mind when you decide whether or not to use the feature, and links to sections that describe the implementation details.
Representation of instance variables
For design considerations, see “Instance Variable Types” below.
For implementation details, see “Basic Accessor Methods.”
Accessor methods
For design considerations, see “Accessor Methods” below.
For implementation details, see “Basic Accessor Methods,” and for Core Data see Managed Object Accessor Methods.
Key-value coding and key-value observing compliance
For design considerations, see: “Accessor Methods” and “Key-Value Coding and Key-Value Observing” below.
For implementation details, see “Basic Accessor Methods,” Managed Object Accessor Methods, and “Key-Value Technology Compliance.”
Copying
For design considerations, see “Copying” below.
For implementation details, see “Copying.”
Archiving
For design considerations, see “Archiving” below.
For implementation details, see “Archiving.”
Key-value observing notifications for dependent values
For design considerations, see “Business Logic” below.
For implementation details, see “Dependent Values.”
Initialization
For design considerations, see “Business Logic” below.
For implementation details, see “Initialization.”
Validation
For design considerations, see “Business Logic” below.
For implementation details, see “Model Object Validation.”
You can represent attribute values with an object or with a scalar or a C structure (a struct
such as NSRect
). There are different considerations to bear in mind when using either type. If you use an object, you must ensure correct data encapsulation (see “Encapsulation”). If you represent an attribute as a scalar value (such as int
, float
, or double
) or as a struct
, it may be easier for you to perform arithmetic calculations (you do not have to convert from an object representation to a scalar value) and there are no memory management issues.
The basic scalar types and a limited set of common structures (NSRect
, NSPoint
, NSSize
, and NSRange
) integrate transparently with key-value coding and key-value observing (see “Key-Value Technology Compliance”)—that is, the key-value technologies automatically convert between the scalar or struct
representation and a corresponding object representation, such as an instance of NSNumber
or NSValue
.
The main disadvantage of using non-object types is that non-object types cannot unambiguously represent a nil value. In addition, integration with other technologies (notably key-value coding and key-value observing) may require conversion of an attribute’s value to an object representation anyway, which incurs some overhead. Depending on the pattern of usage of your application, it may be that any savings made by using non-object representations are outweighed by the overhead of conversion to and from object form (at least in terms of runtime efficiency—programmer effort is also a consideration). Finally, note that the granularity of representation for some types may be insufficiently fine. In a financial application it may be inappropriate to represent numeric data using, for example, a float
due to the inherent inaccuracy of the float
type. It may be more appropriate to use an NSNumber
or NSDecimalNumber
representation, as these provide greater accuracy and a rich set of rounding behaviors.
If you are using Core Data, then there are additional constraints on the types you can use to represent a persistent attribute. Core Data natively supports only strings, numbers, dates, and binary data. You can, however, use transformable or transient values to work around this restriction, as described in Non-Standard Persistent Attributes.
The primary goal of accessor methods is to provide access to property values. There are two basic forms of accessor—get accessors and set accessors, used (predictably) to get and and set a property value respectively. You should implement accessors to preserve encapsulation.
You can also use accessor methods to simplify and streamline memory management, and to facilitate integration with other Cocoa technologies (in particular key-value coding and key-value observing, and through them, Cocoa bindings—see “Key-Value Coding and Key-Value Observing”). The key-value coding protocol defines patterns for collection accessors for sets and arrays which further extend the concept of encapsulation and provide additional functionality.
The same principles that apply to “standard” model classes also apply to NSManagedObject
subclasses used with Core Data, in order to provide additional functionality Core Data requires a different set of implementations for accessor methods. These are described in Managed Object Accessor Methods.
If you use the Objective-C declared properties feature, most of these considerations are taken care of for you.
If an attribute is represented by an object, that object may be accessible by other parts of your application. To ensure proper encapsulation of data, you frequently ensure that model objects maintain their own copies of attribute values, and that get accessors advertise the attribute value as being immutable. For example, an employee’s firstName
attribute may be declared as an NSString
, but it is possible that at some point the value passed to the set accessor may be an instance of NSMutableString
. If you simply set the model object’s instance variable to that string, this would leave open the possibility that the contents of the string could be altered externally without the employee instance being aware of the change (thus violating the principle of encapsulation). In the set accessor, you might therefore copy the new attribute value (or if the property should be mutable, you make a mutable copy).
If there are mutable and immutable versions of a class you use to represent a property—such as NSArray
and NSMutableArray
—you should typically declare the return value of the get accessor as an immutable object even if internally the model uses a mutable object. Declaring the return value as an immutable object signals that the value should not be modified externally.
You should implement your accessor methods such that they take care of memory management, as described in“Basic Accessor Methods.” If your accessors provide a clean API for modifying property values, and if you use the accessors pervasively when modifying values, then you will avoid most memory management issues that arise in Cocoa.
The key-value coding protocol defines patterns for collection accessors for sets and arrays. By implementing accessor methods that follow these patterns you derive a number of benefits.
These accessor methods are key-value observing compliant (see “Key-Value Technology Compliance” for more details). That is, if you invoke any of the mutator methods (such as insertObject…
, removeObject…
, or replaceObject…
), then suitable key-value observing notifications are sent. This typically makes it easier to modify a collection directly than by using the proxy returned by mutableArrayValueForKey:
or mutableSetValueForKey:
.
If you do use mutableArrayValueForKey:
or mutableSetValueForKey:
, the collection accessors are invoked automatically when you make modifications to the collection proxy. If you do not implement these accessors, the collection proxy must replace the whole collection (using the simple set accessor) for each modification, which can incur unnecessary overhead.
You can use the collection accessors to hide underlying implementation details. There is no need for a collection to be implemented using the corresponding collection object—that is, for example, an array relationship doesn’t actually have to be represented by an array. All that’s required is that the accessor methods get and set values appropriately.
Key-value coding (KVC) and key-value observing (KVO—see Key-Value Observing Programming Guide) are fundamental technologies that are required for integration with Cocoa bindings and with Core Data’s change management mechanism.
You can use key-value coding to access an object’s property using the property name as a key. Key-value coding includes a consistent API for property value validation which is described in “Model Object Validation.”
You can use key-value observing to detect changes to property values. You can also use it as a means of registering dependencies between keys to denote that a change in the value of one key will result in a change in the value of another dependent key. For example, a fullName
key may depend on the values of firstName
and lastName
. This latter feature is described in “Key-Value Technology Compliance.”
To complete the picture, you must also of course use the KVO-compliant methods. You can also use key-value coding methods such as setValue:forKey:
, mutableArrayValueForKey:
, and mutableSetValueForKey:
.
In some simple situations it is clear what “copy” means. In many cases, however, you must decide what it actually means to “copy” an object, and in particular what are the limits you want to impose on the copied object graph. You must also decide whether it is appropriate to copy all an object’s attributes—for example, should an employee ID be copied? You must pay particular attention to relationships. If you “copy” an employee object, does this imply that a related department object is also copied? If you copy a department object, does that imply that related employees are also copied? If the answer to both of these questions is “yes,” then copying a single employee implies copying (as a minimum) also the department to which they belong, and the other employees in that department… .
You can use archiving to save your model objects to a persistent store or to send to other processes. The role of archiving as a means to save your objects to a persistent store is largely superseded by Core Data, which manages object persistence for you. You may also, however, use archiving to support copy and paste operations and the transfer of data between applications.
When archiving model objects you must decide what properties to add to the archive. Typically you should archive all an object’s non-derived attributes (that is, the attributes that cannot be calculated or derived from other attributes). If you are using archiving to serialize an object graph to save to a file, then you should also add related objects to the archive so that when the archive is unarchived, those relationships are restored.
A common use of archiving is to support copy and paste operations (or to transfer data to other applications). If—especially in the context of Core Data—you use archiving for these purposes, you must decide what it actually means to copy, and what are the limits you want to impose on the copied object graph. A requirement for archiving in these situations is likely to be semantically different from archiving in the context of object graph serialization. When you create an archive for data serialization to a persistent store, you typically want to record all aspects of an object, including its relationships to other objects. When you use archiving to support copy and paste, you typically do not want to traverse relationships.
Rather than using archiving per se, therefore, it is often more appropriate to define a custom method that returns a representation of the object either in property list form or encapsulated in an NSData
object, and then to initialize a copy object using that representation.
Note that in some situations, you may not actually want to copy an object. If you want to support drag and drop of objects at the destination of a relationship (for example, if you want to use drag and drop to transfer employees from one department to another) or if you support cross-store relationships, you should probably use managed object IDs or URI representations of managed object IDs.
There are two forms of archiving, classic archiving and keyed archiving. Using classic archiving you must add instance variables directly to an archive and extract them in the same order. Using keyed archiving you encode and decode values as key-value pairs. This approach gives you more flexibility than with the classic technique. First, the order in which variables are encoded and decoded does not matter. Second, the archive is more robust against schema changes—the decoder does not fail if a value is not present in an archive, and it ignores any extra values in the archive. Finally, the output file may be human-readable, which may aid in any debugging process.
You can combine archiving techniques to ensure that your model objects can be archived using either classic or keyed archiving. Typically (since it offers a richer and more robust approach) you should choose keyed archiving over classic archiving.
Version handling is typically easier with keyed archives, especially if you make only minor modifications to the schema (adding, removing, or renaming attributes). Old versions can still read keyed archives—keys present in the archive that are not present in the old schema are simply ignored. Problems may still arise, however, if the old version depends on the presence of a key that is absent in the new schema. For more details, see Forward and Backward Compatibility for Keyed Archives in Archives and Serializations Programming Guide.
Business logic is a broad term that encompasses actions performed on or using model objects. You are free to implement whatever methods you wish to support your application. You can provide methods to calculate values ranging from simple examples—such as the full name of an employee represented by a concatenation of first and last names—to complex—such as salary overhead for a department.
Cocoa provides a special API to formalize logic for validation, initialization, and dependent values. In an initialization method you can specify default values for an object’s attributes. In validation methods, you can ensure that property values meet various constraints. To integrate with key-value observing, you may also need to notify observers if a change is made to a property on which a derived value depends.
There are many situations in which the value of one property depends on that of one or more other properties. If the value of one attribute changes, then the value of the derived property should also be flagged for change—for example, if the lastName
property of an employee changes, the fullName
also changes.
You use validation methods to ensure that data values meet various criteria that you specify. There are two types of validation—property-level and inter-property. You use property-level validation methods to ensure the correctness of individual values; you use inter-property validation methods to ensure the correctness of combinations of values where individual values may be valid but a combination may not be. For example, a person object may have attributes age
and hasDriversLicense
, with corresponding values 14
and YES
. The individual values may be valid, the combination of values is invalid.
Last updated: 2009-08-03