— boreal-kiss.net

Archive
Tag "objective-c"

In order to make an application universal one has to detect the device’s idiom (iPhone/iPad) at run time. This is easily done with the following conditional:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
	//iPad specific codes.
}
else{
	//iPhone specific codes.
}

You might have used this conditional, for example, when you want to control a view’s rotation or a modal view’s behavior depending on the current device’s idiom. The above conditional is simple and easy to implement when the application is simple. However once the application has lots of behaviors varying on the device’s idiom here and there, your source codes will get messy and hard to read and/or maintain, due to the duplication of this snippet spreading over your project.

Fortunately this will be easily resolved by using the well known State pattern. As the name suggests, State pattern deals with the internal state of its participating context and does some work without unnecessarily exposing other states that the context could have. As describing such patterns only in words is always hard to figure out, I’ll give you an example to walk through with.

Example

Consider the following situation. You want to control a view’s rotation depending on the device’s idiom:

  • On iPhone, the application allows one of portrait modes (Home button at the bottom).
  • On iPad, the application allows both portrait and landscape modes.

The implementation for this in a straightforward way looks like this:

-(BOOL)shouldAutorotateToInterfaceOrientation:
					(UIInterfaceOrientation)interfaceOrientation{
 
	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
		//iPad.
		return YES;
	}
	else{
		//iPhone.
		if (orientation == UIInterfaceOrientationPortrait){
			return YES;
		}
		return NO;
	}
}

In this example, I’ll add some tweaks for the above snippet to look cleaner. That is:

  1. Wrap the codes inside the conditional into a block object.
  2. Register them in a dictionary object.
  3. Retrieve the block object from the dictionary when needed.
  4. Invoke the block.

1-2. Create blocks and register them in dictionaries

The key trick here is wrapping into blocks will done for iPhone and iPad separately:

//For iPhone.
BOOL (^blockiPhone)(UIInterfaceOrientation orientation) = 
						^(UIInterfaceOrientation orientation){
	if (orientation == UIInterfaceOrientationPortrait){
		return YES;
	}
	return NO;
};
 
[_dictionaryForiPhone setObject:[[blockiPhone copy] autorelease] 
						forKey:@"viewRotation"];
//For iPad.
BOOL (^blockiPad)(UIInterfaceOrientation orientation) = 
						^(UIInterfaceOrientation orientation){
	return YES;
};
 
[_dictionaryForiPad setObject:[[blockiPad copy] autorelease] 
						forKey:"viewRotation"];

Here the dictionary objects as a registry assume to be instance variables in the class.

NSMutableDictionary *_dictionaryForiPhone;
NSMutableDictionary *_dictionaryForiPad;

Note that although the value of the key string here is arbitrary, you would better give a meaningful name for its later usage. Also note that since block objects are yet in the stack, they must be copied to the heap before adding to the collections.

3. Retrieve blocks

Then I provide a method retrieving a appropriate block object corresponding to the device’s idiom:

//Incomplete.
-(id)blockForKey:(NSString *)key{
	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
		return [_dictionaryForiPad objectForKey:key];
	}
	else{
		return [_dictionaryForiPhone objectForKey:key];
	}
}

This is redundant since the conditional inside the method gets evaluated each time the method gets called. So add I additional tweaks. These look like this;

//Complete
-(id)blockForKey:(NSString *)key{
	if (!_idiomDetected){
		if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
			_dictionaryForCurrentIdiom = _dictionaryForiPad;
		}
		else{
			_dictionaryForCurrentIdiom = _dictionaryForiPhone;
		}
		_idiomDetected = YES;
	}
 
	return [_dictionaryForCurrentIdiom objectForKey:key];
}

Here two new instance variables are introduced:

BOOL _idiomDetected;
NSDictionary *_dictionaryForCurrentIdiom;

The boolean value is used for testing whether the application already detected the device’s idiom or not. If detected the application skips the conditional. If not, the application tries to detect the current device’s idiom and assign the reference to the appropriate block dictionary to _dictionaryForCurrentIdiom. Doing this, block objects will be retrieved only from this referencing dictionary, hiding the fact that there are actually two different collections of blocks (iPhone/iPad), thanks to State pattern.

4. Invoke blocks

Finally the above method will get called in the right place:

-(BOOL)shouldAutorotateToInterfaceOrientation:
					(UIInterfaceOrientation)interfaceOrientation{
	BOOL (^block)(UIInterfaceOrientation) = [self blockForKey:@"viewRotation"];
	return block(interfaceOrientation);
}

Note that in order to invoke a block correctly one must know the type of the block properly (Since you are the adder of blocks, you must know well their types, right?).

Now my codes are free from conditional, looking very tidy. Although the example here is trivial, this pattern will give you its real strength when the application has more and more device specific behavior (controlling modal views, if iPhone 4S, if iPad 2, and so on).

BKUIIdiomController

If you want to try this but save your time, here is my idiom controller class. The class only has two APIs:

-(void)setBlockForiPhone:(id)blockForiPhone 
				blockForiPad:(id)blockForiPad forKey:(NSString *)key;
-(id)blockForKey:(NSString *)key;

The first method for registering blocks (one for iPhone, the other for iPad) associated with a given key string. The second is for retrieving an appropriate block for the current device’s idiom. You could easily understand how to use this if you read this article. The demo project with full source codes are below:

Read More

Why subclassing? Because I’m bored by preparing a bare-bone singleton from scratch.

+[NSObject initialize] is invoked only once in a thread-safe manner before a class object receives any message (for more detail see NSObject Class Reference). Therefore this method could be a good place for a singleton instance to be created.

Here is my singleton class. I won’t show the complete implementation here. To be robust you should control methods concerning allocation such as alloc, init, copy, etc. However these are not of importance here. To check these visit the following site:

//BKSingleton.h
 
#import <Foundation/Foundation.h>
 
@interface BKSingleton : NSObject{
 
}
 
/**
 * Returns a singleton instance.
 */
+(id)sharedInstance;
 
@end
//BKSingleton.m
 
#import "BKSingleton.h"
 
static NSMutableDictionary *_dictionary = nil;
 
@implementation BKSingleton
 
+(id)sharedInstance{
	NSString *className = NSStringFromClass(self);
	return [_dictionary objectForKey:className];
}
 
+(void)initialize{
	if (!_dictionary){
		_dictionary = [[NSMutableDictionary dictionary] retain];
	}
 
	NSString *className = NSStringFromClass(self);
 
	if (![_dictionary objectForKey:className]){
		[_dictionary setObject:[[[self alloc] init] autorelease] forKey:className];
	}
}
 
@end

When a subclass of this class is first received a message, the initialize method of this class is invoked (i.e., +[BKSingleton initialize]) unless this is overridden in the subclass.

The first trick here is that there is no hard-coded class names inside the initialize method (e.g., something like [MyClass alloc]). Thanks to this even the subclass is received its superclass’s initialize method, it is always instantiated by the correct class.

The second trick is that the instance of a subclass is stored in a static class dictionary associated with its class name if there is not such an object with the same key found in the dictionary. Doing this ensures that the dictionary holds a unique instance for each subclass.

Let’s see an example. There are two subclasses inherited from BKSingleton. They have nothing newly implemented.

//Singleton1
 
#import "BKSingleton.h"
 
@interface Singleton1 : BKSingleton{
 
}
 
@end
 
@implementation Singleton1
 
@end
//Singleton2
 
#import "BKSingleton.h"
 
@interface Singleton2 : BKSingleton{
 
}
 
@end
 
@implementation Singleton2
 
@end

Then run the following code.

//main.m
 
#import <Foundation/Foundation.h>
#import "BKSingleton.h"
#import "Singleton1.h"
#import "Singleton2.h"
 
int main(int argc, char *argv[]) {
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 
	NSLog(@"BKSingleton: %@", [BKSingleton sharedInstance]);
	NSLog(@"Singleton1: %@", [Singleton1 sharedInstance]);
	NSLog(@"Singleton2: %@", [Singleton2 sharedInstance]);
 
	//Once again
	NSLog(@"BKSingleton: %@", [BKSingleton sharedInstance]);
	NSLog(@"Singleton1: %@", [Singleton1 sharedInstance]);
	NSLog(@"Singleton2: %@", [Singleton2 sharedInstance]);
 
	[pool release];
	return 0;
}

The output is the following, ensuring they are al unique, i.e., singletons.

Test[1153:207] BKSingleton: <BKSingleton: 0xe0a880>
Test[1153:207] Singleton1: <Singleton1: 0xe08d20>
Test[1153:207] Singleton2: <Singleton2: 0xe0b620>
Test[1153:207] BKSingleton: <BKSingleton: 0xe0a880>
Test[1153:207] Singleton1: <Singleton1: 0xe08d20>
Test[1153:207] Singleton2: <Singleton2: 0xe0b620>

Related links

Read More