Hour 15. Organizing Data with Collections


What You’ll Learn in This Hour

Image Working with collection objects

Image Reviewing property lists

Image Creating a collection

Image Enumerating a collection

Image Testing membership in a collection

Image Accessing a collection member


Collecting Objects

Objective-C adds objects to C itself with as few additions to that language as possible. This contrasts with other languages that dramatically change and expand C as well as object-oriented languages that are not built on C (Smalltalk, one of the most important influences on the development of Objective-C, comes to mind).

C types (often referred to as primitive types) such as int, float, and the like are available for use in Objective-C. However, as part of its classes, Objective-C declares objects that are frequently used instead of the primitive types. Many of these objects are declared and implemented in the Foundation frameworks of Cocoa and Cocoa Touch.

One of the advantages of using Objective-C objects instead of primitive types is that the objects contain much of the functionality that you implement (and reimplement and reimplement) with code to manipulate the primitive types. For example, how many times have you iterated through an array in C by writing the code shown in Listing 15.1?

LISTING 15.1 Iterating Through a C Array


int counter = 0;
int limit = 100;
float myCArray [limit];
// fill the array

  for (counter = 0; counter < limit; counter++ )
{
  //do something with myCArray [counter]
}


Even if the array consists of objects (for example, NSString *s[100]), you still need to write the code to iterate through the array.

Collection objects incorporate both the ability to store and organize data just as a C array does along with the most common functionality for which you would otherwise have to write code (that you have probably written over and over). In the case of NSArray, the array object can create an instance of NSEnumerator, which can convert the code just shown into the code in Listing 15.2.

LISTING 15.2 Iterating Through an NSArray


NSArray *myArray;
// initialize the array
myArray = @[@"Obj1", "Obj2", "Obj3", "Obj4"];

NSEnumerator *enumerator = [myArray objectEnumerator];
id anObject;
myArray = [NSArray array];
while (anObject = [enumerator nextObject]) {
  //do something with anObject
}


GO TO Image Hour 19, “Using Associative References and Fast Enumeration,” p. 249, to find out about a more concise way of iterating through collections.

The code is roughly comparable, but you do not have to worry about setting the counter and limit: They are automatically set when the NSArray object creates its NSEnumerator object. Also, note that the code in the Objective-C version has to be passed by the compiler. If you type enkmerator instead of enumerator, the compiler objects, but if you set the limit in the C language version to 1000 instead of to 100, the compiler has no problem with that.

This is definitely a small point, but the efficiencies and safeguards that derive from objects handling their own routine operations make your code less error-prone and more maintainable.

Go To Image Hour 16, “Managing Memory and Runtime Objects,” p. 221, for discussion of the implications of adding objects to collections.

Collections are Foundation framework classes: NSSet, NSArray, and NSDictionary. They share common functionalities:

Image Enumeration—Collections can enumerate their objects. Some people prefer the term iteration. In both cases, the process results in the objects in a collection being returned one by one based on certain rules.

Image Membership—A collection can quickly determine if a given object is in the collection.

Image Access—A collection can return one of its objects based on criteria.

Collections come in pairs, one of which is mutable. In the case of a mutable collection, you can add and remove objects to and from it. Some people come to the concept of collections with experience primarily using arrays in other languages. In many languages, the concept of mutable and immutable collections does not exist, so you may find yourself gravitating to the use of mutable collections to the exclusion of the other collections. Explore all of the collections because you will find that the built-in functionality can reduce the amount of code that you write and, perhaps more important, improve your app’s functionality.


Note

Immutable collections have significant performance advantages over mutable ones in most cases.



Collections: NSSet, NSArray, and NSDictionary

In this hour, you see how to use the collection classes. Before exploring them, it is important to note that there is no NSCollection class. The collection classes themselves are immediate descendants of NSObject and are not related to one another in the Cocoa class hierarchy. However, they share the basic functionalities described here. Also, as noted, each one of them comes in both mutable and immutable variations (the mutable variation is a subclass of the immutable base class).


Getting Familiar with Property Lists

Property lists are used throughout Cocoa and Cocoa Touch. You have seen property list files (with extension .plist) in your projects, and you have viewed property lists in your project, as shown in Figure 15.1.

Image

FIGURE 15.1 Property lists store project settings.

Property lists are one of the places where the boundary between the Objective-C language and the Cocoa and Cocoa Touch frameworks warrants your attention. Property lists are clearly framework objects; however, getting the most out of them requires you to not only be familiar with basic Objective-C types, but also be familiar with the collection objects that are the topic of this hour.

Property lists interact with collections in two ways. They are described in the following sections.

Using Collections in Property Lists

First, a property is a set of key-value pairs. For example, if you look closely at Figure 15.1, you can see that the main storyboard file base name (iPad) key (toward the bottom of the list) has the value Main_iPad.

In addition to the key and the value, each entry in a property list has a type. The supported scalar types are the following:

Image NSDate

Image NSNumber (through the intValue accessor)

Image NSNumber (through the boolValue accessor)

Image NSNumber (through the floatValue accessor)

In addition, two collections and a composite structure are supported:

Image NSArray

Image NSDictionary

Image NSString

Xcode Labels for Collection Types

Xcode uses simple labels for the various types: Array, Dictionary, Boolean, Data, Date, Number, and String.

Property lists can be read and written as binary streams or as XML documents. (An older ASCII format is supported today only for reading.) Because support for property lists is built into Cocoa, property lists are widely used for storing relatively small amounts of data. Often, as shown previously in Figure 15.1, they are strings. But by using collections, more complex data structures can be stored in a simple property list.

Building Collections from Property Lists at Runtime

You can use property lists to initialize collection instances using utility methods. This is part of the generalized framework support for property lists. By making it simple to move from a property list to an in-memory collection instance, all of the collection management tools (enumeration, membership, and access) can quickly be brought to bear on property lists.

Comparing the Collection Classes

As noted previously, there are six collection classes: immutable and mutable versions of NSArray, NSSet, and NSDictionary. The three basic classes need little introduction because they are based on standard programming concepts. All of the classes contain objects, but other than being objects, they need no other characteristics.


Note

Of course, you can enforce your own rules so that, for example, you can have an NSArray consisting only of MySpecialObject instances.


Other than the mutable/immutable variation, the most significant difference among the three basic classes has to do with the way in which their elements are arranged:

Image Arrays are ordered. Each object has an index number that can be used to access it. This is the standard behavior of arrays in most programming languages.

Image Dictionaries are arranged by keys, which are usually strings but can be any object (an id). When the keys are strings, access is implemented using a hash function. The keys are unique, so there are no duplicate objects in a dictionary.

Image Sets are not arranged. With the exception of NSCountedSet, there are no duplicate objects in a set.

Each of the collection classes has its strengths and weaknesses. For example, it is easy to test for membership in a collection, but that test is generally fastest in a set. Inserting an object in the middle of an array takes varying amounts of time; accessing the first and last elements takes constant amounts of time as does accessing elements of sets and of dictionaries (this last point is true for good hash functions).

You can use enumerators to loop through any of the collection classes, and there are a variety of methods you can use to find subsets of any collection and then enumerate through the subset. Using tools such as these, you can easily take all or part of a collection and turn it into a new collection of a different type.

Creating a Collection

Although each collection class has its own characteristics, all of them implement the basic functionalities (enumeration, membership, and access). The methods that they use for these implementations are parallel and sometimes identical, but there are minor variations that reflect the difference in the classes. In this and the sections that follow, the main functions of collections are shown and compared.

The first thing you need to do with a collection is to create it. Class factory methods let you do this easily. You can start with an empty collection and then add to it as well as delete and rearrange it, or you can start with a populated collection. The difference is that in the first case, you need to create a mutable collection so that you can do your manipulation of the collection’s objects. In the second case, you just create and populate it in one step. If you are using a mutable collection, you can create and populate it in one step and then continue to manipulate the collection as you want.


Collections Are Highly Optimized

Remember the fact that you can also take an immutable collection and use it as the basis of another collection that might or might not be mutable. This process of building a new collection from all or part of an existing collection can let you work with immutable collections as if they were mutable in many ways. Whether or not you choose this structure depends on what you are doing with the data and, of course, how big your collection is.

Keep in mind that the code that implements collections in Cocoa and Cocoa Touch is some of the oldest code in the frameworks. It has been optimized extensively over time, and it is used all through the operating systems and various software products. Sometimes, engineers are cautious about using tools such as collections because they understand the complexity of the operations that are performed behind the scenes, and they might fear that the convenience of using a tool such as a collection is counterbalanced by performance issues. If you are worried about the performance of collections, create a small testbed app and test it. In most cases, you will find that your fears are exaggerated. And, if you do find that collections are not giving you the performance that you want (this is sometimes the case with very large collections), take a look at Core Data, which might be just what you need.


Using the Common Collection Creation Methods

These methods are implemented by all three collection classes; the only difference is in the name of the method.

You can create a collection instance using a class factory method like this (there are similar ones for other collections):

+ (instancetype)array

There also are factory methods that convert data from other collections and even files into the newly created collection as in this method:

+ (instancetype)arrayWithArray:(NSArray *)anArray:

Unless you are creating mutable versions of collections, you almost always create them with a method such as this one that populates the collection. However with an immutable instance, you do get one chance to modify it with an init method if you have created it using alloc rather than the simple factory method such as array. The init methods parallel the factory methods. Thus, just as there is an arrayWithArray: factory method, there is an initWithArray:.

You can create a collection instance using an existing collection:

+ (instancetype)arrayWithArray:(NSArray *)anArray
+ (instancetype)setWithSet:(NSSet *)set
+ (instancetype)dictionaryWithDictionary:(NSDictionary *)otherDictionary

There are more of these in the documentation for each collection class.

There is a trio of methods that enable you to create a set or array with a single object, with a comma-delimited list of objects that ends with nil, or with a C array of objects (in this case, you specify the count of objects to be used).

+ (instancetype)arrayWithObject:(id)anObject
+ (instancetype)arrayWithObjects:(id)firstObj, ...
+ (instancetype)arrayWithObjects:(const id [])objects count:(NSUInteger)count

Those three methods differ slightly for dictionaries because a dictionary consists of key-value pairs. In addition, the second and third methods use an NSArray of objects rather than a comma-delimited or C-style list. Also, note that although dictionaries typically use strings for the keys, the key arguments in these methods are of type id:

+ (instancetype)dictionaryWithObject:(id)anObject forKey:(id<NSCopying>) aKey
+ (instancetype)dictionaryWithObjects:(NSArray *)objects forKeys:(NSArray *)keys
+ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)count


Note: nil-Terminate init Lists

The methods that take lists such as initWithObjects typically take comma-separated lists that must be terminated with a final value of nil. See the following section on Objective-C literal syntax for a simpler and more modern way of specifying lists.


In addition to these generic methods, there are some specific creation methods for the specific types of collections. They are described in the following sections.

Using Objective-C Literal Syntax

In recent versions of Xcode, you have the ability to create collections using literals rather than the methods shown in the previous section.

For example, instead of using arrayWithObjects:, you can use the following code:

myArray = @[ a, b, c];

a, b, and c are assumed to be NSObject instances (or instances of a subclass).

In addition, you can construct a dictionary with key-value pairs using syntax such as the following:

myDictionary = @{key1: value1, key2: value2};

You also can use literal subscripts for both arrays and dictionaries. These replace objectAtIndex: and objectForKey:.

anArrayElement = myArray [counter];
aValue = myDictionary {@"key"};

Reading and Writing Arrays

Foundation frequently lets you refer to files both with a file path in an NSString and with a URL in an NSURL instance. Methods let you use both references to read and write arrays. In both cases, you have a pair of methods. In chronological order, they are a method to write out an array and then a method to read in an array.


Note

Recent code samples from Apple tend to use the NSURL style.


Working with File Paths

Given an NSArray instance in memory, here is how you can write it out to a file:

– (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag

The atomically argument provides a degree of protection in case of failure. If it is YES, a scratch copy of the file is written out and saved. Then, the name is changed. If it is NO, the file is simply written out, and if there is a hardware or other failure during the process, you might lose data.

The companion method is a class method; it reads in a file written by an instance:

+ (id)arrayWithContentsOfFile:(NSString *)aPath

Working with URLs

If you are using URLs, here is the instance method to write out an NSArray:

– (BOOL)writeToURL:(NSURL *)aURL atomically:(BOOL)flag

The companion method is the following:

+ (id)arrayWithContentsOfURL:(NSURL *)aURL

Reading and Writing Dictionaries

Dictionaries are read and written like arrays in the sense that you can use either a file path or a URL. The main difference is in the format that is used: The read and write methods use property lists for their format, which means that the content of the dictionary must conform to the data types for a property list (NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary).

Working with File Paths

Here is the instance method to write out a dictionary as a property list:

– (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag

To create a new dictionary from a file path, here is the method. Note that this is a class method, not an instance method.

+ (id)dictionaryWithContentsOfFile:(NSString *)path

Working with URLs

Here is the instance method to write out a dictionary:

– (BOOL)writeToURL:(NSURL *)aURL atomically:(BOOL)flag

This is the companion class method to create a dictionary from a URL:

+ (id)dictionaryWithContentsOfURL:(NSURL *)aURL

Creating Sets

There are some additional methods to create sets. For example, you can create a set from an array. Sets are not ordered, so the order of an array is lost, but sometimes that does not matter.

+ (instancetype)setWithArray:(NSArray *)array

You also have the ability to create a new set from an existing one by adding a single object, an array, or a set to the existing set. The result is a new set. Here are the three relevant methods:

– (NSSet *)setByAddingObject:(id)anObject
– (NSSet *)setByAddingObjectsFromArray:(NSArray *)other
– (NSSet *)setByAddingObjectsFromSet:(NSSet *)other

Enumerating a Collection

Enumerators (class NSEnumerator) enable you to step through a collection. A collection instance can return an NSEnumerator that is configured properly for that instance. Enumerators have only two methods: nextObject and allObjects.

Examining NSEnumerator Methods

This enumerator method takes you to the next object in the collection. If you have just created the enumerator, the next object is the first object.

– (id)nextObject


Note: Getting Specific Objects

Ordered collections let you get specific objects such as the first and last, as you will see in the “Accessing an Object in a Collection” section later in this hour.


This enumerator method returns an array of all not-yet-enumerated objects:

– (NSArray *)allObjects

Creating NSEnumerator Instances for Collections

With all three types of collections, you can create an NSEnumerator by using the method objectEnumerator:

– (NSEnumerator *)objectEnumerator

For an NSArray instance, you can also create a reverse enumerator:

– (NSEnumerator *)reverseObjectEnumerator

For an NSDictionary, you can enumerate the keys as well as the objects (values):

– (NSEnumerator *)keyEnumerator

Remember that sets are not ordered, so the suggestion that nextObject implies sequence does not apply to NSSet instances.

GO TO Image Hour 19, “Using Associative References and Fast Enumeration,” p. 249, for more on enumerating.

Testing Membership in a Collection

Each collection class has its own way of testing for membership. You can add code after the last step in the previous Try It Yourself activity to test for membership. Listing 15.3 shows how you can add your objects to a set. You can then test to see if one of the objects is in that set using the member method. It will return the object or nil if the object is not in the collection.

– (id)member:(id)object

The code snippet is shown in Listing 15.3.

LISTING 15.3 Testing for Membership in a Set


NSSet *mySet = [NSSet setWithObjects: myString, myNumber, myDate, nil];
NSLog (@"mySet:%@ ", [mySet description]);

anObject = [mySet member:myDate];
NSLog(@"member:%@ ",[anObject description]);


Experiment with adding other objects (and not adding objects) to watch the results. You can also simply ask if an object is a member of a collection (the result is YES or NO rather than the object itself).

For NSArray and NSSet instances, the code is

(BOOL)containsObject:(id)anObject

For NSDictionary instances, you check for the key and receive an object back if it is in the dictionary:

– (id)objectForKey:(id)aKey

The class references for the collection classes show additional methods for testing membership.

Accessing an Object in a Collection

As with memberships, accessing objects differs across the collection classes, and the class references are the fundamental guides. For NSDictionary instances, objectForKey, which was just shown, is a good place to start.

For NSSet, in addition to member, there is an interesting access method. It returns any object in the set, not a randomly chosen object, but, according to the class reference, “The object returned is chosen at the set’s convenience.” For example, if you construct a set of transactions for the last customer served, you can choose any of those transaction objects to get the name of the last customer served.

– (id)anyObject

For NSArray instances, the subscript is the commonly used means of access:

– (id)objectAtIndex:(NSUInteger)index

It is worthwhile to look through the class references for collections to see what other access and membership methods there are. You can access objects not only in the ways described here, but also based on conditions. The speed of the collection classes is impressive.

Summary

Collection classes not only let you manage arrays, sets, and dictionaries, but they also incorporate sophisticated, powerful, and remarkably efficient functionality that manages the data for you. In fact, the methods of the collection classes pretty much incorporate the bulk of an introductory programming course with algorithms that you no longer have to write for yourself.

Q&A

Q. What is the relationship between dictionaries and property lists?

A. Property lists can contain dictionaries. Also, dictionaries are read and written as property lists.

Q. What do the collection classes do?

A. They store arrays, sets, and dictionaries. They also incorporate the basic functionality for accessing, enumerating, and managing membership for the collections.

Workshop

Quiz

1. If you use an immutable collection, can you ever make a change to it?

2. Why can you create a set from an array but you cannot create an array from a set?

Quiz Answers

1. No. You can populate it with one of the init methods if you have created it with alloc, but that is the extent of the “modifications” you can make.

2. Arrays are ordered, and sets are unordered. Converting an array (ordered) to an unordered set means ignoring a piece of data. Doing the reverse would require you to create the order. You can use the NSSet allObjects method to convert the set to an array and then use arrayWithArray to create an array, but allObjects presents the data in an undefined order (which is set behavior).

Activities

Create a collection with half a dozen objects in it. Use one of the immutable classes. Now write code to extract three of the objects into a new collection. To become even more familiar with collections, extract objects based on some characteristic. This helps you become familiar with additional methods of the collection classes.

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

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