- •C and Objective-C
- •How this book works
- •How the life of a programmer works
- •Installing Apple’s developer tools
- •Getting started with Xcode
- •Where do I start writing code?
- •How do I run my program?
- •So what is a program?
- •Don’t stop
- •Types
- •A program with variables
- •Challenge
- •Boolean variables
- •When should I use a function?
- •How do I write and use a function?
- •How functions work together
- •Local variables, frames, and the stack
- •Recursion
- •Looking at the frames in the debugger
- •return
- •Global and static variables
- •Challenge
- •printf()
- •Integer operations
- •Integer division
- •Operator shorthand
- •Floating-point numbers
- •Tokens for displaying floating-point numbers
- •The while loop
- •The for loop
- •break
- •continue
- •The do-while loop
- •Challenge
- •Getting addresses
- •Storing addresses in pointers
- •Getting the data at an address
- •How many bytes?
- •NULL
- •Stylish pointer declarations
- •Challenges
- •Writing pass-by-reference functions
- •Avoid dereferencing NULL
- •Creating and using your first object
- •Message anatomy
- •Objects in memory
- •Challenge
- •Nesting message sends
- •Multiple arguments
- •Sending messages to nil
- •Challenge
- •Challenge
- •NSMutableArray
- •Reference pages
- •Quick Help
- •Other options and resources
- •Accessor methods
- •Dot notation
- •Properties
- •self
- •Multiple files
- •Challenge
- •Overriding methods
- •super
- •Challenge
- •Object ownership and ARC
- •Creating the Asset class
- •Adding a to-many relationship to Employee
- •Challenge
- •Retain cycles
- •Weak references
- •Zeroing of weak references
- •For the More Curious: Manual reference counting and ARC History
- •Retain count rules
- •NSArray/NSMutableArray
- •Immutable objects
- •Sorting
- •Filtering
- •NSSet/NSMutableSet
- •NSDictionary/NSMutableDictionary
- •Preprocessor directives
- •#include and #import
- •#define
- •Global variables
- •enum
- •#define vs global variables
- •Writing an NSString to a file
- •Reading files with NSString
- •Writing an NSData object to a file
- •Reading an NSData from a file
- •Target-action
- •Helper objects
- •Notifications
- •Which to use?
- •Callbacks and object ownership
- •Challenge
- •Getting started with iTahDoodle
- •BNRAppDelegate
- •Adding a C helper function
- •Objects in iTahDoodle
- •Model-View-Controller
- •The application delegate
- •Setting up views
- •Running on the iOS simulator
- •Wiring up the table view
- •Adding new tasks
- •Saving task data
- •For the More Curious: What about main()?
- •Edit BNRDocument.h
- •A look at Interface Builder
- •Edit BNRDocument.xib
- •Making connections
- •Revisiting MVC
- •Edit BNRDocument.m
- •Writing init methods
- •A basic init method
- •Using accessors
- •init methods that take arguments
- •Deadly init methods
- •Property attributes
- •Mutability
- •Lifetime specifiers
- •copy
- •More about copying
- •Advice on atomic vs. nonatomic
- •Key-value coding
- •Non-object types
- •Defining blocks
- •Using blocks
- •Declaring a block variable
- •Assigning a block
- •Passing in a block
- •typedef
- •Return values
- •Memory management
- •The block-based future
- •Challenges
- •Anonymous block
- •NSNotificationCenter
- •Bitwise-OR
- •Bitwise-AND
- •Other bitwise operators
- •Exclusive OR
- •Complement
- •Left-shift
- •Right-shift
- •Using enum to define bit masks
- •More bytes
- •Challenge
- •char
- •char *
- •String literals
- •Converting to and from NSString
- •Next Steps
- •Index
Key-value coding
Key-value coding
Key-value coding is the ability to read and set a property using its name. The key-value coding methods are defined in NSObject, and thus every object has this capability.
Open main.m and find the line:
[a setProductName:@"Washing Machine"];
Rewrite the same line to use key-value coding:
[a setValue:@"Washing Machine" forKey:@"productName"];
In this case, the setValue:forKey: method, as defined in NSObject, will go looking for a setter method named setProductName:. If the object doesn’t have a setProductName: method, it will access the instance variable directly.
You can also read the value of a variable using key-value coding. Add a line to main.m that prints out the product name:
int main (int argc, const char * argv[])
{
@autorelease {
Appliance *a = [[Appliance alloc] init]; NSLog(@"a is %@", a);
[a setValue:@"Washing Machine" forKey:@"productName"]; [a setVoltage:240];
NSLog(@"a is %@", a);
NSLog(@"the product name is %@", [a valueForKey:@"productName"]);
}
return 0;
}
In this case, the valueForKey: method, as defined in NSObject, goes looking for an accessor named productName. If there is no productName method, the instance variable is accessed directly.
If you type the name of the property wrong, you won’t get warning from the compiler, but there will be a runtime error. Make this mistake in main.m:
NSLog(@"the product name is %@", [a valueForKey:@"productNammmme"]);
When you build and run it, you will see an error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Appliance 0x100108dd0> valueForUndefinedKey:]:
this class is not key value coding-compliant for the key productNammmme.'
Fix the error before you go on.
Why is key-value coding interesting? Anytime a standard framework wants to push data into your objects, it will use setValue:forKey:. Anytime a standard framework wants to read data from your objects, it will use valueForKey:. For example, Core Data is a framework that makes it easy to save your objects to a SQLite database and then bring them back to life. It manipulates your custom databearing objects using key-value coding.
221
Chapter 30 Properties
To prove that key-value coding will manipulate your variables even if you have no accessors, comment out the @property declaration for productName in Appliance.h:
#import <Foundation/Foundation.h>
@interface Appliance : NSObject { NSString *productName;
int voltage;
}
//@property (copy) NSString *productName; @property (nonatomic) int voltage;
//The designated initializer
- (id)initWithProductName:(NSString *)pn;
@end
Also, remove all use of the methods setProductName: and productName from Appliance.m:
@implementation Appliance
@synthesize voltage;
-(id)initWithProductName:(NSString *)pn
{
self = [super init]; if (self) {
productName = [pn copy];
[self setVoltage:120];
}
return self;
}
-(id)init
{
return [self initWithProductName:@"Unknown"];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %d volts>", productName, voltage];
}
@end
Build and run the program. Note that even though you have no accessor methods for productName, the variable can still be set and read from other methods. This is an obvious violation of the idea of object encapsulation – methods of an object are public, but the instance variables are delicate and should be kept private. If key-value coding weren’t astonishingly useful, no one would tolerate it.
Non-object types
The key-value coding methods are designed to work with objects, but some properties hold a nonobject type, like an int or a float. For example, voltage is an int. How do you set voltage using key-value coding? You use an NSNumber.
222
Non-object types
In main.m, change the line for setting the voltage from this:
[a setVoltage:240];
to this:
[a setValue:[NSNumber numberWithInt:240] forKey:@"voltage"];
Add an explicit accessor to Appliance.m so that you can see it getting called:
- (void)setVoltage:(int)x
{
NSLog(@"setting voltage to %d", x); voltage = x;
}
Build and run the program.
Similarly, if you ask for the valueForKey:@"voltage", you will get back an NSNumber containing the value of voltage.
223
This page intentionally left blank
31
Categories
Categories let a programmer add methods to any existing class. For example, Apple gave us the class NSString. We don’t get the source code to that class, but categories give us the ability to add new methods to NSString.
Create a new Foundation Command Line Tool called VowelCounter. Then create a new file that is an
Objective-C category. Name the category VowelCounting and make it a category on NSString.
Now open NSString+VowelCounting.h and declare a method that you want to add to the NSString class:
#import <Foundation/Foundation.h>
@interface NSString (VowelCounting)
- (int)vowelCount;
@end
Now implement the method in NSString+VowelCount.m:
#import "NSString+VowelCounting.h"
@implementation NSString (VowelCounting)
- (int)vowelCount
{
NSCharacterSet *charSet =
[NSCharacterSet characterSetWithCharactersInString:@"aeiouyAEIOUY"];
NSUInteger count = [self length]; int sum = 0;
for (int i = 0; i < count; i++) {
unichar c = [self characterAtIndex:i]; if ([charSet characterIsMember:c]) {
sum++;
}
}
return sum;
}
@end
Now use the new method in main.m:
225
Chapter 31 Categories
#import <Foundation/Foundation.h>
#import "NSString+VowelCounting.h"
int main (int argc, const char * argv[])
{
@autorelease {
NSString *string = @"Hello, World!";
NSLog(@"%@ has %d vowels", string, [string vowelCount]);
}
return 0;
}
Build and run the program. Nifty, eh? Categories turn out to be very useful.
It is important to note that only this program has the category. If you want the method available in another program, you must add the file to your project and compile the category in when you build that program.
226