iPhone SDK開發範例大全第一章:2009年08月20日星期四
iPhone SDK開發範例大全即iPhone Developer's CookBook的中文譯本。第一章的程式HelloWorld可由erica網站下載程式, chapter1 / 0a1-CoreHelloWorldREvised。本文深入了解該 HelloWorld程式。 首先, 先講該程式的目的,見下圖, 目標: Navigation Bar上有project名HelloWorld ( (四)CFBundleName ), Application area上有hello World這張圖 (圖名 helloworld.png檔案在 (七)中".... setImage : [UIImage imageNamed: @"helloworld.png"];" load進)。 詳細步驟如下:
創造出一個Window:(三)的UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
作一個NavigationViewController:(四)的UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController: [[HelloController alloc] init] ];
將project名"HelloWorld"放入HelloController的title內:(五)的self.title = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"CFBundleName"];
將有navigation bar、有application content圖片的navigation view controller nav放入window成為subview,讓window成為main window:(六) (七)
(一)問:33頁,HelloWorld的main程式內,UIApplicationMain是何東東?並拆解main()內的一行:
int retVal = UIApplication(argc, argv, nil, @"SampleAppDelegate);
答:SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIApp可找到UIApplication Class, 其Overview第二段講到 UIApplicationMain會產生一個 UIApplication object。
UIApplicationMain是在 UIKit Function Reference定義的, 參看UIKit Function Reference可了解 UIApplicationMain如下。(A)型式:int UIApplicationMain( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
(B)說明:UIApplicationMain的主要功能即為將control交到application手中。
(甲)參數:
argc, argv即為C的argc, argv。
principalClassName: UIApplication class(或subclass名),若為nil,則以UIApplication為名。
delegateClassName:實體化application delegate "delegateClassName",若為nil,則從main nib load delegate object。 其實, 一般 "delegateClassName"均為 nil, 現說明nil的情形如下:
見下表及下圖。此圖為helloObjc project,helloObjc是Xcode的viewbase application。 MainWindow.xib是自動產生的, 仔細看圖下方的MainWindow.xib, 將其內容整理如下表:
Name Type 說明 File's Owner UIApplication 由以上UIApplicationMain會產生一個UIApplication object可了解。 First Responder UIResponder HelloObjc App Delegate helloObjcAppDelegate 指定helloObjcAppDelegate Class為application delegate,由UIApplicationMain直接跳入helloObjcAppDelegate.m的applicationDidFinishLaunching。 Hello Objc View Controller helloObjcViewController 指定helloObjcViewController為Object View Controller。由此可知為何Interface Builder可以Link Target/Action(例如:按下Button,顯示出字)等等。因為,處理的Object View Controller由此指定。(參看第5頁、Note:NIB和XIB檔案.nib .xib是由Interface Builder所產生的檔案....) window UIWindow
(乙)功能:所以,不難理解,UIApplicationMain的說明中講到其作用為:
- instantiate the application object from the principle class。
- instantantiate the delegate fom the given class。
- sets the delegate for the application。
- set up main event loop, including the application's run loop, and begins processing event。
- If the application's Info.pist file specifies a main nib file to be loaded, by including the NSMainNibFile key and a valid file name for the value, this function loads the nib file。
這第5點可以參看iPhone課的lab1 project,找到lab1-info.plist,最下一行:
Main nib file base name MainWindow
有關 including the NSMainNibFile key and a valid file name for the value,可參看lab1的 lab1ViewController內 initWithNibName(comment掉,因 UIApplicationMain已自動load nib file了。)。
(二)問:iPhone程式由那一點進入,進入後的程序如何?
答:27頁、1-7、建置一個iPhone應用程式的基本骨架,提到main()、 applicationDidFinishLaunching、 applicationWillTerminate、 LoadView、 ShouldAutorotateToInterfaceOrientation, main()即為iPhone程式的進入點,這和一般的C程式相同, 緊接著進入 applicationDidFinishLaunching ( 這仍是受main()內的程式決定, 一般main()只有幾行,其中一行是進入 applicationDidFinishLaunching), applicationDidFinishLaunching才是真正的主程式,在 applicationDidFinishLaunching中會跳至 LoadView。現以 HelloWorld為例, 實際設 break point,以便了解,如下:
(A)設那些breakpoint?在HelloWorld中共有兩個class,HelloController及SampleAppDelegate,另有main()。
HelloController中有init、loadView、shouldAutorotateToInterfaceOrientation、didReceiveMemoryWarning、dealloc五個method。
SampleAppDelegate中有applicationDidFinishLaunching、applicationWillTerminate、dealloc三個method。
所以main()、HelloController五個method、SampleAppDelegate三個method共九個 method, 各在其第一行設 breakpoint。 另在main()的最後一行設 breakpoint。 共十個breakpoint。
因applicationWillTerminate是空的,所以加入一行printf。
(B)試breakpoint結果:break在main()、applicationDidFinishLaunching ( 由 UIApplication跳入此處、見(一))、 init、 shouldAutorotateToInterfaceOrientation ( 由 [window addSubview:nav.view]; 跳入此處、 見(六))、 loadView ( 由 [window addSubview:nav.view]; 跳入此處、 見 (六)) 五個method的第一行。HelloController中的 didReceiveMemoryWarning、 dealloc, SampleAppDelegate中的 applicationWillTerminate、 dealloc, main()最後一行五處都沒有 break。 最奇怪的是main()最後一行到不了?
此時按下iPhone simulator下方結束紐,會break在applicationWillTerminate,但是仍無法回到main()最後一行?
答:猜想是在一個loop中,等user action, 按下iPhone simulator下方結束紐造成無預期中斷,因而跳至 applicationWillTerminate,執行完不再回 main(), 如此一來, pool不是無法 release了?
(三)問:拆解applicationDidFinishLaunching中的第一行:
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
答: 首先,本行的功能是創造出一個Window,(並將Default.png放到螢幕上?)。拆解如下:
(A)"[UIWindow alloc] initWithFrame:":[UIWindow alloc]產生一UIWindow object,然後呼叫initWithFrame,要注意的是,
在 SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/中找不到 UIWindow的alloc和initWithFrame,但因為 UIWindow是 UIView的 subclass,所以看 UIView,即可找到。
看 UIView的initWithFrame就可知道其需要一個 CGRect的object作為parameter。並 return一個id object。所以要看看 initWithFrame:後的東西了!
(B)[[UIScreen mainScreen] bounds]]: [UIScreen mainScreen]會產生一個 screen object,成為 iPhone的 device's screen。然後呼叫 bounds,bounds是 UIScreen的一個 property(而且read-only),所以是種getter,回傳的是 "bounding rectangle of the screen, measured in points.",也就是說,回傳 CGRect structure。
CGRect是:
struct CGRect{
CGPoint origin;
CGSize size;
};
CGPoint是:
struct CGPoint{
CGFloat x;
CGFloat y;
};
typedef struct CGPoint CGPoint;
CGSize是:
struct CGSize{
CGFloat width;
CGFloat hwight;
};
typedef struct CGSize CGSize;
(C)由以上兩個說明合起來看,[[UIScreen mainScreen] bounds]]正好回傳[UIWindow alloc] initWithFrame:"需要的parameter----CGRect structure。
注意:在SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/中,CGRect需找CGGeometry reference。打CGRect找不到。
(D)[[UIWindow alloc] initWithFrame : [[UIScreen mainScreen] bounds]];表示出 UIWindow必需和 UIScreen(產生frame)合用, 由書中 58頁2-1-2的說明及 60頁的例子也可看到一個 window必需有個frame。
(E)在SampleAppDelegate.m的applicationDidFinishLaunching中加入:
printf("x= %f, y = %f, width = %f, height = %f \n",[window frame].origin.x, [window frame].origin.y,[window frame].size.width, [window frame].size.height );
即可印出window的frame。上printf會印出"x = 0, y =0, width = 320, height = 480",也就是說iPhone整個320x480是frame。
(四)問:拆解applicationDidFinishLaunching中的第二行:
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[HelloController alloc] init] ];
答:SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIKit framework reference,有 class references、 protocol references、 other references, 在 class references下, 找到 UINavigationController.
也可以:UINavigationController是UIKIT的external class,type是UIKIT_EXTERN_CLASS,它的定義在UIKIT.framework裡的UINavigationController.h裡。
找到後, 可知UINavigationController是UIViewController的subclass,[[UINavigationController alloc] initWithRootViewController:....]完成後, 回傳id,也就是 UINavigationController。
現在來看 initWithRootViewController所需的parameter--UIViewController,這是程式設計師自訂的 HelloController,而 HelloController是 UIViewController的 subclass, 所以是個 UIViewController,看一下[[HelloController alloc] init] ]的init。在init中有一行:
self.title = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
接下來就來看這一行。
(五)問:拆解HelloController中init的第三行:
self.title = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
答:本行是找出HelloWorld program並assign給HelloController。self.title就是未來display在navigation controller上的字眼, 試試修改成self.title = @ "Kiss my ass"; ,則 navigation controller上會出現 Kiss my ass!
self.title:這是seter,也就是setTitle的意思。title是個NSString pointer。
本例中的self是由[[HelloController alloc] init] 中的[HelloController alloc]而來,所以是個HelloController。因為是 UIViewController的 subclass,所以繼承了其 instance variable及method。找UIViewController,用 SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIKit framework reference,在 class中找到 UIViewController ( UIViewController是 UIResponder的 subclass)。在UIViewController中可找到 title,self.title是:
Subclasses should set the title to a human-readable string that represents the views to the user, if the receiver is a navigation controller, the default value is the top view controller's title.
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
用SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for NSBundle可找到NSBundle class, NSBundle object代表 file system上某個地方 ( location),該地方有可執行的 program。 [NSBundle mainBundle]回傳指向 NSBundle的pointer。 [[NSBundle mainBundle] infoDictionary] 回傳指向 NSDictionary的pointer。
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]:
NSDictionary可用 SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for NSDictionary找到, objectForKey: @"CFBundleName"傳回 [ NSBundle mainBundle]可執行的 program的 file system上某個地方 (location)。在本例,即為 project名稱: HelloWorld。
至此,和(四)合起來看,在(四)中的UINavigationController *nav是個UINavigationController,且包含有一個RootViewController、"指向project名稱:HelloWorld"的HelloController。
(六)問:拆解applicationDidFinishLaunching中的第四行:
[window addSubview:nav.view];
答:SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIKit framework reference,有 class references、 protocol references、 other references,在 class references下,找到 UINavigationController,再找到其superclass UIViewController的property--- view,由 view的內容:
If you access this property and its value is nil, the view controller automatically calls the loadView。
可知,因為從未assign過nav.view,自然是nil,因而call loadView。
(七)問 :拆解applicationDidFinishLaunching中的第五行:
[windows makeKeyAndVisible];
答:SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIWindow, windows makeKeyAndVisible]讓 window成為 main window並 display in front of other windows。
(八)問:自(五)跳入loadView後,loadView內下列五行拆解:
UIImageView *contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
[[contentView setImage:[UIImage imageNamed:@"helloworld.png"]];
contentView.autoresizesSubview = YES;
contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.view = contentView;
答:
UIImageView *contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
UIImageView用SDK3.0 Xcode/Help/documentation/iPhone OS3.0 Library/search for UIImageView可找到,是 UIView的subclass,所以使用其 initWithFrame, [[UIScreen mainScreen] applicationFrame]]回傳 CGRect struct為 parameter,詳參考 (三),與 (三)不同的是 applicationFrame method ( (三)用bound s), applicationFrame回傳的值是 CGRect,但是,是減掉 status bar之後的CGRect。
在code中加入一行 printf("x= %f, y = %f, width = %f, height = %f \n",[contentView frame].origin.x, [contentView frame].origin.y , [contentView frame].size.width, [contentView frame].size.height ); ,答案是:
x = 0, y = 20, width =320, height =460。
[[contentView setImage:[UIImage imageNamed:@"helloworld.png"]]; // setImage、 imageNamed均在UIImageView class內
[UIImage imageNamed:@"helloworld.png"]在系統cache中找helloworld.png,如果沒有,則從file system中 loadc上 cache。回傳 pointer to UIImage。
再將此pointer to UIImage pass給 [contentView setImage:...],contentView內的image就成了helloworld.png。
contentView.autoresizesSubview = YES; // 巨UIView class內
YES時,當bounds改變,contentView改變其subView。
contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); //在UIView class內
當Width或Height改變時,resize。
self.view = contentView; // 將contentView assign給self.view,以本例來看,self為applicationDidFinishLaunching中的nav、 pointer to UINavigationController。 所以, contentView也進了 UINavigationController裡了。
所以,navigation controller裡已init好HelloController,有helloworld.png在contentView((八) 的 [ UIImage imageNamed: @"helloworld.png"]), 並有navigation bar 上的字眼 HelloWord ( (五)的self.title ),一切就緒。 nav.view也加入 window subView((六)),而且讓 window在最上面((七))。
(九)問:如何使用erica網站下載程式、 erica/chapter1/0a1-CoreHelloWorldREvised最好玩?
答:本程式已改寫,加入break point及printf messages。一邊break一邊看gdb。有時將一些comment 掉的code uncomment, 看看不同結果。 本文目的有二:
- 確實了解程式是如何一步一步走的。
- 確實了解每一行裡的每一個class、method、property...。