2. Objects, Messaging, and the Runtime
Objects are the building blocks of programming in an object-oriented language such as Objective-C, providing the means by which data is stored and moved around. Messaging is the process by which objects talk to each other to move data around and make things happen. A deep understanding of how both of these features work is crucial to building efficient and maintainable code.
The Objective-C runtime is the code that powers the language once an application is running. The runtime provides the crucial functions that enable messaging between objects to work and all the logic behind how instances of classes are created. Understanding how all this fits together makes you a better developer.
Item 6: Understand Properties
Properties are an Objective-C feature providing encapsulation of the data an object contains. Objects in Objective-C will usually contain a set of instance variables to store the data they need to work. Instance variables are usually accessed through accessor methods. A getter is used to read the variable, and a setter is used to write the variable. This concept was standardized and became part of the Objective-C 2.0 release through a feature called properties, which allow the developer to tell the compiler to write accessor methods automatically. This feature introduced a new “dot syntax” to make accessing the data stored by classes less verbose. You have probably used properties already, but you may not know about all the options. Also, you may not be aware of some of the intricacies surrounding properties. Item 6 illustrates the background surrounding the problem solved by properties and points out their key features.
A class to describe a person might store the person’s name, date of birth, address, and so on. You can declare instance variables in the public interface for a class as follows:
@interface EOCPerson : NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
This will be familiar if you are coming from the worlds of Java or C++, where you can define the scope of instance variables. However, this technique is rarely used in modern Objective-C. The problem with the approach is that the layout of an object is defined at compile time. Whenever the _firstName variable is accessed, the compiler hardcodes the offset into the memory region where the object is stored. This works fine until you add another instance variable. For example, suppose that another instance variable were added above _firstName:
@interface EOCPerson : NSObject {
@public
NSDate *_dateOfBirth;
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
The offset that was once pointing to _firstName is now pointing to _dateOfBirth. Any code that had the offset hardcoded would end up reading the wrong value. To illustrate that point, Figure 2.1 shows the memory layout of the class, assuming 4-byte pointers, before and after adding the _dateOfBirth instance variable.
Figure 2.1 Class data layout before and after adding another instance variable
Code that makes use of calculating the offset at compile time will break unless recompiled when the class definition changes. For example, code may exist in a library that uses an old class definition. If linked with code using the new class definition, there will be an incompatibility at runtime. To overcome this problem, languages have invented a variety of techniques. The approach Objective-C has taken is to make instance variables special variables held by class objects (see Item 14 for more on class objects) storing the offset. Then at runtime, the offset is looked up so that if the class definition changes, the offset stored is updated; whenever an access to the instance variable is made, the correct offset is used. You can even add instance variables to classes at runtime. This is known as the nonfragile Application Binary Interface (ABI). An ABI defines, among other things, the conventions for how code should be generated. The nonfragile ABI also means that instance variables can be defined in a class-continuation category (see Item 27) or in the implementation. So you don’t have to have all your instance variables declared in the interface anymore, and you therefore don’t leak internal information about your implementation in the public interface.
Encouraging the use of accessor methods rather than accessing instance variables directly is another factor overcoming this problem. Properties are backed by instance variables, but they provide a neat abstraction. You could write accessors yourself, but in true Objective-C style, accessors follow strict naming patterns. Because of this strict naming, it was possible to introduce a language construct to provide a means of automatically creating accessor methods. This is where the @property syntax comes in.
You use properties in the definition of an object interface to provide a standard means of accessing the data encapsulated by an object. As such, properties can also be thought of as shorthand for saying that there are going to be accessors to a variable of a given type and a given name. For example, consider the following class:
@interface EOCPerson : NSObject
@property
NSString *firstName;
@property
NSString *lastName;
@end
To a consumer of the class, this is equivalent to writing the class like this:
@interface EOCPerson : NSObject
- (NSString*)firstName;
- (void)setFirstName:(NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName:(NSString*)lastName;
@end
To use a property, you use the dot syntax, similar to how you would access a member of a stack-allocated struct in plain C. The compiler turns the dot syntax into calls to the accessors, just as if you had invoked them directly. Thus, there is absolutely no difference between using the dot syntax and calling the accessors directly. The following code sample shows the equivalence:
EOCPerson *aPerson = [Person new];
aPerson.firstName = @"Bob"; // Same as:
[aPerson setFirstName:@"Bob"];
NSString *lastName = aPerson.lastName; // Same as:
NSString *lastName = [aPerson lastName];
But there’s more to properties than that. If you let it, the compiler will automatically write the code for you for these methods through a process called autosynthesis. It is important to note that it’s the compiler doing this, at compile time, so you won’t see source code in your editor for the synthesized methods. Along with generating the code, the compiler also automatically adds an instance variable to the class, of the required type with the name of the property prefixed with an underscore. In the preceding example, there would be two instance variables: _firstName and _lastName. It is possible to control the name of this instance variable by using the @synthesize syntax within the class’s implementation, like so:
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
Using the preceding syntax would yield instance variables called _myFirstName and _myLastName instead of the defaults. It is unusual to want to change the name of the instance variable from the default; however, if you are not a fan of using the underscore approach to naming instance variables, you can use this to name them however you want. But I encourage you to use the default naming scheme, as it makes code readable by everyone if everyone sticks to the same conventions.
If you don’t want the compiler to synthesize the accessor methods for you, you can implement the methods yourself. However, if you implement only one of the accessors, the compiler will still synthesize the other for you. Another way to stop it from synthesizing is to use the @dynamic keyword, which tells the compiler to not automatically create an instance variable to back the property and to not create the accessors for you. Also, when compiling code that accesses the property, the compiler will ignore the fact that the accessors have not been defined and trust you that they will be available at runtime. For example, this is used when subclassing CoreData’s NSManagedObject, where the accessors are dynamically created at runtime. NSManagedObject opts for this approach because the properties are not instance variables. The data comes from whatever database back end is being used. For example:
@interface EOCPerson : NSManagedObject
@property
NSString *firstName;
@property
NSString *lastName;
@end
@implementation EOCPerson
@dynamic firstName, lastName;
@end
In this class, no accessors or instance variables would be synthesized. Nor would the compiler warn if you attempted to access either property.
Property Attributes
Another aspect of properties that you should be aware of is all the attributes that can be used to control the accessors generated by the compiler. An example using three attributes looks like this:
@property (nonatomic, readwrite, copy) NSString *firstName;
Four categories of attributes can be applied.
Atomicity
By default, synthesized accessors include locking to make them atomic. If you supply the attribute nonatomic, no locking is used. Note that although there is no atomic attribute (atomic is assumed by the lack of the nonatomic attribute), it can still be applied with no compiler errors in case you want to be explicit. If you define the accessors yourself, you should provide the specified atomicity.
Read/Write
readwrite Both a getter and a setter are available. If the property is synthesized, the compiler will generate both methods.
readonly Only a getter is available, and the compiler will generate it only if the property is synthesized. You may want to use this if you expose a property only for read externally but redeclare it as read/write internally in the class-continuation category. See Item 27 for more information.
Memory-Management Semantics
Properties encapsulate data, and that data needs to have concrete ownership semantics. This affects only the setter. For example, should the setter retain the new value or simply assign it to the underlying instance variable? When the compiler synthesizes the accessors, it uses these attributes to determine what code to write for you. If you create your own accessors, you must stick to what you specify for this attribute.
assign The setter is a simple assign operation used for scalar types, such as CGFloat or NSInteger.
strong This designates that the property defines an owning relationship. When a new value is set, it is first retained, the old value is released, and then the value is set.
weak This designates that the property defines a nonowning relationship. When a new value is set, it is not retained; nor is the old value released. This is similar to what assign does, but the value is also nilled out when the object pointed to by the property at any time is destroyed.
unsafe_unretained This has the same semantics as assign but is used where the type is an object type to indicate a nonowning relationship (unretained) that is not nilled out (unsafe) when the target is destroyed, unlike weak.
copy This designates an owning relationship similar to strong; however, instead of retaining the value, it is copied. This is often used when the type is NSString* to preserve encapsulation, since the value passed into the setter might be an instance of the subclass NSMutableString. If it’s this mutable variant, the value could be mutated after the property is set, without the object’s knowing. So an immutable copy is taken to ensure that the string cannot change from underneath the object. Any object that may be mutable should take a copy.
Method Names
The names of the accessor methods can be controlled by using the following attributes:
getter=<name> Specifies the name of the getter. This method is usually used for Boolean properties where you want the getter to be prefixed with is. For example, on the UISwitch class, the property for whether the switch is on or off is defined like so:
@property (nonatomic, getter=isOn) BOOL on;
setter=<name> Specifies the name of the setter. This method is not commonly used.
You can use these attributes to get fine-grained control over the synthesized accessors. However, you should note that if you implement your own accessors, you should make them adhere to the specified attributes yourself. For example, a property declared as copy should ensure that a copy of the object is taken in the setter. Otherwise, users of the property would be under false impressions, and bugs could be introduced from the contract’s not being upheld.
Ensuring that you adhere to the semantics laid out in the property definition is important even in other methods that may set the property. For example, consider an extension of the EOCPerson class. It declares the properties’ memory-management semantics as copy because the value might be mutable. It also adds an initializer that sets up the initial values of the first and last names:
@interface EOCPerson : NSManagedObject
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
- (id)initWithFirstName:(NSString*)firstName
lastName:(NSString*)lastName;
@end
In the implementation of the custom initializer, it is important to adhere to the copy semantics laid out in the property definitions. The reason is that the property definitions document the contract that the class has with the values that are set. So an implementation of the initializer would look like this:
- (id)initWithFirstName:(NSString*)firstName
lastName:(NSString*)lastName
{
if ((self = [super
init])) {
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return
self;
}
You may wonder why you can’t simply use the properties’ setter methods instead, which will always ensure that the correct semantics are used. You should never use your own accessors in an init (or dealloc) method, as explained further in Item 7.
If you’ve read Item 18, you’ll know that, if possible, it’s best to make an object immutable. Applied to EOCPerson, you’d make both properties read-only. The initializer will set the values, after which they cannot be changed. In this scenario, it is important to still declare what memory-management semantics you use for the values. So the property definitions would end up looking like this:
@property (copy, readonly) NSString *firstName;
@property (copy, readonly) NSString *lastName;
Even though no setters are created, because the properties are read-only, it is still important to document what the semantics are when the initializer is run to set the values. Without this documented, a consumer of the class could not assume that it takes a copy and therefore might make an extra copy before calling the initializer. Doing so would be both redundant and less efficient.
You may wonder how atomic and nonatomic differ. As described earlier, atomic accessors include locks to ensure atomicity. This means that if two threads are reading and writing the same property, the value of the property at any given point in time is valid. Without the locks, or nonatomic, the property value may be read on one thread while another thread is midway through writing to it. If this happens, the value that’s read could be invalid.
If you’ve been developing for iOS at all, you’ll notice that all properties are declared nonatomic. The reason is that, historically, the locking introduces such an overhead on iOS that it becomes a performance problem. Usually, atomicity is not required anyway, since it does not ensure thread safety, which usually requires a deeper level of locking. For example, even with atomicity, a single thread might read a property multiple times immediately after one another and obtain different values if another thread is writing to it at the same time. Therefore, you will usually want to use nonatomic properties on iOS. But on Mac OS X, you don’t usually find that atomic property access is a performance bottleneck.
Things to Remember
The @property syntax provides a way of defining what data an object encapsulates.
Use attributes to provide the right semantics for the data being stored.
Ensure that anywhere a property’s backing instance variable is set, the declared semantics are adhered to.
Use nonatomic on iOS, since performance is severely impacted if atomic is used.
Item 7: Access Instance Variables Primarily Directly When Accessing Them Internally
Properties should always be used to access instance variables of an object externally, but how you access instance variables internally is a hotly debated topic within the Objective-C community. Some suggest always using a property to access instance variables, some suggest always accessing the instance variable directly, and some suggest a mixture of the two. I strongly encourage you to read instance variables using direct access but to set them using the property, with a few caveats.
Consider the following class:
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
// Convenience for firstName + " " + lastName:
- (NSString*)fullName;
- (void)setFullName:(NSString*)fullName;
@end
The convenience methods fullName and setFullName: might be implemented like this:
- (NSString*)fullName {
return [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
}
/** The following assumes all full names have exactly 2
* parts. The method could be rewritten to support more
* exotic names.
*/
- (void)setFullName:(NSString*)fullName {
NSArray *components =
[fullName componentsSeparatedByString:@" "];
self.firstName = [components objectAtIndex:0];
self.lastName = [components objectAtIndex:1];
}
In both the getter and the setter, we access the instance variables via the accessor methods, using the property dot syntax. Now suppose that you rewrote both methods to access the instance variables directly:
- (NSString*)fullName {
return [NSString
stringWithFormat:@"%@ %@",
_firstName, _lastName];
}
- (void)setFullName:(NSString*)fullName {
NSArray *components =
[fullName componentsSeparatedByString:@" "];
_firstName = [components objectAtIndex:0];
_lastName = [components objectAtIndex:1];
}
The two styles have a few differences.
Direct access to the instance variables will undoubtedly be faster, as it does not have to go through Objective-C method dispatch (see Item 11). The compiler will emit code that directly accesses the memory where the object’s instance variables are stored.
Direct access bypasses the property’s memory-management semantics defined by the setter. For example, if your property is declared as copy, directly setting the instance variable will not cause a copy to be made. The new value will be retained and the old value released.
Key-Value Observing (KVO) notifications would not be fired when accessing the instance variables directly. This may or may not be a problem, depending on how you want your objects to behave.
Accessing through properties can make it easier to debug issues surrounding a property, since you can add a breakpoint to the getter and/or setter to determine who is accessing the properties and when.
A good compromise is to write instance variables using the setter and to read using direct access. Doing so has the benefit of fast reading and not losing the control of writing via properties. The most important reason for writing via the setter is that you will ensure that the memory-management semantics are upheld. There are, however, a few caveats to that approach.
The first caveat is when values are set within an initializer method. Here, you should always use direct instance variable access, because subclasses could override the setter. Consider that EOCPerson has a subclass EOCSmithPerson that is designed to be used only for people whose last name is “Smith.” This subclass might override the setter for lastName like so:
- (void)setLastName:(NSString*)lastName {
if (![lastName isEqualToString:@"Smith"]) {
[NSException
raise:NSInvalidArgumentException
format:@"Last name must be Smith"];
}
self.lastName = lastname;
}
The base class EOCPerson might set the last name to the empty string in its default initializer. If it did this through the setter, the subclass’s setter would be called and throw an exception. However, there are some cases in which you must use the setter in an initializer. This is when the instance variable is declared within a superclass; you cannot access the instance variable directly anyway, so you must use the setter.
Another caveat is when the property uses lazy initialization. In this case, you have to go via the getter; if you don’t, the instance variable will never get a chance to be initialized. For example, the EOCPerson class might have a property to give access to a complex object representing each person’s brain. If this property is infrequently accessed and expensive to set up, you might initialize it lazily in the getter, like this:
- (EOCBrain*)brain {
if (!_brain) {
_brain = [Brain new];
}
return _brain;
}
If you were to access the instance variable directly and the getter had not been called yet, brain would not have been set up, and you would need to call the accessor for all accesses to the brain property.
Things to Remember
Prefer to read data directly through instance variables internally and to write data through properties internally.
Within initializers and dealloc, always read and write data directly through instance variables.
Sometimes, you will need to read data through properties when that data is being lazily initialized.
Item 8: Understand Object Equality
Being able to compare objects for equality is extremely useful. However, comparing using the == operator is usually not what you want to do, since doing so compares the pointers themselves rather than the objects to which they point. Instead, you should use the isEqual: method declared within the NSObject protocol to check any two objects for equality. Usually, however, two objects of a different class are always determined to be unequal. Some objects also provide special equality-checking methods that you can use if you already know that the two objects you are checking are of the same class. Take, for example, the following code:
NSString *foo = @"Badger 123";
NSString *bar = [NSString
stringWithFormat:@"Badger %i", 123];
BOOL equalA = (foo == bar); //< equalA = NO
BOOL equalB = [foo isEqual:bar]; //< equalB = YES
BOOL equalC = [foo isEqualToString:bar]; //< equalC = YES
Here, you can see the difference between == and using equality methods. NSString is an example of a class that implements its own equality-checking method, called isEqualToString:. The object passed to this method must also be an NSString; otherwise, the results are undefined. This method is designed to be faster than calling isEqual:, which has to do extra steps because it doesn’t know the class of the object being compared.
The two methods at the heart of equality checking from the NSObject protocol are as follows:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
The default implementations of these methods from the NSObject class itself work such that two objects are equal if and only if their pointer values are exactly the same. To understand how to override these for your own objects, it’s important to understand the contract. Any two objects determined to be equal using the isEqual: method must return the same value from the hash method. However, two objects that return the same value from the hash method do not have to be equal according to the isEqual: method.
For example, consider the following class:
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end
Two EOCPerson objects are equal if all the fields are equal. So the isEqual: method would look like this:
- (BOOL)isEqual:(id)object {
if (self == object) return
YES;
if ([self class] != [object class]) return
NO;
EOCPerson *otherPerson = (EOCPerson*)object;
if (![_firstName isEqualToString:otherPerson.firstName])
return
NO;
if (![_lastName isEqualToString:otherPerson.lastName])
return
NO;
if (_age != otherPerson.age)
return
NO;
return
YES;
}
First, the object is checked for direct pointer equality to self. If the pointers are equal, the objects must be equal, since they are the same object! Next, the class of the two objects is compared. If the class is not the same, the two objects cannot be equal. After all, an EOCPerson cannot be equal to an EOCDog. Of course, you may want an instance of EOCPerson to be equal to an instance of a subclass of it: for example, EOCSmithPerson. This illustrates a common problem in inheritance hierarchies with equality. You should consider this when implementing your isEqual: methods. Last, each property is checked for equality. If any of them are not equal, the two objects are deemed unequal; otherwise, they are equal.
That leaves the hash method. Recall the contract whereby equal objects must return the same hash, but objects with the same hash do not necessarily need to be equal. Therefore, this is essential to override if you override isEqual:. A perfectly acceptable hash method would be the following:
- (NSUInteger)hash {
return
1337;
}
However, this could lead to performance problems if you ever put these objects in a collection, since the hash is used as an index within the hash tables that collections use. A set implementation might use the hash to bin objects into different arrays. Then when an object is added to the set, the array corresponding to its hash is enumerated to see whether any objects in that array are equal. If they are, the object is already in the set. Therefore, if you return the same hash value for every object and you add 1,000,000 objects to the set, each further addition to the set has to scan each of those 1,000,000 objects.
Another implementation of the hash method might be:
- (NSUInteger)hash {
NSString *stringToHash =
[NSString
stringWithFormat:@"%@:%@:%i",
_firstName, _lastName, _age];
return [stringToHash hash];
}
This time, the algorithm of NSString’s hash method is piggybacked by creating a string and returning the hash of that. Doing so adheres to the contract, since two EOCPerson objects that are equal will always return the same hash. However, the downside of this approach is that it is much slower than returning a single value, since you have the overhead of creating a string. This can again cause performance issues when adding the object to a collection, since the hash has to be calculated for the object being added to the collection.
A third and final approach is to create a hash like this:
- (NSUInteger)hash {
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
This approach is a middle ground between efficiency and creating at least some range of hashes. There will, of course, be collisions with hashes created using this algorithm, but at least multiple return values are possible. The tradeoff between collision frequency and a computationally intensive hash method is something that you should experiment with and see what works for your object.
Class-Specific Equality Methods
Other than NSString as described earlier, classes that provide a specific equality method include NSArray (isEqualToArray:) and NSDictionary (isEqualToDictionary:), both of which will throw an exception if the object being compared is not an array or a dictionary, respectively. Objective-C has is no strong type checking at compile time, so you could easily accidentally pass in an object of the wrong type. Therefore, you need to be sure that the object you’re passing in is indeed of the correct type.
You may decide to create your own equality method if equality is likely to be checked frequently; therefore, the extra speed from not having to check types is significant. Another reason for providing a specific method is purely cosmetic where you think that it looks better and is more readable, which is likely part of the motivation for NSString’s isEqualToString: method. Code that uses this method is easier to read, as you don’t have to hunt for the types of the two objects being compared.
If you do create a specific method, you should override the isEqual: method also and pass through if the class of the object being compared is the same as the receiver. If it’s not, passing through to the superclass implementation is common practice. For example, the EOCPerson class could implement the following:
- (BOOL)isEqualToPerson:(EOCPerson*)otherPerson {
if (self == object) return
YES;
if (![_firstName isEqualToString:otherPerson.firstName])
return
NO;
if (![_lastName isEqualToString:otherPerson.lastName])
return
NO;
if (_age != otherPerson.age)
return
NO;
return
YES;
}
- (BOOL)isEqual:(id)object {
if ([self class] == [object class]) {
return [self isEqualToPerson:(EOCPerson*)object];
} else {
return [super isEqual:object];
}
}
Deep versus Shallow Equality
When you create an equality method, you need to decide whether to check the whole object for equality or simply a few fields. NSArray checks whether the two arrays being compared contain the same number of objects and if so, iterates through them and calls isEqual: on each. If all objects are equal, the two arrays are deemed to be equal, known as deep equality. Sometimes, however, if you know that only a selection of the data determines equality, it is valid to not check every bit of data for equality.
For example, using the EOCPerson class, if instances had come from a database, they might have another property added with a unique identifier used as the primary key in the database:
@property NSUInteger identifier;
In such a scenario, you may decide to check only that the identifiers match, especially if the properties were declared readonly externally such that you can be certain that if two objects have the same identifier, they are indeed representing the same object and are therefore equal. This would save on having to check through every single bit of data that the EOCPerson object contains when you can assert that if the identifiers match, so must the rest of the data, since it came from the same data source.
Whether or not you check all fields in your equality method depends entirely on the object in question. Only you can know what it means for two instances of your object to be equal.
Equality of Mutable Classes in Containers
An important scenario to consider is when mutable classes are put into containers. Once you add an object to a collection, its hash should not change. Earlier, I explained how objects are binned according to their hash. If their hash changes once in a bin, the objects would be in the wrong bin. To get around this problem, you can either ensure that the hash is not dependent on the mutable portions of the object or simply not mutate objects once they are in collections. In Item 18, I explain why you should make objects immutable. This is a great example of such a reason.
You can see this problem in action by testing with an NSMutableSet and a few NSMutableArrays. Start by adding one array to the set:
NSMutableSet *set = [NSMutableSet
new];
NSMutableArray *arrayA = [@[@1, @2]
mutableCopy];
[set addObject:arrayA];
NSLog(@"set = %@", set);
// Output: set = {((1,2))}
The set contains one object: an array with two objects in it. Now add an array that contains equal objects in the same order, such that the array already in the set and the new one are equal:
NSMutableArray *arrayB = [@[@1, @2]
mutableCopy];
[set addObject:arrayB];
NSLog(@"set = %@", set);
// Output: set = {((1,2))}
The set still contains just a single object, since the object added is equal to the object already in there. Now we add to the set an array that is not equal to the array already in the set:
NSMutableArray *arrayC = [@[@1]
mutableCopy];
[set addObject:arrayC];
NSLog(@"set = %@", set);
// Output: set = {((1),(1,2))}
As expected, the set now contains two arrays: the original one and the new one, since arrayC does not equal the one already in the set. Finally, we mutate arrayC to be equal to the other array already in the set:
[arrayC addObject:@2];
NSLog(@"set = %@", set);
// Output: set = {((1,2),(1,2))}
Ah, oh dear, now two arrays in the set are equal to each other! A set is not meant to allow this, but it has been unable to maintain its semantics because we’ve mutated one of the objects that was already in the set. What’s even more awkward is if the set is then copied:
NSSet *setB = [set copy];
NSLog(@"setB = %@", setB);
// Output: setB = {((1,2))}
The copied set has only a single object in it, just as if the set had been created by making an empty set and one by one adding the entries from the original. This may or may not be what you expected. You may have thought that it would copy verbatim the original, with the corruption included. Or you may have thought that it would do what it did. Both would be valid copying algorithms, which further illustrates the point that the set had become corrupt and so all bets are off when dealing with it.
The moral of this story is to be aware of what can happen when you mutate an object that’s in a collection. It’s not to say you should never do it, but you should be aware of the potential problems and code accordingly.
Things to Remember
Provide isEqual: and hash methods for objects that you will want to check for equality.
Objects that are equal must have the same hash, but objects that have the same hash do not necessarily have to be equal.
Determine what is necessary to test for equality rather than bluntly testing every property.
Write hash methods that will be quick but provide a reasonably low level of collisions.
Item 9: Use the Class Cluster Pattern to Hide Implementation Detail
A class cluster is a great way to hide implementation detail behind an abstract base class. This pattern is prevalent within the Objective-C system frameworks. An example from UIKit, the user interface framework for iOS, is UIButton. To create a button, you call the following class method:
+ (UIButton*)buttonWithType:(UIButtonType)type;
The type of the object returned will depend on the button type passed in. However, all classes inherit from the same base class, UIButton. The point of doing this is that the consumer of the UIButton class does not care about the type of the button being created and the implementation detail behind how that button draws itself. All it needs to know is how to create a button; set attributes, such as the title; and add targets for touch actions.
Going back to basics, this problem could be solved by having a single class that handles all button drawing and switches based on the type:
- (void)drawRect:(CGRect)rect {
if (_type == TypeA) {
// Draw TypeA button
} else if (_type == TypeB) {
// Draw TypeB button
} /* ... */
}
It’s clear, however, that this approach would become very cumbersome if many methods required switching on the type. A good programmer at this point might refactor such that there are multiple subclasses that do the specialist work required of each button type. However, doing so would require a user to know about all the different subclasses. This is where the Class Cluster pattern comes into its own and provides the flexibility of multiple subclasses while keeping a clean interface by hiding them away behind an abstract base class. You don’t create instances of the subclasses; you let the base class create them for you.
Creating a Class Cluster
As an example of how to create a class cluster, consider a class for handling employees, all of whom have a name and salary and can be told to do a day’s work. What happens when each does a day’s work, however, is different for each type of employee. The manager of the business in which these employees are held doesn’t care about what happens when each individual does his or her work but simply tells the employees to do it.
First, you need to define the abstract base class:
typedef
NS_ENUM(NSUInteger, EOCEmployeeType) {
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property (copy) NSString *name;
@property
NSUInteger salary;
// Helper for creating Employee objects
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
// Make Employees do their respective day's work
- (void)doADaysWork;
@end
@implementation EOCEmployee
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type {
switch (type) {
case EOCEmployeeTypeDeveloper:
return [EOCEmployeeDeveloper
new];
break;
case EOCEmployeeTypeDesigner:
return [EOCEmployeeDesigner
new];
break;
case EOCEmployeeTypeFinance:
return [EOCEmployeeFinance
new];
break;
}
}
- (void)doADaysWork {
// Subclasses implement this.
}
@end
Each concrete subclass inherits from the base class. For example:
@interface EOCEmployeeDeveloper : EOCEmployee
@end
@implementation EOCEmployeeDeveloper
- (void)doADaysWork {
[self writeCode];
}
@end
In this example, the base class implements a method declared as a class method, which switches based on the type of employee being created and allocates an instance of the required class. This Factory pattern is one way of creating a class cluster.
Unfortunately, Objective-C gives no language feature for designating that the base class is abstract. Instead, convention for how to use a class should be made in documentation. In this case, there is no init family method defined in the interface, which indicates that perhaps instances should not be created directly. Another way to ensure that instances of the base class are not used would be to throw an exception in doADaysWork in the base class. However, this is quite extreme and usually unnecessary.
It is important to take care when using objects that are members of a class cluster during introspection (see Item 14). The reason is that even though you think you have created an instance of one class, you have in fact created an instance of a subclass. In the Employee example, you might have thought that calling [employee isMemberOfClass:[EOCEmployee class]] would return YES, but it’s not really an Employee that has been returned, so this call would return NO.
Class Clusters in Cocoa
There are many class clusters in the system frameworks. Most of the collection classes are class clusters, such as NSArray, and its mutable counterpart, NSMutableArray. So, in fact, there are two abstract base classes: one for immutable arrays and one for mutable arrays. It’s still a class cluster but with two public interfaces. The immutable class defines the methods common to all arrays, and the mutable class defines the methods that are present only on mutable arrays. The fact that it is a class cluster means that it’s easy to both share code behind the scenes between the two array types and support creating copies that change the mutability.
In the case of NSArray, when an instance is allocated, it’s an instance of another class that’s allocated (during a call to alloc), known as a placeholder array. This placeholder array is then converted to an instance of another class, which is a concrete subclass of NSArray. This is a pretty little dance but beyond the scope of this book to explain fully.
Being aware that classes like NSArray (and most of the other collection classes, for that matter) are class clusters is important because otherwise, you might write the following code:
id maybeAnArray = /* ... */;
if ([maybeAnArray class] == [NSArray
class]) {
// Will never be hit
}
Knowing that NSArray is a class cluster should make you understand why this code is incorrect and the if statement will never be true. The class returned from [maybeAnArray class] will never be the NSArray class, since instances returned from NSArray’s initializers are instances of internal types behind the class cluster public facade.
Note that it is still possible to check the class of an instance of a class cluster. Instead of the preceding, you should use the introspection methods. Item 14 illustrates these methods. Instead of checking equality of class objects, you should do the following:
id maybeAnArray = /* ... */;
if ([maybeAnArray isKindOfClass:[NSArray
class]]) {
// Will be hit
}
Adding a concrete implementation into a class cluster is a common requirement, but caution should be observed when attempting it. In the case of the Employee example, adding a new employee type would be impossible without having the source of the factory method to add to. In the case of Cocoa’s class clusters, such as NSArray, it can be done, but a few rules must be obeyed. They are as follows.
The subclass should inherit from the class cluster’s abstract base class.
In the case of NSArray, this could be either the immutable or the mutable base class.
The subclass should define its own storage.
This part is often where people get stuck in subclassing something like NSArray. You must have inside your subclass an instance variable that holds the objects the array contains. This seems somewhat contrary to expectations, as surely that’s what NSArray itself does. But remember that NSArray itself is simply a shim around other hidden objects that purely defines the interface to arrays. A good choice of object to use to hold the instances of a custom array subclass would be an NSArray itself.
The subclass should override a documented set of methods of the superclass.
Each abstract base class has a set of methods that subclasses must implement. In the case of NSArray, the methods that need to be implemented are count and objectAtIndex:. Other methods, such as lastObject, don’t have to be implemented, since they use these two methods themselves.
All the specifics of how to subclass a class cluster should be defined in the class’s documentation, so you should always read that first.
Things to Remember
The Class Cluster pattern can be used to hide implementation detail behind a simple public facade.
Class clusters are commonly used in the system frameworks.
Care should be taken when subclassing a class cluster’s public abstract class, and documentation, if it exists, should always be read.
Item 10: Use Associated Objects to Attach Custom Data to Existing Classes
Sometimes, you want to associate information with an object. Normally, you would do this by subclassing the object’s class and use that instead. However, you can’t always do this, since instances of the class might be created for you by some means and you cannot tell it to create instances of your class instead. That’s where the powerful Objective-C feature called Associated Objects comes in handy.
Objects are associated with other objects, using a key to identify them. They are also designated a storage policy to govern memory-management semantics of the stored value. The storage policies are defined by the enumeration objc_AssociationPolicy, which contains the values shown in Table 2.1 against the @property attribute for the equivalent if the association were a property (see Item 6 for further information on properties).
Table 2.1 Object Association Types
Management of associations is performed using the following methods:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
Sets up an association of object to value with the given key and policy.
id objc_getAssociatedObject(id object, void *key)
Retrieves the value for the association on object with the given key.
void objc_removeAssociatedObjects(id object)
Removes all associations against object.
The accessing of associated objects is functionally similar to imagining that the object is an NSDictionary and calling [object setObject:value forKey:key] and [object objectForKey:key]. An important difference to note, though, is that key is treated purely as an opaque pointer. Whereas with a dictionary, keys are regarded equal if they return YES for isEqual:, the key for associated objects must be the exact same pointer for them to match. For this reason, it is common to use static global variables for the keys.
An Example of Using Associated Objects
In iOS development, it’s common to use the UIAlertView class, which provides a standard view for showing an alert to the user. There’s a delegate protocol to handle when the user taps a button to close it; however, using delegation splits up the code of creation of the alert and handling the tap. This makes it slightly awkward to read, as the code is split between two places. Here is an example of what using a UIAlertView would look like normally:
- (void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView
alloc]
initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[self
doCancel];
} else {
[self
doContinue];
}
}
This pattern gets even more complicated if you ever want to present more than one alert in the same class, since you then have to check the alertView parameter passed into the delegate method and switch based on that. It would be much simpler if the logic for what to do when each button is tapped could be decided when the alert is created. This is where an associated object can be used. A solution is to set a block against an alert when it is created and then read that block out when the delegate method is run. Implementing it would look like this:
#import <objc/runtime.h>
static
void *EOCMyAlertViewKey = "EOCMyAlertViewKey";
- (void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView
alloc]
initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
};
objc_setAssociatedObject(alert,
EOCMyAlertViewKey,
block,
OBJC_ASSOCIATION_COPY);
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView*)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^block)(NSInteger) =
objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
block(buttonIndex);
}
With this approach, the code for creating the alert and handling the result is all in one place, making it more readable than before, as you don’t have to flick between two portions of code to understand why the alert view is being used. You would, however, need to be careful with this approach, as retain cycles could easily be introduced if the block captured. See Item 40 for more information on this problem.
As you can see, this approach is very powerful, but it should be used only when there’s no other way of achieving what you need to do. Widespread use of this approach could get out of hand very quickly and make debugging difficult. Retain cycles become harder to reason, since there is no formal definition of the relationship between the associated objects, as the memory-management semantics are defined at association time rather than in an interface definition. So proceed with caution when using this approach, and do not use it purely because you can. An alternative way of achieving the same with UIAlertView would be to subclass it and add a property to store the block. I would suggest this approach over associated objects if the alert view were to be used more than once.
Things to Remember
Associated objects provide a means of linking two objects together.
The memory-management semantics of associated objects can be defined to mimic owning or nonowning relationships.
Associated objects should be used only when another approach is not possible, since they can easily introduce hard-to-find bugs.
Item 11: Understand the Role of objc_msgSend
One of the most common things you’ll do in Objective-C is call methods on objects. In Objective-C terminology, this is called passing a message. Messages have names, or selectors, take arguments, and may return a value.
Since Objective-C is a superset of C, it’s a good idea to start by understanding that calling a function in C uses what is known as static binding, which means that the function being called is known at compile time. For example, consider the following code:
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
} else {
printGoodbye();
}
return
0;
}
Ignoring inlining, when this is compiled, printHello and printGoodbye are known, and the compiler emits instructions to directly call the functions. The addresses of the functions are effectively hardcoded into the instructions. Consider now if that had been written like this:
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
} else {
fnc = printGoodbye;
}
fnc();
return
0;
}
Here, dynamic binding is used, since the function being called is unknown until runtime. The difference in the instructions the compiler emits will be that in the first example, a function call is made inside both the if and the else statements. In the second example, only a single function call is made but at the cost of having to read the address of which function to call rather than being hardcoded.
Dynamic binding is the mechanism by which methods in Objective-C are invoked when a message is passed to an object. All methods are plain old C functions under the hood, but which one is invoked for a given message is decided entirely at runtime and can even change throughout the course of an app running, making Objective-C truly dynamic.
A message being called on an object looks like this:
id returnValue = [someObject messageName:parameter];
In this example, someObject is referred to as the receiver, and messageName is the selector. The selector combined with the parameters is known as the message. When it sees this message, the compiler turns it into a standard C function call to the function at the heart of messaging, objc_msgSend, which has the following prototype:
void objc_msgSend(id self, SEL cmd, ...)
This is a variadic function that takes two or more parameters. The first parameter is the receiver, the second parameter is the selector (SEL is the type of a selector), and the remaining parameters are the message parameters in the order they appear. A selector is the name that refers to a method. The term selector is often used interchangeably with the term method. The preceding example message will be converted to the following:
id returnValue = objc_msgSend(someObject,
@selector(messageName:),
parameter);
The objc_msgSend function calls the correct method, depending on the type of the receiver and the selector. In order to do this, the function looks through the list of methods implemented by the receiver’s class and, if it finds a method that matches the selector name, jumps to its implementation. If not, the function traverses up the inheritance hierarchy to find the method to jump to. If no matching method is found, message forwarding kicks in. For further explanation on message forwarding, see Item 12.
This may all sound like a lot of work to go through whenever a method is invoked. Fortunately, objc_msgSend caches the result in a fast map, one for each class, so that future messages to the same class and selector combination are executed quickly. Even this fast path is slower than for a statically bound function call but not by very much once the selector is cached; in reality, message dispatch is not the bottleneck in an application. If it were, you could drop down to writing a C function anyway and call that, passing in any state from the Objective-C object as required.
The preceding stands only for certain messages. Additional functions are exposed by the Objective-C runtime to handle certain edge cases:
objc_msgSend_stret
For sending messages that return a struct. This function can handle only messages that return a type that fits into CPU registers. If the return type doesn’t fit—for example, if a struct is returned—another function is called to perform the dispatch. In this case, another function is called to handle returning the struct via a stack-allocated variable.
objc_msgSend_fpret
For sending messages that return floating-point values. Some architectures require special handling of floating-point registers across function calls, meaning that the standard objc_msgSend is not good enough. This function exists to handle such slightly odd cases that crop up for such architectures as x86.
objc_msgSendSuper
For sending messages to the superclass, such as [super message:parameter]. There are also equivalents of objc_msgSend_stret and objc_msgSend_fpret for calling super as well.
I alluded earlier to the fact that objc_msgSend and friends “jump to” the correct method implementation once it has been looked up. The way this works is that every method of an Objective-C object can be thought of as a simple C function, whose prototype is of the following form:
<return_type> Class_selector(id self, SEL _cmd, ...)
The name of the function is not quite like this, but I’ve shown it as a combination of the class and the selector just to illustrate the point. Pointers to functions like this are held in a table within each class, keyed against the selector name. It’s this that the objc_msgSend family of methods looks through to find the implementation to jump to. Note that the prototype is strangely similar to the objc_msgSend function itself. This is no coincidence. It makes jumping to the method simpler and can make good use of tail-call optimizations.
Tail-call optimization occurs when the last thing a function does is call another function. Instead of pushing a new stack frame, the compiler can emit code to jump to the next function. This can be done only if the final thing a function does is call another function and does not need to use the return value for anything. Using this optimization is crucial for objc_msgSend because without it, the stack trace would show objc_msgSend right before every Objective-C method. Also, stack overflow would occur prematurely.
In reality, you do not need to worry about all this when writing Objective-C, but it is good to understand the fundamentals of what is going on under the hood. If you understand what happens when a message is invoked, you can appreciate how your code is executing and why you see objc_msgSend constantly in backtraces when debugging.
Things to Remember
A message consists of a receiver, a selector, and parameters. Invoking a message is synonymous with calling a method on an object.
When invoked, all messages go through the dynamic message dispatch system whereby the implementation is looked up and then run.
Item 12: Understand Message Forwarding
Item 11 explains why it’s important to understand the way messages are sent to objects. Item 12 explores why it’s important to understand what happens when a message is sent to an object that it doesn’t understand.
A class can understand only messages that it has been programmed to understand, through implementing methods. But it’s not a compile-time error to send a message to a class that it doesn’t understand, since methods can be added to classes at runtime so the compiler has no way of knowing whether a method implementation is going to exist. When it receives a method that it doesn’t understand, an object goes through message forwarding, a process designed to allow you as the developer to tell the message how to handle the unknown message.
Even if you are unaware of it, you will very likely already have encountered messages going through the message-forwarding pathways. Every time you’ve seen a message such as the following in the console, it’s because you’ve sent a message to an object that it doesn’t understand and the default NSObject implementation of the forwarding path has kicked in:
-[__NSCFNumber lowercaseString]: unrecognized selector sent to
instance 0x87
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFNumber
lowercaseString]: unrecognized selector sent to instance 0x87'
This is an exception being thrown from NSObject’s method called doesNotRecognizeSelector: telling you that the receiver of the message is of type __NSCFNumber and that it doesn’t understand the selector lowercaseString. That’s not really surprising in this case, since NSNumber (__NSCFNumber is the internal class used in toll-free bridging—see Item 49—which is created when you allocate an NSNumber). In this case, the forwarding pathways ended in the application’s crashing, but you can hook into the forwarding pathways in your own classes to perform any desired logic instead of crashing.
The forwarding pathways are split into two avenues. The first gives the class to which the receiver belongs a chance to dynamically add a method for the unknown selector. This is called dynamic method resolution. The second avenue is the full forwarding mechanism. If the runtime has made it this far, it knows that there’s no longer a chance for the receiver itself to respond to the selector. So it asks the receiver to try to handle the invocation itself. It does this in two steps. First, it asks whether another object should receive the message instead. If there is, the runtime diverts the message, and everything proceeds as normal. If there is no replacement receiver, the full forwarding mechanism is put into effect, using the NSInvocation object to wrap up the full details about the message that is currently unhandled and gives the receiver one final chance to handle it.
Dynamic Method Resolution
The first method that’s called when a message is passed to an object that it doesn’t understand is a class method on the object’s class:
+ (BOOL)resolveInstanceMethod:(SEL)selector
This method takes the selector that was not found and returns a Boolean to indicate whether an instance method was added to the class that can now handle that selector. Thus, the class is given a second opportunity to add an implementation before proceeding with the rest of the forwarding mechanism. A similar method, called resolveClassMethod:, is called when the unimplemented method is a class method rather than an instance method.
Using this approach requires the implementation of the method to already be available, ready to plug in to the class dynamically. This method is often used to implement @dynamic properties (see Item 6), such as occurs in CoreData for accessing properties of NSManagedObjects, since the methods required to implement such properties can be known at compile time.
Such an implementation of resolveInstanceMethod: for use with @dynamic properties might look like this:
id autoDictionaryGetter(id
self, SEL _cmd);
void autoDictionarySetter(id
self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ( /* selector is from a @dynamic property */ ) {
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self,
selector,
(IMP)autoDictionarySetter,
"v@:@");
} else {
class_addMethod(self,
selector,
(IMP)autoDictionaryGetter,
"@@:");
}
return
YES;
}
return [super resolveInstanceMethod:selector];
}
The selector is obtained as a string and then checked to see whether it looks like a setter. If it is prefixed with set, it is assumed to be a setter; otherwise, it is assumed to be a getter. In each case, a method is added to the class for the given selector pointing to an implementation defined as a pure C function. In these C functions there would be code to manipulate whatever data structure was being used by the class to store the properties’ data. For example, in the case of CoreData, these methods would talk to the database back end to retrieve or update values accordingly.
Replacement Receiver
The second attempt to handle an unknown selector is to ask the receiver whether a replacement receiver is available to handle the message instead. The method that handles this is:
- (id)forwardingTargetForSelector:(SEL)selector
The unknown selector is passed in, and the receiver is expected to return the object to act as its replacement or nil if no replacement can be found. This method can be used to provide some of the benefits of multiple inheritance by combining its use with composition. An object could own a range of other objects internally that it returns in this method for selectors that they handle, making it look as though it is itself handling them.
Note that there is no way to manipulate the message using this part of the forwarding path. If the message needs to be altered before sending to the replacement receiver, the full forwarding mechanism must be used.
Full Forwarding Mechanism
If the forwarding algorithm has come this far, the only thing left to do is to apply the full forwarding mechanism. This starts by creating an NSInvocation object to wrap up all the details about the message that is left unhandled. This object contains the selector, target, and arguments. An NSInvocation object can be invoked, which causes the message-dispatch system to whir into action and dispatch the message to its target.
The method that gets called to attempt forwarding this way is:
- (void)forwardInvocation:(NSInvocation*)invocation
A simple implementation would change the target of the invocation and invoke it. This would be equivalent to using the replacement receiver method, so such a simple implementation is rarely used. A more useful implementation would be to change the message in some way before invoking it, such as appending another argument or changing the selector.
An implementation of this method should always call its superclass’s implementation for invocations it doesn’t handle. This means that once all superclasses in the hierarchy have been given a chance to handle the invocation, NSObject’s implementation will be called. This invokes doesNotRecognizeSelector: to raise the unhandled selector exception.
The Full Picture
The process through which forwarding is handled can be described by a flow diagram like that shown in Figure 2.2.
Figure 2.2 Message forwarding
At each step, the receiver is given a chance to handle the message. Each step is more expensive than the one before it. The best scenario is that the method is resolved at the first step, since the method that was resolved will end up being cached by the runtime such that forwarding does not have to kick in at all next time the same selector is invoked on an instance of the same class. At the second step, forwarding a message to another receiver is simply an optimization of the third step for the case in which a replacement receiver can be found. In that case, the only thing that needs to be changed about the invocation is the target, which is much simpler to do than the final step, in which a complete NSInvocation needs to be created and handled.
Full Example of Dynamic Method Resolution
To illustrate how forwarding can be useful, the following example shows the use of dynamic method resolution to provide @dynamic properties. Consider an object that allows you to store any object in it, much like a dictionary, but provides access through properties. The idea of the class will be that you can add a property definition and declare it @dynamic, and the class will magically handle storing and retrieving that value. That would be pretty fantastic, right?
The interface for the class will be like this:
#import <Foundation/Foundation.h>
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;
@end
It doesn’t particularly matter for this example what the properties are. In fact, I’ve shown a variety of types just to illustrate the power of this feature. Internally, the values for each property will be held in a dictionary, so the start of the implementation of this class would look like the following, including declaring the properties as @dynamic such that instance variables and accessors are not automatically created for them:
#import "EOCAutoDictionary.h"
#import <objc/runtime.h>
@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation EOCAutoDictionary
@dynamic string, number, date, opaqueObject;
- (id)init {
if ((self = [super
init])) {
_backingStore = [NSMutableDictionary
new];
}
return
self;
}
Then comes the fun part: the resolveInstanceMethod: implementation:
+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self,
selector,
(IMP)autoDictionarySetter,
"v@:@");
} else {
class_addMethod(self,
selector,
(IMP)autoDictionaryGetter,
"@@:");
}
return
YES;
}
@end
The first time it encounters a call to a property on an instance of EOCAutoDictionary the runtime will not find the corresponding selector, since they’re not implemented directly or synthesized. For example, if the opaqueObject property is written, the preceding method will be invoked with a selector of setOpaqueObject:. Similarly, if the property is read, it will be invoked with a selector of opaqueObject. The method detects the difference between set and get selectors by checking for a prefix of set. In each case, a method is added to the class for the given selector pointing to a function called either autoDictionarySetter or autoDictionaryGetter, as appropriate. This makes use of the runtime method class_addMethod, which adds a method dynamically to the class for the given selector, with the implementation given as a function pointer. The final parameter in this function is the type encoding of the implementation. The type encoding is made up from characters representing the return type, followed by the parameters that the function takes.
The getter function would then be implemented as follows:
id autoDictionaryGetter(id self, SEL _cmd) {
// Get the backing store from the object
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// The key is simply the selector name
NSString *key = NSStringFromSelector(_cmd);
// Return the value
return [backingStore objectForKey:key];
}
Finally, the setter function would be implemented like this:
void autoDictionarySetter(id self, SEL _cmd, id value) {
// Get the backing store from the object
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
/** The selector will be for example, "setOpaqueObject:".
* We need to remove the "set", ":" and lowercase the first
* letter of the remainder.
*/
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
// Remove the ':' at the end
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
// Remove the 'set' prefix
[key deleteCharactersInRange:NSMakeRange(0, 3)];
// Lowercase the first character
NSString *lowercaseFirstChar =
[[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1)
withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
} else {
[backingStore removeObjectForKey:key];
}
}
Using EOCAutoDictionary is then a simple matter of the following:
EOCAutoDictionary *dict = [EOCAutoDictionary
new];
dict.date = [NSDate
dateWithTimeIntervalSince1970:475372800];
NSLog(@"dict.date = %@", dict.date);
// Output: dict.date = 1985-01-24 00:00:00 +0000
The other properties on the dictionary could be accessed just like the date property, and new properties could be introduced by adding a @property definition and declaring it @dynamic. A similar approach is employed by CALayer, part of the CoreAnimation framework on iOS. This approach allows CALayer to be a key-value-coding-compliant container class, meaning that it can store a value against any key. CALayer uses this ability to allow the addition of custom animatable properties whereby the storage of the property values is handled directly by the base class, but the property definition can be added in a subclass.
Things to Remember
Message forwarding is the process that an object goes through when it is found to not respond to a selector.
Dynamic method resolution is used to add methods to a class at runtime as and when they are used.
Objects can declare that another object is to handle certain selectors that it doesn’t understand.
Full forwarding is invoked only when no previous way of handling a selector has been found.
Item 13: Consider Method Swizzling to Debug Opaque Methods
The method to call when a message is sent to an object in Objective-C is resolved at runtime, as explained in Item 11. It might then occur to you that perhaps the method invoked for a given selector name could be changed at runtime. You’d be correct. This feature can be used to great advantage, as it can be used to change functionality in classes for which you don’t have the source code, without having to subclass and override methods. Thus, the new functionality can be used by all instances of the class rather than only instances of the subclass with overridden methods. Such an approach is often referred to as method swizzling.
A class’s method list contains a list of selector names to implementation mappings, telling the dynamic messaging system where to find the implementation of a given method. The implementations are stored as function pointers called IMPs and have the following prototype:
id (*IMP)(id, SEL, ...)
The NSString class responds to selectors called lowercaseString, uppercaseString, and capitalizedString, among others. Each selector points to a different IMP, making up a table like the one shown in Figure 2.3.
Figure 2.3 NSString’s selector table
This table can be manipulated by using a few different functions exposed by the Objective-C runtime. You can add selectors to the list, change the implementation pointed to for a given selector, or swap the implementation pointed to by two selectors. After performing a few of these operations, the class’s method table might look something like Figure 2.4.
Figure 2.4 NSString’s selector table after performing a few operations on it
A new selector called newSelector has been added, the implementation of capitalizedString has been changed, and the implementations of lowercaseString and uppercaseString have been swapped. All this can be done without writing a single subclass, and the new method table layout will be used for each instance of NSString in the application. A very powerful feature, I’m sure you’ll agree!
The topic of this item refers to the process of exchanging implementations. In doing so, additional functionality can be added to a method. However, before explaining how it can be used to add functionality, I will explain how to simply swap two existing method implementations. To exchange implementations, you use the following function:
void method_exchangeImplementations(Method m1, Method m2)
This function takes as its arguments the two implementations to exchange, which can be obtained by using the following function:
Method class_getInstanceMethod(Class aClass, SEL aSelector)
This function retrieves a method from a class for the given selector. To swap the implementations of lowercaseString and uppercaseString as in the preceding example, you would perform the following:
Method originalMethod =
class_getInstanceMethod([NSString
class],
@selector(lowercaseString));
Method swappedMethod =
class_getInstanceMethod([NSString
class],
@selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
From then on, whenever an NSString instance has lowercaseString called on it, the original implementation of uppercaseString will be invoked and vice versa:
NSString *string = @"ThIs iS tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
NSLog(@"lowercaseString = %@", lowercaseString);
// Output: lowercaseString = THIS IS THE STRING
NSString *uppercaseString = [string uppercaseString];
NSLog(@"uppercaseString = %@", uppercaseString);
// Output: uppercaseString = this is the string
That explains how to exchange method implementations, but in reality, simply swapping two implementations like that is not very useful. After all, there’s a good reason why the implementation for uppercaseString and lowercaseString do what they do! There’s no reason why you’d want to swap them. But the same approach can be used to add functionality to an existing method implementation. What if you wanted to log something every time lowercaseString was called? The same approach can be used to achieve just that. It involves adding another method that implements the additional functionality and then calls through to the original implementation.
The additional method can be added using a category, like so:
@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;
@end
This method is going to be swapped with the original lowercaseString method, so the method table will end up looking like the one in Figure 2.5.
Figure 2.5 Swapping the implementations of lowercaseString and eoc_myLowercaseString
The implementation of this new method would look like this:
@implementation NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString {
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
}
@end
This might look like a recursive call, but remember that the implementations are going to be swapped. So at runtime, when the eoc_myLowercaseString selector is looked up, it’s the implementation of lowercaseString that gets called. Finally, to swap the method implementations, the following is used:
Method originalMethod =
class_getInstanceMethod([NSString class],
@selector(lowercaseString));
Method swappedMethod =
class_getInstanceMethod([NSString class],
@selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
From then on, whenever any NSString has lowercaseString called on it, the log line will be printed out:
NSString *string = @"ThIs iS tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
// Output: ThIs iS tHe StRiNg => this is the string
Being able to add in logging like this to methods that are completely opaque to you can be a very useful debugging feature. However, this is usually useful only for debugging. Rarely will you find a need other than debugging to perform method swizzling like this to alter functionality of a class globally. Don’t feel that you should use such a feature just because you can. Overuse can easily lead to code that is difficult to read and unmaintainable.
Things to Remember
Method implementations for a given selector of a class can be added and replaced at runtime.
Swizzling is the process of swapping one method implementation for another, usually to add functionality to the original implementation.
Meddling with methods through the runtime is usually good only for debugging and should not be used just because it can.
Item 14: Understand What a Class Object Is
Objective-C is extremely dynamic in nature. Item 11 explains how the implementation for a given method call is looked up at runtime, and Item 12 explains how forwarding works when a class does not immediately respond to a certain selector. But what about the receiver of a message: the object itself? How does the runtime know what type an object is? The type of an object is not bound at compile time but rather is looked up at runtime. Moreover, a special type, id, can be used to denote any Objective-C object type. In general, though, you specify a type whenever possible so that the compiler can warn about sending messages that it thinks the receiver doesn’t understand. Conversely, any object that is of type id will be assumed to respond to all messages.
As you’ll know from Item 12, though, the compiler cannot actually know all the selectors a certain type understands, since they can be dynamically inserted at runtime. However, even if this technique is used, the compiler expects to see the method prototype defined in a header somewhere such that it can know the full method signature to be able to emit the correct code to perform the message dispatch.
Inspecting the type of an object at runtime is known as introspection and is a powerful and useful feature baked into the Foundation framework as part of the NSObject protocol, to which all objects that inherit from the common root classes (NSObject and NSProxy) conform. Using these methods rather than directly comparing classes of objects is prudent, as I will explain. However, before looking at introspection techniques, here is some background as to what an Objective-C object is.
Every Objective-C object instance is a pointer to a blob of memory. That’s why you see the * next to the type when declaring a variable:
NSString *pointerVariable = @"Some string";
If you’ve come from a C world of programming, you’ll understand exactly what this means. For the non-C programmers among you, this means that pointerVariable is a variable holding a memory address, where the data stored at that memory address is the NSString itself. The variable therefore “points to” the NSString instance. This is how all Objective-C objects are referred to; if you tried to allocate the memory for an object on the stack instead, you would receive an error from the compiler:
NSString stackVariable = @"Some string";
// error: interface type cannot be statically allocated
The generic object type, id, is already a pointer in itself, so you use it like so:
id genericTypedString = @"Some string";
This definition is semantically the same as if the variable were of type NSString*. The only difference with specifying the type fully is that the compiler can help and warn if you attempt to call a method that doesn’t exist for instances of that class.
The data structure behind every object is defined in the runtime headers along with the definition of the id type itself:
typedef
struct
objc_object {
Class isa;
} *id;
Therefore, each object contains as its first member a variable of type Class. This variable defines the class of the object and is often referred to as the “is a” pointer. For example, the object “is a” NSString. The Class object is also defined in the runtime headers:
typedef struct
objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const
char *name;
long version;
long info;
long instance_size;
struct
objc_ivar_list *ivars;
struct
objc_method_list **methodLists;
struct
objc_cache *cache;
struct
objc_protocol_list *protocols;
};
This structure holds metadata about the class, such as what methods instances of the class implement and what instance variables instances have. The fact that this structure also has an isa pointer as its first variable means that a Class is itself an Objective-C object. This structure also has another variable, called super_class, which defines the class’s parent. The type of a class (i.e., the class the isa pointer points to) is another class, known as the metaclass, which is used to describe the metadata about instances of the class itself. This is where class methods are defined, since they can be thought of as instance methods of instances of a class. There is only ever one instance of a class, though, and only one instance of its associated metaclass.
The hierarchy of a class called SomeClass that inherits from NSObject looks like the one shown in Figure 2.6.
Figure 2.6 Class hierarchy for instances of SomeClass, which inherits from NSObject, including the metaclass hierarchy
The super_class pointer creates the hierarchy, and the isa pointer describes the type of an instance. You can manipulate this layout to perform introspection. You can find out whether an object responds to a certain selector and conforms to a certain protocol and determine information about what part of the class hierarchy the object belongs to.
Inspecting the Class Hierarchy
The introspection methods can be used to inspect the class hierarchy. You can check whether an object is an instance of a certain class by using isMemberOfClass: or whether an object is an instance of a certain class or any class that inherits from it by using isKindOfClass:. For example:
NSMutableDictionary *dict = [NSMutableDictionary
new];
[dict isMemberOfClass:[NSDictionary
class]]; ///< NO
[dict isMemberOfClass:[NSMutableDictionary
class]]; ///< YES
[dict isKindOfClass:[NSDictionary
class]]; ///< YES
[dict isKindOfClass:[NSArray
class]]; ///< NO
This kind of introspection works by using the isa pointer to obtain the object’s class and then walking the inheritance hierarchy, using the super_class pointer. Given the dynamic nature of objects, this feature is extremely important. You cannot ever fully know the type without introspection, unlike other languages with which you might be familiar.
Introspection of the class of an object is extremely useful given the dynamic typing used by Objective-C. Introspection is commonly used when retrieving objects from collections, since they are not strongly typed, meaning that when objects are retrieved from collections, they are usually of type id. Introspection can then be used if the type needs to be known: for example, when needing to generate a comma-separated string from objects stored in an array to be saved to a text file. The following code could be used in that scenario:
- (NSString*)commaSeparatedStringFromObjects:(NSArray*)array {
NSMutableString *string = [NSMutableString
new];
for (id object in array) {
if ([object isKindOfClass:[NSString
class]]) {
[string appendFormat:@"%@,", object];
} else if ([object isKindOfClass:[NSNumber
class]]) {
[string appendFormat:@"%d,", [object intValue]];
} else if ([object isKindOfClass:[NSData
class]]) {
NSString *base64Encoded = /* base64 encoded data */;
[string appendFormat:@"%@,", base64Encoded];
} else {
// Type not supported
}
}
return string;
}
It is also possible to check class objects for equality. You can do so by using the == operator rather than an isEqual: method, as you would usually use when comparing Objective-C objects (see Item 8). The reason is that classes are singletons, and so only a single instance of each class’s Class object is within an application. Thus, another way to test whether an object is exactly an instance of a class would be to do the following:
id object = /* ... */;
if ([object class] == [EOCSomeClass
class]) {
// 'object' is an instance of EOCSomeClass
}
However, you should always prefer the introspection methods to direct equality of class object, because the introspection methods are able to take into account objects that make use of message forwarding (see Item 12). Consider an object that forwards all selectors to another. Such an object is called a proxy, and NSProxy is a root class specifically for objects like that.
Usually, such proxy objects will return the proxy class (i.e., subclass of NSProxy) if the method class is called rather than the class of the object being proxied. However, if introspection methods, such as isKindOfClass:, are called on them, they will proxy the message through to the proxied object. This means that the answer to the message will be as though the proxied object is being inspected. Therefore, this will yield a result different from inspecting the class object returned from calling the class method, since that will be the proxy class itself rather than the class of the proxied object.
Things to Remember
The class hierarchy is modeled through Class objects that each instance has a pointer to in order to define its type.
Introspection should be used when the type of the object cannot be known for certain at compile time.
Always prefer introspection methods where possible, rather than direct comparison of class objects, since the object may implement message forwarding.