OC基礎編程技巧-iphone開發必看

來源:互聯網
上載者:User
文章目錄
  • 根類和 Objective-C 對象
  • 建立對象
  • 用對象的術語來思考
  • 管理對象圖,避免記憶體流失
  • 管理對象的可變性
  • 建立並使用值對象
  • 建立並使用群體
  • 在運行時檢驗對象能力
  • 比較對象
  • 拷貝對象

OC基礎編程技巧

正如它的名字所傳達的含義,Foundation 架構是所有 iOS 和 Mac OS X 編程所使用的基本工具。要成為這兩個平台上成功的開發人員,必須對這套工具了如指掌。

Foundation 架構定義了數量眾多的類以及協議,它們各司其職。但三種類和協議的地位更加突出,它們是最基本的部分:

  • 根類和相關的協議。根類,即 NSObject,還伴有一個同名的協議。它確定了所有 Objective-C 對象的基本介面和行為。同時也有一些協議,其他類可以採用這些協議來拷貝這些類的執行個體並對編碼它們的狀態。
  • 數值類。數值類能夠產生一個執行個體(稱為數值對象),也就是將字串、數字、日期、位元據等基本類型資料封裝起來的物件導向封裝。
  • 群體類。群體類的一個執行個體(稱為群體)管理著一組對象。區分不同類型的群體就要看訪問它所包含的對象的方式是什麼。通常,群體中包含的項目都是一系列數值對象。

群體和數值對象是 Objective-C 編程中極其重要的內容,因為它們經常被用作方法的參數和傳回值。

根類和 Objective-C 對象

在類繼承中,根類不從其他類繼承,同時所有其他的類都最終繼承自根類。NSObject 是 Objective-C 繼承中的根類。其他類都從 NSObject 繼承一套基本的介面到 Objective-C 運行時體系中。這些類的執行個體又都是從 NSObject 繼承而獲得 Objective-C 最根本的特性。

但就其自身而言,NSObject 的執行個體做不了什麼有趣的事,頂多隻是個對象而已。要使用更多屬性和邏輯來定製你的程式,就必須創造一個或多個繼承自 NSObject 的類,或者使用已有的直接或間接繼承自 NSObject 的類。

NSObject 採用了 NSObject 的協議,它聲明了一些附加方法,可以被所有對象的介面使用。另外,NSObject.h(包含了 NSObject 類定義的標頭檔)中包含 NSCopying、NSMutableCopying 和 NSCoding 協議。當某個類採用了這些協議後,它便獲得了對象拷貝和對象編碼的基本對象行為。模型類(封裝了應用資料並管理這些資料的執行個體的類)經常採用對象拷貝和對象編碼協議。

NSObject 類和相關協議定義了建立對象、瀏覽繼承鏈、查閱對象的特徵和功能、比較對象、拷貝對象和把對象進行編碼等的一系列方法。本文接下來主要講述的就是這類任務的基本要求。

建立對象

通常,建立對象時,要先為它分配記憶體,然後將它初始化。雖然這是兩個單獨的步驟,但它們聯絡甚密。有些類可以通過調用它們的Factory 方法來建立對象。

建立對象 – 分配記憶體和初始化

要為對象分配記憶體,對它的類發送一個 alloc 訊息就能得到該類的一個“原始”(未初始化)的執行個體。當你為一個對象分配記憶體時,Objective-C 運行時會在應用的虛擬記憶體中為該對象預留足夠大的記憶體空間。除了分配記憶體本身之外,這個環節還有另外幾個用途,例如把執行個體變數全部設為 0 等。

為原始執行個體分配好記憶體之後,你必須將其初始化。初始化也就是將對象設定為初始狀態,換句話說,就是讓它的執行個體變數和屬性為合理的值,然後再返回這個對象。初始化是為了保證返回的對象可以被使用。

你會發現在不少架構中都含有 initializers(初始器)方法,即可以初始化對象的方法。它們的形式大多類似。初始器是執行個體方法,方法開頭為 init,返回一個 id 類型的對象。根對象 NSObject 聲明了 init 方法,所有其他的類都繼承了這個方法。其他的類當然也可以聲明自己的初始器,各自要有自己的關鍵字和參數類型。例如,NSURL 類聲明了如下初始器:

- (id)initFileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir

當你為一個對象分配記憶體並將其初始化的時候,可以將記憶體配置方法和初始化方法嵌套起來。如果使用上邊這個初始器的話,可以寫成這樣:

NSURL *aURL = [[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory() isDir:YES];

作為一種安全的編程習慣,你可以檢查返回的對象以驗證對象的建立是否正確。如果建立過程中發生了意外而導致對象建立失敗,初始器將返回 nil。雖然 Objective-C 允許對 nil 發送訊息而不會產生任何副作用(比如拋出異常),但你的代碼顯然不可能正常工作,因為沒有任何方法能夠被調用。你不應該使用 alloc 返回的執行個體,而要使用初始器返回的執行個體。

通過調用類的Factory 方法來建立對象

通過調用類的Factory 方法也能建立一個對象。Factory 方法是一種類方法,它能夠分配記憶體、初始化,並返回執行個體自身。類的Factory 方法屬於一種便捷方法,因為它們只需一步就可以建立對象,而不是上文講過的兩步。它們的形式是這樣的:

+ (type)className… (這裡的 類名稱 不包含任何首碼)

Objective-C 架構中的類有些會定義一種Factory 方法,這種Factory 方法實際上起到了初始器的作用。比如,NSString 就聲明了如下兩種方法:

- (id)initWithFormat:(NSString *)format, …;

+ (id)stringWithFormat:(NSString *)format, …;

下邊的例子就是 NSString Factory 方法的一種用法:

NSString *myString = [NSString stringWithFormat:@"Customer: %@", self.record.customerName];

用對象的術語來思考

在運行時,每個應用都是一組互相協作的對象構成的;這些對象互相之間可以通訊,以完成應用所需的工作。每個對象都有自己的角色,至少要對一件事負責,並且至少串連一個其他對象。(孤立的對象毫無價值。)如所示,對象所組成的網路中既有架構對象也有應用程式物件。應用程式物件時自訂的子類的執行個體,一般繼承自某個父類架構。這些對象組成的網路一般被成為對象圖。

你需要建立這些串連,或者關係,在各個對象之間進行引用。引用的語言形式有很多,其中有執行個體變數、全域變數,甚至包括(在有限的範圍內)本地變數。而關係,可以是一對一關聯性,也可以是一對多關聯性,可以表示出一系列從屬關係的概念。這些關係就是某個對象對其他對象進行訪問、溝通或者控制的手段。被引用的對象自然也就成了訊息的接收者。

應用的對象間傳遞的訊息是讓應用持續工作的重要因素。好比樂團中的演奏家一樣,應用中的每個對象都有各自的角色,為應用的運行履行自己的這部分職責。有的對象可以顯示一個橢圓形的介面響應點按動作,有的會管理一些承載各種資料的資料集合,有的則控制整個應用生命週期內的各大事件。但為了完成它們各自的任務,它們還必須能夠互相交流。每個對象都要有向同一應用中別的對象發送訊息的能力,也要有接收別的對象發來的訊息的能力。

有些對象之間緊密成對,即互相之間直接相連,它們互發訊息時是很容易的。但還有些非緊密成對的對象,即在對象圖中被分隔開的對象,它們之間要進行通訊就要另想辦法。Cocoa 和 Cocoa Touch 架構含有許多協助非緊密成對的對象進行通訊的功能和機制(如所示)。這些機制和技術都建立在一些設計模式之上(我們會在後面探討),這樣就使得應用更加高效並且具有超強的可擴充性。

管理對象圖,避免記憶體流失

Objective-C 程式裡的對象共同組成一張對象圖:由各個對象和其他對象的關係(或引用)而形成的網路。對象之間的引用分為一對一和一對多(通過對象集合)引用。對象圖十分重要,因為它是使對象保持生命力的關鍵因素。編譯器會檢查對象圖中引用的強弱,並根據需要保持對象發出或釋放對象訊息。

在 C 語言或 Objective-C 語言中,可以使用含有全域變數、執行個體變數或本地變數的結構來構造對象間的引用。這些結構各自都有自己暗含的範圍。比如,本地變數引用的一個對象的範圍就是聲明它的函數塊所在的位置。同樣重要的是,對象間的引用也是分強弱的。強引用會指示出自己的所有者是誰;指向別人的對象擁有被指向的對象。弱引用則是指向別人的對象和被指向的對象之間沒有從屬關係。對象的生命週期由它的強引用數量多少決定。只要對象有強參考關聯性,它就不會被釋放。

Objective-C 裡的引用預設都是強引用。通常來說這很方便,讓編譯器管理對象的運行時生命週期,當你使用對象時它們不會被釋放。但是如果粗心未作全面檢查,對象間的強引用可能會形成無限迴圈,如左邊所示。這樣的迴圈鏈在運行時會導致運行時不會釋放任何一個對象,它們都有指向自己的強引用。繼而,這樣的死迴圈就造成了記憶體泄露。

就圖中的對象而言,如果你取消 A 和 B 之間的引用,則 B、C、D、E 構成的子物件圖則“永遠”不會從記憶體中釋放,因為這些對象每一個都有強引用,形成了一個死迴圈。如果在 E 和 B 之間引入弱引用,就可以打破強引用死迴圈了。

為了修正強引用死迴圈的問題,精明的程式員會使用弱引用。運行時會持續跟蹤對象的弱引用。一旦對象不再有強引用,運行時就會從釋放該對象,並將所有指向該對象的引用改為 nil。對變數來說(全域、執行個體和本地變數),在對象名前面加上 __weak 限定詞就可以將其標記為弱引用。對於屬性來說,可以使用 weak 選項。在以下這幾類引用中,你應該使用弱引用:

  • 委託

    @property(weak) id delegate;

    在《設計模式》篇裡,“用設計模式讓應用開發流水線化”教程將向你詳解委託和目標機制。

  • 未被頂級對象引用的插座變數(Outlet)

    @property(weak) IBOutlet NSString *theName;

    插座變數是對象間的一種串連(或引用),被歸檔在故事版檔案或 nib 檔中,當應用運行並載入故事版或 nib 檔時就會恢複插座變數。故事版或 nib 檔中頂級對象的插座變數一般而言是視窗、視圖、視圖控制器或其他控制器等,應該為 強引用(預設的,或未標記的)。

  • 目標

    (void)setTarget:(id __weak)target

  • 塊對象中指向 self 的引用

    __block typeof(self) tmpSelf = self;
    [self methodThatTakesABlock:^ {
        [tmpSelf doSomething];
    }];

    塊對象會對它捕獲的變數產生強引用。如果你在塊對象裡使用了 self,則會對 self 產生強引用。所以,如果 self 對塊對象也有強引用(通常都會這樣),就形成了強引用死迴圈。為了避免死迴圈,你需要在塊對象的外面建立一個指向 self 的 弱(或 __block)引用,如上邊的範例所示。

管理對象的可變性

可變對象是指在你建立後能夠變更其狀態的對象。一般來說你需要使用屬性或存取方法來進行改變。不可變對象則是建立後便被封裝好,狀態不可改變的對象。在 Objective-C 架構中建立的大部分類的執行個體都是可變的,但有幾種是不可變的。不可變對象具有如下優點:

  • 在使用不可變對象時,不用擔心它的值會發生意外變化。
  • 對於許多類型的對象而言,不可變對象能夠提升應用程式的效能。

在 Objective-C 架構中,不可變類的執行個體通常是封裝起來的離散值或緩衝區值的集合,比如數組和字串。這些類通常帶有一個可變的衍生類,類名裡多出“Mutable”(可變)一詞。比如有一個 NSString 類(不可變)和 NSMutableString 類。需要注意的是,對於 NSNumber 或 NSDate 等封裝了離散值的不可變對象,就沒必要存在可變的衍生類了。

如果你需要經常改變對象的內容,那麼就使用可變對象,而不使用不可變對象。如果你從架構中接收到的對象是個不可變對象,請遵照返回的類型來行事,不要嘗試改變對象的內容。

建立並使用值對象

值對象是指封裝了(C 語言類型的)基礎資料型別 (Elementary Data Type)值的對象,並提供一系列與該值有關的功能。值對象在對象表中代表的是標量類型。Foundation 架構為你提供了下列類,用來產生字串、位元據、日期和時間、數字等值對象:

  • NSString 和 NSMutableString
  • NSData 和 NSMutableData
  • NSDate
  • NSNumber
  • NSValue

值對象在 Objective-C 編程中十分重要,因為應用會把這些對象當作方法、函數的參數和傳回值進行調用。通過傳遞值對象,架構裡的各個部分甚至不同的架構之間便能夠交換資料。因為值對象代表的是標量值,因此你可以在集合或者其他需要用到對象的地方使用它們。值對象除了有普通資料類型的相同特徵和作為編程的必要成分之外,還有更大的優勢:你能夠通過更加有效而且十分優雅的方式對這些封裝起來的值進行操作。就拿 NSString 類來舉例,它有搜尋並替換字串的方法,有寫入字串到檔案或(更常用)到 URL 的方法,還有構建檔案系統路徑的方法等。

在有些場合中,你可能覺得使用基礎資料型別 (Elementary Data Type)更加有效和直接,例如 int(整數型)、float(浮點型)等等。舉個具體的例子就是在計算某個值的時候。所以 NSNumber 和 NSValue 對象很少被當作架構中方法的參數和傳回值。然而,需要注意到許多架構會聲明自己的數值資料類型,並把這些資料類型當作參數和傳回值進行傳遞和調用,比如 NSInteger 和 CGFloat。你需要在合適的場合使用這些架構定義的資料類型,這樣能夠協助你把代碼提煉出來,遠離底層平台。

使用值對象的基本方法

建立值對象的基本模式是:為你的代碼或架構代碼利用基礎資料型別 (Elementary Data Type)值建立一個值對象(可能稍後就將其作為方法的參數傳遞出去)。在你的代碼中,你稍後就會訪問對象中封裝的資料了。用 NSNumber 類來舉例再合適不過了:

int n = 5; // 基礎資料型別 (Elementary Data Type)的賦值
NSNumber *numberObject = [NSNumber numberWithInt:n]; // 利用基礎資料型別 (Elementary Data Type)建立一個值對象
int y = [numberObject intValue]; // 從值對象中獲得封裝後的數值(y == n)

多數“值”類會聲明一個初始器以及用來建立執行個體的Factory 方法。有些類——比如 NSString 和 NSData 不僅提供了初始器,還有利用儲存在本地、遠程檔案甚至記憶體中的資料來建立執行個體的Factory 方法。這些類還提供了一些補充方法,可以將字串和位元據寫入某個檔案或 URL 制定的位置中。下邊的範例代碼示範了 initWithContentsOfURL: 方法利用一個 URL 對象中制定的檔案的內容建立了一個 NSData 對象;在使用完資料之後,代碼將資料對象寫迴文件系統中:

NSURL *theURL = // 利用字串路徑建立檔案 URL 的代碼…
NSData *theData = [[NSData alloc] initWithContentsOfURL:theURL];
// 使用得到的資料…
[theData writeToURL:theURL atomically:YES];

大多數值類除了能夠建立值對象並讓你訪問封裝好的值以外,還提供一系列簡單的操作比如比較對象等。

字串

作為 C 語言的超集,Objective-C 關於字串的用法和 C 語言一樣。換句話說,單個字母用單括弧包裹,字串用雙括弧。不過,Objective-C 架構一般而言不會使用 C 風格的字串,而是使用 NSString 對象。

在《你的第一個 iOS 應用》教程中,在編寫 HelloWorld 應用時你曾建立了一個格式化的字串:

NSString *greeting = [[NSString alloc] initWithFormat:@”Hello, %@!”, nameString];

NSString 類為字串提供了一個對象包裹,因此內建有不定長字串儲存的記憶體管理功能、支援眾多字元編碼(尤其是 Unicode 編碼)、以及 printf 風格的格式化文法。因為你會經常用到字串,因此 Objective-C 提供了利用常量建立 NSString 對象的快捷形式。要使用這種快捷形式,只需在常規的雙引號包裹的字串前邊加上 @ 符號,像下面的範例中這樣:

// 建立字串“My String”並帶上一個分行符號
NSString *myString = @”My String\n”;
// 建立一個格式化字串“1 String”
NSString *anotherString = [NSString stringWithFormat:@"%d %@", 1, @"String"];
// 利用一個 C 語言字串建立 Objective-C 字串
NSString *fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];

時間和日期

NSDate 對象和其他的值對象不同,因為它在根本上是時間而不是基礎資料型別 (Elementary Data Type)。日期對象利用參考時間,按秒封裝了一個間隔值。參考時間就是 GMT 2001 年 1 月 1 日的第一個執行個體。

光是 NSDate 的執行個體自身可能用處還不是很大。它確實能代表某個時刻,但沒有日曆、時區和個別地區的時間約定等這些上下文,並無太大意義。幸好 Foundation 類提供了這些概念的實體:

  • NSCalendar 和 NSDateComponents:你可以將日期和日曆聯絡起來,包括由此引申出的時間單位例如年、月、小時、一周中的某一天等。你還可以進行日期的計算。
  • NSTimeZone:當日期和時間必須反映出某個地區的時區時,你可以將時區對象和日曆關聯起來。
  • NSLocale:本地化對象,裡面封裝了和時間有關的文化和語言格式的規約。

下面的程式碼片段展示了如何使用 NSDate 對象配合上述這些對象來擷取你需要的資訊(本例中,目前時間的列印格式為小時,分鐘,秒)。請參考程式碼片段下邊對應的數字項後邊的注釋:

NSDate *now = [NSDate date]; // 1
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; // 2
[calendar setTimeZone:[NSTimeZone systemTimeZone]]; // 3
NSDateComponents *dc = [calendar components:(NSHourCalendarUnit|NSMinuteCalendarUnit|
    NSSecondCalendarUnit) fromDate:now];  // 4
NSLog(@”The time is %d:%d:%d”, [dc hour], [dc minute], [dc second]); // 5

  1. 建立一個表示目前時間的日期對象。
  2. 建立一個表示西曆的對象。
  3. 用代表系統喜好設定中設定的時區的對象,設定日曆對象的時區。
  4. 調用日曆對象的 components:fromDate: 方法,將第一步裡建立的日期對象作為參數傳遞。調用此方法後會返回一個包含了時間對象的小時、分鐘和秒元素的對象。
  5. 在控制台列印出當前的時、分、秒。

雖然這個範例最終將結果列印出來,但更加推薦的使用方式是利用日期格式化器(NSDateFormatter 類的執行個體)在應用的介面上顯示日期資訊。在進行日期計算時一定要選用正確的類和方法;不要對時、分、秒、日等數值單位進行寫入程式碼。

建立並使用群體

群體也是一種對象,它能夠以特定方式儲存其他對象並允許客戶訪問那些對象。你通常會將群體當作方法和函數的參數進行傳遞,也常常從方法和函數的傳回值獲得一個群體。群體往往包含值對象,但其實它們可以包含任何類型的對象。大部分群體對它們所包含的對象會產生強引用。

Foundation 架構種有好幾種群體,其中三種在 Cocoa 和 Cocoa Touch 編程中極其重要:數組、字典和集合。這些群體的類同樣分別有不可變與可變的形式。可變群體能夠添加和移除對象,不可變群體只能含有它們建立時所包含的對象。所有群體都可以進行枚舉,也就是輪流檢查所包含的每個對象。

不同類型的群體會以各自不同的方式組織它們所包含的對象:

  • NSArray 和 NSMutableArray:數組是按順序儲存的一系列對象。你可以通過某個對象的位置序號來找到它(也就是它的索引)。數組中的第一個對象索引為 0(數字零)。
  • NSDictionary 和 NSMutableDictionary:字典將條目以“索引值對(Key-Value)”的形式儲存在一起。鍵是唯一識別碼,通常是字串;值就是你想要儲存的對象本身。你可以通過鍵來直接存取它對應的對象。
  • NSSet 和 NSMutableSet:集合裡的對象是無序儲存的,並且每個對象只能出現一次。通常要訪問集合裡的某個或某幾個對象時,你必須使用篩選或對對象進行判斷等方式。

由於它們的儲存、訪問和效能各有不同,在不同的場合也就各有利弊。

在數組中以特定順序儲存物件

數組中的對象是按順序儲存的。因此,當順序比較重要時你就可以選擇數組。舉個例子,許多應用都採用數組來儲存表格視圖中的內容或者菜單中的項目;索引值為 0 的對象代表第一排,索引值 1 上的對象對應第二排,以此類推。訪問數組中對象的速度比訪問集合的速度稍慢。

NSArray 類有多個初始器和類Factory 方法用來建立和初始化數組,其中有幾個尤其常用。你可以利用一系列對象來建立數組,使用 arrayWithObjects:count: 和 arrayWithObjects: 方法(及其對應的初始器)即可。前邊一個方法的第二個參數可以用來限制第一個參數中的對象個數;後面的方法中你可以使用 nil 來中止一系列用半形逗號分隔的對象。

// 建立一個含有字串對象的靜態數組
NSString *objs[3] = {@”One”, @”Two”, @”Three”};
// 用該靜態對象建立一個新數組對象
NSArray *arrayOne = [NSArray arrayWithObjects:&(*objs) count:3];
// 建立一個用 nil 結尾的對象列表的數組
NSArray *arrayTwo = [[NSArray alloc] initWithObjects:@”One”, @”Two”, @”Three”, nil];

在建立可變數組時,你可以使用 arrayWithCapacity:(或 initWithCapacity:)方法來建立此數組。容量參數只是作為期待數組大小的預設值,能夠讓數組在運行時更加高效。也就是說,數組的實際大小可以超過所指定的容量。

一般情況下,要通過索引位置(從 0 起始)訪問數組中的對象時需要調用 objectAtIndex: 這個方法:

NSString *theString = [arrayTwo objectAtIndex:1]; // 返回數組中的第二個對象

NSArray 還有其他方法,你可以訪問數組中的對象,也可以訪問它們的索引。比如 lastObject、firstObjectCommonWithArray: 和 indexOfObjectPassingTest: 方法。

數組的另一個重要功能是對所包含的每個對象均進行操作,這個過程叫做枚舉。你通常會枚舉某個數組,以此判斷某個或某些對象是否符合某個值或者條件,如果條件成立則可以進一步進行操作。共有三種枚舉方式可供選用:快速枚舉,塊對象枚舉,或者使用 NSEnumerator 對象。快速枚舉正如其名稱所示,一般而言在擷取數組中的對象時比其他枚舉方式更快。快速枚舉有其特定的文法:

for (type variable in array) { /* 規定 variable,並執行所需的操作 */ }

比如此例:

NSArray *myArray = // 擷取數組
for (NSString *cityName in myArray) {
    if ([cityName isEqualToString:@"Cupertino"]) {
        NSLog(@”We’re near the mothership!”);
        break;
    }
}

有幾種 NSArray 方法是通過塊對象進行枚舉的,最簡單的一個是 enumerateObjectsUsingBlock:。塊對象有三個參數:當前對象,它的索引值,以及一個布爾值,如果它為 YES 則枚舉結束。塊對象中的代碼效果和花括弧裡的快速枚舉效果完全一樣:

NSArray *myArray = // 擷取數組
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([(NSString *)obj isEqualToString:@"Cupertino"]) {
        NSLog(@”We’re near the mothership!”);
        *stop = YES;
    }
}];

NSArray 還有數組排序、搜尋、對數組中每個對象起作用等的方法。

若要往可變數組中添加對象,則要調用 addObject: 方法;新增的對象會被放置在數組末尾。你也可以使用 insertObject:atIndex: 將對象放在數組中的某特定位置。通過調用 removeObject: 或者 removeObjectAtIndex: 方法就可以將對象從數組中移除了。

用字典儲存索引值對

利用字典可以將對象以索引值對的形式儲存在群體中,索引值對是指一個標識符(鍵)與一個對象(值)組成的對子。字典是無序群體,因為索引值對可以以任何順序儲存。雖然鍵可以是任意形式,但最好是能夠描述值的字串,比如 NSFileModificationDate 或 UIApplicationStatusBarFrameUserInfoKey(都是字串常量)。當它們是公有鍵時,用字典在任意類型的對象之間傳遞資訊再好不過了。

通過它的初始器和類Factory 方法,NSDictionary 類有許多建立字典的方式,但其中兩個是最為常用的:dictionaryWithObjects:forKeys: 和 dictionaryWithObjectsAndKeys:(或者它們對應的初始器)。前一個方法中你需要傳入一個對象數組和鍵數組;鍵和值要在位置上一一對應。後面一個方法中你需要指定第一個對象值和它的鍵、第二個對象值和它的鍵、第三個、第四個,以此類推;用 nil 便可以結束這個對象系列。

// 首先建立一個鍵的數組以及一個值的補充數組
NSArray *keyArray = [NSArray arrayWithObjects:@"IssueDate", @"IssueName", @"IssueIcon", nil];
NSArray *valueArray = [NSArray arrayWithObjects:[NSDate date], @”Numerology Today”,
    self.currentIssueIcon, nil];
// 建立字典,將鍵數組和值數組傳入
NSDictionary *dictionaryOne = [NSDictionary dictionaryWithObjects:valueArray forKeys:keyArray];
// 用值、鍵輪流的方式建立數組,用 nil 來結束本字典
NSDictionary *dictionaryTwo = [[NSDictionary alloc] initWithObjectsAndKeys:[NSDate date],
    @”IssueDate”, @”Numerology Today”, @”IssueName”, self.currentIssueIcon, @”IssueIcon”, nil];

要訪問字典中的對象值,需要調用 objectForKey: 方法,並在參數中指定一個鍵。

NSDate *date = [dictionaryTwo objectForKey:@"IssueDate"];

你可以向可變字典中通過調用 setObject:forKey: 添加條目,也可以用 removeObjectForKey: 刪除條目,還可以用 setObject:forKey: 來替換任何給定鍵所對應的值。這些方法運行速度都很快。

用集合儲存無序對象

集合與數組相似也是對象群體,但集合中的條目是無序儲存的。你無法通過索引或者鍵來訪問集合裡的對象,而是隨機訪問(anyObject),通過枚舉群體或者使用篩選器、測試等方式尋找對象。

雖然在 Objective-C 中集合對象不像字典和數組那麼常用,它們仍然是某些技術中非常重要的群體類型。在 Core Data(一種資料管理技術)中,當你聲明一個一對多關聯性的屬性時,屬性類型就應該是 NSSet 或者 NSOrderedSet。集合在 UIKit 架構中的原生觸摸事件處理中也是非常重要的,比如:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
    // 處理代碼……
}

有序集合是集合定義之外的一個特例。在有序集合中,條目的順序十分重要。測試某個條目是否存在時,有序集合比數組的速度更快。

在運行時檢驗對象能力

內省(Introspection)是 Objective-C 中 NSObject 類的一個強大而實用的特性,可以讓你在運行時獲知關於對象的一些資訊。這樣,你就能避免一些錯誤,比如將訊息發送給一個不認識它的對象,或者以為某個對象繼承自另一個對象,實際上卻不是。

在運行時,對象可以傳達關於它自己的三種重要類型的資訊。

  • 它是否是某個類或子類的執行個體
  • 它是否能響應某條訊息
  • 它是否遵守某個協議

探究對象是否是某個類或其子類的執行個體

這樣做的方式是對對象調用 isKindOfClass: 方法:

static int sum = 0;
for (id item in myArray) {
    if ([item isKindOfClass:[NSNumber class]]) {
        int i = (int)[item intValue];
        sum += i;
    }
}

isKindOfClass: 方法需要一個 Class 類型的對象作為參數;要獲得這個對象,在類符號上調用 class 方法便可。檢查此方法返回的布爾值並進行下一步操作。

NSObject 還聲明了其他用來探究對象繼承資訊的方法。比如 isMemberOfClass: 方法會告訴你對象是否是某個指定類的執行個體,而 isKindOfClass: 會告訴你對象是否是某個類或其子類的成員。

探究對象是否能夠響應某個訊息

這樣做的方法是對對象調用 respondsToSelector: 方法:

if ([item respondsToSelector:@selector(setState:)]) {
    [item setState:[self.arcView.font isBold] ? NSOnState : NSOffState];
}

respondsToSelector: 方法需要一個選取器作為參數。選取器是 Objective-C 的一個資料類型,可以在運行時標識某個方法;利用 @selector 編譯器指令可以指定該選取器。在你的代碼中,檢查該方法返回的布爾值並進行下一步操作。

為了標識要發送給對象的訊息,通常是調用 respondsToSelector: 方法,這比檢測類的類型要更有用。比如,某個類的最新版本中可能實現了一箇舊版本中不存在的方法。

探究對象是否遵守某個協議

這樣做的方法是對對象調用 conformsToProtocol: 方法:

- (void) setDelegate:(id __weak) obj {
    NSParameterAssert([obj conformsToProtocol:
        @protocol(SubviewTableViewControllerDataSourceProtocol)]);
    delegate = obj;
}

conformsToProtocol: 方法需要協議的運行時標識符作為參數;利用 @protocol 編譯器指令可以指定此標識符。接下來檢查該方法返回的布爾值並進行下一步操作。

比較對象

利用 isEqual: 方法可以將兩個對象進行比較。接收到此訊息的對象將和作為參數傳入的對象進行比較;如果它們相同則此方法返回 YES。範例:

BOOL objectsAreEqual = [obj1 isEqual:obj2];
if (objectsAreEqual) {
    // 執行某些操作…
}

注意,對象的相等並不是對象的同一。對象的同一性要用 == 來檢測兩個變數是否指向同一個執行個體。

那麼當你比較兩個對象時,究竟是在比較什麼內容呢?這要視具體的類而定了。根類 NSObject 使用指標相等性來作為比較的基本點。其下任何層級的子類均可將父類比較的基本點的實現按照特定類的條件進行重寫,比如對象狀態。舉例來說,假設有個 Person(人)對象和另一個 Person 對象,如果它們的姓、名、生日屬性都相同,那麼就判定它們相等。

Foundation 架構的值和群體對象以 isEqualToType: 的形式聲明比較方法,這裡的 Type 是去掉“NS”首碼的類名稱,例如 isEqualToString: 和 isEqualToDictionary:。比較方法可以用來確定傳入的對象是否適合於某個給定的類型,以便接下來進行其他形式的比較。

拷貝對象

要拷貝某個對象,對其調用 copy 方法即可:

NSArray *myArray = [yourArray copy];

作為被拷貝的對象,接收訊息的對象的類必須遵守 NSCopying 協議。要讓對象能夠被拷貝,你必須採用這個協議的 copy 方法並實現它。

當你需要使用程式另一個地方的對象並想要完全保持對象的各項狀態的話,就需要拷貝這個對象。

拷貝行為是根據類的不同而各自區別的,而且還依賴於個別執行個體的特性。大多數類實現了深拷貝,它會複製執行個體中的所有執行個體變數和屬性;有些類則實現淺拷貝,它只會複製執行個體變數和屬性的引用。

擁有可變與不可變兩種變體的類還會聲明一個 mutableCopy 方法,用來建立對象的可變拷貝。比如,如果對一個 NSString 對象調用 mutableCopy 方法,你會得到一個 NSMutableString 執行個體。

 轉:http://www.guomii.com/posts/23681

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.