preventing memory leaks
retaining cycle are a very common source of memory leaks.
to find retain cycles in your programming, you can use apple's profiling tool, instruments.
how do you fix a retain cycle? use a weak reference. A weak reference is a pointer that does not imply ownership.
In a parent-child relationship, the general rule for preventing this type of retain cycle is the parent owns the child, but the child should not own the parent.
when the object that a weak reference points to is deallocated, the pointer variable is zeroed, or set to nil.
Callbacks and object ownership
Notification centers do not own their observers. typically, it will remove itself from the notification center in its dealloc method.
- (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
Objects do not own their delegates or date sources.
- (void) dealloc { [windowThatBossesMeAround setDelegate:nil]; [tableViewThatBegsForDate setDataSource:nil]; }
Objects do not own their targets. If you create an object that is a target, your object should zero the target pointer in its dealloc method:
- (void) dealloc { [buttonThatKeepsSendingMeMessages setTarget:nil]; }
Ch27 Your First iOS Applications
BNRAppDelegate conforms to the UIApplicationDelegate protocol.
prefix.pch --precompiled header file for this project
add a c helper function
in BNRAppDelegate.h file
//declare a helper function before @interface... NSString *docPath(void);
in BNRAppDelegate.m file
NSString *docPath() { NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES); return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@"data.td"];
When an iOS application first launches, there is a lot of behind-the-scenes setting up. During this phase, an instance of UIApplication is created to control your application's state and act as liaison to the operating system. An instance of BNRAppDelegate is also created and set as the delegate of the UIApplication instance( which explains the name "app delegate").
While the application is being launched, it is not ready for work or input. When this changes, the UIApplication instance sends its delegate the message
application:didFinishLaunchingWithOptions:. This method is very important. It's where you put everything that needs to happen or needs to be in place before the user interacts with the application.
In application:didFinishLaunchingWithOptions: to send a message to the table view that makes the BNRAppDelegate instance its data source:
[tableView setDataSource:self];
the BNRAppDelegate must conforms to the UITableViewDataSource protocol.
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath { UITableViewCell *c =[taskTable dequeueReusableCellWithIdentifier:@"Cell"]; if(!c){ c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; } //configure NSString *item= [tasks objectAtIndex:[indexPath row]]; [[c textLabel] setText:item]; return c; }
add task
- (void) addTask: (id) sender { NSString *t = [taskField text]; if( [t isEqualToString:@""]){ return; } //add it to our working array [tasks addObject:t]; [taskTable reloadData]; [taskField setText:@""]; [taskField resignFirstResponder]; }
saving data
when a cocoa touch application quits or is sent to the background, it sends its delegate a message from the UIApplicationDelegate protocol so that the delegate can take care of business and respond to these events gracefully.
- (void) applicationDidEnterBackground:(UIApplication *) application { [task writeToFile:docPath() atomically:YES]; } - (void) applicationWillTerminate:(UIApplication *) application { [tasks writeToFile:docPath() atomically:YES]; }
Ch29 init
Appliance *a = [[Appliance alloc]init];
Because Appliance doesn't implement an init method, it will execute the init method defined in NSObject.
When this happens, all the instance variables specific to Appliance are zero-ed out. Thus, the productName will be nil , and voltage will be zero.
Let's say that every instance of Appliance should start its life with a voltage of 120.
- (id) init { self = [super init]; if(self){ voltage = 120;
//or using accessors
//[self setVoltage:120]; } return self; }
init with product name
- (id) initWithProductName: (NSString *)pn { self = [super init]; if(self){ [self setProductName:pn]; [self setVoltage:120]; } return self; } - (id) init { return [self initWithProductName:@"Unknown"]; }
The initializer is the designated initializer for that class. init is the designated initializer for NSObject, initWithProductName: is the designated initializer for Applicance, and initWithProductName:firstOwnerName: is the designated initializer for OwnedAppliance.
A class has only one designated initializer method. If the class has other initializers, then the implementation of those initializers must call (directly or indirectly) the designated initializer.
You have a responsibility to document your designated initializer in the header file.
#import "Appliance.h" @interface Appliance:NSObject{ NSString *productName; int voltage; } @property (copy) NSString *productName; @property int voltage; //The designated initializer - (id) initWithProductName:(NSString *)pn; @end
and in OwnedAppliance.h file
#import "Appliance.h" @interface OwnedAppliance : Appliance{ NSMutableSet *ownerNames; } //The designated initializer - (id) initWithProductName:(NSString *)pn firstOwnerName:(NSString *)n; ... @end
The best thing to do is to override the superclass's designated initializer in a way that lets developers know that they have made a mistake and tells them how to fix it.
- (id) init { @throw [NSException exceptionWithName:@"WallSafeInitialization" reason:@"Use initWithSecretCode:, not init" userInfo:nil]; }