Chapter     16

Objective-C Literals

Since its inception, the Objective-C language has provided support for programming literals, notations that represent fixed values in the source code. Until recently, literal notation was confined to scalar types. In this chapter, you will learn all about one of the more recent additions to the language, Objective-C Literals, which provides support for object literals. You will learn their syntax, associated semantics, and guidelines for their usage. The chapter will also cover object subscripting, another recent addition to the language.

Literals

In computer-programming, a literal is a notation for representing a fixed value in source code. As its name implies, literal notation enables you to write, in code, the literal symbol for a datatype. This is important because without literal notation it would be very tedious and error-prone to write constant values in code. For example, consider the following assignment statement.

int luckyNumber = 7;

The symbol 7 represents literal notation for an integer value of seven. Without literal notation, you would have to construct the value of seven in code, perhaps as shown in Listing 16-1.

Listing 16-1.  Creating an Integer Value in Code Without Literals

static int zero;                             // Statics are initialized to 0
int one = zero++;
int two = one++;
int luckyNumber = (two * two) + two + one;   // 7 = 4 + 2 + 1

Having to go through all of this trouble to code values for simple datatypes would quickly discourage you from programming. Thus the advantages of literal notation are self-evident. Now this may seem pretty obvious to you. After all, don’t all programming languages support coding literal values? Well, in fact, the extent of support for literal notation differs among the various languages.

Traditionally, the Objective-C language has provided support for literal notation of primitive/scalar types (i.e., int, float, Boolean types), along with text strings (NSString instances). Objective-C Literals enable the creation of object literals; specifically, NSNumber literals, collection literals, (boxed) expression literals, and (boxed) C-string literals. In the next few sections, you will learn how to use these object literals in code.

NSNumber Literals

You may recall that Chapter 10 provided a brief overview of NSNumber literals. To recap, the Foundation Framework class NSNumber is a container for primitive (i.e., scalar) types. An instance of NSNumber provides an object wrapper of a scalar value (integer value, floating-point value, or Boolean value). NSNumber objects are useful in a variety of scenarios; for example, with the Foundation Framework collection classes that can’t hold primitive types. However, the creation and initialization of an NSNumber requires the use of the NSNumber APIs, which can be tedious for cases with a lot of objects (e.g., a collection class initialized with many NSNumber objects).

An NSNumber literal uses a simple notation for creating and initializing an NSNumber object from a scalar literal expression, its syntax is

@ScalarValue

The literal begins with the ampersand (@) symbol, followed by a ScalarValue that represents any character, numeric, or Boolean literal value. Listing 16-2 demonstrates the use of NSNumber literals in a statement that creates an NSArray object named numbers.

Listing 16-2.  Creating an NSArray Instance Using NSNumber Literals

NSArray *numbers = [NSArray arrayWithObjects:@'A', @YES, @-3, @21U, @250L,
                    @9876543210LL, @3.14F, @-52.687, nil];

The use of NSNumber literals in the preceding statement greatly simplifies the amount of code you have to write, thereby reducing the chance for errors. So that’s all well and good, but you may be wondering, how does this all work? Well, the compiler translates each NSNumber literal into a convenience constructor that creates the corresponding NSNumber instance at runtime. In effect, the following two expressions are equivalent:

@17
[NSNumber numberWithInt:17]

The same is true for scalar data types with modifiers; the next two expressions are also equivalent:

@3.14f
[NSNumber numberWithFloat:3.14]

So when you write the NSNumber literal @17 in code, the compiler translates that into the expression [NSNumber numberWithInt:17]. In effect, the literal notation is a shortcut for an NSNumber object creation.

As NSNumber literals are, in effect, NSNumber objects, they have available the full range of NSNumber APIs; for example, the following statement compares the values of two NSNumber literals.

NSComparisonResult comparison = [@17 compare:@16];

Note that NSNumber literal objects are created from scalar values, not expressions. Also be aware that NSNumber literals are evaluated at runtime; as such, they are not compile-time constants and hence cannot be used to initialize static or global variables. The following statement will not compile:

static NSNumber *badNumber = @0;              // ERROR!

Container Literals

Objective-C provides support for the creation of a variety of container literals; specifically, literal notation for arrays and dictionaries (i.e., NSArray and NSDictionary objects).

NSArray Literals

Chapter 10 provided a brief overview of NSArray literals. This notation simplifies the creation of NSArray instances. The syntax for an array literal is

@[firstObj, secondObj, ...]

An NSArray literal begins with an ampersand (@) symbol, followed by a comma-separated list of objects enclosed in brackets. The list of objects is not terminated with a nil. As with the NSArray APIs, the objects of the array must be Objective-C object pointer types, including other Objective-C Literals (NSNumber literals, NSArray literals, etc.). Listing 16-3 modifies the example shown in Listing 16-2, this time using array literal notation to create an NSArray object.

Listing 16-3.  Creating an NSArray Literal

NSArray *numbers = @[@'A', @YES, @-3, @21U, @250L,
                    @9876543210LL, @3.14F, @-52.687];

Array literals can be nested, thereby enabling the creation of arrays within arrays. Listing 16-4 demonstrates the creation of a nested NSArray literal.

Listing 16-4.  Nested NSArray Literals

NSArray *groups = @[@[@0, @1, @2], @[@"alpha", @"beta"]];

As the previous examples demonstrate, NSArray literal notation both simplifies the creation of arrays and reduces programmer errors.

The compiler translates each NSArray literal into the NSArray convenience constructor arrayWithObjects:count:. This method is invoked at runtime to create the corresponding NSArray instance. In effect, the NSArray literal

@[@"Hello", @"World"];

is equivalent to the code fragment shown in Listing 16-5.

Listing 16-5.  Creating an NSArray Instance (Without Array Literal Notation)

NSString *strings[2];
strings[0] = @"Hello";
strings[1] = @"World";
[NSArray arrayWithObjects:strings count:2];

Array literal notation is used to create immutable array instances. You can create a mutable array (e.g., an NSMutableArray) from an array literal by sending it a mutableCopy message, as follows.

NSMutableArray *mutableWords = [@[@"Hello", @"World"] mutableCopy];

NSDictionary Literals

Objective-C also provides support for creating NSDictionary literals. The syntax for a dictionary literal is

@{keyObj:valueObj, ...}

An NSDictionary literal begins with an ampersand symbol, followed by one or more comma-separated key-value pairs, all enclosed in braces. For each key-value pair, a colon is placed between the key and its corresponding value. Each key must be an object pointer type that conforms to the NSCopying protocol, and each value must be an object pointer type. Neither keys nor values in a dictionary literal can have a nil value; NSNull must be used to represent null objects in a container. Listing 16-6 creates a simple dictionary literal with two entries—an order for one cheeseburger and an order for two hot dogs.

Listing 16-6.  Creating an NSDictionary Literal

NSDictionary *orders = @{@1111:@"1 Cheeseburger",
                         @1112:@"2 Hot dogs"};

Notice that the NSDictionary literal syntax for key-value pairs is key:value, whereas the standard NSDictionary APIs use a value, key syntax for creating a dictionary instance (e.g., the NSDictionary method dictionaryWithObjects:forKeys:). As with array literals, dictionary literals can be nested; that is, the value in the key-value pair can be a container instance (including another NSDictionary literal). Listing 16-7 demonstrates the creation of a nested NSDictionary literal (currentOrders) with two entries, where the value for each entry is an NSDictionary literal.

Listing 16-7.  Nested NSDictionary Literal

NSDictionary *order1 = @{@1111:@"1 Cheeseburger",
                         @1112:@"2 Hot dogs"};
NSDictionary *order2 = @{@1113:@"1 large cheese pizza"};
NSDictionary *currentOrders = @{@"table1":order1, @"table2":order2};

The compiler translates each NSDictionary literal expression into the NSDictionary convenience constructor dictionaryWithObjects:forKeys:count:. This method is invoked at runtime to create the corresponding NSDictionary instance—in effect, the following dictionary literal:

@{@1111:@"1 Cheeseburger"}

is equivalent to the code fragment shown in Listing 16-8.

Listing 16-8.  Creating an NSDictionary Instance (Without Dictionary Literal Notation)

NSString *values[1];
values[0] = @"1 Cheeseburger";
NSNumber *keys[1];
keys[0] = @1111;
[NSDictionary dictionaryWithObjects:values forKeys:keys count:1];

Dictionary literal notation is used to create immutable dictionary instances. As specified with array literals, you can create a mutable dictionary (e.g., an NSMutableDictionary) from a dictionary literal by sending it a mutableCopy message.

Expression Literals

Expression literals, also known as boxed expressions, enable you to create literals from parentheses-enclosed expressions. The syntax of an expression literal is

@( <expression> )

The expression evaluates to either a scalar (numeric, enumerations, BOOL) or C-string pointer type. The type of the resultant object corresponds to the type of the expression. Scalar types produce NSNumber instances and C strings produce NSString objects using UTF-8 character encoding. The following statement creates an NSNumber instance using a boxed expression.

NSNumber *degrees2Radians = @( M_PI / 180.0 );

In the preceding example, the arithmetic expression M_PI/180.0 is evaluated and the result is boxed; that is, it is used to create an NSNumber instance. The equivalent statement for this boxed expression is

NSNumber *degrees2Radians = [NSNumber numberWithDouble:(M_PI / 180.0)];

The next example creates an NSString instance using a boxed expression.

NSString *currentUser = @(getenv("USER"));

The getenv() function retrieves the value of an environment variable, where the input parameter is the name of the variable to be retrieved. The return value to the function is a pointer to a C string.

Although enumerations are typically used to define constant scalar values, they cannot be used directly to create NSNumber literals; for example, the following attempt to create an NSNumber literal from an enum (shown in Listing 16-9) will not compile.

Listing 16-9.  Attempt to Create an NSNumber Literal from an enum

enum
{
  Zero = 0,
  One
};
NSNumber *zeroNumber = @Zero;      // Error!

Instead, an enum value must be placed inside an expression literal (i.e., a boxed enum) to create an NSNumber instance. Listing 16-10 corrects the example shown in Listing 16-9 to create an NSNumber instance from a boxed enum.

Listing 16-10.  Creating an NSNumber from a Boxed enum

enum
{
  Zero = 0,
  One
};
NSNumber *zeroNumber = @(Zero);      // Correct!

If an enum has a fixed underlying type (i.e., it specifies the type used to store its values), the NSNumber instance will be created with the corresponding type. The example shown in Listing 16-11 creates an NSNumber instance from a boxed enum; as the enumeration is of type unsigned int, the NSNumber instance is created with the corresponding type.

Listing 16-11.  Creating an NSNumber from a Boxed enum with a Fixed Underlying Type

enum : unsigned int
{
  Zero = 0,
  One
};
NSNumber *zeroNumber = @(Zero);    // invokes [NSNumber numberWithUnsignedInt:]

Object Subscripting

Object subscripting is a recent addition to the Objective-C language that enables Objective-C objects to be used with the C subscripting operator ([]). It provides a clean, concise syntax for getting and setting the elements of a Foundation Framework array and dictionary (e.g., NSArray and NSDictionary). In addition, the subscripting operators are generic, and thus enable the corresponding methods to be implemented by any class to provide custom subscripting.

NSArray Subscripting

When applied to NSArray and NSMutableArray objects, the subscript operand has an integral type. NSArray supports getting the elements of an array using the subscript operand. The example shown in Listing 16-12 creates an NSArray literal and then retrieves an element from the resultant array using subscripting.

Listing 16-12.  Retrieving an NSArray Element Using Subscripting

NSArray *words = @[@"Hello", @"World"];
NSString *word = words[0];

NSMutableArray supports both getting and setting the elements of an array using the subscript operand. Listing 16-13 creates a mutable array from an NSArray literal, and then sets an element of the array to a new value.

Listing 16-13.  Setting an NSMutableArray Element Using Subscripting

NSMutableArray *words = [@[@"Hello", @"World"] mutableCopy];
words[1] = @"Earthlings";

The NSArray and NSMutableArray classes use the subscript operand (i.e., an index) to access the appropriate element from its collection. The subscripting operator does not allow you to add elements to a mutable array; it only allows you to replace existing elements. For either NSArray or NSMutableArray instances, if the subscripting operand is outside of the array bounds, an NSRangeException is thrown. In addition, when setting an NSMutableArray element using subscripting, the object the array element is being set to must not be nil.

When the subscript operand is applied on an NSArray or NSMutableArray object, the expression is rewritten to use one of two different selectors, depending on whether the element is being read or written. For read (i.e., get) operations, the compiler translates this into an expression using the NSArray method objectAtIndexedSubscript:. In effect, the following two expressions are equivalent:

NSString *word = words[0]
NSString *word = [words objectAtIndexedSubscript:1]

For write (i.e., set) operations, the compiler translates this into an expression that uses the NSMutableArray method setObject:atIndexedSubscript:. In effect, the following two expressions are equivalent:

words[1] = @"Earthlings";
[words setObject:@"Earthlings" atIndexedSubscript:1];

The NSArray method objectAtIndexedSubscript: takes an NSUInteger (e.g., an unsigned integer) as its input parameter and returns a value of some Objective-C object pointer type. The NSMutableArray method setObject:atIndexedSubscript: takes an Objective-C pointer type as its first parameter and an NSUInteger as its second parameter.

NSDictionary Subscripting

When applied to NSDictionary and NSMutableDictionary objects, the subscript operand has an Objective-C pointer type. NSDictionary supports getting the elements of a dictionary using the subscript operand. The example shown in Listing 16-14 creates an NSDictionary literal, and then retrieves an element from the resultant array using subscripting.

Listing 16-14.  Retrieving an NSDictionary Element Using Subscripting

NSDictionary *order1 = @{@1111:@"1 Cheeseburger",
                         @1112:@"2 Hot dogs"};
NSString *order = order1[@1111];

NSMutableDictionary supports both getting and setting the elements of an array using the subscript operand. Listing 16-15 creates a mutable dictionary from an NSDictionary literal, and then sets an element of the array to a new value.

Listing 16-15.  Setting an NSMutableDictionary Element Using Subscripting

NSMutableDictionary *order1 = [@{@1111:@"1 Cheeseburger",
                                 @1112:@"2 Hot dogs"} mutableCopy];
order1[@1112] = @"1 Cheese pizza";

The NSDictionary and NSMutableDictionary classes use the subscript operand (i.e., a key) to access the appropriate element from its collection. The subscripting operator does not allow you to add elements to a mutable array; it only allows you to replace existing elements. For NSDictionary or NSMutableDictionary instances, attempting to retrieve an element via subscripting returns nil if there is no value associated with the input key. When setting an NSMutableDictionary element using subscripting, the method raises an NSInvalidArgument exception if the key or object is nil.

When the subscript operand is applied on an NSDictionary or NSMutableDictionary object, the expression is rewritten to use one of two different selectors, depending on whether the element is being read or written. For read (i.e., get) operations, the compiler translates this into an expression using the NSDictionary method objectForKeyedSubscript:. In effect, the following two expressions are equivalent:

NSString *order = order1[@1111];
NSString *order = [order1 objectForKeyedSubscript:@1111];

For write (i.e., set) operations, the compiler translates this into an expression that uses the NSMutableDictionary method setObject:forKeyedSubscript:. In effect, the following two expressions are equivalent:

order[@1111] = @"1 Cheeseburger";
[order setObject:@"1 Cheeseburger" forKeyedSubscript:@1111];

The NSDictionary method objectForKeyedSubscript: takes an Objective-C pointer type that conforms to the NSCopying protocol as its input parameter and returns a value of some Objective-C object pointer type. The NSMutableDictionary method setObject:forKeyedSubscript: takes an Objective-C pointer type as its first parameter and an Objective-C pointer type that conforms to the NSCopying protocol as its second parameter.

As with Objective-C Literals, the object subscripting operations are evaluated at runtime; as such, they are not compile-time constants and cannot be used to initialize static or global variables.

Custom Subscripting

As noted earlier, the object subscripting operators support any type of Objective-C object. The compiler translates these operators into message selectors, and your code implements one or more of the corresponding methods to support object subscripting. In effect, there are two types of subscript expressions: array-style and dictionary-style. You were introduced to this with the NSArray and NSDictionary subscripting operations presented earlier. In sum, array-style subscript expressions use integer typed subscripts, and dictionary-style subscript expressions use Objective-C object pointer typed subscripts (i.e., a key). Each type of subscript expression is mapped to a message send operation using a predefined selector.

The instance methods for array-style subscripting are

- (id)objectAtIndexedSubscript:(NSUInteger)index
- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index

The objectAtIndexedSubscript: method retrieves the value at the specified index, whereas the setObject:atIndexedSubscript: method sets the value at the specified index. As an example, given a class named Hello that supports array-style subscripting, an array-style subscript expression is used to return a value in the following assignment statement.

id value = helloObject[2];           // helloObject is a Hello class instance

In this case (i.e., retrieving a value for an associated index), the subscript expression (helloObject[2]) is translated by the compiler into a message send with the selector objectAtIndexedSubscript:, and is thus equivalent to the statement

id value = [helloObject objectAtIndexedSubscript:2];

In the next example, an array-style subscript expression is used to set a value in an assignment statement.

helloObject[2] = @"Howdy";

When setting a value via an array-style subscript expression, the expression (in this case helloObject[2]) is translated by the compiler into a message send with the selector setObject:atIndexedSubscript:, and is thus equivalent to the statement

[helloObject setObject:@"Howdy" atIndexedSubscript:2];

The message-send operations are type-checked and performed just like explicit Objective-C message sends. The implementing class determines the meaning of the index.

The instance methods for dictionary-style subscripting are

- (id)objectForKeyedSubscript:(id)key
- (void)setObject:(id)anObject forKeyedSubscript:(id<NSCopying>)key

These methods are analogous to the array-style subscripting methods, using a key (of type id—i.e., an object pointer) instead of an index to get or set the associated value. Given a class named Order that supports dictionary-style subscripting, a dictionary-style subscript expression is used to return a value in the following statement.

id item = orderObject[@"1234"];      // orderObject is an Order class instance

In this case, the subscript expression (orderObject[@"1234"]) is translated by the compiler into a message send with the selector objectForKeyedSubscript:, and is thus equivalent to the statement

id item = [orderObject objectForKeyedSubscript:@"1234"];

Conversely, a dictionary-style subscript expression is used to set a value in the following assignment statement.

orderObject[@"1234"] = @"Cheeseburger";

In this case, the subscript expression (orderObject[@"1234"]) is translated by the compiler into a message send with the selector setObject:forKeyedSubscript:, and is thus equivalent to the statement.

[orderObject setObject:@"Cheeseburger" forKeyedSubscript:@"1234"];

As shown in these examples, the custom subscripting operations should be used to get and set the elements of an object, and not for other purposes (e.g., appending or deleting objects, etc.). This enables the syntax to remain consistent with its intent, and thus identical to that employed for the Foundation Framework array and dictionary subscript operations.

Editing Register Values Using Custom Subscripting

Now you will create a program that demonstrates the use of custom subscripting. In addition, it also features extensive use of several Foundation Framework APIs presented in earlier chapters. The program you’ll create is called RegEdit and consists of a header file along with two classes. RegEdit simulates a 32-bit wide computer memory register. It supports operations for setting and manipulating the register’s contents. The program has three command-line inputs: the initial register settings (a 32-bit hexadecimal value), the selected register byte to get/set, and the (optional) register (hexadecimal) byte value to set.

In Xcode, create a new project by selecting New image Project … from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify RegEdit for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

First, you’re going to create a header file that defines several constants used in other classes. Select New image File … from the Xcode File menu, select Header File template (from the OS X C and C++ selection), and name the header file RegEditConstants. Select the RegEdit folder for the files location and the RegEdit project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named RegEditConstants.h and update the interface, as shown in Listing 16-16.

Listing 16-16.  RegEditConstants Interface

#ifndef RegEdit_RegEditConstants_h
#define RegEdit_RegEditConstants_h

#define kByteMultiplier  0xFF
#define kRegisterSize (sizeof(uint32))

#endif

Listing 16-16 shows that the include guard prevents recursive inclusion of the header file (necessary if the file is imported using the #include directive). The header file defines two constants: kByteMultiplier is used to perform register bit manipulation operations, and kRegisterSize defines the width (in bits) of the register. Next, you’re going to create a class used to parse command-line inputs for the program. Select New image File … from the Xcode File menu, select the Objective-C class template, and name the class CLIParser. Make the class a subclass of NSObject, select the RegEdit folder for the files location and the RegEdit project as the target, and then click the Create button. In the Xcode project navigator pane, select the resulting header file named CLIParser.h and update the interface, as shown in Listing 16-17.

Listing 16-17.  CLIParser Interface

#import <Foundation/Foundation.h>

@interface CLIParser : NSObject

- (id) initWithCount:(int)argc arguments:(const char *[])argv;
- (BOOL) parseWithRegister:(uint32 *)registerValue
                byteNumber:(NSInteger *)byteN
                 doSetByte:(BOOL *)doSet
                 byteValue:(unsigned int *)byteValue
                     error:(NSError **)anError;

@end

The initWithCount:arguments: method initializes a CLIParser object, setting its state to the input parameter values. The parseWithRegister:byteNumber:doSetByte: method parses the command-line inputs, setting the value of the input parameters by indirection. The actual implementation of the class will be discussed later, but for now let’s create the actual RegEdit class. Select New image File … from the Xcode File menu, select the Objective-C class template, and name the class RegEdit. Make the class a subclass of NSObject, select the RegEdit folder for the files location and the RegEdit project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named RegEdit.h and update the interface, as shown in Listing 16-18.

Listing 16-18.  RegEdit Interface

#import <Foundation/Foundation.h>

@interface RegEdit : NSObject

@property (readonly) uint32 regSetting;

- (id) initWithValue:(uint32)value;
- (id) objectAtIndexedSubscript:(NSInteger)index;
- (void) setObject:(id)newValue atIndexedSubscript:(NSInteger)index;

@end

The interface begins by declaring a read-only property named regSetting. It is used to store the binary value (1/0) of each bit of a register. The property is of type uint32, a standard C type for an unsigned, (exactly) 32-bit wide integer; hence the register is 32-bits wide. The initWithValue: method initializes a RegEdit instance with the desired initial register settings. The objectAtIndexedSubscript: and setObject:atIndexedSubscript: methods are used to implement custom subscripting of RegEdit objects.

Now select the RegEdit.m file and update the implementation, as shown in Listing 16-19.

Listing 16-19.  RegEdit Implementation

#import "RegEdit.h"
#import "RegEditConstants.h"

@interface RegEdit()

@property (readwrite) uint32 regSetting;

@end

@implementation RegEdit

- (id) initWithValue:(uint32)value
{
  if ((self = [super init]))
  {
    _regSetting = value;
  }
  return self;
}

- (id) objectAtIndexedSubscript:(NSInteger)index
{
  NSUInteger byteNumber = index * 8;
  if ((1 << byteNumber) > self.regSetting)
  {
    [NSException raise:NSRangeException
                format:@"Byte selected (%ld) exceeds number value", index];
  }
  unsigned int byteValue =
    (self.regSetting & (kByteMultiplier << byteNumber)) >> byteNumber;
  return [NSNumber numberWithUnsignedInt:byteValue];
}

- (void) setObject:(id)newValue atIndexedSubscript:(NSInteger)index
{
  if (newValue == nil)
  {
    [NSException raise:NSInvalidArgumentException
                format:@"New value is nil"];
  }
  
  NSUInteger byteNumber = index * 8;
  if ((1 << byteNumber) > self.regSetting)
  {
    [NSException raise:NSRangeException
                format:@"Byte selected (%ld) exceeds number value", index];
  }
  uint32 mask = ∼(kByteMultiplier << byteNumber);
  uint32 tmpValue = self.regSetting & mask;
  unsigned char newByte = [newValue unsignedCharValue];
  self.regSetting = (newByte << byteNumber) | tmpValue;
}
9
@end

The RegEdit class extension at the beginning of the file makes the regSetting property writeable in order to enable its value to be updated by other methods within the class implementation. The RegEdit implementation begins by defining the initWithValue: method. This method initializes the regSetting property with the input parameter value. Next, the implementation defines the objectAtIndexedSubscript: method. This method retrieves the value of a byte from the register, where the byte retrieved is that specified by the input parameter (index). The RegEdit register represents a little-endian machine; hence the least significant byte has the lowest memory address (i.e., byte 0). The selected register byte is returned as an unsigned integer wrapped in an NSNumber instance. The method also includes logic that checks the input parameter value and raises an exception if the byte selected exceeds the register value.

if ((2 << (byteNumber-1)) > self.regSetting)
{
  [NSException raise:NSRangeException
              format:@"Byte selected (%ld) exceeds number value",
   (unsigned long)index];
}

The setObject:atIndexedSubscript: method implementation begins by executing logic that verifies the selected register is within range and the input value is not nil, raising an exception if either condition is not true. The method then sets the value of the selected register byte to the input value.

uint32 mask = ∼(kByteMultiplier << byteNumber);
uint32 tmpValue = self.regSetting & mask;
unsigned char newByte = [newValue unsignedCharValue];
self.regSetting = (newByte << byteNumber) | tmpValue;

Notice that both custom subscripting methods perform bit-level operations to get/set the register’s values at the level of its individual bits. The bitwise left-shift (<<) and right-shift (>>) operators are used to move each bit in a number the specified number of bits to the left or right. The bitwise complement operator () is used to flip the bits of a number. The bitwise OR operator (|) performs a bit-by-bit, logical OR operation on two numbers, whereas the bitwise AND operator (&) performs a bit-by-bit, logical AND operation on two numbers.

OK, that summarizes the key details of the RegEdit class implementation. Now you will implement the CLIParser class, select the CLIParser.m file, and update the CLIParser class implementation, as shown in Listing 16-20.

Listing 16-20.  CLIParser Implementation

#import "CLIParser.h"
#import "RegEditConstants.h"

NSString *HelpCommand =
@"   RegEdit -n [Hex initial register settings] -b [byte number] -v [hex byte value]";
NSString *HelpDesc = @" NAME   RegEdit - Get/set selected byte of a register.";
NSString *HelpSynopsis = @" SYNOPSIS";
NSString *HelpOptions = @" OPTIONS";
NSString *HelpRegValue = @"   -n The initial register settings.";
NSString *HelpRegByte = @"   -b The byte to retrieve from the register.";
NSString *HelpByteValue = @"   -v Value to set for the selected register byte.";

@implementation CLIParser

// Private instance variables
{
  const char **argValues;
  int argCount;
}

- (id) initWithCount:(int)argc arguments:(const char *[])argv
{
  if ((self = [super init]))
  {
    argValues = argv;
    argCount = argc;
  }
  return self;
}

- (BOOL) parseWithRegister:(uint32 *)registerValue
                byteNumber:(NSInteger *)byteN
                 doSetByte:(BOOL *)doSet
                 byteValue:(unsigned int *)byteValue
                     error:(NSError **)anError
{
  // Retrieve command line arguments using NSUserDefaults
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  NSString *numberString = [defaults stringForKey:@"n"];
  NSString *byteString = [defaults stringForKey:@"b"];
  NSString *valueString = [defaults stringForKey:@"v"];

  // Display help message if register value or byte number not provided
  if (!numberString || !byteString)
  {
    NSString *help = [NSString stringWithFormat:@"%@%@%@%@%@%@%@",
                      HelpDesc, HelpSynopsis, HelpCommand,
                      HelpOptions, HelpRegValue, HelpRegByte,
                      HelpByteValue];
    printf("%s ", [help UTF8String]);
    return NO;
  }
  
  // Scan input register value
  NSScanner *scanner = [NSScanner scannerWithString:numberString];
  if (!numberString ||
      ([numberString length] == 0) ||
      ([numberString length] > kRegisterSize*2) ||
      ![scanner scanHexInt:registerValue])
  {
    // Create error and return NO
    if (anError != NULL)
    {
      NSString *msg =
      [NSString stringWithFormat:
       @"ERROR!, Register value must be from 1-%ld hexadecimal characters",
       kRegisterSize*2];
      NSString *description = NSLocalizedString(msg, @"");
      NSDictionary *info = @{NSLocalizedDescriptionKey:description};
      int errorCode = 1;
      *anError = [NSError errorWithDomain:@"CustomErrorDomain"
                                     code:errorCode
                                 userInfo:info];
    }
    return NO;
  }
  
  // Scan input register byte number
  scanner = [NSScanner scannerWithString:byteString];
  if (!byteString ||
      ([byteString length] == 0) ||
      [scanner scanInteger:byteN])
  {
    unsigned int numberLength = (unsigned int)(ceil([numberString length] * 0.5));
    if ((*byteN < 0) || (*byteN > (numberLength - 1)))
    {
      // Create error and return NO
      if (anError != NULL)
      {
        NSString *msg = [NSString stringWithFormat:
                         @"ERROR!, Register byte number must be from 0-%d",
                         numberLength-1];
        NSString *description = NSLocalizedString(msg, @"");
        NSDictionary *info = @{NSLocalizedDescriptionKey:description};
        int errorCode = 2;
        *anError = [NSError errorWithDomain:@"CustomErrorDomain"
                                       code:errorCode
                                   userInfo:info];
      }
      return NO;
    }
  }
  
  // Scan input value to set register byte
  if (valueString)
  {
    *doSet = YES;
    scanner = [NSScanner scannerWithString:valueString];
    if ([scanner scanHexInt:byteValue])
    {
      if (*byteValue > UCHAR_MAX)
      {
        if (anError != NULL)
        {
          NSString *msg =
          [NSString stringWithFormat:
           @"ERROR!, Register byte value must be 1-2 hexadecimal characters"];
          NSString *description = NSLocalizedString(msg, @"");
          NSDictionary *info = @{NSLocalizedDescriptionKey:description};
          int errorCode = 3;
          *anError = [NSError errorWithDomain:@"CustomErrorDomain"
                                         code:errorCode
                                     userInfo:info];
        }
        return NO;
      }
    }
  }
  
  return YES;
}

@end

Listing 16-20 shows that the parseWithRegister:byteNumber:doSetByte: method contains quite a few lines of code. However, it doesn’t introduce many new ideas; just about all of the code here is based on concepts and/or APIs that you encountered earlier in this book. Let’s begin with the NSUserDefaults class. It is used here to retrieve command-line arguments when the program is run.

NSString *numberString = [defaults stringForKey:@"n"];
NSString *byteString = [defaults stringForKey:@"b"];
NSString *valueString = [defaults stringForKey:@"v"];

Next, an NSScanner object is used to scan the input values retrieved using the NSUserDefaults object, converting these values into the appropriate type and format, as shown in the following excerpt from Listing 16-20.

NSScanner *scanner = [NSScanner scannerWithString:numberString];
[scanner scanHexInt:registerValue]

The code also validates each command-line input value. It creates an NSError object to return to the caller if the value fails the validation checks, as follows.

if (anError != NULL)
{
  NSString *msg =
  [NSString stringWithFormat:
   @"ERROR!, Register value must be from 1-%ld hexadecimal characters",
   kRegisterSize*2];
  NSString *description = NSLocalizedString(msg, @"");
  NSDictionary *info = @{NSLocalizedDescriptionKey:description};
  int errorCode = 1;
  *anError = [NSError errorWithDomain:@"CustomErrorDomain"
                                 code:errorCode
                             userInfo:info];
}

As shown in Listing 16-20, this logic is used to retrieve the command-line inputs and format them appropriately for use as inputs to the appropriate RegEdit instance method. OK, now that you have finished examining these two classes, let’s move on to the main() function. In the Xcode project navigator, select the main.m file and update the main() function, as shown in Listing 16-21.

Listing 16-21.  RegEdit main( ) Function

#import <Foundation/Foundation.h>
#import "RegEdit.h"
#import "CLIParser.h"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // First retrieve command line arguments using CLI parser
    CLIParser *parser = [[CLIParser alloc] initWithCount:argc arguments:argv];
    uint32 registerValue;
    NSInteger registerByte;
    unsigned int byteValue;
    BOOL isSetByte;
    NSError *error;
    BOOL success = [parser parseWithRegister:&registerValue
                                  byteNumber:&registerByte
                                   doSetByte:&isSetByte
                                   byteValue:&byteValue
                                       error:&error];
    if (!success)
    {
      // log error and quit
      if (error)
      {
        NSLog(@"%@", [error localizedDescription]);
        return -1;
      }
    }
    else
    {
      // Now create a RegEdit instance and set its initial value
      RegEdit *regEdit = [[RegEdit alloc] initWithValue:registerValue];
      NSLog(@"Initial register settings -> 0x%X", (uint32)[regEdit regSetting]);
      
      // Get selected register byte using custom subscripting
      NSNumber *byte = regEdit[registerByte];
      NSLog(@"Byte %ld value retrieved -> 0x%X", (long)registerByte,
            [byte intValue]);
      
      // Set selected register byte to input value using custom subscripting
      if (isSetByte)
      {
        NSLog(@"Setting byte %d value to -> 0x%X", (int)registerByte, byteValue);
        regEdit[registerByte] = [NSNumber numberWithUnsignedInt:byteValue];
        NSLog(@"Updated register settings -> 0x%X", [regEdit regSetting]);
      }
    }
  }
  return 0;
}

The main() function begins by creating CLIParser object. It parses the command-line arguments, logging an error if the arguments are not parsed successfully. It then creates a RegEdit instance and gets/sets a byte in the register as specified by the command-line arguments.

Now you will edit the project scheme (as you did earlier in an example from Chapter 9) to set the values for the program’s command-line arguments. In Xcode, select RegEdit in the Scheme button at the top of the toolbar, and then select Edit Scheme … from the scheme drop-down (as shown in Figure 16-1).

9781430250500_Fig16-01.jpg

Figure 16-1. Edit RegEdit project scheme

In the Scheme editing dialog, select the Arguments tab to edit the arguments passed on program launch. Under the Arguments Passed on Launch section, select the + button to add the argument values. As shown in Figure 16-2, the initial register setting is specified with a -n argument, the register byte with a -b argument, and the byte value with a -v argument.

9781430250500_Fig16-02.jpg

Figure 16-2. Adding command-line arguments using Scheme editor

The command-line argument settings provided in Figure 16-2 are

-n 1FB2C3A6 -b 0 -v A5

Hence, the initial register setting is 0x1FB2C3A6, the register byte selected is 0, and the value of that byte will be updated to 0xA5.

The command-line arguments will now be passed to the RegEdit program on launch.  When you compile and run the program, you should observe the messages in the output pane shown in Figure 16-3.

9781430250500_Fig16-03.jpg

Figure 16-3. RegEdit program output

The output pane shows that initial register settings, the retrieved byte, and the updated register settings (with the new byte value) are displayed appropriately. Try running the program with different inputs to observe how it operates, and once you become comfortable with custom subscripting, feel free to add other features to enhance it. Cool. Great job making it this far! This program demonstrates the use of Objective-C Literals and object subscripting.

Roundup

In this chapter, you learned about Objective-C Literals, a recent addition to the Objective-C language. It covered the new literal syntax and the new object literals for creating NSNumber, NSArray, and NSDictionary instances. It also examined expression literals, object subscripting, and extending object subscripting in your own classes with the custom subscripting APIs. The key takeaways from this chapter include:

  • NSNumber literals provide simple, concise notation for creating and initializing NSNumber objects from scalar literal expression.  NSNumber literals can be created for any character, numeric, or Boolean literal value.
  • NSArray literals simplify the creation of NSArray instances.  The objects of the array must be Objective-C object pointer types.  Array literals can be nested, thereby enabling the creation of arrays within arrays.
  • NSDictionary literals simplify the creation of NSDictionary instances.  Each key in the literal must be an object pointer type that conforms to the NSCopying protocol, and each value must be an object pointer type.
  • The object literals (NSNumber, NSArray, NSDictionary) are evaluated at runtime; as such, they are not compile-time constants and cannot be used to initialize static or global variables.
  • Expression literals (i.e., boxed expressions) enable you to create literals from parentheses-enclosed expressions.  The expression evaluates to either a scalar (numeric, enumerations, BOOL) or C-string pointer type.  The type of the resultant object corresponds to the type of the expression; scalar types produce NSNumber instances and C strings produce NSString objects using UTF-8 character encoding.
  • Object subscripting provides a simple, concise syntax for getting and setting the elements of a Foundation Framework array and dictionary (e.g., NSArray and NSDictionary).  In addition, the subscripting operators are generic, and thus enable the corresponding methods to be implemented by any class to provide custom subscripting.
  • The custom subscripting operations should be used to get and set the elements of an object, and not for other purposes (e.g., appending or deleting objects, etc.).  This enables the syntax to remain consistent with that employed for the Foundation Framework array and dictionary subscript operations, and thus provide ease of use.

Now you should be well on your way to mastering Objective-C object literals and object subscripting. Feel free to review the material of this chapter and tinker with the program and examples to let it all sink in. The topic of the next chapter is concurrent programming, so when you’re ready, turn the page to begin.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.24.30