◎ - (NSString *)description is a useful method to override (it's %@ in NSLog()).
◎ NSString
1. International (any language) strings using Unicode.
2. Used throughout iOS instead of C language's char * type.
3. An NSString instance can not be modified! They are immutable.
4. Usual usage pattern is to send a message to an NSString and it will return you
a new one.
◎ NSMutableString (Mutable version of NSString.)
Can do some of the things NSString can do without creating a new one
(i.e. in-place changes).
◎ NSNumber
1. Object wrapper around primitive types like int, float, double, BOOL, etc.
2. ex:
NSNumber *num = [NSNumber numberWithFloat:36.5];
float f = [num floatValue];
◎ NSValue
1. Generic object wrapper for other non-object data types.
2. ex:
CGPoint point = CGPointMake(25.0, 15.0);
NSValue *val = [NSValue valueWithCGPoint:point];
◎ NSData (Bag of bits)
Used to save/restore/transmit data throughout the iOS SDK.
◎ NSDate
Used to find out the time right now or to store past or future times/dates.
◎ NSArray
1. Ordered collection of objects.
2. Immutable. That's right, you cannot add or remove objects to it once it's created.
3. Some methods:
- (void)makeObjectsPerformSelector:(SEL)aSelector;
ex: [array makeObjectsPerformSelector:@selector:(doSomething)];
-(id)lastObject; //returns nil if there are no objects in the array(convenient)
◎ NSDictionary
1. Hash table. Look up objects using a key to get a value.
2. Immutable. That's right, you cannot add or remove objects to it once it's created.
3. Keys are objects which must implement - (NSUInteger)hash &
- (BOOL)isEqual:(NSObject *)obj
4. Keys are usually NSString objects.
◎ NSSet
1. Unordered collection of objects.
2. Immutable. That’s right, you cannot add or remove objects to it once it’s created.
3. Some methods:
- (id)anyObject; // 隨機丟出一個 object
-----------------------------------------------------------------------------------------------------
◎ Enumeration
1. Looping through members of a collection in an efficient manner
2. Language support using for-in (similar to Java)
3. Example: NSSet of id (could just as easily be an NSArray of id)
NSSet *mySet = ...;
for (id obj in mySet) {
// do something with obj, but make sure you don't send it a message it does
// not respond to
if ([obj isKindOfClass:[NSString class]]) {
// send NSString messages to obj with impunity
}
}
// or use: isMenberOfClass:
◎ Property List (.plist)
1. The term "Property List" just means a collection of collections
2. Specifically, it is any graph of objects containing only the following classes:
NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData
3. Why define this term?
Because the SDK has a number of methods which operate on Property Lists.
Usually to read them from somewhere or write them out to somewhere.
[plist writeToFile:(NSString *)path atomically:(BOOL)];
// plist is NSArray or NSDictionary
◎ NSUserDefaults
1. Lightweight storage of Property Lists.
2. It's basically an NSDictionary that persists between launches of your application.
Not a full-on database, so only store small things like user preferences.
3. Read and write via a shared instance obtained via class method
standardUserDefaults:
[[NSUserDefaults standardUserDefaults] setArray:rvArray
forKey:@"RecentlyViewed"];
4. Always remember to write the defaults out after each batch of changes!
[[NSUserDefaults standardUserDefaults] synchronize];
-----------------------------------------------------------------------------------------------------
Objects
Creating Objects
◎ It is a two step operation to create an object in Objective-C
1. Allocating (almost always done with the NSObject class method alloc)
2. Initializing (done with a method that starts with the four letters init)
◎ alloc makes space in heap for the class's instance variables
Also sets them all to zero, so instance variables which are object pointers
start out nil
◎ Each class has a "designated" initializer method
1. NSObject's is init (by convention, all init methods start with the four letters init)
2. When you subclass an object, you must call your superclass's
designated initializer from your designated initializer
(and make sure that it did not fail and return nil)
3. All init methods should be typed (in their declaration) to return id
(not statically typed)
4. Callers should statically type though, e.g., MyObject *obj = [[MyObject alloc] init];
Initializing Objects
◎ Example: A direct subclass of NSObject
@implementation MyObject
- (id)init
{
if (self = [super init]) {
// initialize our subclass here
}
return self;
}
@end
◎ A Tale of Initializer
@implementation MyView
- (id)initWithFrame:(CGRect)aRect {
if (self = [super initWithFrame:aRect]) {
// initialize my subclass here
}
return self;
}
- (id)initToFit:(Shape *)aShape {
CGRect fitRect = [MyView sizeForShape:aShape];
return [self initWithFrame:fitRect];
}
@end
-----------------------------------------------------------------------------------------------------
Object Ownership
◎ When do you take ownership?(所有權)
1. You immediately own any object you get by sending a message starting
with new, alloc or copy.
2. The most common one of these is, of course, the combination
of alloc followed by init...
3. If you get an object from any other source you do not own it, but you can take
ownership by sending it the NSObject message retain.
◎ How do you give up ownership when you are done?
1. Send the object the NSObject message release.
2. Do not send release to an object you do not own. This is very bad.
◎ ex for retain count:
NSString *str = [[NSString alloc]initWithString:@"foo"]; // retain count: 1
NSString *strB = [str retain]; // retain count: 2
[str release]; // retain count: 1
[strB release]; // retain count: 0 ; it will invoke [str dealloc];
Temporary Ownership
◎ So how does this "temporary ownership" thing work?
1. If you want to give someone an object with the "option" for them to take
ownership of it, you must take ownership of it yourself, then send the
object the message autorelease.
2. Your ownership will "expire" at some future time
(but not before the current event is finished).
In the meantime, someone else can send retain to the object if they want to
own it themselves.
◎ Best understood by example
- (Money *)showMeTheMoney:(double)amount {
Money *theMoney = [[Money alloc] init:amount];
[theMoney autorelease]; // We are responsible for sending it release
return theMoney;
}
// Caller
Money *myMoney = [bank showMeTheMoney:4500.00];
[myMoney retain];
Collections and autorelease
◎ Loading up an array or dictionary to return to a caller
@implementation MyObject
- (NSArray *)coolCats
{
NSMutableArray *returnValue = [[NSMutableArray alloc] init];
[returnValue addObject:@"Steve"];
[returnValue addObject:@"Ankush"];
[returnValue addObject:@"Sean"];
[returnValue autorelease];
return returnValue;
}
@end
// But there's a better way. Get an autoreleased NSMutableArray in the first place.
@implementation MyObject
- (NSArray *)coolCats
{
NSMutableArray *returnValue = [NSMutableArray array];
[returnValue addObject:@"Steve"];
[returnValue addObject:@"Ankush"];
[returnValue addObject:@"Sean"];
return returnValue;
}
@end
// But there's an even better way! Use collection classes "create with" methods.
@implementation MyObject
- (NSArray *)coolCats
{
return [NSArray arrayWithObjects:@"Steve", @"Ankush", @"Sean", nil];
}
@end
Other convenient create with methods (all return autoreleased objects):
1. [NSString stringWithFormat:@"Meaning of %@ is %d", @"life", 42];
2. [NSDictionary dictionaryWithObjectsAndKeys:ankush, @"TA", janestudent,
@"Student", nil];
3. [NSArray arrayWithContentsOfFile:(NSString *)path];
Other Ownership Rules
◎ Collections take ownership when an object is added to them
NSArray, NSDictionary, NSSet
(NSDictionary takes ownership of both keys and values).
They then release ownership when an object is removed.
◎ Think of @"string" as autoreleased
In reality, they are constants, so retain and release have no effect on them.
◎ NSString objects are usually sent copy rather than retain (會產生新物件)
1. That gives you an immutable version of the string to hold on to.
2. The method copy in NSMutableString returns an NSString,
not an NSMutableString.
3. Of course, you still release it when you are done with it.
Deallocation
◎ What happens when the last owner calls release?
A special method, dealloc, is called on the object
& the object's memory is returned to the heap.
After this happens, sending a message to the object will crash your program.
◎ You should override dealloc in your classes, but NEVER call it!
1. It only gets called by release when the last owner has released the object.
2. The one exception about calling it is that you must call [super dealloc]
in your dealloc.
◎ Example
- (void)dealloc
{
[brain release];
[otherObjectInstanceVariable release];
[super dealloc];
}
@property
◎ There are three options for setters made by @synthesize
1. @property (retain) NSArray *myArrayProperty; // retain count + 1
2. @property (copy) NSString *someNameProperty; // retain count + 1
3. @property (assign) id delegate;
◎ Example for retain:
@property (retain) NSString *name;
@synthesize
// will create a setter equivalent to this ...
- (void)setName:(NSString *)aString
{
[name release];
name = [aString retain];
}
// Note that @synthesize will release the previous object
// (if any, could be nil) before setting and retaining the new one.
◎ Example for copy:
@property (copy) NSString *name;
@synthesize
// will create a setter equivalent to this ...
- (void)setName:(NSString *)aString
{
[name release]; // Still release-ing before copying.
name = [aString copy];
}
◎ Example for assign:
@property (assign) NSString *name;
@synthesize
// will create a setter equivalent to this ...
- (void)setName:(NSString *)aString
{
name = aString; // No release here because we never retain or copy.
}
-----------------------------------------------------------------------------------------------------
Protocols
◎ Similar to @interface, but no implementation
@protocol Foo
- (void)doSomething; // implementors must implement this
@optional
- (int)getSomething; // implementors do not need to implement this
@required
- (NSArray *)getManySomethings:(int)howMany; // must implement
@end
// The above is added to a header file
◎ Classes then implement it
// They must proclaim that they implement it in their @interface
@interface MyClass : NSObject <Foo>
....
@end
◎ You must implement all non-@optional methods
◎ Can now declare id variables with added protocol requirement
id<Foo> obj = [[MyClass alloc] init]; //多型: compiler will love this!
◎ Also can declare arguments to methods to require protocol
- (void)giveMeFooObject:(id <Foo>)anObjectImplementingFoo;
// If you call this and pass an object which does not implement Foo
// ... compiler warning!
◎ Number one use of protocols in iOS: delegates and dataSources
The delegate or dataSource is always defined as an assign @property
@property (assign) id <UISomeObjectDelegate> delegate;
-----------------------------------------------------------------------------------------------------
Views
◎ Hierarchical
1. Only one superview - (UIView *)superview
2. Can have many (or zero) subviews - (NSArray *)subviews
3. Subview order (in that array) matters:
those later in the array are on top of those earlier
◎ it can be done in code as well
- (void)addSubview:(UIView *)aView;
- (void)removeFromSuperview;◎ View Transparency(透明度)
1. By default, drawing is fully opaque(預設: 不透明)
2. you can hide a view completely by setting hidden property
@property BOOL hidden;
myView.hidden = YES; // view will not be on screen and will not handle events
(不畫)
◎ View Memory Management
1. A superview retains its subviews
Once you put a view into the view hierarchy, you can release your ownership
if you want
2. Be careful when you remove a view from the hierarchy
(1). If you want to keep using a view, retain ownership before you send
removeFromSuperview
(2). Removing a view from the hierarchy immediately causes a release on it
(not autorelease)
(3). So if there are no other owners, it will be immediately deallocated
(and its subviews released)
3. IBOutlets are retained
-----------------------------------------------------------------------------------------------------
IBOutlet Memory Management
◎ UIViewController calls a method on itself after view load/unload
- (void)viewDidLoad is called just after the Controller's view has been created
(& outlets are set). This method is an awesome place to set initial state
in an outlet (if you couldn't do it in IB).
- (void)viewDidUnload is called just after the view has been "unloaded"
This is the place we can release our outlets in the case of unloading.
◎ Here's how we release our outlets in viewDidUnload & dealloc
// private method to share code between viewDidUnload and dealloc
- (void)releaseOutlets {
self.myOutlet = nil;
self.myOtherOutlet=nil;
}
- (void)viewDidUnload {
[self releaseOutlets];
}
- (void)dealloc {
[self releaseOutlets];
[super dealloc];
}
-----------------------------------------------------------------------------------------------------
Coordinates
◎ Origin of a view's coordinate system is upper left
◎ Units are "points" (not pixels)
@property CGFloat contentScaleFactor;
// returns pixels per point on the screen this view is on.
// This property is not (readonly), but you should basically pretend that it is for this class.
◎ Views have 3 properties related to their location and size
1. @property CGRect bounds;
// your view's internal drawing space's origin and size
The bounds property is what you use inside your view's own implementation
It is up to your implementation as to how to interpret bounds.origin
2. @property CGPoint center;
// the center of your view in your superview's coordinate space
3. @property CGRect frame;
// a rectangle in your superview's coordinate space which entirely
// contains your view's bounds.size
-----------------------------------------------------------------------------------------------------
Creating Views
◎ How do you create a UIView in code (i.e. not in IB)?
Just use alloc and initWithFrame: (UIView's designated initializer).
◎ Example:
CGRect buttonRect = CGRectMake(20, 20, 120, 37);
UIButton *button = [[UIButton alloc] initWithFrame:buttonRect];
button.titleLabel.text = @"Do it!";
[window addSubview:button];
[button release]; // okay because button is in view hierarchy now
-----------------------------------------------------------------------------------------------------
Custom Views
◎ Drawing is easy ... create a UIView subclass & override 1 method
- (void)drawRect:(CGRect)aRect; // 會使用 CGCore Graphic, CPU 畫圖效能好
You can optimize by not drawing outside of aRect if you want (but not required).
◎ NEVER call drawRect:!! EVER! Or else!
Instead, let iOS know that your view's drawing is out of date with one of
these UIView methods:
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)aRect;
It will then set everything up and call drawRect: for you at an appropriate time
Obviously, the second version will call your drawRect: with only rectangles that
need updates
◎ So how do I implement my drawRect:?
Use the Core Graphics framework
The API is C (not object-oriented)
-----------------------------------------------------------------------------------------------------
Context
◎ How to get this magic context?
Call the following C function inside your drawRect: method to get
the current graphics context ...
CGContextRef context = UIGraphicsGetCurrentContext();
You do not have to free it or anything later, just use it for all drawing.
-----------------------------------------------------------------------------------------------------
Graphics State
◎ Special considerations for defining drawing "subroutines"
- (void)drawGreenCircle:(CGContextRef)ctxt {
UIGraphicsPushContext(ctxt);
[[UIColor greenColor] setFill];
// draw my circle
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)aRect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setFill];
// do some stuff
[self drawGreenCircle:context];
// do more stuff and expect fill color to be red
}
-----------------------------------------------------------------------------------------------------
Drawing Images
◎ Use UIImageView to draw images, but if you feel you must ...
◎ Create a UIImage object from a file in your Resources folder
UIImage *image = [UIImage imageNamed:@"foo.jpg"];
◎ Or create one from a named file or from raw data
UIImage *image = [[UIImage alloc] initWithContentsOfFile:(NSString *)fullPath];
UIImage *image = [[UIImage alloc] initWithData:(NSData *)imageData];
◎ Or you can even create one by drawing with CGContext functions
UIGraphicsBeginImageContext(CGSize);
// draw with CGContext functions
UIImage *myImage = UIGraphicsGetImageFromCurrentContext(); // screenshot
UIGraphicsEndImageContext();
◎ Now blast the UIImage's bits into the current graphics context
UIImage *image = ...;
[image drawAtPoint:(CGPoint)p]; // p is upper left corner of the image
[image drawAtPoint:(CGPoint)p]; // p is upper left corner of the image
[image drawInRect:(CGRect)r]; // scales the image to fit in r
[imagedrawAsPatternInRect:(CGRect)patRect; //tiles the image into patRect
◎ Aside: You can get a PNG or JPG data representation of UIImage
NSData *jpgData =
UIImageJPEGRepresentation((UIImage *)myImage, (CGFloat)quality);
NSData *pngData =
UIImagePNGRepresentation((UIImage *)myImage);