CSCI 251: iPhone Application Development Spring Term 2012 Lecture #2: Coding in Objective-C (Chapter 4) Thursday, May 10, 12 Declaring and Defining Classes In Python and Java, all code for a class goes into the same file: class Circle(Shape): class Circle extends Shape { double cx, cy; // center double r; // radius def __init__(self, cx, cy, r): self.cx = cx # center X self.cy = cy # center Y self.r = r # radius public void move(double dx, double dy) { def move(self, dx, dy): self.cx += dx self.cy += dy Thursday, May 10, 12 } } cx += dx; cy += dy; Declaring and Defining Classes In Objective-C, we put the declarations into an interface file, with extension .h // ImAwesome.h #import <UIKit/UIKit.h> @interface ImAwesomeViewController : UIViewController { ! UILabel!*label; ! UIButton *button; } @property (nonatomic, retain) IBOutlet UILabel *label; -(IBAction)sayHello:(id) sender; @end Thursday, May 10, 12 The implementation code goes into a file with the extention .m // ImAwesome.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"M**********r I’m awesome"; // No you’re not dude, don’t lie } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 Good News & Bad News • The Bad News: this is all very confusing • C/C++ programmers: confusing • Java programmers: very confusing • Python programmers: WTF?! Thursday, May 10, 12 Good News & Bad News • The Good News • Xcode writes most of the code for you, “automagically” • We will break it down piece-by-piece, through a “close reading” of the ImAwesome app. Thursday, May 10, 12 import The import statement (like Java's import) tells the compiler to include a file in yours: // ImAwesomeViewController.m #import "ImAwesomeViewController.h" // ImAwesomeViewController.h #import <UIKit/UIKit.h> Thursday, May 10, 12 import The import statement (like Java's import) tells the compiler to include a file in yours: // ImAwesomeViewController.m #import "ImAwesomeViewController.h" // ImAwesomeViewController.h #import <UIKit/UIKit.h> Thursday, May 10, 12 quotes are for your code in your project import The import statement (like Java's import) tells the compiler to include a file in yours: // ImAwesomeViewController.m #import "ImAwesomeViewController.h" // ImAwesomeViewController.h #import <UIKit/UIKit.h> angle brackets are for Apple's toolkit code Thursday, May 10, 12 Pointers • • In Python and Java, a variable can be used to store either a primitive type or an object: n = 5 int n = 5; pi = 3.14159 double pi = 3.14159; c = Circle(100,75, 10) Circle c = new Circle(100,75, 10); How does this actually work? Thursday, May 10, 12 Pointers Primitive types are stored directly in memory: Address 0 1 2 3 4 Thursday, May 10, 12 11010001011010000011010101110101 00000000000000000000000000000101 00000000000000000000001111110100 00000000000000000000000000000000 11110000101000110111010011010101 n = 5 Pointers Objects are stored as pointers to other memory locations: Address 0 1 2 3 4 11010001011010000011010101110101 00000000000000000000000000000101 00000000000000000000001111110100 00000000000000000000000000000000 11110000101000110111010011010101 1012 1013 00100100100011100110100010010101 1014 00000000000001111111110101010101 01 .. . .. . Thursday, May 10, 12 .. . 11111111110000000000000111111010 .. . n = 5 c = Circle(100,75, 10) Pointers Objective-C requires us to declare pointers explicitly, using the * notation: ! UILabel!*label; ! UIButton *button; Thursday, May 10, 12 Properties • When memory is tight and performance crucial, we can help the OS avoid problems by declaring how an object is going to be used; i.e., its properties: @property (nonatomic, retain) IBOutlet UILabel *label; • Let's break these down.... Thursday, May 10, 12 (non)atomic @property (nonatomic, retain) IBOutlet UILabel *label; • Atomic: from Greek α-τοµ-, “indivisible” (ironic, huh?) • atomic declaration (the default) makes variable access “robust in multithreaded environments”; i.e., one operation on a variable must be allowed to complete before another can start. • But this slows things down; so we declare variables nonatomic Thursday, May 10, 12 retain @property (nonatomic, retain) IBOutlet UILabel *label; • retain tells the compiler how memory should be managed for this object • We will return to this topic shortly .... Thursday, May 10, 12 IBOutlet @property (nonatomic, retain) IBOutlet UILabel *label; IBOutlet tells the compiler that this object will be an Interface Builder outlet; i.e., something that we will be able to see and connect to in IB, once we’ve compiled the code. Thursday, May 10, 12 Method Declaration -(IBAction)sayHello:(id) sender; Thursday, May 10, 12 -(IBAction)sayHello:(id) sender; Thursday, May 10, 12 • The - (minus sign) means that this method is an instance method: it has access to the class’s instance variables. • Use of a + (plus sign) would indicate a class method: no access to the class’s instance variables (like static methods in Java). -(IBAction)sayHello:(id) sender; Return type Thursday, May 10, 12 -(IBAction)sayHello:(id) sender; Method name Thursday, May 10, 12 -(IBAction)sayHello:(id) sender; • • Thursday, May 10, 12 Parameter type Here, it is polymorphic (unspecified). The idea is that different types of objects can send a sayHello message to the ViewController. -(IBAction)sayHello:(id) sender; Parameter name Thursday, May 10, 12 // ImAwesomeViewController.h #import <UIKit/UIKit.h> @interface ImAwesomeViewController : UIViewController { ! UILabel!*label; ! UIButton *button; } @property (nonatomic, retain) IBOutlet UILabel *label; -(IBAction)sayHello:(id) sender; @end Thursday, May 10, 12 Implementation Details // ImAwesomeViewController.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"ImAwesome"; } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 // ImAwesomeViewController.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"ImAwesome"; } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 @synthesize • The @synthesize declaration in the implementation is the partner of the @property declaration in the interface • @synthesize tells the compiler to generate accessor methods automagically, according to what you specified in the @property declaration • Thursday, May 10, 12 This seems to go on behind the scenes, so we can just ignore it for now // ImAwesomeViewController.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"ImAwesome"; } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 ! ! label.text = @"ImAwesome"; • • This looks pretty much like Java or Python Equivalent to ! [label setText:@"ImAwesome"]; Thursday, May 10, 12 // ImAwesomeViewController.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"ImAwesome"; } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 Memory Management (Traditional) - (void)dealloc { [label release]; [button release]; [super dealloc]; } Every class you write must have a dealloc method, which 1. releases (calls release on) the object’s instance variables 2. calls the superclass’s dealloc method Thursday, May 10, 12 The alloc ... and release Cycle • • Each object is associated with a reference count. • When the object is released with release, When the object is created with alloc, the new object’s reference count is set to 1. • • Thursday, May 10, 12 the reference count is decremented by 1, and if the reference count is 0, the object’s dealloc method is called automatically The alloc ... and release Cycle 1 alloc +1 Thursday, May 10, 12 0 release -1 The alloc ... and release Cycle • • Thursday, May 10, 12 It’s actually a little more complicated than that What if we want to • Retain an object beyond the duration of the class that created it? • Copy the contents of one object to another? #import <Foundation/NSObject.h> #import <Foundation/NSString.h> @interface AddressCard: NSObject { NSString *first; NSString *last; NSString *email; } // ... -(void) setFirst: (NSString*) f; -(void) setLast: (NSString*) l; @end Thursday, May 10, 12 http://www.otierney.net/objective-c.html#retain #import "AddressCard.h" @implementation AddressCard -(void) setFirst: (NSString*) f { [f retain]; [first release]; first = f; } http://www.otierney.net/objective-c.html#retain Thursday, May 10, 12 The alloc, retain, copy, and release Cycle • • • • Each object is associated with a reference count. When the object is created with alloc (or a new copy is made with copy) the new object’s reference count is set to 1. When retain is called on the object, its reference count is incremented by 1. When the object is released with release, • the reference count is decremented by 1, and • if the reference count is 0, the object’s dealloc method is called automatically Thursday, May 10, 12 The alloc, retain, copy, and release Cycle 1 alloc +1 Thursday, May 10, 12 2 1 0 retain +1 release -1 release -1 The Autorelease Pool Sometimes we want to create an object (e.g. string) without using alloc: • Constant objects: NSString *msg = @"I talk to myself on my Facebook wall"; • Objects created by class methods (c.f. Java "factory methods"): NSString *msg = [NSString stringWithString : @"I'm out of breath"]; Thursday, May 10, 12 The Autorelease Pool • Storage for such objects is managed behind-thescenes by the autorelease pool • XCode creates this for you in your main: // Older version int main(int argc, char *argv[]) { } NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return retVal; // Current version int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([BMAppDelegate class])); } } Thursday, May 10, 12 Other Times to Release Memory // ImAwesomeViewController.m #import "ImAwesomeViewController.h" @implementation ImAwesomeViewController @synthesize label; -(IBAction) sayHello:(id) sender { ! ! label.text = @"ImAwesome"; } - (void)didReceiveMemoryWarning { ! // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; ! ! // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { ! // Release any retained subviews of the main view. ! // e.g. self.myOutlet = nil; } - (void)dealloc { [label release]; [button release]; [super dealloc]; } @end Thursday, May 10, 12 Automatic Reference Counting • Makes ObjectiveC more like a garbage-collected (GC) language (Java, Python) • Without ARC, XCode adds explicit retain / release / autorelease statements automatically, and you are expected to add more as needed. • With ARC, these operations take place behind-the scenes: sufficient for most apps • Conclusion: Unless you need to manage very large data objects explicitly, use ARC! Thursday, May 10, 12 Automatic Reference Counting Thursday, May 10, 12 Delegates • In ordinary language, a delegate is someone you assign to do a certain set of tasks for you. • In Cocoa, a delegate is a class containing methods that perform useful tasks for another class: typically, handling application events (window launch, etc.) • Typically, a delegate implements a set of protocols (like Java's interface mechanism) Thursday, May 10, 12 Delegates // HelloWorldAppDelegate.h #import <UIKit/UIKit.h> @class HelloWorldViewController; @interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; HelloWorldViewController *viewController; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet HelloWorldViewController *viewController; @end Thursday, May 10, 12 // HelloWorldAppDelegate.m #import "HelloWorldAppDelegate.h" #import "HelloWorldViewController.h" @implementation HelloWorldAppDelegate @synthesize window; @synthesize viewController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after app launch [window addSubview:viewController.view]; [window makeKeyAndVisible]; } return YES; - (void)dealloc { [viewController release]; [window release]; [super dealloc]; } @end Thursday, May 10, 12 Protocols Java uses the interface mechanism to enforce a “contract” for services offered by a class: // Movable.java interface Movable { } void move(double dx, double dy); // Circle.java class Circle extends ClosedCurve implements Movable { double cx, cy; // center double r; // radius } Thursday, May 10, 12 public void move(double dx, double dy) { cx += dx; cy += dy; } Objective-C uses a protocol: // Movable.h @protocol Movable -(void) move:(double)dx : withDY:(double) dy; // Circle.h #import "Movable.h" @interface Circle: ClosedCurve <Movable> { double cx, dy; double r; } // Circle.m #import "Circle" @implementation Circle -(void) move:(double)dx : withDY:(double) dy { cx += dx; cy += dy; } Thursday, May 10, 12 Protocols Like Java, Objective-C • Uses this protocol mechanism to avoid relying on multiple inheritance (not supported) • Supports multiple protocols for a class: // Circle.h #import "Movable.h" #import "Resizable.h" @interface Circle: ClosedCurve <Movable, Resizable> { double cx, dy; double r; } Thursday, May 10, 12 Foundation Framework Classes Most languages provide common (foundational) objects via libraries (frameworks): • Strings • Lists / Arrays • Dictionaries / Hashtables Thursday, May 10, 12 Foundation Framework Classes Objective-C inherited its foundation classes from NextStep, so they all begin with NS • NSString • NSArray • NSDictionary Thursday, May 10, 12 NSString NSString *s = @"I met all my friends online"; int n = [s length]; char c = [s characterAtIndex:3]; // etc. Thursday, May 10, 12 NSArray NSArray *arr = [[NSArray alloc] initWithObjects: @"Ken", @"Sara", @"Joshua", nil]; NSMutableArray *mut = [[NSMutableArray alloc] init]; [mut addObject:@"Simon"]; [mut addObject:@"Joshua"]; Thursday, May 10, 12 NSArray int n = [mut count]; int i = [arr indexOfObject:@"Simon]; int j = [arr objectAtIndex:2]; [mut sortArrayUsingSelector: @selector( caseInsensitiveCompare: )]; // etc. Thursday, May 10, 12 NSEnumerator NSEnumerator *e = [arr objectEnumerator]; id obj; while (obj = [e nextObject]) { // do something with obj } Thursday, May 10, 12 NSDictionary NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: @"one", [NSNumber numberWithInt: 1], @"two", [NSNumber numberWithInt: 2], @"three", [NSNumber numberWithInt: 3], nil]; Thursday, May 10, 12 NSDictionary NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init]; // add objects [mutable setObject: @"Tom" forKey: tom@jones.com"]; [mutable setObject: @"Bob" forKey: @"bob@dole.com" ]; Thursday, May 10, 12 Reflection (Introspection) • With dynamic typing, the class to which an object belongs may not be established until run-time • Hence we may want to query this: // get class of this object [myObject class] • We can also get other info: // does object respond to method? [myObject respondsToSelector:@selector(foo)] // is this class a subclass of another? [class1 isSubclassOfClass:class2] Thursday, May 10, 12 What We're Skipping • Categories • Posing • Exceptions • See http://www.otierney.net/objective-c.html#retain interested Thursday, May 10, 12 if you're Syntax Notes #1 Single-argument functions are pretty straightforward: -(void) setEmail: (NSString*) e { [e retain]; [email release]; email = e; } Thursday, May 10, 12 Syntax Notes #1: Passing Arguments • Multi-argument functions use a special syntax: @implementation AddressCard -(AddressCard*) initWithFirst: (NSString*) f ! ! ! ! ! ! last: (NSString*) l ! ! ! ! ! ! email: (NSString*) e { } • // ... So what is the actual name of this method? Thursday, May 10, 12 • Multi-argument functions use a special syntax: @implementation AddressCard -(AddressCard*) initWithFirst: (NSString*) f ! ! ! ! ! ! last: (NSString*) l ! ! ! ! ! ! email: (NSString*) e { } • // ... Its name is initWithFirst:last:email Thursday, May 10, 12 Calling syntax requires (redundant) naming, too: NSString *first =[[NSString alloc] initWithCString: "Tom"]; NSString *last = [[NSString alloc] initWithCString: "Jones"]; NSString *email = [[NSString alloc] initWithCString: "tom@jones.com"]; AddressCard *tom = [[AddressCard alloc] initWithFirst: first ! ! ! ! ! ! ! ! ! ! ! ! ! last: last ! ! ! ! ! ! ! ! ! ! ! ! ! email: email]; Thursday, May 10, 12 Syntax Notes #2: Conditionals / Assignment • What you're used to: if ( x == foo() ) // Java if x == foo(): • This won't work: if ( x = foo() ) if x = foo(): Thursday, May 10, 12 # Python • Q: So why does the author do this: if (self = [super initWithNibName:...]) { } Thursday, May 10, 12 • Q: So why does the author do this: if (self = [super initWithNibName:...]) { } • A: This code is shorthand for self = [super initWithNibName:...]; if (self != nil) { } Thursday, May 10, 12 • What you're used to: if ( x == 3 ) // Java if x == 3: Thursday, May 10, 12 # Python • Q: So why does the author do this: if ( nil == x ) Thursday, May 10, 12 • Q: So why does the author do this: if ( nil == x ) • A: Putting a constant on the left side of the double-equal is a good habit in a language that supports single-equal in conditionals Thursday, May 10, 12 Syntax Notes #3: Less is More Author does this: -(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath { } Thursday, May 10, 12 UITableViewCell *cell = nil; if (indexPath.row == 0) { cell = nameCell; } else { cell = descriptionCell; } return cell; If this is all you want to do, it can be simplified as: -(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath { } Thursday, May 10, 12 return indexPath.row == 0 ? nameCell : descriptionCell; Author does this: if (editing == YES) { // ... } Thursday, May 10, 12 I prefer this: if (editing) { // ... } Thursday, May 10, 12 Syntax Notes # 4: When Is an Object Not an Object? From pp 171-172: Reachability *reach = [[Reachability reachabilityForInternetConnection] retain]; NetworkStatus status = [reach currentReachabilityStatus]; // ... - (NSString *)stringFromStatus:(NetworkStatus)status { NSString *string; switch(status) { // ... Thursday, May 10, 12 /* File: Reachability.h ... */ typedef enum { NotReachable = 0, ReachableViaWiFi, ReachableViaWWAN } NetworkStatus; Thursday, May 10, 12 Syntax Notes #5: Type Casting @implementation WeatherForecast // ... (void)queryService:(NSString *)city withParent: (UIViewController *)controller! { ! viewController = (MainViewController *)controller; Thursday, May 10, 12 @interface WeatherForecast : NSObject { MainViewController *viewController; // ... } Thursday, May 10, 12 Syntax Notes #6: Iterating with in @implementation WeatherForecast // ... (NSString *)fetchContent:(NSArray *)nodes { ! NSString *result = @""; ! for (NSDictionary *node in nodes) { ! ! for (id key in node) { ! ! ! if ([key isEqualToString:@"nodeContent"]) { ! ! ! ! result = [node objectForKey:key]; ! ! ! } ! ! } ! } ! return result; } Thursday, May 10, 12 Syntax Notes #7: Repeating Code What's wrong with this picture? ! // Populate the humidity (an NSString object) ! xpathQueryString = @"//current_conditions/humidity/@data"; ! nodes = PerformXPathQuery(responseData, xpathQueryString); ! humidity = [self fetchContent:nodes]; ! NSLog(@"humidity = %@", humidity); ! ! // Populate the wind (an NSString object) ! xpathQueryString = @"//current_conditions/wind_condition/@data"; ! nodes = PerformXPathQuery(responseData, xpathQueryString); ! wind = [self fetchContent:nodes]; ! NSLog(@"wind = %@", wind); ! ! // Populate the condition (an NSString object) ! xpathQueryString = @"//current_conditions/condition/@data"; ! nodes = PerformXPathQuery(responseData, xpathQueryString); ! condition = [self fetchContent:nodes]; ! NSLog(@"condition = %@", condition); ! Thursday, May 10, 12