UIWindow.rootViewControllerって?

iOSの古いソースコードの初期処理をみていて、そういえば昔とだいぶ違ってきたなぁ……と思ったので、ちょっとUIWindowまわりを調べてみました。

初期化処理、古いコードと新しいコード。

まず、こちらが古いコード。 iOS2.0のころのAppleが出していたHello Worldのサンプルコードです。

#import "HelloWorldAppDelegate.h"
#import "MyViewController.h"

@implementation HelloWorldAppDelegate

@synthesize window;
@synthesize myViewController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    
    // Set up the view controller
    MyViewController *aViewController = [[MyViewController alloc] initWithNibName:@"HelloWorld" bundle:[NSBundle mainBundle]];
    self.myViewController = aViewController;
    [aViewController release];
        
    // Add the view controller's view as a subview of the window
    UIView *controllersView = [myViewController view];
    [window addSubview:controllersView];
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [myViewController release];
    [window release];
    [super dealloc];
}

@end

こちらが新しいコード。 Xcode 4.6.3で作ったものです。(ARC使用、storyboard未使用)

#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
}

- (void)applicationWillTerminate:(UIApplication *)application
{
}

@end

applicationDidFinishLaunching

まず、初期化処理のシグネチャー自体が異なりますね。

古いコードではこちらのメソッドが、 - (void)applicationDidFinishLaunching:(UIApplication )application; 新しいコードではこちらの起動オプション付きのメソッドが使われています。 - (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions (Appleが推奨しているのは起動オプション付きです。)

上のコード例にはでてきませんが、iOS6.0からこのメソッドも初期化処理で呼ばれるようになっています。 - (BOOL)application:(UIApplication )application willFinishLaunchingWithOptions:(NSDictionary )launchOptions

didFinishLaunchingWithOptionsがよばれたときには、アプリのUIは作成されていませんが、アプリ自体はactive stateになっています。それに対してwillFinishLaunchingWithOptionsがよばれたタイミングではアプリはまだinactive stateなので、その時点で行いたい処理がある場合に使えば良いようです。

RootViewController

新しいコードも古いコードも、メインとなるUIViewControllerを作成して、そのviewをUIWindowsに関連づける処理を行っていますが、その処理が異なっています。

古いコードの18行目に、こんなコードがありますね。 [window addSubview:controllersView]; メインとなるUIWindowに対して、メインのUIViewControllerのviewをaddSubviewしています。 これで画面最前面に指定されたviewが表示される仕組みですが、通常のUIViewの操作とほとんど変わりません。

それに対して、新しいコードでは10行目のこのコード。 self.window.rootViewController = self.viewController; メインとなるUIWindowのrootViewControllerにメインのUIViewControllerを設定しています。

rootViewControllerは、UIWindowのiOS3.0から導入されたプロパティです。 メインのUIViewControllerをrootViewControllerに設定することで、そのUIViewControllerのviewの表示処理も自動的に行ってくれるので、UIViewControllerとUIViewの両方を設定する必要がありません。

rootViewControllerを使わずにaddSubviewでviewを設定しても動作しないことはないんですが、実行時に「Application windows are expected to have a root view controller at the end of application launch」というWarningがでます。

storyboardを使うと、このあたりのrootViewControllerの設定を自動的にすませてくれて意識する必要はありません。

UIWindow

そもそもUIWindowというのは、スクリーン領域の一部に対しての描画およびイベント処理を行うための基本クラスです

OS Xでも同様のクラスとしてNSWindowがあります。 テキストエディットで複数の書類を開くと別々のウィンドウが開いて、画面の別々のエリアにテキストの描画を行いますよね。 それぞれのウィンドウが別々のNSWindowインスタンスとして実装されています。 でもiOSのメモ帳で複数のファイルを開いても同じ領域の描画が切り替わるだけで、イベントハンドリングのエリアも変わりません。 この場合にはUIViewを差し替えればすんでしまい、別ウィンドウを生成する必要がありません。 ですので、ふだんiOSプログラミングをしているとUIWindowを使う機会はほとんどないはずです。

ただし、外部ディスプレイ対応とAirPlay対応を行う場合だけ、メインのUIWindowとは別のUIWindowを作成する必要があります。 (別Windowを作成しない場合、ミラーリング対応となります。)

OS Xでは、複数のウィンドウのなかにkey windowとよばれるウィンドウが一つだけ存在し、イベントハンドリングを行う仕組みになっています。(でもタッチイベントはちょっと違ったりするんですが。) iOSでは基本的にメインウィンドウのみが key windowになることができます。

また、iOSのシステムではAlert Viewと Input Accessary ViewはメインのUIWindowとは別のUIWindowとして実装されているそうです。

なお、UIWindowsはUIViewの継承クラスです。 ただし、OSXではこの二つのクラスに相当するNSWindowとNSViewには直接の継承関係はありません。

このあたりは Multiple Display Programming Guide for iOSに詳しく解説されています。

その他いろいろ

古いコードにUIWindowのallocがありませんが、昔のプロジェクトテンプレートではUIWindowsインスタンスの生成をxibファイルで行っていたからです。 xibファイルで生成してもソースコード上で生成しても代わりがありませんが、ほとんど使用することのないUIWindow用のxibファイルを作ってDeveloperを混乱させるより、ソースコードに書いておけばいいや、ということなんんでしょうね。

あと、古いコードはARCもModern Objective-Cもないので、retainや@synthesizeがあったりします。

そういえば、日本で初めてのiPhoneが発売されたのは2008年7月11日。 そのころからiOS開発をしているんですが、あれからもう5年もたったのかと思うといろいろと感慨深いですね。