Helper Objects are used throughout Cocoa and CocoaTouch, and usually take the form of a _delegate or _dataSource. They are commonly used to add functionality to an existing class without having to subclass it.
In software engineering, the delegation pattern is a technique where an object outwardly expresses certain behaviour but in reality delegates responsibility for implementing that behavior to an associated object in an Inversion of Responsibility. The delegation pattern is the fundamental abstraction that underpins composition (also referred to as aggregation), mixins and aspects.
http://en.wikipedia.org/wiki/Delegation_pattern
The most common use of helper objects in iPhone development is when using UITableViews. When you instantiate a UITableViewController this class is automatically assigned to be the delegate and dataSource of the table view it holds.
self.tableView.delegate = self;
self.tableView.dataSource = self;
The UITableView then calls the appropriate delegate method when it needs information, such as:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
This ask the delegate, what is the height for the row at a given index path, so in your UITableViewController (the delegate) you would write the following, if you wanted the row to be 44 pixels high (the default).
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44;
}
Writing your own delegate
Most delegates are declared as a protocol, so the compiler knows that the delegate implements the required methods.
Example:
You have created a subclass of UITextView (e.g. OBCTextView), that you use in your application to enter both Tweets and SMS. You want to use the same class for both, BUT tweets have a character limit of 140 and SMS have a character limit of 160 (we will pretend that we are still in the 90s).
So we need to create a delegate that asks for the character limit.
- (NSInteger)charachterLimitForTextView:(OBCTextView *)textView;
As mentioned before this should be declared as a protocol
@protocol OBCTextViewDelegate
- (NSInteger) charachterLimitForTextView: (OBCTextView *)textView;
@end
The OBCTextView would call this method like so:
- (void)askDelegateForCharachterLimit {
NSInterger charachterLimit = [delegate charachterLimitForTextView:self];
// Do something with character limit
}
In our SMS view controller we would declare that we implement the delegate protocol.
@interface OBCSMSViewController : UIViewController <OBCTextViewDelegate> {
}
@end
And then in the implementation file you would implement the following method
- (NSInteger) charchterLimitForTextView: (OBCTextView *)textView {
return 160;
}
This method is required for OBCTextView to work so we should also add the key word @required to the protocol
@protocol OBCTextViewDelegate
@required
- (NSInteger)charchterLimitForTextView:(OBCTextView *)textView;
@end
Optional methods
In your delegate, you may also want to implement optional methods. So for this example we will ask for the text color. You can specify that a method is optional using @optional.
@protocol OBCTextViewDelegate
@required
- (NSInteger)charachterLimitForTextView:(OBCTextView *)textView;
@optional
- (UIColor *)textViewColorForTextView:(OBCTextView *)textView;
@end
As it is optional the delegate class (helper object), does not have to implement it. If it does not implement it, and we attempt to call it, the application will crash. So how do we know if we should call the method or not ? The answer is respondsToSelector.
respondsToSelector allows you to ask an object at run time, if it implements a given selector. So for our example:
- (void)getTextColor {
if ([delegate respondsToSelector: @selector(textViewColorForTextView:)]) {
UIColor *textColor = [delegate textViewColorForTextView:self];
// Do something with the text color
}
}
if your class wants to return a textview color, then you simply implement the method.
- (NSInteger)textViewColorForTextView:(OBCTextView *)textView {
return [UIColor redColor];
}
So that finishes off this post on helper objects, and how you can create your own. As always this example was a simple one to make it easy to follow. You could probably implement the above using setter methods.
The first time you see NSNumber your probably say to yourself “What is the point of NSNumber? I already have int, float, double etc”. Although this may be true, there are many occasions when you actually need to use NSNumber.
Adding a number to an array
NSArray (NSMutableArray etc) does not allow you to add primitive types to it. This is because an NSArray is simple a set of pointers to the Objects you add to it. This means if you want to add a number to the array, you first have to wrap it in a NSNumber.
NSArray *numbers = [NSArray arrayWithObject:[NSNumber numberWithInteger:2]];
Number type conversion
NSNumber allows you to easily convert the type of a number e.g. from an int to a float
NSNumber *number = [NSNumber numberWithInteger:2];
float floatValue = [number floatValue];
Once you have used Objective-C for a while you may come across performSelector
. This allows you to carry out advanced techniques like performing a selector after a given delay.
[magicObject performSelector:@selector(performMagicWithNumber:) withObject:[NSNumber numberWithInteger:2] afterDelay:1.0];
This the equivilant of doing the following without a delay:
[magicObject performMagicWithNumber: [NSNumber numberWithInteger:2]];
Persisting Objects (CoreData)
Archiving Objects is a lot easier than archiving primitive types, as NSNumber inherits from NSObject you can use various archiving and de-archiving methods on them. CoreData actually requires you to use NSNumber for persistent storage. One thing that may not be obvious at first glance, is that CoreData (and Objective-C) considers BOOL as a NSNumber.
Summary
You may write the best selling application on the Appstore and never use NSNumber, but if you need to persist your objects or you need access to the various performSelector calls, NSNumber is obviously the way to go.
One thing that took me a while to find out, was how to make an iPhone vibrate. In the end I found out that it is in the audio APIs, and it is just the one line of code.
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
Ever thought “Oh I wish Apple had included method X for class Y”, well that is what categories are for.
Categories are normally used for 2 things.
- Extending a class (using methods, not variables !!!)
- Logically separating a class into different functional areas
So lets look at something that is extremely popular at the moment ….. Twitter, and in paticular the 140 charachter limit.
Apple (for obvious reasons) don’t include Twitter related method calls on there string class NSString.
At the moment you would have to write everywhere in you code, something the resembles the following
NSString *twitterString = @"Objective-C rocks, I never want to look at Java again";
if ([[twitterString] length] < 141) {
// Post to twitter
}
Now this is good, but what if twitter changed their character limit, you would have to go an edit your code everywhere, where you used this check.
So we are going add a method to the NSString class:
- (BOOL)isUnderTwitterCharachterLimit;
This will make are code look like this
NSString *twitterString = @"Objective-C rocks, I never want to look at Java again";
if ([twitterString isUnderTwitterCharachterLimit]) {
// Post to twitter
}
You now need an interface and implementation file, Im going to call them NSStringTwitterCategory.h and NSStringTwitterCategory.m
The interface file (NSStringTwitterCategory.h) is extremely small:
#import <Foundation/Foundation.h>
@interface NSString (Twitter)
- (BOOL)isUnderTwitterCharachterLimit;
@end
Instead of defining a new class in the format:
@interface NewClass: SuperClass {
}
You extend an existing class using the format:
@interface ClassYourExtending (CategoryName)
You can call the category whatever you want, but obviously you want to make it clear.
You then declare the methods like you always do.
The implementation file (NSStringTwitterCategory.m) is also not very complex:
#import "NSStringTwitterCategory.h"
@implementation NSString (Twitter)
- (BOOL)isUnderTwitterCharachterLimit {
if ([self length] < 141)
{
return YES;
} else {
return NO;
}
}
@end
Again you extend the class in the same way as you did in the header file:
@implementation NSString (Twitter)
And you simple define the method as you normally would, nothing strange or fancy. If you want to use this category you just need to import the header file where you want to use it.
This is obviously a very simple example, but I hope it shows you how to use categories in Objective-C.
Here is a little tip on how to tell if a string contains another string, using the underused data type NSRange.
NSRange gives the starting location and the length of a given value, and is often used with arrays and strings. On this occasion we will use it to find the range of a substring within another string. If the range has a location, it contains the given substring. The following code does just that.
NSRange textRange = [string rangeOfString:substring];
if (textRange.location != NSNotFound) {
// Does contain the substring
}
Making this a case insensitive compare is also very trivial, and can be done by lowercasing both strings
NSRange textRange = [[string lowercaseString] rangeOfString:[substring lowercaseString]];
if (textRange.location != NSNotFound) {
// Does contain the substring
}
Welcome to my blog about Cocoa programming and all things Apple. I’ll warn you this is going to get very geeky :)