More Objective-C
- creating objects
- nil
- The very important type id (and the concept of 'dynamic binding')
- Introspection
- Demo (improving match: with introspection)
Foundation
- NSObject, NSArray, NSNumber, NSDate, NSDictionary, et al.
- Property lists and NSUserDefaults
- NSRange
- UIFont and UIColor (not actually foundation, they are in UIKit)
- NSAttributedString (and its UIKit extensions)
- Attributed string in UITextView and UILabel
Creating Objects
- Most of the time we create objects with alloc and init...
NSMutableArray *cards = [[NSMutableArray alloc] init];
CardMatchingGame *game = [[CardMatchingGame alloc] initWithCardCount:12 usingDeck:d];
- Or with class methods
- NSString's
+ (id)stringWithFormat:(NSString *) format, ...
NSString *moltuae = [NSString stringWithFormat:@"%d", 42];
- UIButton's
+ (id)buttonWithType:(UIButtonType)buttonType;
- NSMutableArray's
+ (id)arrayWithCapacity:(int)count;
- NSArray's
+ (id)arrayWithObject:(id)anObject;
- Sometimes both a class creator method and init method exist
[NSString stringWithFormat:…]
same as[[NSString alloc] initWithFormat:…];
- Don't be disturbed by this, using either version is fine.
- iOS seems to be mooving towards the alloc/init versions with the new API, but is mostly neutral.
- You can also ask other objects to create new objects for you
- NSString's
- (NSString *)stringByAppendingString:(NSString *)otherString;
- NSArray's
- (id)objectAtIndex:(int)index;
- Unless the method has the word "
copy
" in it, if the object already exists, you get a pointer to it. - If the object does not already exist (like the first two examples), then you're creating.
- NSString's
nil
- Sending messages to
nil
is (mostly) OK: No code is executed. - If the method returns a value, it will return 0
int i = [obj methodWhichReturnsAnInt]; // i will be 0 if obj is nil
- It is absolutely fine to depend upon this and write code that uses it, don't get to cute though.
- Be careful, if the method returns a C struct, the return value is undefined!
CGPoint p = [obj getLocation]; // p will have an undefined value if obj is nil
Dynamic Binding
- Objective-C has an important type called
id
- It means "pointer to an object of unknown/unspecified" type.
- In reality all object pointers eg:
(NSString *)
are treated likeid
at runtime. - But at compile time, if you type something as
NSString *
instead ofid
, the compiler can help you. - It can find bugs and suggest which methods would be appropriate to send it to, etc.
- If you type something using
id
the compiler can't help very much, because it doesn't know much. - Figuring out the code to execute when a message is sent at runtime is called "dynamic binding".
- Is it safe?
- Treating all object pointers as "pointers to unknown type" at runtime seems dangerous, right?
- What stops you from sending a message to an object that it doesn't understand?
- Nothing! and your program crashes if you do so. … Oh my, Objective-C programs must crash a lot!
- Not really.
- Because we mostly use static typing (eg:
NSString *
) and the compiler is really smart. - Static typing
NSString *s = @"x"; // "statically" typed (compiler will warn if s is sent non NSString messages).
id obj = s; // Not statically typed, but perfectly legal; compiler can't catch [obj rank]
NSArray *a = obj; // also legal, but obviously could lead to some big trouble!
- Compiler will not complain about assignments between an
id
and a statically typed variable. - Sometimes you are silently doing this. You have already done so!
-
- (int)match:(NSArray *)otherCards { PlayingCard *otherCard = [otherCards firstObject]; // firstObject returns id }
- Never use
id *
(that would "a pointer to a pointer to and object").
Object Typing
-
@interface Vehicle - (void)move; @end @interface Ship : Vehicle - (void)shoot; @end Ship *s = [[Ship alloc] init]; [s shoot]; [s move]; Vehicle *v = s; [v shoot]; id obj = …; [obj shoot]; // No compiler warning! The compiler knows that the method shoot exists. // So it is not impossible that obj might respond to it. But we have not // typed obj enough for the compiler to be sure that this is wrong so no // warning! This might crash at runtime if obj is not a Ship (or an // object of some other type that implements the shoot method). NSString *hello = @"hello"; [hello shoot]; // Compiler warning! The compiler knows that NSString // objects do not respond to shoot. Guaranteed crash at // runtime. Ship *helloShip = (Ship *)hello; // No compiler warning! We are 'casting' here. The // compiler thinks that we know what we're doing. [helloShip shoot]; // No compiler warning! We have forced the compiler to think that // the NSString is a Ship, guaranteed crash at run time. [(id)hello shoot]; // No compiler warning! We have forced the compiler to ignore the // object type by 'casting' in line. Guaranteed crash at run time.
Dynamic Binding
- So when would we ever intentionally use this dangerous thing?
- When we want to mix objects of different classes in a collection (eg: NSArray).
- When we want to support the 'blind, structured' communication in MVC (ie. delegation).
- And there are other generic or blind communication needs.
- But to make these things safer, we're going to use two things: Introspection and protocols.
- Introspection
- Asking at runtime what class an object is or what messages can be sent to it.
- Protocols
- A syntax that is in between
id
and static typing. - Does not specify the class of an object pointed to, but does specify which methods it implements.
- Example …
id
scrollViewDelegate;
Introspection
- All objects that inherit from
NSObject
know these methods … isKindOfClass:
returns whether an object is that kind of class (inheritance included).isMemberOfClass:
returns whether an object is that kind of class (no inheritance).respondsToSelector:
returns whether an object responds to a given method.- It calculates the answer to these questions at runtime (i.e. the instant that you send them).
- Arguments to these methods are a little tricky
- Class testing methods take a
Class
- You get a
Class
by sending the class methodclass
to a class (not the instance method class). -
>if ([obj isKindOfClass:[NSString class]]) { NSString *s = [(NSString *)obj stringByAppendingString:@"xyzzy"]; }
- Method testing methods take a selector (
SEL
) - Special
@selector()
directive turns the name of a method into a selector. -
if ([obj respondsToSelector:@selector(shoot)]) { [obj shoot]; } else if ([obj respondsToSelector:@sellector(shootAt)]) { [obj shootAt:target]; }
SEL
is the Objective-C 'type' for a selectorSEL shootSelector = @selector(shoot);
SEL shootAtSelector = @selector(shootAt);
SEL moveToSelector = @selector(moveTo:withPenColor:);
- If you have a
SEL
, you can also ask an object to perform it … - Using the
performSelector:
orperformSelector:withObject:
methods inNSObject
[obj performSelector:shootSelector];
[obj performSelector:shootAtSelector withObject:coordinate];
- Using
makeObjectsPerformSelector:
method inNSArray
[array makeObjectsPerformSelector:shootSelector]; // cool huh?
[array makeObjectsPerformSelector:shootAtSelector withObject:target];
- In UIButton,
- (void)addTarget:(id)anObject action:(SEL)action …;
[button addTarget:self action:@selector(digitPressed:) …];
Demo
-
- (int)match:(NSArray *)otherCards { int score = 0; if ([otherCards count] == 1) { id card = [otherCards firstObject]; if ([card isKindOfClass:[PlayingCard class]]) { PlayingCard *otherCard = (PlayingCard *)card; if ([self.suit isEqualToString:otherCard.suit]) { score = 1; } else if (self.rank == otherCard.rank) { score = 4; } } } return score; }
Foundation Framework
NSObject
- Base class for pretty much every object in the iOS SDK
- Implements introspection methods mentioned earlier.
- (NSString *)description
is a useful method to override (it's%@
inNSLog
).- Example …
NSLog(@"array contents are %@", myArray);
- The
%@
is replaced with the result of involkingmyArray description];
- Copying objects. This is an important concept to understand (why mutable vs. immutable?).
-
- (id)copy; // Not all objects implement mechanism (raises exception if not) - (id)mutableCopy; // Not all objects implement mechanism (raises exception if not)
- It's not uncommon to have and array or dictionary and make a
mutableCopy
and modify that. - Or to have a mutable array or a dictionary and
copy
it to 'freeze it' and make it immutable. - Making copies of collection classes is very efficient, so don't sweat doing so.
NSArray
- Ordered collection of objects.
- Immutable.
- All objects in the array are held onto strongly.
- Usually created by manipulating other arrays or with
@[]
. - You already know these key methods …
- (NSUInteger)count;
- (id)objectAtIndex:(NSUInteger)index; // Crashes if index is out of bounds, returns id
- (id)lastObject; // returns nil and does not crash if there are no objects in the array
- (id)firstObject; // retursn nil and does not crash if there are no objects in the array
- But there are a lot of very interesting methods in this class. Examples …
- (NSArray *)sortedArrayUisingSelector:(SEL)aSelector;
- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(id)selectorArgument
- (NSString *)componentsJoinedByString:(NSString *)separator;
NSMutableArray
- Mutable version of
NSArray
. - Create with alloc/init or …
+ (id)arrayWithCapacity(NSUInteger)numItems; // numItems is a performance hint only
+ (id)array; // [NSMutableArray array] is just like [[NSMutableArray alloc] init]
NSMutableArray
inherits all ofNSArray
s methods.- Not just
count
,objectAtIndex:
, etc. but also the more interesting ones mentioned previously. - And you know that it implements these key methods as well …
- (void)addObject:(id)object; // To the end of the array, note that id is the type
- (void)insertObject:(id)object atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
Enumeration
- Looping though members of an array in an efficient manner
- Language support using
for
-in
- Example:
NSArray
ofNSString
objects -
NSArray *myArray = …; for (NSString *string in myArray) { // no way for compiler to know what myArray contains double value = [string doubleValue]; // crash here if string not an NSString }
- Example:
NSArray
ofid
-
NSArray *myArray = …; for (id obj in myArray) { // do something with obj but make sure that you don't send it a message it does not respond to if ([obj isKindOfClass:[NSString class]]) { // send NSString messages to obj with no worries } }
NSNumber
- Object wrapper around types like
int, float, double, BOOL, enum
s etc. NSNumber *n = [NSNumber numberWithIn:36];
float f = [n floatValue]; // would return 36.0 as a float (i.e. will convert types)
- Useful when you want to put these primitive types in a collection (i.e.
NSArray
orNSDictionary
). - New syntax for creating
NSNumber
in iOS6:@()
NSNumber *three = @3;
NSNumber *underline = @(NSUnderlineStyleSingle); // enum
NSNumber *match = @([card match:@[otherCard]]); // expression that returns a primitive type
NSValue
- Generic object wrapper for some non-object, non-primitive data types (i.e. C structs).
NSValue *edgeInsetsObject = [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1,1,1,1)];
NSData
- "bag of bits." Used to save/restore/transmit raw data throughout the iOS SDK.
NSDate
- Used to find the time right now or to store past or future times/dates.
- See also
NSCalendar, NSDateFormatter, NSDateComponents
. - If you are going to display a date in your UI, make sure you study this in detail (localisation).
NSSet
/NSMutableSet
- Like and array but no ordering (no objectAtIndex: method).
member:
is and important method (returns and object if if there is one in the setisEqual:
to it).- Can union and intersect other sets.
NSOrderedSet
/NSMutableOrderedSet
- Sort of a cross between
NSArray
andNSSet
. - Objects in an ordered set are distinct. You can't put the same object in multiple times like array.
NSDictionary
- Immutable collection objects looked up by key (simple hash table).
- All keys and values are held onto strongly by an
NSDictionary
. - Can create with this syntax:
@{ key1 : value1, key2 : value2, key3 : value3 }
-
NSDictionary *colors = @{ @"green" : [UIColor greenColor], @"blue" : [UIColor blueColor], @"red" : [UIColor redColor] };
- Look up using "array like" notation …
-
NSString *colorString = …; UIColor *colorObject = colors[colorString]; // works the same as objectForKey: below - (NSInteger)count; - (id)objectForKey:(id)key; // key is an object that must implement the hash and isEqual: methods properly NSStrings make good keys because they hash based on their characters and do isEqual:. Beware NSObject's hash and isEqual: are pretty bad (pointer is the hash, isEqual: is ==)
NSMutableDictionary
- Mutable version of
NSDictionary
. - Create using
alloc
/init
or one of the+ (id)dictionary
… class methods. - In addition to all the methods inherited from
NSDictionary
, here are some important methods … - (void)setObject:(id)object forKey:(id)key;
- (void)removeObjectForKey:(id)key;
- (void)removeAllObjects;
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary;
- Enumeration, looping though the keys or values of a dictionary
- Example …
-
NSDictionary *myDictionary = …; for (id key in myDictionary) { // do something with key here id value = [myDictionary objectForKey:key]; // do something with value here }
Property List
- The term "Property List" just means a collection of collections
- It's just a phrase (not a language thing). It means any graph of objects containing only:
NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData
(or mutable subclass thereof)- An
NSArray
is a Property List if all of its members are too - So an
NSArray
ofNSString
is a Property List. - So is an
NSArray
ofNSArray
as long as thoseNSArray
's members are Property Lists. - An
NSDictionary
is one only if all keys and values are too - An
NSArray
ofNSDictionary
s whose keys areNSString
s and values areNSNumber
s is one. - Why define this term?
- Because the SDK has a number of methods which operate on Property Lists.
- Usually to read them from somewhere or to write them out to somewhere. Example:
- (void)writeToFile:(NSString *)path atomically:(BOOL)atom;
- This can only be sent to an
NSArray
or anNSDictionary
that contains only property list objects.
NSUserDefalts
- Lightweight storage of Property Lists.
- 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.
- Read and write via a shared instance obtained via class method
standardUserDefaults
… [[NSUserDefaults standardUserDefaults] setArray:rvArray forKey:@"RecentlyViewed"];
- Sample methods:
- (void)setDouble:(double)aDouble forKey:(NSString *)key;
- (NSInteger)integerForKey:(NSString *)key; // NSInteger is a typedef to 32 or 64 bit int
- (void)setObject:(id)obj forKey:(NSString *)key; // obj must be a property list
- (NSArray *)arrayForKey:(NSString *)key; // will return nil if value for key is not NSArray
- Always remember to write the defaults out after each batch of changes!
[[NSUserDefaults standardUserDefaults] synchronize];
NSRange
- C struct (not a class)
- Used to specify subranges inside strings and arrays (et. al).
-
typedef struct { NSUInteger location; NSUInteger length; }
- Important
location
valueNSNotFound
. -
NSString *greeting = @"hello world"; NSString *hi = @"hi"; NSRange r = [greeting rangeOfString:hi; // finds range of hi characters inside greeting if (r.locaiton == NSNotFound) { /* couldn't find hi inside greeting */ }
Colors
UIColor
- An object representing a color.
- Initializers for creating a color based upon RGB, HSB and even pattern (UIImage).
- Colors can also have alpha
(UIColor *color = [otherColor colorWithAlphaComponent:0.3]).
- A handful of "standard" colors have class methods (e.g.
[UIColor greenColor]
). - A few "system" colors also have class methods (e.g.
[UIColor lightTextColor]
).
Fonts
UIFont
- It is best to get a
UIFont
by asking for the preferred font for a given text style … UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
- Some other styles (see UIFontDescriptor documentation for even more styles) …
UIFontTextStyleHeadline, UIFontTextStyleCaption1, UIFontTextStyleFootnote
etc.- There are also "system" fonts.
- They are used in places like button titles.
+ (UIFont *)systemFontOfSize:(CGFloat)pointSize;
+ (UIFont *)boldSystemFontOfSize:(CGFloat)pointSize;
- You should never use these for your user's content.
- Use
preferredFontForTextStyle:
for that. UIFontDescriptor
- Fonts are designed by artists.
- They aren't always designed to fit any sort of categorization.
- Some fonts have Narrow or Bold or Condensed faces, some do not.
- Even "size" is sometimes a designed-in aspect of a particular font.
- A
UIFontDescriptror
attempts to categorize a font anyway. - It does so by family, face, size and other attributes.
- You can then ask for fonts that have these attributes and get a "best match".
- Understand that a best match for a "bold" font may not be bold if there is no such designed face.
Attributed String
- How text looks on screen
- The font has a lot to do with how text looks on screen.
- But there are a lot of other determiners (color, whether it is "outlined", stroke width, underlining, etc.).
- You put the text together with a font and these other determiners using
NSAttributedString.
NSAttributedString
- Think of it as an
NSString
where each character has anNSDictionary
of "attributes". - The attributes are things like font, the color, underlining or not, etc, of the character.
- It is not however, a subclass of
NSString
(more on this in a moment). - Getting Attributes
- You can ask an
NSAttributedString
all about the attributes at a given location in the string. -
- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)range;
- The range is returned and lets you know for how many characters the attributes are identical.
- There are also methods to ask just about a certain attribute you might be interested in.
NSRangePointer
is essentially anNSRange *
. It's OK to passNULL if you don't care.
NSAttributedString
is not anNSString
- It does not inherit from
NSString
so you can not useNSString
methods on it. - If you need to operate on the characters, there is this great method in
NSAttributedString
… - (NSString)string;
- For example, to find a substring in an
NSAttributedString
, you can do this … -
NSAttributedString *attributedString = …; NSString *substring = …; NSRange r = [[attringutedString string] rangeOfString:substring];
- The method
string
is guaranteed to be high performance but is volatile. - If you want to keep it around, make a copy of it.
NSMutableAttributedString
- Unlike
NSString
, we almost never use mutable attributed string. - Adding or setting attributes on the characters
- You can add an attribute to a range of characters …
- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range;
- … which will change the values of attributes in attributes and not touch other attributes.
- Or you can set the attributes in a range …
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range;
- … which will remove all other attributes in that range in favour of the passed attributes.
- You can also remove a specific attribute from a range …
- (void)removeAttribute:(NSString *)attributeName range:(NSRange)range;
- Modifying the contents of the string (changing the characters)
- You can do that with methods to append, insert, delete or replace characters.
- Or call the
NSMutableAttributedString
method- (NSMutableString *)mutableString
- and modify the returned
NSMutableString
(attributes will incredibly be preserved). - So what kind of attributes are there?
-
UIColor *yellow = [UIColor yellowColor]; UIColor *transparentYellow = [yellow colorWithAlphaComponent:0.3]; @{ NSFontAttributeName : [UIFont preferredFontWithTextStyle:UIFontTextStyleHeadline] NSForegroundColorAttributeName : [UIColor greenColor], NSStrokeWidthAttributeName : @-5, /* negative value sets stroke to outline with no fill */ NSStrokeColorAttributeName : [UIColor redColor], NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle), NSBackgroundColorAttributeName : transparentYellow }
- Where do attributed string get used?
- UIButton's
- (void)setAttributedTitle:(NSAttributedString *)title forState:…;
- UILabel's
@property (nonatomic, strong) NSAttributedString *attributedText;
- UITextView's
@property (nonatomic, readonly) NSTextStorage *textStorage;
UIButton
- Extremely useful here
- Drawing strings directly
NSAttributedString
s know how to draw themselves on screen, for example …- (void)drawInRect:(CGRect)aRect;
UILabel
- You can set its content using the
NSString
propertytext
. - But it also has a property to set/get its text using an
NSAttributedString
… @property (nonatomic, strong) NSAttributedString *attributedText;
- Not that this attributed string is not mutable
- So to modify what is in a UILabel, you must make a
mutableCopy
, modify it, then set it back. -
NSMutableAttributedString *labelText = [myLabel.attributedText mutableCopy]; [lableText setAttributes:…]; myLabel.attributedText = labelText;
- Don't need this very often
- There are properties inside
UILabel
like font, textColor, etc, for setting the look of all characters. - The attributed string in
UILabel
would be used mostly for "speciality labels".