Classes NSArray and NSMutableArray

Objective-C has two types of array: the C array we saw in Manhattan, and the NSArray (with its variant NSMutableArray) we are about to see. Like an NSSet, an NSArray is a big object that contains (pointers to) little objects. Unlike an NSSet, an NSArray keeps the elements in the original order. See Arrays: Ordered Collections.

The advantage of a C array is that it can hold elements of any data type: ints, CGFloats, structures, pointers to objects, etc. An NSArray can hold only pointers to objects. For example, an int can not be held directly in an NSArray. The int would have to be wrapped in an NSValue object, and the address of the NSValue object would be stored in an array.

The advantage of an NSArray, or at least of an NSMutableArray, is that it can expand to receive additional elements. This is what the method addObject: of class NSMutableArray does.

Source code in Array.zip

  1. main.m

See the setWithObjects: method of class NSSet here for an earlier example of a method that accepts a variable number of arguments ending with nil. I declared the variable borough to be a pointer to an NSString because I knew in advance that all the elements of the array were NSStrings.

Output

2013-10-30 20:33:22.987 Array[9413:a0b] boroughs.count == 5
2013-10-30 20:33:22.991 Array[9413:a0b] Bronx
2013-10-30 20:33:22.991 Array[9413:a0b] Brooklyn
2013-10-30 20:33:22.992 Array[9413:a0b] Manhattan
2013-10-30 20:33:22.993 Array[9413:a0b] Queens
2013-10-30 20:33:22.994 Array[9413:a0b] Staten Island
2013-10-30 20:33:22.995 Array[9413:a0b] The first borough is Bronx.
2013-10-30 20:33:23.002 Array[9413:a0b] The last borough is Staten Island.

Things to try

  1. Change boroughs to an NSMutableArray.
    	NSMutableArray *boroughs = [[NSMutableArray alloc] init];	//born empty
    	[boroughs addObject: @"Bronx"];
    	[boroughs addObject: @"Brooklyn"];
    	[boroughs addObject: @"Manhattan"];
    	[boroughs addObject: @"Queens"];
    	[boroughs addObject: @"Staten Island"];
    	//Do not add nil at the end.
    

  2. The familyNames method of class UIFont returns another array of strings we can loop through. Insert the following code into the main function of any app immediately before the return statement.
    	for (NSString *name in [UIFont familyNames]) {
    		NSLog(@"%@", name);
    	}
    
    2013-10-30 20:47:03.517 Array[9520:a0b] Marion
    2013-10-30 20:47:03.518 Array[9520:a0b] Copperplate
    2013-10-30 20:47:03.519 Array[9520:a0b] Heiti SC
    2013-10-30 20:47:03.520 Array[9520:a0b] Iowan Old Style
    2013-10-30 20:47:03.521 Array[9520:a0b] Courier New
    2013-10-30 20:47:03.522 Array[9520:a0b] Apple SD Gothic Neo
    2013-10-30 20:47:03.522 Array[9520:a0b] Heiti TC
    2013-10-30 20:47:03.523 Array[9520:a0b] Gill Sans
    2013-10-30 20:47:03.523 Array[9520:a0b] Thonburi
    2013-10-30 20:47:03.524 Array[9520:a0b] Marker Felt
    etc.
    
    To display a one-line sample of each font, we will use a UIWebView here and here.

  3. There are many flavors of alphabetical order: plain vanilla, case insensitive, etc. The selector indicates which one we want.
    	//caseInsensitiveCompare: is a method of class NSString.
    	SEL sel = @selector(caseInsensitiveCompare:);
    
    	NSArray *sorted = [[UIFont familyNames] sortedArrayUsingSelector: sel];
    
    	for (NSString *name in sorted) {
    		NSLog(@"%@", name);
    	}
    
    2013-10-30 20:52:48.982 Array[9534:a0b] Academy Engraved LET
    2013-10-30 20:52:48.983 Array[9534:a0b] Al Nile
    2013-10-30 20:52:48.984 Array[9534:a0b] American Typewriter
    2013-10-30 20:52:48.984 Array[9534:a0b] Apple Color Emoji
    2013-10-30 20:52:48.985 Array[9534:a0b] Apple SD Gothic Neo
    2013-10-30 20:52:48.985 Array[9534:a0b] Arial
    2013-10-30 20:52:48.986 Array[9534:a0b] Arial Hebrew
    2013-10-30 20:52:48.986 Array[9534:a0b] Arial Rounded MT Bold
    2013-10-30 20:52:48.987 Array[9534:a0b] Avenir
    2013-10-30 20:52:48.987 Array[9534:a0b] Avenir Next
    etc.
    

    A C programmer has a rage to telescope as much code as possible into a single expression:

    	for (NSString *name in [[UIFont familyNames] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)]) {
    		NSLog(@"%@", name);
    	}
    
  4. Nested Loops. Every application has an array of windows, although the array probably contains only one window. Every window (and in fact every view) has an array of subviews, although the array might contain only one subview.

    Write nested loops to print information about each (highest level) subview of each window. Insert the following code into the applicationDidFinishLaunchingWithOptions: method of an application delegate (e.g., of Touch) immediately after the addSubview: statement(s).

    	for (UIWindow *w in [UIApplication sharedApplication].windows) {
    		CGRect f = w.frame;
    		NSLog(@"window frame == (%g, %g), %g × %g",
    			f.origin.x, f.origin.y, f.size.width, f.size.height);
    
    		for (UIView *v in w.subviews) {
    			f = v.frame;
    			NSLog(@"   view frame == (%g, %g), %g × %g",
    				f.origin.x, f.origin.y, f.size.width, f.size.height);
    		}
    	}
    
    2013-10-30 21:36:44.274 Touch[9653:a0b] window frame == (0, 0), 320 × 568
    2013-10-30 21:36:44.276 Touch[9653:a0b]    view frame == (0, 20), 320 × 548
    

    To display the subviews of the subviews all the way down, we would need recursion. Is anyone up for this?


  5. An NSArray cannot hold numbers. It can hold only pointers to objects. The workaround is to wrap each number in an NSNumber object and store the address of the NSNumber object into the array. The @3.14 is an Objective-C literal that creates an NSNumber object.
    	NSArray *a = [NSArray arrayWithObjects:
    		[NSNumber numberWithFloat: 3.14],	//or @3.14,
    		[NSNumber numberWithFloat: 2.71],	//or @2.71,
    		nil
    	];
    
    	for (NSNumber *n in a) {
    		CGFloat x = n.floatValue;
    		NSLog(@"%g", x);
    	}
    
    2013-10-30 21:38:41.099 Array[9669:a0b] 3.14
    2013-10-30 21:38:41.113 Array[9669:a0b] 2.71
    

    An NSMutableArray cannot hold numbers either. We use the same workaround.

    	NSMutableArray *a = [[NSMutableArray alloc] init];
    	[a addObject: [NSNumber numberWithFloat: 3.14]];	//or [a addObject: @3.14];
    	[a addObject: [NSNumber numberWithFloat: 2.71]];
    
    	for (NSNumber *n in a) {
    		CGFloat x = n.floatValue;
    		NSLog(@"%g", x);
    	}
    

  6. Sort an array of numbers.
    	NSArray *a = [NSArray arrayWithObjects:
    		[NSNumber numberWithFloat: 3.14],
    		[NSNumber numberWithFloat: 2.71],
    		nil
    	];
    
    	//compare: is a method of class NSNumber.
    	SEL sel = @selector(compare:);
    
    	NSArray *sorted = [a sortedArrayUsingSelector: sel];
    
    	for (NSNumber *n in sorted) {
    		CGFloat x = n.floatValue;
    		NSLog(@"%g", x);
    	}
    
    2013-10-30 21:54:42.955 Array[9704:a0b] 2.71
    2013-10-30 21:54:42.958 Array[9704:a0b] 3.14
    

  7. An NSArray cannot hold structures. It can hold only pointers to objects. The workaround is to wrap each structure in an NSValue object and store the address of the NSValue object into the array.
    	NSArray *a = [NSArray arrayWithObjects:
    		[NSValue valueWithCGPoint: CGPointMake(10, 20)],
    		[NSValue valueWithCGPoint: CGPointMake(30, 40)],
    		nil
    	];
    
    	for (NSValue *v in a) {
    		CGPoint p = v.CGPointValue;
    		NSLog(@"(%g, %g)", p.x, p.y);
    	}
    
    2013-10-30 21:55:26.693 Array[9721:a0b] (10, 20)
    2013-10-30 21:55:26.696 Array[9721:a0b] (30, 40)
    

    An NSMutableArray cannot hold structures either. We use the same workaround.

    	NSMutableArray *a = [[NSMutableArray alloc] init];
    	[a addObject: [NSValue valueWithCGPoint: CGPointMake(10, 20)]];
    	[a addObject: [NSValue valueWithCGPoint: CGPointMake(30, 40)]];
    
    	for (NSValue *v in a) {
    		CGPoint p = v.CGPointValue;
    		NSLog(@"(%g, %g)", p.x, p.y);
    	}
    


  8. An out-of-range argument of objectAtIndex: will throw an Objective-C exception. A “dylib” is a dynamic library.
    		//For our array of 5 boroughs, the index must be in the range
    		//0 to 4 inclusive.  5 is out of range.
    
    		NSString *borough = [boroughs objectAtIndex: 5];
    		NSLog(@"borough == %@", borough);	//never arrive here
    
    2013-10-31 07:42:11.491 Array[10790:a0b] *** Terminating app due to uncaught exception 'NSRangeException',
    reason: '*** -[__NSArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
    *** First throw call stack:
    (
    	0   CoreFoundation                      0x0000000101886795 __exceptionPreprocess + 165
    	1   libobjc.A.dylib                     0x00000001015e9991 objc_exception_throw + 43
    	2   CoreFoundation                      0x000000010183efcf -[__NSArrayI objectAtIndex:] + 175
    	3   Array                               0x0000000100001ef4 main + 916
    	4   libdyld.dylib                       0x0000000101f147e1 start + 0
    	5   ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb) 
    

    To catch and recover from the exception,

    		@try {
    			NSString *borough = [boroughs objectAtIndex: 5];
    			NSLog(@"borough == %@", borough);	//never arrive here
    		}
    
    		@catch (NSException *exception) {
    			NSLog(@"name is %@", [exception name]);
    			NSLog(@"%@", exception);
    		}
    
    		@finally {
    			NSLog(@"Arrive here whether or not the exception was thrown.");
    		}
    
    2013-10-31 07:31:02.218 Array[10769:a0b] name is NSRangeException
    2013-10-31 07:31:02.219 Array[10769:a0b] *** -[__NSArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 4]
    2013-10-31 07:31:02.219 Array[10769:a0b] Arrive here whether or not the exception was thrown.