Table of Contents Previous Next
Logo
Client-Side Slice-to-Objective-C Mapping : 18.8 Mapping for User-Defined Types
Copyright © 2003-2010 ZeroC, Inc.

18.8 Mapping for User-Defined Types

Slice supports user-defined types: enumerations, structures, sequences, and dictio­naries.

18.8.1 Mapping for Enumerations

Enumerations map to the corresponding enumeration in Objective‑C. For example:
["objc:prefix:EX"]
module Example {
    enum Fruit { Apple, Pear, Orange };
};
The generated Objective‑C definition is:
typedef enum {
    EXApple, EXPear, EXOrange
} EXFruit;

18.8.2 Mapping for Structures

The mapping for structures maps Slice structures to Objective‑C classes. For each Slice data member, the Objective‑C class has a corresponding property. For example, here is our Employee structure from Section 4.9.4 once more:
struct Employee {
    long number;
    string firstName;
    string lastName;
};
The Slice-to-Objective‑C compiler generates the following definition for this structure:
@interface EXEmployee : NSObject <NSCopying>
{
    @private
        ICELong number;
        NSString *firstName;
        NSString *lastName;
}

@property(nonatomic, assign) ICELong number;
@property(nonatomic, retain) NSString *firstName;
@property(nonatomic, retain) NSString *lastName;

(id) init:(ICELong)number firstName:(NSString *)firstName
                           lastName:(NSString *)lastName;
+(id) employee:(ICELong)number firstName:(NSString *)firstName
                               lastName:(NSString *)lastName;
+(id) employee;
// This class also overrides copyWithZone,
// hash, isequal, and dealloc.
@end

Mapping for Data Members

For each data member in the Slice definition, the Objective‑C class contains a corresponding private instance variable of the same name, as well as a property definition that allows you to set and get the value of the corresponding instance variable. For example, given an instance of EXEmployee, you can write the following:
ICELong number;
EXemployee *e = ...;
[e setNumber:99];
number = [e number];

// Or, more concisely with dot notation:

e.number = 99;
number = e.number;
Properties that represent data members always use the nonatomic property attribute. This avoids the overhead of locking each data member during access. The second property attribute is assign for integral and floating-point types and retain for all other types (such as strings, structures, and so on.)

Creating and Initializing Structures

Structures provide the usual (inherited) init method:
EXEmployee *e = [[EXEmployee alloc] init];
// ...
[e release];
As usual, init initializes the instance variables of the structure with zero-filled memory.
In addition, a structure provides a second init method that accepts one parameter for each data member of the structure:
(id) init:(ICELong)number firstName:(NSString *)firstName
                           lastName:(NSString *)lastName;
Note that the first parameter is always unlabeled; the second and subsequent parameters have a label that is the same as the name of the corresponding Slice data member. The additional init method allows you to instantiate a structure and initialize its data members in a single statement:
EXEmployee *e = [[EXEmployee alloc] init:99 firstName:@"Brad"
                                            lastName:@"Cox"];
// ...
[e release];
init applies the memory management policy of the corresponding properties, that is, it calls retain on the firstName and lastName arguments.
Each structure also provides two convenience constructors that mirror the init methods: a parameter-less convenience constructor and one that has a parameter for each Slice data member:
+(id) employee;
+(id) employee:(ICELong)number firstName:(NSString *)firstName
                               lastName:(NSString *)lastName;
The convenience constructors have the same name as the mapped Slice structure (without the module prefix). As usual, they allocate an instance, perform the same initialization actions as the corresponding init methods, and call autore­lease on the return value:
EXEmployee *e = [EXEmployee employee:99 firstName:@"Brad"
                                        lastName:@"Cox"];

// No need to call [e release] here.

Copying Structures

Structures implement the NSCopying protocol. Structures are copied by assigning instance variables of value type and calling retain on each instance variable of non-value type. In other words, the copy is shallow:
EXEmployee *e = [EXEmployee employee:99 firstName:@"Brad"
                                        lastName:@"Cox"];
EXEmployee *e2 = [e copy];
NSAssert(e.number == e2.number);
NSAssert([e.firstName == e2.firstName]); // Same instance
// ...
[e2 release];
Note that, if you assign an NSMutableString to a structure member and use the structure as a dictionary key, you must not modify the string inside the struc­ture without copying it because doing so will corrupt the dictionary.

Deallocating Structures

Each structure implements a dealloc method that calls release on each instance variable with a retain property attribute. This means that structures take care of the memory management of their contents: releasing a structure auto­matically releases all its instance variables.

Structure Comparison and Hashing

Structures implement isEqual, so you can compare them for equality. Two structures are equal if all their instance variables are equal. For value types, equality is determined by the == operator; for non-value types other than classes, equality is determined by the corresponding instance variable’s isEqual method. Classes (see Section 18.15) are compared by comparing their identity: two class members are equal if they both point at the same instance.
The hash method returns a hash value is that is computed from the hash value of all of the structure’s instance variables.

18.8.3 Mapping for Sequences

The Objective‑C mapping uses different mappings for sequence of value types (such as sequence<byte>) and non-value types (such as sequence<string>).

Mapping for Sequences of Value Types

The following Slice types are value types:
• Integral types (bool, byte, short, int, long)
• Floating point types (float, double)
• Enumerated types
Sequences of these types map to a type definition. For example:
enum Fruit { Apple, Pear, Orange };

sequence<byte> ByteSeq;
sequence<int> IntSeq;
sequence<Fruit> FruitSeq;
The three Slice sequences result in the following Objective‑C definitions:
typedef enum {
    EXApple, EXPear, EXOrange
} EXFruit;

typedef NSData EXByteSeq;
typedef NSMutableData EXMutableByteSeq;

typedef NSData EXIntSeq;
typedef NSMutableData EXMutableIntSeq;

typedef NSData EXFruitSeq;
typedef NSMutableData EXMutableFruitSeq;
As you can see, each sequence definition creates a pair of type definitions, an immutable version named <moduleprefix><Slicename>, and a mutable version named <moduleprefix>Mutable<Slicename>. This constitutes the entire public API for sequence of value types, that is, sequences of value types simply map to NSData or NSMutableData. The NS(Mutable)Data sequences contain an array of the corresponding element type in their internal byte array.1
For example, here is how you could initialize a byte sequence of 1024 elements with values that are the modulo 128 of the element index in reverse order:
int limit = 1024;
EXMutableByteSeq *bs = [NSMutableData dataWithLength:limit];
ICEByte *p = (ICEByte *)[bs bytes];
while (limit > 0) {
    *p++ = limit % 0x80;
}
Naturally, you do not need to initialize the sequence using a loop. For example, if the data is available in a buffer, you could use the dataWithBytes:length or dataWithBytesNoCopy:length methods of NSData instead.
Here is one way to retrieve the bytes of the sequence:
const ICEByte* p = (const ICEByte *)[bs bytes];
const ICEByte* limitp = p + [bs length];
while (p < limitp) {
    printf("%d\n", *p++);
}
For sequences of types other than byte or bool, you must keep in mind that the length of the NSData array is not the same as the number of elements. The following example initializes an integer sequence with the first few primes and prints out the contents of the sequence:
const int primes[] = { 1, 2, 3, 5, 7, 9, 11, 13, 17, 19, 23 };
EXMutableIntSeq *is = [NSMutableData dataWithBytes:primes
                                     length:sizeof(primes)];

const ICEInt *p = (const ICEInt *)[is bytes];
int limit = [is length] / sizeof(*p);
int i;
for(i = 0; i < limit; ++i) {
    printf("%d\n", p[i]);
}
The code to manipulate a sequence of enumerators is very similar. For portability, you should not assume a particular size for enumerators. That is, instead of relying on all enumerators having the size of, for example, an int, it is better to use sizeof(EXFruit) to ensure that you are not overstepping the bounds of the sequence.

Mapping of Sequences of Non-Value Types

Sequences of non-value types, such as sequences of string, structures, classes, and so on, map to mutable and immutable type definitions of NSArray. For example:
sequence<string> Page;
sequence<Page> Book;
This maps to:
typedef NSArray EXPage;
typedef NSMutableArray EXMutablePage;

typedef NSArray EXBook;
typedef NSMutableArray EXMutableBook;
You use such sequences as you would use any other NSArray in your code. For example:
EXMutablePage *page1 = [NSArray arrayWithObjects:
                                @"First line of page one",
                                @"Second line of page one",
                                nil];

EXMutablePage *page2 = [NSArray arrayWithObjects:
                                @"First line of page two",
                                @"Second line of page two",
                                nil];

EXMutableBook *book = [NSMutableArray array];
[book addObject:page1];
[book addObject:page2];
[book addObject:[NSArray array]]; // Empty page
This creates a book with three pages; the first two pages contain two lines each, and the third page is empty. You can print the contents of the book as follows:
int pageNum = 0;
for (EXPage *page in book) {
    ++pageNum;
    int lineNum = 0;
    if ([page count] == 0) {
        printf("page %d: <empty>\n", pageNum);
    } else {
        for (NSString *line in page) {
            ++lineNum;
            printf("page %d, line %d: %s\n",
                        pageNum, lineNum, [line UTF8String]);
        }
    }
}
This prints:
page 1, line 1: First line of page one
page 1, line 2: Second line of page one
page 2, line 1: First line of page two
page 2, line 2: Second line of page two
page 3: <empty>
If you have a sequence of proxies or a sequence of classes, to transmit a null proxy or class inside a sequence, you must insert an NSNull value into the NSArray. In addition, the mapping also allows you to use NSNull as the element value of an NSArray for elements of type string, structure, sequence, or dictionary. For example, instead of inserting an empty NSArray into the book sequence in the preceding example, we could also have inserted NSNull:
EXMutableBook *book = [NSMutableArray array];
[book addObject:page1];
[book addObject:page2];
[book addObject:[NSNull null]]; // Empty page

18.8.4 Mapping for Dictionaries

Here is the definition of our EmployeeMap from Section 4.9.4 once more:
dictionary<long, Employee> EmployeeMap;
The following code is generated for this definition:
typedef NSDictionary EXEmployeeMap;
typedef NSMutableDictionary EXMutableEmployeeMap;
Similar to sequences, Slice dictionaries map to type definitions for NSDic­tionary and NSMutableDictionary, with the names <moduleprefix><Slicename> and <moduleprefix>Mutable<Slicename>.
As a result, you can use the dictionary like any other NSDictionary, for example:
EXMutableEmployeeMap *em = [EXMutableEmployeeMap dictionary];
EXEmployee *e = [EXEmployee employee];
e.number = 42;
e.firstName = @"Stan";
e.lastName = @"Lippman";
[em setObject:e forKey:[NSNumber numberWithLong:e.number]];

e = [EXEmployee employee];
e.number = 77;
e.firstName = @"Herb";
e.lastName = @"Sutter";
[em setObject:e forKey:[NSNumber numberWithLong:e.number]];
To put a value type into a dictionary (either as the key or the value), you must use NSNumber as the object to hold the value. If you have a dictionary that uses a Slice enumeration as the key or the value, you must insert the enumerator as an NSNumber that holds an int.
To insert a null proxy or null class instance into a dictionary as a value, you must insert NSNull.
As a convenience feature, the Objective‑C mapping also allows you to insert NSNull as the value of a dictionary if the value type of the dictionary is a string, structure, sequence, or dictionary. If you send such a dictionary to a receiver, the Ice run time marshals an empty string, default-initialized structure, empty sequence, or empty dictionary as the corresponding value to the receiver, respec­tively.

1
We chose to map sequences of value types to NSData instead of NSArray because of the large overhead of placing each sequence element into an NSNumber container instance.


Table of Contents Previous Next
Logo