Language Features - iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

Chapter 14. Language Features

With the introduction of Objective-C 2.0 in 2006, continued support for Clang and LLVM compiler enhancements, and Xcode 4 in 2011, many useful new features have been added that enhance code readability and reduce the amount of code needed to meet a requirement. This chapter highlights some of the most useful new features that have been added, and some features that have been around a while that are not as well known, including the following:

Image Literals, which make declaring numbers, arrays, and dictionaries as easy and simple to read as string declarations.

Image Automatic Reference Counting (ARC), which provides an automated way to handle memory management, without many of the drawbacks of garbage collection or manual memory management. Using ARC can reduce the amount of code needed in a project and help avoid common memory management issues.

Image Properties, which have been enhanced to the point that declaring instance variables for a class is rarely necessary. The compiler can generate accessor methods that respect memory management, read/write, and threading settings.

Image Blocks, which are anonymous functions that can be passed around like parameters and can make use of variables in their local scope. These are very effective tools for simplifying code and improving code readability. Many of the iOS APIs have added support for blocks.

Image Fast enumeration, which provides a simple, readable approach to iterating over a collection, and performing actions on each item in a collection.

Image Method swizzling, which allows the developer to dynamically swap out method implementations at runtime. This can be used to enhance existing functionality when the source code is not available, and when neither subclassing nor using categories is a good option.

Literals

When Xcode 4.4 was released in July of 2012, the compiler was updated to add support for several new types of literal syntax that make declaring and using NSNumber, NSArray, and NSDictionary much simpler and much more readable. The new syntax follows the lead ofNSString literals by using the @ prefix with syntactical elements to define Objective-C objects:

NSString *aString = @"This is a string.";

Now, an @ symbol followed by numbers, brackets, curly braces, or parentheses has special meaning and represents an Objective-C object:

NSNumber *integerNumber = @45;
NSArray *workArray = @[aString, floatNumber, integerNumber];
NSDictionary *workDict = @{ @"float": floatNumber,
@"integer" : integerNumber,
@"array" : workArray };
NSNumber *radiansPerDegree = @(M_PI / 180);

In addition, new syntax has been added to make accessing elements in arrays or dictionaries simpler.

NSString *myString = workArray[0];
NSNumber *myNumber = workDict[@"float"];

NSNumber

Previously, to declare and instantiate an NSNumber, a developer had to either alloc/init or use a class method with a scalar like so:

NSNumber *floatNumber = [NSNumbernumberWithFloat:3.14159];
NSNumber *doubleNumber = [NSNumbernumberWithDouble:3.14159];

NSNumber *integerNumber = [NSNumbernumberWithInteger:45];

NSNumber *unsignedIntegerNumber =
[NSNumbernumberWithUnsignedInteger:45U];

NSNumber *longNumber = [NSNumbernumberWithLong:1234567L];

NSNumber *longLongNumber =
[NSNumbernumberWithLongLong:1234567890LL];

NSNumber *boolNumber = [NSNumbernumberWithBool:YES];
NSNumber *charNumber = [NSNumbernumberWithChar:'Q'];

With the new syntax, this is the equivalent of using the class methods on NSNumber:

NSNumber *floatNumber = @3.14159F;
NSNumber *doubleNumber = @3.14159;

NSNumber *integerNumber = @45;
NSNumber *unsignedIntegerNumber = @45U;
NSNumber *longNumber = @1234567L;
NSNumber *longLongNumber = @1234567890LL;

NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'Q';

NSArray

To declare and instantiate an NSArray with a known set of elements previously, a class method or equivalent alloc/init needed to be used:

NSArray *workArray = [NSArrayarrayWithObjects: aString,
floatNumber, integerNumber, nil];

With the new syntax, declaring and instantiating an NSArray is much simpler and less verbose, and the nil terminator is no longer required.

NSArray *workArray = @[aString, floatNumber, integerNumber];

Be sure to note, however, that putting a nil object in the brackets either will cause a warning if the compiler can detect that the value will be nil at compile time, or will generate a runtime exception.

In addition to supporting a new approach to instantiating arrays, a new method of accessing and updating objects in an array is now supported. The previous approach was to call a method on NSArray to access an element.

NSString *myString = [workArray objectAtIndex:0];

Now C-style subscripting is supported to access elements in an array.

NSString *myString = workArray[0];

This type of subscripting also works for setting elements in a mutable array.

mutableArray[0] = @"Updated Element";

These C-style subscripting methods to access array elements are supported by the compiler by translating the expressions into method calls. The objectAtIndexedSubscript: method is how the accessor subscript is implemented.

NSString *myString = [workArray objectAtIndexedSubscript:0];

The mutator subscript is implemented with the setObject:atIndexedSubscript: method.

[mutableArray setObject:@"Updated Element" atIndexedSubscript:0];

NSDictionary

To declare and instantiate an NSDictionary with a known set of keys and values previously, a class method or equivalent alloc/init needed to be used:

NSDictionary *workDict =
[NSDictionarydictionaryWithObjectsAndKeys:@"float",
floatNumber, @"integer", integerNumber, @"array",
workArray, nil];

With the new syntax, declaring and instantiating an NSDictionary is much simpler, much less verbose, and easier to read:

NSDictionary *workDict = @{ @"float": floatNumber,
@"integer" : integerNumber,
@"array" : workArray };

Be sure to note, however, that putting a nil object as either a key or a value either will cause a warning if the compiler can detect that the value will be nil at compile time, or will generate a runtime exception.

In addition to supporting a new method to instantiate dictionaries, a new method of accessing and updating objects in a dictionary is now supported. The previous approach was to call a method on NSDictionary to access an element by specifying a key.

NSNumber *myNumber = [workDict objectForKey:@"float"];

Now elements in a dictionary can be accessed using the key as a subscript.

NSNumber *myNumber = workDict[@"float"];

Elements in a mutable dictionary can also be set using a key as a subscript.

workDict[@"float"] = @3.14159F;

These subscripting methods are supported by the compiler by translating the expressions into method calls. The objectForKeyedSubscript: method is how the accessor subscript is implemented.

NSNumber *myNumber = [workArray objectForKeyedSubscript:0];

The mutator subscript is implemented with the setObject:forKeyedSubscript: method.

[mutableDict setObject:@12.345F forKeyedSubscript:@"float"];

Boxed Expressions

The compiler also supports boxed C expressions, including scalars, enums, and C string pointer types. A boxed expression will convert the result of the expression contained in parentheses into an appropriate Objective-C object.

NSNumber *radiansPerDegree = @(M_PI / 180);
NSNumber *myFavButtonType = @(UIButtonTypeInfoDark);

char *cString = "The quick brown fox...";
NSString *stringFromCString = @(cString);

Automatic Reference Counting

Automatic Reference Counting (ARC) is LLVM compiler support for automatically handling memory management for Objective-C objects. Instead of the developer calling retain, release, and autorelease methods for objects created in a project, the compiler will calculate the lifetime of each object created and automatically insert the appropriate retain, release, and autorelease calls as necessary. ARC can completely avoid the error-prone and tedious manual approach to memory management, while reducing the amount of code needed and potentially even make code perform faster. ARC code can also work seamlessly with non-ARC code; for example, if a third-party library that a project depends on is not using ARC, the project can still be updated to take advantage of ARC without updating the third-party library. ARC has matured to the point that Apple recommends using it on all new projects.

Using ARC in a New Project

In Xcode 5, ARC is the default for all new projects. For Xcode 4.6 holdouts, new projects must be enabled for ARC by selecting the option when creating it using Xcode 4.6, as shown in Figure 14.1.

Image

Figure 14.1 Xcode 4 New Project dialog displaying checked Use Automatic Reference Counting option.

Converting an Existing Project to ARC

Existing projects written without ARC can be converted to use ARC by selecting Edit, Refactor, Convert to Objective-C ARC from the menu in Xcode, as shown in Figure 14.2.

Image

Figure 14.2 Xcode menu, Convert to Objective-C ARC.

Xcode will ask which targets should be converted to ARC, using a dialog as shown in Figure 14.3.

Image

Figure 14.3 Xcode Convert to Objective-C ARC, Select Targets to Convert dialog.

After you have selected the targets to be converted to ARC and clicked Check, Xcode will examine the code in the target to determine whether there will be any issues in converting to ARC. If there are problems, Xcode will present an error dialog, and any errors with the code preventing conversion to ARC will be displayed in Xcode’s Issue Navigator. Resolve any outstanding issues and rerun the check process by selecting Convert to Objective-C ARC from Xcode’s Edit, Refactor menu, and repeating the steps described. After the issues are all resolved, Xcode will display a dialog explaining the steps that will be taken to convert the project to ARC, as shown in Figure 14.4.

Image

Figure 14.4 Xcode Convert to Automatic Reference Counting dialog.

After you click Next, Xcode will preview the changes that need to be made to convert the project to ARC, as shown in Figure 14.5. Xcode will display each file that will require changes, and each specific change that will be needed. This preview highlights how the amount of code in the project will be reduced.

Image

Figure 14.5 Xcode Source Comparison View.

Clicking Save will make the recommended changes to the project.

A project can be set to use ARC manually by specifying a build setting, as shown in Figure 14.6. After this setting is changed, the compiler will issue errors for any non-ARC issues found in the project, and it is up to the developer to address those manually.

Image

Figure 14.6 Xcode Build Settings for Objective-C Automatic Reference Counting.

Basic ARC Usage

There are new coding rules and guidelines to follow for projects using ARC. The compiler will enforce the ARC-specific rules, and generate warnings or errors as appropriate:

Image Create instances of variables and properties as normal, using alloc/init, new, copy, or class methods.

Image Using retain, release, and autorelease is not allowed. The compiler will automatically insert retain, release, and autorelease for objects allocated in project code at compile time. Note that the inserted statements are not visible.

Image Use qualifiers for properties (described in more detail in the “Properties” section later in the chapter) or variables to specify ownership and lifecycle. ARC qualifiers, described in the next subsection, tell the compiler when a retain is needed to indicate ownership, or when an object is owned somewhere else and a retain is not needed. The qualifiers help ARC understand the intended life cycle of an object so that the compiler can properly release and/or nil an object pointer when it is no longer needed.

Image Avoid dealloc. The compiler will create dealloc methods automatically as needed, and dealloc must not be called directly by code. Custom dealloc methods can be created to handle special situations or objects not handled directly by ARC; however,[super dealloc]cannot be called from those custom dealloc methods.

Image Use @autorelease blocks instead of NSAutoreleasePool. Blocks are described in more detail later in the chapter.

Image When using or interacting with Core Foundation objects, use the new qualifiers __bridge, __bridge_retain, __bridge_transfer to indicate ownership intent.

ARC Qualifiers

There are four ARC qualifiers for object ownership that tell the compiler how to handle each object. The qualifiers are specified either implicitly (__strong is the default) or explicitly when declaring an object.

Image __autoreleasing: autoreleasing is not the same as autorelease; rather, it is used for passing an object by reference, as in this example:

__autoreleasing NSError *error = nil;

self.listArray = [moc executeFetchRequest:fetchReq
error:&error];

Image __strong: strong is the default, and it indicates that the object should be retained for the lifetime of its current scope. The object will be set to nil before it is destroyed.

Image __weak: weak indicates that the object should not be retained. The compiler will automatically set the object to nil before it is destroyed.

Image __unsafe_unretained: Same as weak, but the object is not set to nil before it is destroyed.

Declaring properties have different qualifiers, which each imply one of the four ARC qualifiers as described in the “Properties” section later in the chapter.

Blocks

Blocks are a new C language feature, made available in Objective-C and in iOS starting with version 4.0. Since then, Apple has added support for blocks in significant portions of the iOS SDK. Blocks are anonymous functions, which can accept parameters and return a value, and are able to capture the state of the runtime when they are executed. In addition, blocks are actually Objective-C objects, so they can be passed around as parameters; can be stored in Foundation arrays, dictionaries, and sets; and can take advantage of manual memory management or ARC. Last but not least, blocks can modify objects in the same lexical scope. All that adds up to the capability for blocks to make previously cumbersome programming patterns simple and readable.

In practice, blocks are widely used to replace delegates and completion handlers. Rather than setting up a property for a delegate, adding a protocol definition, implementing delegate methods, and devising methods to pass state variables to the delegate methods, you can implement delegate logic in a block.

Declaring and Using Blocks

One method of using blocks is to create a block variable, which can be assigned a block and then executed inline in code. A block variable declaration can have a name, can declare parameter types, and can optionally declare a return type (the compiler can infer the return type, but it is good coding form to declare it).

NSString* (^repeatString) (int, NSString*);

In this case, NSString* at the left of the line of code declares the return type. In parentheses, after the caret symbol (^), repeatString is the name of the declared block. The second set of parentheses declares that two parameters are required for the block, first an int and second anNSString*. After these are declared, the block can be assigned using a block literal, indicated by the caret symbol.

repeatString = ^NSString* (int numTimes, NSString *repeat)
{
NSMutableString *retString = [[NSMutableString alloc] init];

for (int i=0; i<numTimes; i++)
{
[retString appendString:repeat];
}
return [NSString stringWithString:retString];
};

The block literal requires restating the return type after the caret symbol, and then naming the parameters that must match the declaration. The block code is specified between brackets, and a semicolon after the final bracket indicates that the assignment of the block variable is complete. After the block variable is assigned, it can be used in code just like a function.

NSString *repeatTest = repeatString(5,@"Test ");

NSLog(@"The result is: %@",repeatTest);

This will log the following result:

The result is: Test Test Test Test Test

Block definitions can also be established using a typedef for simplicity and clarity:

typedefvoid (^MyCompletionBlock)(id result, NSError *error);

After a block has a type definition, it can be used to create a block variable or in method declarations.

Capturing State with Blocks

When assigning a block literal, variables from the current lexical scope can be used in the block. An important characteristic of this use is that, by default, the value of those variables is “captured” or locked to the value at the time when the block is assigned. Even if the value of that variable is changed before the block is executed, the value at the time of assignment is what is used in the block execution. This example modifies the previous example to use an NSString* from the enclosing scope in the block, rather than a parameter.

NSString *blockTestString = @"Block Test ";

NSString *(^repeatString) (int);

repeatString = ^NSString* (int numTimes)
{
NSMutableString *retString = [[NSMutableStringalloc] init];

for (int i=0; i<numTimes; i++)
{
[retString appendString:blockTestString];
}
return retString;
};

blockTestString = @"Block Test Change";

NSString *repeatTest = repeatString(5);

NSLog(@"The result is: %@",repeatTest);

This approach will log the following:

The result is: Block Test Block Test Block Test Block Test Block Test

If the block needs to be able to use the updated value of the variable, or needs to update the variable directly, that variable can be declared with the __block modifier to indicate that blocks should access the variable directly rather than capturing its value at the time of block assignment. To modify the previous example to demonstrate the block using the current value of a variable from the enclosing scope, and the block updating that variable, first the variable is declared with the __block modifier.

__block NSString *blockTestString = @"Block Test ";

Then the block is modified to be return type void, and the block literal is changed to update the variable directly rather than return a value.

void (^repeatString) (int);

repeatString = ^ (int numTimes)
{
NSMutableString *retString = [[NSMutableString alloc] init];

for (int i=0; i<numTimes; i++)
{
[retString appendString:blockTestString];
}
blockTestString = retString;
};

After the block is defined and assigned, the blockTestString variable is changed, and then the block is executed.

blockTestString = @"Block Test Change ";

repeatString(5);

NSLog(@"The result is: %@",blockTestString);

This logs the following result, indicating that the blockTestString variable was used with the most recent value before the block was executed, and that the blockTestString string was changed by the block:

The result is: Block Test Change Block Test Change Block Test Change
Block Test Change Block Test Change

Having both approaches available gives the developer a great deal of flexibility in using blocks to achieve desired results.

Using Blocks as Method Parameters

Another method of using blocks is to use them as method parameters. A common use case for this is a completion handler for a long-running or asynchronous task. To use a block as a completion handler, first a method must be declared with a completion block as a parameter.

- (void)longRunningTaskWithCompletionHandler:
(void(^)(void))completionBlock
{
...
if (completionBlock) {
completionBlock();
}
}

The syntax might look a bit awkward, but the method declares that it should receive a block with no parameters and no return type as a parameter. When the method has completed, it will execute the passed-in completion block. This allows the caller to avoid having to set up separate delegate methods to handle the completion logic, and thus the caller can specify the starting and completion logic all in one place, making it easy to read and check.

[self.submitButton setEnabled:NO];
[self.progressIndicator setHidden:NO];
[self.progressIndicator startAnimating];

ICFViewController* __weak weakSelf = self;

[selflongRunningTaskWithCompletionHandler:^{
[weakSelf.submitButton setEnabled:YES];
[weakSelf.progressIndicator stopAnimating];
[weakSelf.progressIndicator setHidden:YES];
}];

In this example, a UIButton called submitButton and a UIActivityIndicatorView called progressIndicator have been set up as properties (described in the next section) in the class. When the Submit button is tapped, a method will disable the button to prevent additional submissions while the long-running task is running. The method will display the progressIndicator and start animation, and then call the long-running task. The completion handler block is assigned directly in the method call to the long-running task as a parameter, so all the completion logic is visible right where the long-running task method call is made. The completion logic will reenable the submitButton and hide the progressIndicator. Although this example is trivial, it should be evident that this pattern can be very useful in common situations, especially when handling error conditions locally.


Note

A subclass of NSOperation called NSBlockOperation has been added, which offers support for using a block in an operation queue. In addition, Grand Central Dispatch offers support for executing blocks on a specific queue. See Chapter 17, “Grand Central Dispatch for Performance,” for more information on using operation queues and Grand Central Dispatch.


Memory, Threads, and Blocks

When a block literal is assigned, memory for the block is automatically allocated on the stack. This memory takes a snapshot of the values of any variables from the enclosing scope used in the block, and captures a pointer to any variables that use the __block modifier. While the block exists on the stack, calling retain on that block will have no practical effect. To retain a block in memory either as a property on a class or as a parameter to a method call, the block must be copied, which will move it to the heap. After it’s on the heap, normal memory management rules apply.

Because blocks are initially allocated on the stack, there is a practical limit on the number of blocks that can be allocated at once. If blocks are created in a loop, it is possible to create so many blocks at once that the stack is “blown,” and a stack overflow condition can be created. If this situation arises, it can be addressed by copying the blocks to the heap as they are allocated.

One special memory management case to be aware of when using blocks is a retain cycle. Presume that a view controller has a custom button, which can be given a block. The view controller has a strong reference to the custom button.

@property(nonatomic,strong) IBOutlet ICFCustomButton *customButton;

The custom button has a property established for the block, which correctly specifies copy so that the block will be copied to the heap, and will be guaranteed to be available when the button needs it.

@property (nonatomic, copy) MyButtonBlock myButtonBlock;

Now presume that the view controller would like to update some UI elements when the button executes the block.

[self.customButton setMyButtonBlock:^(){
[self.submitButton setEnabled:YES];
[self.progressIndicator stopAnimating];
[self.progressIndicator setHidden:YES];
}];

Because the block contains a reference to the enclosing object and the block is copied to the heap, the block will by default retain the enclosing object. In that case it will not be able to be released and a retain cycle will be created. The compiler might pick up this situation and display a warning, as shown in Figure 14.7, but it is important to note that the compiler might not catch all possible retain-cycle situations.

Image

Figure 14.7 Xcode warning: potential retain cycle in block.

To prevent this problem, a weak reference to self can be created to be used by the block.

ICFViewController* __weak weakSelf = self;

[self.customButton setMyButtonBlock:^(){
[weakSelf.submitButton setEnabled:YES];
[weakSelf.progressIndicator stopAnimating];
[weakSelf.progressIndicator setHidden:YES];
}];

By declaring the reference to self to use __weak, the block will not retain self and the retain cycle will be avoided.

Lastly, when blocks are being used, it is important to understand which thread the block will be executed on. Depending on the consumer of the block, the block might be executed on the main thread or might be executed on a background thread. Not knowing which thread the logic is being executed on can lead to subtle and difficult-to-diagnose bugs. One technique is to put a breakpoint in the block during development to see which thread the block has been executed on, and adjust the block logic accordingly. Another technique is to write the block logic with no assumptions at all about which thread it is being executed on, and use Grand Central Dispatch to execute the block logic on the correct threads as necessary.

Properties

Properties were introduced with Objective-C 2.0. They provide a way to easily declare instance variables for a class, and have the compiler generate accessor methods automatically. In addition, they provide a way to declare ownership intent via qualifiers for both ARC and non-ARC classes; the compiler will generate the correct code for the qualifier specified.

Declaring Properties

Properties are declared in the interface for a class. They can be declared in the header (.h) file for external visibility, or in the implementation file (.m) for properties that should be visible only to the instance. Before properties were available, instance variables were declared like so:

@interface ICFViewController : UIViewController
{
IBOutlet UITextView *myTextView;
IBOutlet UILabel *myLabel;
UIImage *attachmentImage;
UIImagePickerController *picker;
}

In the implementation file, accessor methods needed to be written for each instance variable. This code is tedious and repetitive to write, and simple errors in accessor methods can have dire memory management consequences like leaks or crashes.

With properties, adding the equivalent instance variables and accessor methods is as simple as a declaration:

@property(nonatomic, strong) IBOutlet UITextView *myTextView;
@property(nonatomic, strong) IBOutlet UILabel *myLabel;
@property(nonatomic, strong) UIImage *attachmentImage;
@property(nonatomic, strong) UIImagePickerController *picker;

The compiler will create an instance variable and standard accessor methods according to the qualifiers specified.

There are six memory-related qualifiers that can be used for properties:

Image assign: assign means that the instance variable is not retained, and is not set to nil before destruction. It is semantically identical to __unsafe_unretained with ARC. This is the default for non-object properties; for example, c types like BOOL or int.

Image copy: copy will manage retaining and releasing the instance variable, and will set to nil before destruction. In addition, when the instance variable is being set, it will be copied instead of the pointer being directly assigned. Sets the instance variable to __strong ownership with ARC.

Image retain: retain will manage retaining and releasing the instance variable, and will set to nil before destruction for non-ARC projects. Although it’s not technically meant for usage with ARC (strong is the preferred keyword), using retain will set an instance variable to__strong ownership with ARC.

Image strong: strong will manage retaining and releasing the instance variable, and will set to nil before destruction. It sets the instance variable to __strong ownership with ARC. strong is the default for object properties.

Image weak: weak will not retain the instance variable, but will automatically set it to nil before destruction. It sets the instance variable to __weak ownership with ARC.

Image unsafe_unretained: unsafe_unretained will not retain the instance variable, and will not set it to nil before destruction. It sets the instance variable to __unsafe_unretained with ARC.

In addition to memory qualifiers, properties can have other qualifiers that drive behavior of the accessor methods:

Image readonly/readwrite: readwrite is the default, and tells the compiler to create both a getter and a setter method for the instance variable. The readonly qualifier will tell the compiler to generate only a getter method. There is not an option to generate only a setter method.

Image atomic/nonatomic: atomic is the default, and tells the compiler to add locks so that the property can be safely accessed by multiple threads simultaneously. If that locking mechanism is not needed, nonatomic can be specified to prevent the compiler from generating the locking code. In that case, the property cannot be safely accessed from multiple threads.

Synthesizing Properties

Formerly, properties needed to be synthesized in the implementation (.m) file for the compiler to generate the accessor methods, like so:

@synthesize myImage;

As of Xcode 4.4/LLVM 4.0, this is no longer necessary because the compiler will now automatically synthesize accessor methods. If a custom name is desired for the underlying instance variable, that can be accomplished using @synthesize.

@synthesize myImage = myImageCustomName;

Accessing Properties

If properties have been declared in the header file, they can then be accessed from other classes:

NSString *setString = @"Some string...";

ICFObject *anObject = [[ICFObject alloc] init];
[anObject setMyString:setString];

UIImage *anImage = [anObject myImage];

[anObject release];

The getter method for a property is simply the name of the property. The setter method for the property is “set” prefixed to the name of the property, in camel case. In the previous code example, the string property myString can be set using the method setMyString:.

To access a property internally, the keyword self can be used:

[self setMyString:@"A new string"];

UIImage *image = [self myImage];

The underlying instance variable can be accessed directly as well. Instance variables generated automatically are prefixed with an underscore. Special consideration to threading and memory management should be given when the instance variable is being accessed directly.

NSLog(@"My String is %@",myStringCustomName);

Dot Notation

Introduced with Objective-C 2.0, dot notation provides a simple mechanism for accessing properties that have accessor methods. To get the value of a property, use this:

UIImage *image = self.myImage;

To set the value of a property, use this:

self.myString = @"Another string";

Dot notation can be strung together to simplify accessing or updating values for nested objects.

self.myView.myLabel.text = @"Label Title";


Note

ARC memory qualifiers set on properties do not protect against retain cycles. A retain cycle is when two objects have strong references to each other, and the compiler is never able to release them. An example of this is a delegate relationship, in which a parent object holds a reference to a child object, and the child object keeps a reference to the parent object in order to call delegate methods. If both of the properties used to establish the relationship use the strong qualifier, there will be a retain cycle and the parent will never release the child properly, keeping the memory occupied unnecessarily.


Fast Enumeration

Fast enumeration was added to Objective-C in version 2.0. It is a feature to simplify enumerating the objects in a collection and performing activities on the enumerated objects. Before fast enumeration, there were two approaches to enumerating the objects in a collection. The first approach, which works with collection types such as NSSet, NSArray, and NSDictionary, is to use an NSEnumerator to iterate over the objects (or also keys for an NSDictionary) in a collection.

NSArray *myArray = @[@"Item One", @"Item Two", @"Item Three"];

NSEnumerator *enumerator = [myArray objectEnumerator];
NSString *arrayString;
while ((arrayString = [enumerator nextObject]) != nil)
{
NSLog(@"String is: %@",arrayString);
}

For an NSArray, a for loop can be constructed that will iterate over the indexes of an array instead of using an NSEnumerator.

NSArray *myArray = @[@"Item One", @"Item Two", @"Item Three"];

int startIndex = 0;
int endingIndex = [myArray count] - 1;

for (int counter = startIndex; counter <= endingIndex; counter++)
{
NSString *myString = [myArray objectAtIndex:counter];
NSLog(@"String is: %@",myString);
}

With fast enumeration, the amount of code needed to iterate over the elements in a collection can be significantly reduced.

NSArray *myArray = @[@"Item One", @"Item Two", @"Item Three"];

for (NSString *myString in myArray)
{
NSLog(@"String is: %@",myString);
}

The new for construct specifies an object for each iterated item to be placed in (NSString *myString in the example), and the collection to be enumerated. The object does not have to be a specific type; id can be used for cases in which the collection contains instances of different classes, which can then be examined in the for loop and handled appropriately.

In addition to the new for syntax introduced with Objective-C 2.0, block-based enumerator methods have been added to several of the collection classes such as NSSet, NSArray, and NSDictionary. These methods look similar to this example:

[myArray enumerateObjectsUsingBlock:
^(NSString *myString, NSUInteger index, BOOL *stop) {
NSLog(@"String is: %@",myString);
}];

The block will have access to the object, the index of the object, and a BOOL pointer that can be set to YES to stop iterating over the collection. In addition, some of the new methods support passing in options to iterate over an ordered collection in reverse order, or any collection concurrently.

Method Swizzling

The Objective-C runtime is flexible and provides support for dynamically changing object types and methods during execution of code. This support allows the developer to dynamically replace methods on existing classes, called method swizzling. Method swizzling can be used for a wide variety of use cases, but is typically most useful for replacing or augmenting existing methods on built-in classes where subclassing is not an option, or where using a category is not sufficient to achieve the desired results. For example, one project uses method swizzling to replace thesetTitle: method on the UIViewController class to intelligently set either the title label or a custom image. Another project uses method swizzling to test whether calls to UIKit methods are performed on the correct thread. Apple even uses method swizzling to implement key-value observing. Method swizzling can be used for performance testing, to bring out information that debugging and instruments cannot.

To implement method swizzling, select an existing method to replace or augment. Create a replacement method, and then instruct the Objective-C runtime to replace the implementation or swap implementations. Replacing the implementation will completely replace the existing implementation, and the original implementation will no longer be available. Swapping implementations will keep the original implementation available so that it can be called, much like calling super on an overridden method in a subclass.

As a contrived example, consider swizzling setTitle:forState: on UIButton. The swizzled method will automatically update all four control states for a button, and append the name of the control state to the title for visibility. Note that this could easily be achieved by subclassing the button and overriding that method, but this example shows how the same result can be achieved with method swizzling. The new method is added to a category on UIButton.

- (void)setButtonTitle:(NSString *)buttonTitle
forControlState:(UIControlState)controlState
{
NSString *normTitle =
[buttonTitle stringByAppendingString:@"-Normal"];

[self setButtonTitle:normTitle
forControlState:UIControlStateNormal];

NSString *selTitle =
[buttonTitle stringByAppendingString:@"-Selected"];

[self setButtonTitle:selTitle
forControlState:UIControlStateSelected];

NSString *highTitle =
[buttonTitle stringByAppendingString:@"-Highlighted"];

[self setButtonTitle:highTitle
forControlState:UIControlStateHighlighted];

NSString *disTitle =
[buttonTitle stringByAppendingString:@"-Disabled"];

[self setButtonTitle:disTitle
forControlState:UIControlStateDisabled];
}

An odd side effect of this approach to method swizzling is that calling the original implementation of the method from the new implementation requires using the new method name, which makes it look recursive. In reality, calling the new method name from the new method calls the original method implementation, much like calling super would from an overridden method in a subclass.

Another method is created in the same category to complete the actual method swizzle. Note that the Objective-C runtime library must be imported in order for the method-swapping functions to be recognized by the compiler.

#import <objc/runtime.h>

The method is called swizzleTitleMethod. It first gets a reference to the class and selector of the original method.

- (void)swizzleTitleMethod
{
Class thisClass = [self class];

SEL originalTitleSelector = @selector(setTitle:forState:);

...
}

The swizzleTitleMethod method then gets a reference to the method represented by the selector from the class definition. Note that this is just the method definition, not the actual implementation of the method.

Method originalTitleMethod =
class_getInstanceMethod(thisClass,originalTitleSelector);

Next, references for the new selector and method definition are obtained.

SEL newTitleSelector =
@selector(setButtonTitle:forControlState:);

Method newTitleMethod =
class_getInstanceMethod(thisClass, newTitleSelector);

Finally, the methods are swapped using a function from the Objective-C runtime library.

method_exchangeImplementations(originalTitleMethod,
newTitleMethod);

The swizzleTitleMethod method can be called at any point before it is needed to swap methods, and could even be reversed at some point during execution due to the dynamic nature of the Objective-C runtime.

If the original implementation of the method is not needed, the Objective-C runtime function class_replaceMethod can be used instead to completely replace the method on the class. Note that this function requires a reference to the actual implementation (IMP) of the new method.

Summary

This chapter covered several new features that have been introduced to iOS development since the introduction of Objective-C 2.0, LLVM, and Xcode 4.

New, much simpler literal syntax for NSNumber, NSArray, and NSDictionary instantiations was introduced. In addition, new syntax for accessing and updating elements of NSArray, NSMutableArray, NSDictionary, and NSMutableDictionary variables using subscripts was described. The new syntax can make code much shorter and more readable.

Automatic Reference Counting was discussed. The chapter covered how to create a new project to use ARC and how to convert an existing project to ARC. New memory management qualifiers and new coding rules required by ARC were explained. Proper use of ARC can reduce the amount of code needed in a project, improve memory management, and avoid memory leaks.

Blocks were introduced, with instructions on how to declare a block, how to create a block using a block literal, and how blocks are able to make use of surrounding state. Usage of blocks as parameters was described, as well as some memory management considerations specific to blocks.

Properties, which simplify declaration of instance variables and provide automatic accessor method generation, were described. New syntax for accessing properties using dot notation was demonstrated. Use of properties can greatly reduce the amount of code needed in a custom class, and proper use of qualifiers can standardize usage and avoid bugs in writing boilerplate code.

New fast enumeration capabilities, including new for-in syntax and block-based object iteration, were described. These capabilities simplify and greatly reduce the amount of code needed to iterate over a collection of objects.

Method swizzling, which is a technique for dynamically swapping method implementations at runtime, was introduced. Method swizzling can be used to augment or replace methods on built-in classes when subclassing or categorization are not sufficient.

All these techniques provide many opportunities to reduce the amount of code needed to meet requirements, improve code readability, prevent errors, and generally improve the quality of code.

Exercises

1. Find an old project using manual memory management and convert it to use ARC.

2. Use Xcode’s tool to convert an old project to use modern Objective-C syntax, by selecting Edit, Refactor, Convert to Modern Objective-C Syntax from the menu.