【openFramewoks】oFアプリはどのようにして起動しているのか?【for iOS】
こんばんは.1000chです. 実験やら研修やら,何かと年末は忙しいですね.
最近oF for iOSの記事を書いている訳ですが, 「そもそもiOS上でoFアプリはどのように動いているのだろう?」と気になりました.
そもそもiOSアプリのライフサイクルも分かっていないので,その辺りから調べてまとめようと思います!
iOSにおけるプログラムのライフサイクル
まずは,iOSにおけるobjective-cプログラムのエントリポイントから勉強します.
実際にXcodeでNew->Project->single view applicationを作り,順を追ってみましょう. Xcodeはver6.1.1です.
main関数を見る
まず,objective-cもプログラムの開始はmain
関数から始まります.どこにあるのか,が一瞬分かりにくいですが,supporting files
ディレクトリ配下にいるようです.
#import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
見知ったmain
関数がいらっしゃいます.ARCは置いといて,中ではUIApplicationMain
関数が呼ばれているだけみたいです.docを確認しましょう.
This function is called in the main entry point to create the application object and the application delegate and set up the event cycle.
This function instantiates the application object from the principal class and instantiates the delegate (if any) from the given class and sets the delegate for the application. It also sets up the main event loop, including the application’s run loop, and begins processing events. If the application’s Info.plist file specifies a main nib file to be loaded, by including the NSMainNibFile key and a valid nib file name for the value, this function loads that nib file.
Despite the declared return type, this function never returns. For more information on how this function behaves, see “Core App Objects” in App Programming Guide for iOS.
ざっくり要点を拾うと,
- main関数のエントリポイントでよばれる.
- アプリケーションクラスのインスタンスを生成する.
- 指定されたデリゲートクラスのインスタンスを生成する
- イベントサイクルを初期化する.
- info.plistでnibファイル指定して読み込める(=ストーリーボード?)
という事の様です.となれば,次に見るべきはどんなデリゲートクラスが指定されているかでしょう. この例では
[AppDelegate class]
としてデリゲートクラスのクラス名を取得していますね.
デリゲートクラスを見る
では,実際にAppDelegate
クラスを見てみみます.
AppDelegate.h
#import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end
AppDelegate.m
#import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } - (void)applicationWillResignActive:(UIApplication *)application { } - (void)applicationDidEnterBackground:(UIApplication *)application { } - (void)applicationWillEnterForeground:(UIApplication *)application { } - (void)applicationDidBecomeActive:(UIApplication *)application { } - (void)applicationWillTerminate:(UIApplication *)application { } @end
なんということでしょう.
windowプロパティを持ち,didFinishLaunchingWithOptions
でreturn true
する以外は特になにもやってませんね.
ここでは省いていますが,各メソッドにXXXなときに呼ばれるよ!オーバーライドして使ってね!的なことが書いてあります. 現状のデリゲートクラスは特になにもやってないことがわかりました.
ストーリーボードをみる
先程UIApplicationMain
関数の説明でみたように,info.plist
で指定されたストーリーボードが読み込まれるはずです.開いてみましょう.
First Responder
なるものが ViewController
に指定されています.おそらく初期ビューとして ViewController
クラスが読み込まれるのでしょう.
ViewControllerをみる
ということでビューコントローラをみていきます.
ViewController.h
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end
ViewController.m
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
プロジェクト生成直後の現状ではほとんどなにもないですねw
このあとUIButton
やUILabel
など,UIView
を継承するUIパーツをこのファイルに配置していくことで,iOSアプリとして動作するのでしょう.
ここまでまとめ
というわけでiOSのアプリ起動の流れをまとめると,
- main関数よばれる
- UIApplicationMain関数呼ばれる
- アプリケーションのインスタンスつくられる
- デリゲートのインスタンスつくられる
- storyboardで指定したViewControllerが呼ばれる
という流れのようです.
oF for iOSでは?
では本題,oF for iOSではどうなっているのかを見ていきましょう. 手元のver 0.8.3でみていきます.
main.mmをみる
サンプルとして,example/graphicsExample
をみていきます.
エントリポイントは同じくmain関数でしょう.
#include "ofMain.h" #include "ofApp.h" int main(){ ofSetupOpenGL(1024,768, OF_FULLSCREEN); ofRunApp(new ofApp); }
この中ではofSetupOpenGL
とofRunApp
のふたつが呼ばれています.
ofSetupOpenGLを追う
まずはofSetupOpenGL
を追っていきます.
void ofSetupOpenGL(int w, int h, int screenMode){ #ifdef TARGET_NODISPLAY window = ofPtr<ofAppBaseWindow>(new ofAppNoWindow()); #elif defined(TARGET_OF_IOS) // iosの場合ココ window = ofPtr<ofAppBaseWindow>(new ofAppiOSWindow()); #elif defined(TARGET_ANDROID) window = ofPtr<ofAppBaseWindow>(new ofAppAndroidWindow()); #elif defined(TARGET_RASPBERRY_PI) window = ofPtr<ofAppBaseWindow>(new ofAppEGLWindow()); #else window = ofPtr<ofAppBaseWindow>(new ofAppGLFWWindow()); #endif ofSetupOpenGL(window,w,h,screenMode); // 1. } // 1.で呼ばれる void ofSetupOpenGL(ofPtr<ofAppBaseWindow> windowPtr, int w, int h, int screenMode){ if(!ofGetCurrentRenderer()) { #ifdef USE_PROGRAMMABLE_GL ofPtr<ofBaseRenderer> renderer(new ofGLProgrammableRenderer(false)); #else ofPtr<ofBaseRenderer> renderer(new ofGLRenderer(false)); #endif ofSetCurrentRenderer(renderer,false); } window = windowPtr; if(ofIsGLProgrammableRenderer()){ #if defined(TARGET_RASPBERRY_PI) static_cast<ofAppEGLWindow*>(window.get())->setGLESVersion(2); #elif defined(TARGET_LINUX_ARM) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(2,0); #elif !defined(TARGET_OPENGLES) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(3,2); #endif }else{ #if defined(TARGET_LINUX_ARM) && !defined(TARGET_RASPBERRY_PI) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(1,0); #endif } window->setupOpenGL(w, h, screenMode); // 2. } // 2.でよばれる // setupOpenGLはofAppBaseWindowの抽象メソッド // ofAppiOSWindowではこんな感じの定義 void ofAppiOSWindow::setupOpenGL(int w, int h, int screenMode) { // windowModeはインスタンス変数 windowMode = screenMode; // use this as flag for displaying status bar or not }
ごちゃごちゃ追いましたが,結局
ってだけみたいです.iOSの場合はwindowModeを指定していますね.これは最初に
ofSetupOpenGL(1024,768, OF_FULLSCREEN);
で渡しているので,OF_FULLSCREEN
が入る,ということでしょう.
ofRunAppを追う
では次,ofRunApp(new ofApp)
を追っていきます.
そもそもnew ofApp
はなんだ?って話ですが,これはいつもoF書く時に作っているクラスのことですね.いわゆるsetup, update, draw
などを定義していくアレです.
要するに,いつも作ってるofApp
クラスはココでインスタンス化されてofRunApp
に渡されている,ということですね.
では改めてofRunApp
をみていきます.
void ofRunApp(ofBaseApp * OFSA){ OFSAptr = ofPtr<ofBaseApp>(OFSA); if(OFSAptr){ OFSAptr->mouseX = 0; OFSAptr->mouseY = 0; } #ifndef TARGET_ANDROID atexit(ofExitCallback); #endif #if defined(TARGET_LINUX) || defined(TARGET_OSX) // see http://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals signal(SIGTERM, &sighandler); signal(SIGQUIT, &sighandler); signal(SIGINT, &sighandler); signal(SIGKILL, &sighandler); // not much to be done here signal(SIGHUP, &sighandler); // not much to be done here // http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html#Program-Error-Signals signal(SIGABRT, &sighandler); // abort signal #endif #ifdef WIN32_HIGH_RES_TIMING timeBeginPeriod(1); // ! experimental, sets high res time // you need to call timeEndPeriod. // if you quit the app other than "esc" // (ie, close the console, kill the process, etc) // at exit wont get called, and the time will // remain high res, that could mess things // up on your system. // info here:http://www.geisswerks.com/ryan/FAQS/timing.html #endif // ここからアプリの初期化 window->initializeWindow(); // 初期値設定 ofSeedRandom(); ofResetElapsedTimeCounter(); ofSetWorkingDirectoryToDefault(); // イベントリスナの登録 ofAddListener(ofEvents().setup,OFSAptr.get(),&ofBaseApp::setup,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().update,OFSAptr.get(),&ofBaseApp::update,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().draw,OFSAptr.get(),&ofBaseApp::draw,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().exit,OFSAptr.get(),&ofBaseApp::exit,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().keyPressed,OFSAptr.get(),&ofBaseApp::keyPressed,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().keyReleased,OFSAptr.get(),&ofBaseApp::keyReleased,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().mouseMoved,OFSAptr.get(),&ofBaseApp::mouseMoved,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().mouseDragged,OFSAptr.get(),&ofBaseApp::mouseDragged,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().mousePressed,OFSAptr.get(),&ofBaseApp::mousePressed,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().mouseReleased,OFSAptr.get(),&ofBaseApp::mouseReleased,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().windowEntered,OFSAptr.get(),&ofBaseApp::windowEntry,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().windowResized,OFSAptr.get(),&ofBaseApp::windowResized,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().messageEvent,OFSAptr.get(),&ofBaseApp::messageReceived,OF_EVENT_ORDER_APP); ofAddListener(ofEvents().fileDragEvent,OFSAptr.get(),&ofBaseApp::dragged,OF_EVENT_ORDER_APP); // アプリの開始 window->runAppViaInfiniteLoop(OFSAptr.get()); }
なーんかごちゃごちゃしていますが,序盤は環境毎の差異を吸収しているだけのよう.重要なのはwindow->initializeWindow
以降ですね.各値の初期化やイベントリスナの登録などをしています.基本的にはofEvents().XXX
にofApp::XXX
が対応づけられてるみたいです.
そして最後にrunAppViaInfinitLoop
でアプリ開始っぽいですね.みていきましょう.
// ofAppBaseWindowの抽象メソッド. // ofAppiOSWindowではこんな実装 void ofAppiOSWindow::runAppViaInfiniteLoop(ofBaseApp * appPtr) { startAppWithDelegate("ofxiOSAppDelegate"); }
スタートwithデリゲート...どっかで聞いた感じがしてきましたね.
void ofAppiOSWindow::startAppWithDelegate(string appDelegateClassName) { static bool bAppCreated = false; if(bAppCreated == true) { return; } bAppCreated = true; // さっきiOSでみたのと同じだ! @autoreleasepool { cout << "trying to launch app delegate " << appDelegateClassName << endl; UIApplicationMain(nil, nil, nil, [NSString stringWithUTF8String:appDelegateClassName.c_str()]); } }
startAppWithDelegate
はデリゲートクラスの文字列を引数として,UIApplicationMain
関数を実行してくれるみたいです.このあたり,先程みたiOSのライフサイクルでもでてきましたね.となれば次は,指定されているデリゲートクラスをのぞきたくなります.
こいつが文字列で指定されているせいで探しにくいのですが(笑),addons/ofxiOS/src/core
配下にいらっしゃいました.
ofxiOSDelegate.h
#pragma once #import <UIKit/UIKit.h> @class ofxiOSViewController; @interface ofxiOSAppDelegate : NSObject <UIApplicationDelegate> { NSInteger currentScreenIndex; } @property (nonatomic, retain) UIWindow * window; @property (nonatomic, retain) UIWindow * externalWindow; @property (nonatomic, retain) ofxiOSViewController * glViewController; @property (readonly, assign) NSInteger currentScreenIndex; - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url; - (void)receivedRotate:(NSNotification*)notification; #ifdef __IPHONE_4_3 - (BOOL)createExternalWindowWithPreferredMode; - (BOOL)createExternalWindowWithScreenModeIndex:(NSInteger)screenModeIndex; - (BOOL)destroyExternalWindow; - (BOOL)displayOnScreenWithIndex:(NSInteger)screenIndex andScreenModeIndex:(NSInteger)screenModeIndex; #endif @end #define ofxiPhoneAppDelegate ofxiOSAppDelegate
もはや完全にobjective-cになってきました. 先程のiOSの例ではほっとんど中身の無かったデリゲートクラスですが,今回はいろいろと中身がありそうです.
実装ファイルofxiOSAppDelegate.m
はなかなか膨大なようで,重要なところまでを載せます.
#import "ofMain.h" #import "ofxiOSAppDelegate.h" #import "ofxiOSViewController.h" #import "ofxiOSExtras.h" #import "ofxiOSExternalDisplay.h" @implementation ofxiOSAppDelegate @synthesize window; @synthesize externalWindow; @synthesize glViewController; @synthesize currentScreenIndex; - (void)dealloc { self.window = nil; self.externalWindow = nil; self.glViewController = nil; [super dealloc]; } // アプリ起動時完了時に呼ばれるメソッド - (void)applicationDidFinishLaunching:(UIApplication *)application { // windowの取得 self.window = [[[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]] autorelease]; // 中略. 各値の初期化,イベントリスナの設定など NSString * appDelegateClassName = [[self class] description]; if ([appDelegateClassName isEqualToString:@"ofxiOSAppDelegate"]) { // 中略. デバイス向きの設定 // ビューコントローラの生成 self.glViewController = [[[ofxiOSViewController alloc] initWithFrame:frame app:(ofxiOSApp *)ofGetAppPtr()] autorelease]; // 生成したビューをルートに設定 self.window.rootViewController = self.glViewController; // 中略. デバイス向きの設定 } }
ビュー初期化に関わるところだけを抜くと上記のような感じになります.
アプリの起動が確認されると,デリゲートのapplicationDidFinishLaunching
が呼ばれます.このなかでofxiOSViewController
を生成し,これをwindowのルートビューに指定しています.
これは先程みたiosでいうと,ストーリーボードでのfirst responder指定に値すると言えるでしょう.
ofxiOSViewControllerをみる
では生成されるビューコントローラがどんなものかみていきます.ヘッダファイルはこんなかんじ,
#import <UIKit/UIKit.h> class ofxiOSApp; @class ofxiOSEAGLView; @interface ofxiOSViewController : UIViewController @property (nonatomic, retain) ofxiOSEAGLView * glView; - (id)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app; - (UIInterfaceOrientation)currentInterfaceOrientation; - (void)setCurrentInterfaceOrientation:(UIInterfaceOrientation) orient; - (void)rotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation animated:(BOOL)animated; - (BOOL)isReadyToRotate; @end #define ofxPhoneViewController ofxiOSViewController
ofxiOSEAGLView
がとても怪しいです.実装ファイルの重要そうな部分をみてみましょう.
- (id)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app { currentInterfaceOrientation = pendingInterfaceOrientation = UIInterfaceOrientationPortrait; if((self = [super init])) { currentInterfaceOrientation = pendingInterfaceOrientation = self.interfaceOrientation; bReadyToRotate = NO; bFirstUpdate = NO; self.glView = [[[ofxiOSEAGLView alloc] initWithFrame:frame andApp:app] autorelease]; self.glView.delegate = self; } return self; } - (void)viewDidLoad { [super viewDidLoad]; // glView is added here because if it is added inside initWithFrame, // it automatically triggers viewDidLoad, before initWithFrame has had a chance to return. // so now when we call setup in our OF app, a reference to ofxiOSViewController will exists. [self.view addSubview:self.glView]; [self.glView setup]; [self.glView startAnimation]; }
ざっくり言うと
initWithFrame
でofxiOSEAGLView
インスタンス生成viewDidLoad
でofxiOSEAGLView
をサブビューに追加ofxiOSEAGLView
のsetup
,startAnimation
を呼ぶ
という形みたいです.
でこのofxiOSEAGLView
はsetup
を持ち,また継承元のEAGLView
がstartAnimation
を持っているよう.
setupはこんなかんじ.
- (void)setup { ofNotifySetup(); // ここでofEvents().setupが発火され,ofApp::setupが呼ばれる glClearColor(ofBgColorPtr()[0], ofBgColorPtr()[1], ofBgColorPtr()[2], ofBgColorPtr()[3]); // clear background. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); }
startAnimation
はこんな感じ
- (void) startAnimation { if(!animating) { if(displayLinkSupported) { // CADisplayLink is API new to iPhone SDK 3.1. Compiling against earlier versions will result in a warning, but can be dismissed // if the system version runtime check for CADisplayLink exists in -initWithCoder:. The runtime check ensures this code will // not be called in system versions earlier than 3.1. displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(drawView:)]; [displayLink setFrameInterval:animationFrameInterval]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } else { animationTimer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)((1.0 / 60.0) * animationFrameInterval) target:self selector:@selector(drawView:) userInfo:nil repeats:TRUE]; } animating = YES; [self notifyAnimationStarted]; } }
displayLink
が何を意味するのかはよくわかりませんが,ようはdrawView
を一定時間ごとに呼ぶ何かなんでしょう←
drawView
はofxiOSEAGLView
におり,
- (void)drawView { ofNotifyUpdate(); // ofEvents().uodateが発火 //------------------------------------------ [self lockGL]; [self startRender]; // 中略 //------------------------------------------ draw. ofNotifyDraw(); // ofEvents().drawが発火 //------------------------------------------ // 中略 [super notifyDraw]; // alerts delegate that a new frame has been drawn. }
というように,このメソッドが呼ばれる毎にupdate,drawが呼ばれる形になっていることがわかります.
まとめると
当然ですが後半はがっつりobjcのコードにもぐることになりました. oF for iOSの立ち上がるまでの流れをまとめると,
- main: OpenGL設定
- main: ofRunAppにofAppインスタンスを与える
- ofRunApp: UIApplicationMainにofxiOSAppDelegateを与える
- ofxiOSAppDelegate: applicationDidFinishLaunchingでofxiOSViewControllerをルートに設定
- ofxiOSViewController: サブビューにofxiOSEAGLViewを指定
- ofxiOSEAGLView: setup, startAnimation呼ぶ
- setup: ofEvents().setup発火
- startAnimation: glDrawを定期的に呼ぶタイマーを設定
- glDraw: ofEvents().update,ofEvents().draw発火
という形でした.長い!!!
いやほんと,こんだけ複雑ないろいろをなにも意識せずに利用できるようにしているなんて,ほんとoFつくっている方々はすごいですね...
起動までもこんだけいろいろあることに加え,ofDrawCircle
なども各環境にあわせてラップしていると思うと...またコードリーディングしたくなりますねw