Objective C 教程 @Part-04

● 應用程式框架 ( Framework)

開始前我們要先討論什麼框架(Framework)。框架是由一群以相關種類區分開來開發出來的類別(Class)組成,而各個類別內都提供了相關服務性質的方法內容。在做初始的開發學習階段,我們最常用的三套框架分別為 : (基礎框架) – <UIKit/UIKit.h>(使用者界面框架) – (應用元件框架包含較為廣泛)。

  • Foundation(基礎框架) : 基礎框架不論在開發Mac或iOS應用程式中都會用到,他提供了程式你最常用到的如 數值(Numeric),字串(String),陣列(Array),詞典集合物件(Dictionary),資料讀取與輸出(I/O),就連我們最頭痛的記憶體管理機制都是透過基礎框架提供。
  • UIKit(使用者介面框架) : 簡易來說,UIKit主要用來提供建構與管理iOS應用程式使用者界面,例如 UIAlertView(提示窗面板),使用時機在於你想對使用者告知某些訊息並讓使用者選擇時,可以在適當時機使用UIAlertView show出即時文字視窗,並搭配UIAlertView協定好的物件並實作特定接收到訊息的方法,在使用者做出選擇時,這個協定過的方法將會接收到使用者選擇的結果,而這就是我們之前有提到的Protocol協定,再實際設計應用程式時,他們統稱叫做代理人(delegate),不過說穿了就是CallBack罷了。
  • Application Kit(各應用元件) : 此應用元件應用範圍非常之廣,包含了我們最常用的 UIButton(按鈕),文字框(TextField),捲軸頁面(Scroller)..等UI元件。

在Objective C裡,以上所有框架物件都需要配置記憶體空間才能產生實體加以使用,而正因為之前提過的,Objective C 裡所有的物件都是指標形態,既然是指標指向某個記憶體位址,就意味著我們需要對指標指向的記憶體空間負責生成與釋放。在框架的章節裡我們還是會先用最簡易的方式做記憶體釋放的處理,至於完整的記憶體相關問題會在之後的教學以大篇幅來討論。以下我們提出幾個最常運用到的基礎框架來解釋Objective C裡的Framework使用方法。

● 數值物件 (NSNumber)

NSNumber在OBjective C 裡代表了數值的物件,也就是存放 int, float, double, char, BOOL(布林植) 等基本形態的物件。在OBjective C裡如果你想將int直接存放進Array裡是不被允許的,因為Array只接受存放物件形態的東西,以 int 為例,它就只是個基本且尚未被包裝過的型別 ,所以你必須透過NSNumber物件將其包裝成integer物件,才能正式在OBjective C裡與其它框架搭配使用。或許你學過其他語言諸如Java,vb之類的物件導向語言,他們可以直接將int,float直接存入陣列內,但說到底他們只是在編譯過程中幫你做了autoboxing跟unboxing將int等數值包裝成物件儲存,道理和NSNumber是一樣的。而我們現在就先從最簡單的數值物件作為進入iOS Framework的第一個試驗。
#順道一提 NS 是 Next Step 的縮寫。

*以下表格為針對NSNumber數值物件的生成物件與取得物件值的形態對照表。

資料型態

初始化方法

自動配置方法

取值方法

int

-initWithInt:

+numberWithInt

-intValue

float

-initWithFloat:

+numberWithFloat

-floatValue

double

-initWithDouble:

+numberWithDouble

-doubleValue

char

-initWithChar:

+numberWithChar

-charValue

BOOL

-initWithBool:

+numberWithBool

-boolValue

 NSInteger   

-initWithInteger:    

+numberWithInteger   

-integerValue    

@main

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   NSNumber *intNumber = [NSNumber numberWithInt:10];
   NSNumber *floatNumber = [NSNumber numberWithFloat:1.0f];
   NSNumber *charNumber = [NSNumber numberWithChar:'A'];
   NSNumber *integerNum = [NSNumber numberWithInteger:[intNumber intValue]];

   NSLog(@"intNumber value : %d",[intNumber intValue]);
   NSLog(@"floatNumber value : %f",[floatNumber floatValue]);
   NSLog(@"charNumber value : %c",[charNumber charValue]);
   NSLog(@"integerNum value : %li",[integerNum integerValue]);

   intNumber = [[[NSNumber alloc] initWithInt:50] autorelease];
   floatNumber = [[[NSNumber alloc] initWithFloat:199.5f] autorelease];
   charNumber = [[[NSNumber alloc] initWithChar:'B'] autorelease];
   integerNum = [[[NSNumber alloc] initWithInteger:[intNumber intValue]] autorelease];

   NSLog(@"intNumber value : %d",[intNumber intValue]);
   NSLog(@"floatNumber value : %f",[floatNumber floatValue]);
   NSLog(@"charNumber value : %c",[charNumber charValue]);
   NSLog(@"integerNum value : %li",[integerNum integerValue]);

   if (intNumber != integerNum) {
     NSLog(@"intNumber物件 不等於 integerNum物件");
   }
   if ([intNumber isEqualToNumber:integerNum]) {
     NSLog(@"intNumber物件內的值與integerNum內的值相同");
   }

   [pool drain];
}

®輸出結果 :

 intNumber value : 10
 floatNumber value : 1.000000
 charNumber value : A
 integerNum value : 10
 intNumber value : 50
 floatNumber value : 199.500000
 charNumber value : B
 integerNum value : 50
 intNumber物件 不等於 integerNum物件
 intNumber物件內的值與integerNum內的值相同
  • 仔細看看對照表,每種形態的NSNumber都有兩種生成方法,透過init初始化的NSNumber物件最終還是需要程式人員自己手動管理記憶體。而另一種靜態方式取得回傳物件做生成的方式並不代表它不需要處理記憶體釋放,而是當你透過靜態取得物件回傳時,系統已經幫你將NSNumber依照你的需求型態配置一個新的物件回傳給你並且將此物件放入記憶體釋放池內,在適當時機它會自動幫你做記憶體釋放的動作。
  • 我們以兩種生成NSNumber物件的方式來證明剛剛的論調,但兩種方式的生成對輸出結果來說並不會有所差異。
  • 最後我們做了兩道 if 判斷式,第一個判斷式我們將intNumber 與 integerNum做比對,兩者比對結果是不一致的,這時你可能會認為,明明integerNum的值是intNumber傳過去的,而且印出來的結果也是一樣,會不會是判斷式搞錯了?其實在Objective C的物件比對中,如果將兩個物件做 “=="比對,系統將是判斷兩個物件的指標是否指向同一個記憶體位置,所以得到的結果當然是不同(記憶體方面往後會在加以討論),所以如果我們想單純比對兩個NSNumber物件內的數值是否相同,我們則需要用到第二個判斷式的方法 “isEqualToNumber"來比對兩物件值是否相等。

● 字串物件 (NSString)

在OBjective C裡與其他語言不一樣的地方在於講String這個物件拆成"可變(NSMutableString)"與"不可變(NSString)",不可變字串意即字串內容一經初始後,即無法動態增長或縮減甚至切割本身字串。如你的字串生成後需作動態改變的動作,即需將字串宣告為NSMutableString可變字串物件。
PS : NSString與NSNumber一樣提供了init(需手動管理)與[NSString …](自動生成與管理)兩個字串物件初始生成方式。

@main

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   NSString *str_1 = [[NSString alloc] initWithFormat:@"my first:%d",20];
   NSString *str_2 = [[NSString alloc] initWithString:@"my second:30"];
   NSString *str_3 = @"40";

   NSLog(@"%@",str_1);
   NSLog(@"%@",[str_2 uppercaseString]);
   NSLog(@"%@:%d", [@"my third" uppercaseString], [str_3 intValue]);

   NSMutableString *mutStr = [[NSMutableString alloc] initWithString:@"動態的顯示數字為:"];
   [mutStr appendString:[str_1 substringWithRange:NSMakeRange(6, 2)]];
   NSLog(@"%@",mutStr);
   [mutStr insertString:@"字串" atIndex:2];
   NSLog(@"%@",mutStr);

   [str_1 release];
   [str_2 release];
   [mutStr release];

   [pool drain];
}
  • 一開始我們宣告了三個不可變字串物件(NSString),str_1與str_2分別以格式化文字與一般初始化字串來生成NSString物件。而str_3儘儘用了與其他語言一樣直接給一個字串的方法生成字串,其兩者最顯著的差異在於一個需要管理記憶體一個不需要。雖然在基本框架裡,有許多物件皆有提供須手動管理與不需管理的方法,但在學習過程中,我們終歸需要知道,這些物件在不再需要被利用時都是需要被釋放的。再次強調"雖然有許多情況下我們不需管理記憶體,但我們必須知道他是在生成的時候以被加入autorelease自動釋放池內並在該物件被銷毀時一次性釋放"。
  • 在印出結果時,我們簡單的利用了NSString提供的"uppercaseString"將字母全部轉為大寫,同時也利用了"intValue"將字串轉換為數值。備註 : %@在Objective C裡用來格式化字串用。與其他%d %f等原理相同。
  • 往下看到,我們生成了ㄧ個NSMutableString來處理可變物件並達到不可變物件(NSString)無法達成的目的。首先給予字串變數一段基本字串,生成後利用"appendString : “方法將本身字串在最後一個字元之後連接新的字串,而[str_1 substringWithRange:NSMakeRange(6, 2)]此段用意在於取得str_1的引數6開始往後取得兩個字元並回傳(取得切割的回傳值並不會改變str_1本身的字串內容)。再往下會看到[mutStr insertString:@"字串" atIndex:2]片段,照字義來看,就是在mutStr引數2的位置插入"字串"兩個字,夠簡單吧。在此示範了幾個方法應用其意並不在於訴說有哪些方法提供,而是在於讓學習的人瞭解在Objective C 裡頭使用方法的時機與官方方法名稱的定義習慣,只要掌握住一些在Objective C裡的書寫習性,在往後做開發專案的時候可以以最短的時間找到方法應用。
  • 最後我們將剛剛用到init實際配置記憶體的字串變數用 “release" 方法將之釋放,雖然一樣是釋放記憶體,但這和我們之前所用的 “autorelease" 釋放時機完全不同。使用release即為當下釋放記憶體,但將其加入autorelease則是在最後[pool drain]時將所有指向的記憶體一一釋放。雖然記憶體我們需要在另一邊文章好好的討論,不過現在稍微試試水溫習慣一下也無妨。

● 陣列物件 (Array)

陣列在Objective C分成可變與不可變陣列,不可變陣列NSArray一經初始擇不能刪除或新增甚至替換陣列內容。相反可變陣列卻能達到動態新增移除與交替內容物件排序。而最重要的一點也就是Objective C的陣列物件只能存放繼承了NSObject的物件類型,就像之前所提到的一樣,無法直接存放一般未經過物件包裝過的基礎型別如int,double,char..等。如想將一般數值型態存入陣列,須先將數值轉換為NSNumber。

@main


#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   int i;
   NSArray *ary = [[NSArray alloc] initWithObjects:@"60",@"90",@"100", nil];
   NSMutableArray *mutAry = [[NSMutableArray alloc] init];
   [mutAry addObject:@"國文:"];
   [mutAry addObject:@"英文:"];
   [mutAry addObject:@"數學:"];

   for (NSString *str in ary ) {
     printf("%s\t",[str UTF8String]);
   }
   printf("\n");
   for (i = 0 ; i < [ary count] ; i++) {
     printf("%s\t",[[mutAry objectAtIndex:i] UTF8String]);
   }
   printf("\n---------------------------\n");

   for (i = 0 ; i < [ary count] ; i++) {
     [mutAry insertObject:[ary objectAtIndex:i] atIndex:i*2+1];
   }
   for (i = 0 ; i < [mutAry count] ; i++) {
     printf("%s\t",[[mutAry objectAtIndex:i] UTF8String]);
   }
   printf("\n---------------------------\n");

   [mutAry replaceObjectAtIndex:1 withObject:@"90"];
   [mutAry replaceObjectAtIndex:3 withObject:@"85"];
   [mutAry replaceObjectAtIndex:5 withObject:@"99"];
   for (i = 0 ; i < [mutAry count] ; i++) {
     printf("%s\t",[[mutAry objectAtIndex:i] UTF8String]);
   }

   printf("\n---------------------------\n");

   printf("mutAry陣列目前長度: %ld\n",[mutAry count]);
   [mutAry removeAllObjects];
   printf("mutAry陣列清空後長度: %ld",[mutAry count]);

   [ary release];
   [mutAry release];

   [pool drain];
}

®輸出結果 :

 60	90	100	
 國文:	英文:	數學:	
 ---------------------------
 國文:	60	英文:	90	數學:	100	
 ---------------------------
 國文:	90	英文:	85	數學:	99	
 ---------------------------
 mutAry陣列目前長度: 6
 mutAry陣列清空後長度: 0
  • 範例內生成兩種陣列(可變與不可變),先看看不可變陣列的初始化,必須一次給足所有內容固定其陣列長度,而初始後無法動態做新增減。而可變陣列經初始後即配置一記憶體起始位置,且隨時可以以 “addObject:" 動態新增物件存入陣列。
  • 接著分別印出兩個陣列的所有值,而第一個for迴圈寫法類似JAVA的for each。 一般在其他語言的陣列較常見到這種寫法 “ary物件[index]",而在Objective C需呼叫陣列物件的 “objectAtIndex:" 方法並給予欲取得該物件的陣列引數。 #這裡不使用NSLog輸出原因是因為NSLog會強制換行,在此不便閱讀,故改為使用C的輸出函式搭配\t與\n對齊排列。
  • 第二階段使用了insertObject對mutAry可變陣列分別插入ary不可變陣列內的相對分數內容至適當位置。
  • 第三階段replaceObjectAtIndex用意在於將指定引數位置的物件值替換掉。#在Ojbective C裡的方法名稱有時候落落長,但反而能讓我們更清楚其用意,清楚的名稱能大大減少我們上網查API的時間。
  • 最後試著將mutAry陣列全部內容清除並觀察剩餘長度,輸出結果陣列長度為零,但不代表此陣列物件已被釋放,mutAry本身的陣列物件依然配置在記憶體上,所以,別忘了"release"。

● 詞典物件(Dictionary)

詞典物件(Dictionary)區分為可變與不可變詞典,不可變物件即初始化後不得對其陣列內容做動態新增減,而可變則反之。在Objective C的詞典世界規則是一個Key值對應一個物件,key值可以是任意型別,不過既然是當作key使用,多數以NSString字串型態表示即可,而物件只要不是直接存入nil都是合法指向,但如果非要存入一個null物件可以使用[NSNull null]達成。簡單說詞典物件可以想像為將陣列物件的引數值改為自定義的key值,進而加大應用彈性。

@main

#import <Foundation/Foundation.h>

void print( NSDictionary *dic ) {
   NSEnumerator *enumerator = [dic keyEnumerator];
   id key;
   while ( key = [enumerator nextObject] ) {
       printf( "%s >>> %s\n", [[key description] UTF8String],
       [[[dic objectForKey: key] description] UTF8String] );
   }
}

int main(int argc, const char * argv[])
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
                     @"iPhone", [NSNumber numberWithInt: 1],
                     @"HTC", [NSNumber numberWithInt: 2],
                     @"Samsung", [NSNumber numberWithInt: 3],
                      nil];
   printf( "----不可變詞典物件-------\n" );
   print( dictionary );
   //--------------
   NSMutableDictionary *mutableDic = [[NSMutableDictionary alloc] init];
   [mutableDic setObject: @"USA" forKey:
          [dictionary objectForKey:[NSNumber numberWithInt:1]]];
   [mutableDic setObject: @"Taiwan" forKey:
          [dictionary objectForKey:[NSNumber numberWithInt:2]]];
   [mutableDic setObject: @"Korea" forKey:
          [dictionary objectForKey:[NSNumber numberWithInt:3]]];
   printf( "----可變詞典物件--------\n" );
   print( mutableDic );

   [dictionary release];
   [mutableDic release];
   [pool drain];
}
  • 一開始建立兩個陣列,分別為可變與不可變詞典物件,不可變辭典(NSDictionary)在初始時分別以三組對應的 物件&key 做詞典建構,為了證明key值可以為任意物件型別,所以我們以NSNumber作為key值代表。
  • 而動態詞典物件(NSMutableDictionary)經過初始後仍然可以動態新增key-value對組,而key值則取出dictionary的值代表。#詞典物件取值方法為 “objectForKey : “。
  • 建構完成後利用自定義的方法將所有對應結果輸出,#[dic keyEnumerator]可取出詞典所有key值,[enumerator nextObject]逐一取出內存key值直到引數末端自動結束迴圈,[[key description] UTF8String]將所有值強制轉為書寫型態並轉為C可輸出的UTF8String格式。

®輸出結果 :

 ----不可變詞典物件-------
 3 >>> Samsung
 1 >>> iPhone
 2 >>> HTC
 ----可變詞典物件--------
 iPhone >>> USA
 HTC >>> Taiwan
 Samsung >>> Korea
廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: