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
nilis (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 likeidat 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
idthe 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
idand 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
idand static typing. - Does not specify the class of an object pointed to, but does specify which methods it implements.
- Example …
idscrollViewDelegate;
Introspection
- All objects that inherit from
NSObjectknow 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
Classby sending the class methodclassto 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]; } SELis 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 *)descriptionis 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
mutableCopyand modify that. - Or to have a mutable array or a dictionary and
copyit 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]NSMutableArrayinherits all ofNSArrays 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:
NSArrayofNSStringobjects -
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:
NSArrayofid -
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, enums 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.
NSArrayorNSDictionary). - New syntax for creating
NSNumberin iOS6:@() NSNumber *three = @3;NSNumber *underline = @(NSUnderlineStyleSingle); // enumNSNumber *match = @([card match:@[otherCard]]); // expression that returns a primitive typeNSValue- 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
NSArrayandNSSet. - 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/initor 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
NSArrayis a Property List if all of its members are too - So an
NSArrayofNSStringis a Property List. - So is an
NSArrayofNSArrayas long as thoseNSArray's members are Property Lists. - An
NSDictionaryis one only if all keys and values are too - An
NSArrayofNSDictionarys whose keys areNSStrings and values areNSNumbers 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
NSArrayor anNSDictionarythat contains only property list objects.
NSUserDefalts- Lightweight storage of Property Lists.
- It's basically an
NSDictionarythat 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
locationvalueNSNotFound. -
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
UIFontby 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, UIFontTextStyleFootnoteetc.- 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
UIFontDescriptrorattempts 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
NSStringwhere each character has anNSDictionaryof "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
NSAttributedStringall 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.
NSRangePointeris essentially anNSRange *. It's OK to passNULL if you don't care.NSAttributedStringis not anNSString- It does not inherit from
NSStringso you can not useNSStringmethods 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
stringis 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
NSMutableAttributedStringmethod- (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
NSAttributedStrings know how to draw themselves on screen, for example …- (void)drawInRect:(CGRect)aRect;UILabel- You can set its content using the
NSStringpropertytext. - 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
UILabellike font, textColor, etc, for setting the look of all characters. - The attributed string in
UILabelwould be used mostly for "speciality labels".