iPhone SDK開發範例大全第一章

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進)。 詳細步驟如下:

  1. 整個程式由UIApplication(一)進入applicationDidFinishLaunching(三)。
  2. 在applicationDidFinishLaunching中,

    創造出一個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:(六) (七)

  3. 跳入loadView去loadhello World這張圖:(八)[[contentView setImage:[UIImage imageNamed:@"helloworld.png"]];

(一)問: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的說明中講到其作用為:

  1. instantiate the application object from the principle class。
  2. instantantiate the delegate fom the given class。
  3. sets the delegate for the application。
  4. set up main event loop, including the application's run loop, and begins processing event。
  5. 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, 看看不同結果。 本文目的有二:

  1. 確實了解程式是如何一步一步走的。
  2. 確實了解每一行裡的每一個class、method、property...。